Switch to M3E ExtendedFloatingActionButton

Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
(cherry picked from commit a39b5a56e853a9c11c0984b0385a2e60b0addc3a)
This commit is contained in:
AntsyLich 2025-12-27 01:03:46 +06:00 committed by Cuong-Tran
parent 3ab83f1270
commit f24949748f
7 changed files with 83 additions and 239 deletions

View file

@ -3,12 +3,12 @@ package eu.kanade.presentation.browse.components
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material3.Icon
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.i18n.stringResource
@Composable
@ -17,7 +17,7 @@ fun BrowseSourceFloatingActionButton(
onFabClick: () -> Unit,
modifier: Modifier = Modifier,
) {
ExtendedFloatingActionButton(
SmallExtendedFloatingActionButton(
modifier = modifier,
text = {
Text(

View file

@ -4,11 +4,11 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material3.Icon
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.shouldExpandFAB
@ -18,7 +18,7 @@ fun CategoryFloatingActionButton(
onCreate: () -> Unit,
modifier: Modifier = Modifier,
) {
ExtendedFloatingActionButton(
SmallExtendedFloatingActionButton(
text = { Text(text = stringResource(MR.strings.action_add)) },
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
onClick = onCreate,

View file

@ -1,10 +1,7 @@
package eu.kanade.presentation.manga
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -32,9 +29,11 @@ import androidx.compose.material3.FabPosition
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableFloatState
import androidx.compose.runtime.State
@ -123,7 +122,6 @@ import tachiyomi.i18n.MR
import tachiyomi.i18n.kmk.KMR
import tachiyomi.presentation.core.components.TwoPanelBox
import tachiyomi.presentation.core.components.VerticalFastScroller
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.PullRefresh
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
@ -537,12 +535,23 @@ private fun MangaScreenSmallImpl(
val isFABVisible = remember(chapters) {
chapters.fastAny { !it.chapter.read } && !isAnySelected
}
AnimatedVisibility(
visible = isFABVisible,
enter = fadeIn(),
exit = fadeOut(),
// KMK -->
modifier = Modifier
SmallExtendedFloatingActionButton(
text = {
val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read }
}
Text(
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
)
},
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(),
modifier = Modifier.animateFloatingActionButton(
visible = isFABVisible,
alignment = Alignment.BottomEnd,
)
// KMK -->
.offset { IntOffset(offsetX.roundToInt(), 0) }
.onGloballyPositioned { coordinates ->
fabSize = coordinates.size
@ -566,25 +575,9 @@ private fun MangaScreenSmallImpl(
}
}
},
containerColor = MaterialTheme.colorScheme.primary,
// KMK <--
) {
ExtendedFloatingActionButton(
text = {
val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read }
}
Text(
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
)
},
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(),
// KMK -->
containerColor = MaterialTheme.colorScheme.primary,
// KMK <--
)
}
)
},
// KMK -->
floatingActionButtonPosition = if (fabPosition == FabPosition.End.toString()) {
@ -1002,12 +995,25 @@ private fun MangaScreenLargeImpl(
val isFABVisible = remember(chapters) {
chapters.fastAny { !it.chapter.read } && !isAnySelected
}
AnimatedVisibility(
visible = isFABVisible,
enter = fadeIn(),
exit = fadeOut(),
// KMK -->
modifier = Modifier
SmallExtendedFloatingActionButton(
text = {
val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read }
}
Text(
text = stringResource(
if (isReading) MR.strings.action_resume else MR.strings.action_start,
),
)
},
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(),
modifier = Modifier.animateFloatingActionButton(
visible = isFABVisible,
alignment = Alignment.BottomEnd,
)
// KMK -->
.offset { IntOffset(offsetX.roundToInt(), 0) }
.onGloballyPositioned { coordinates ->
fabSize = coordinates.size
@ -1031,27 +1037,9 @@ private fun MangaScreenLargeImpl(
}
}
},
containerColor = MaterialTheme.colorScheme.primary,
// KMK <--
) {
ExtendedFloatingActionButton(
text = {
val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read }
}
Text(
text = stringResource(
if (isReading) MR.strings.action_resume else MR.strings.action_start,
),
)
},
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(),
// KMK -->
containerColor = MaterialTheme.colorScheme.primary,
// KMK <--
)
}
)
},
// KMK -->
floatingActionButtonPosition = if (fabPosition == FabPosition.End.toString()) {

View file

@ -33,6 +33,7 @@ import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material.icons.outlined.ToggleOn
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
@ -86,7 +87,6 @@ import kotlinx.coroutines.launch
import tachiyomi.i18n.MR
import tachiyomi.i18n.kmk.KMR
import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
@ -178,7 +178,7 @@ class PreMigrationScreen(val migration: MigrationType) : Screen() {
},
// KMK <--
floatingActionButton = {
ExtendedFloatingActionButton(
SmallExtendedFloatingActionButton(
text = { Text(text = stringResource(MR.strings.action_migrate)) },
icon = {
Icon(

View file

@ -1,9 +1,6 @@
package eu.kanade.tachiyomi.ui.download
import android.view.LayoutInflater
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
@ -16,8 +13,10 @@ import androidx.compose.material.icons.outlined.Pause
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@ -57,13 +56,13 @@ import kotlinx.collections.immutable.persistentListOf
import tachiyomi.core.common.util.lang.launchUI
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.Pill
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen
import kotlin.math.roundToInt
object DownloadQueueScreen : Screen() {
@Suppress("unused")
private fun readResolve(): Any = DownloadQueueScreen
@Composable
@ -209,39 +208,37 @@ object DownloadQueueScreen : Screen() {
)
},
floatingActionButton = {
AnimatedVisibility(
visible = downloadList.isNotEmpty(),
enter = fadeIn(),
exit = fadeOut(),
) {
val isRunning by screenModel.isDownloaderRunning.collectAsState()
ExtendedFloatingActionButton(
text = {
val id = if (isRunning) {
MR.strings.action_pause
} else {
MR.strings.action_resume
}
Text(text = stringResource(id))
},
icon = {
val icon = if (isRunning) {
Icons.Outlined.Pause
} else {
Icons.Filled.PlayArrow
}
Icon(imageVector = icon, contentDescription = null)
},
onClick = {
if (isRunning) {
screenModel.pauseDownloads()
} else {
screenModel.startDownloads()
}
},
expanded = fabExpanded,
)
}
val isRunning by screenModel.isDownloaderRunning.collectAsState()
SmallExtendedFloatingActionButton(
text = {
val id = if (isRunning) {
MR.strings.action_pause
} else {
MR.strings.action_resume
}
Text(text = stringResource(id))
},
icon = {
val icon = if (isRunning) {
Icons.Outlined.Pause
} else {
Icons.Filled.PlayArrow
}
Icon(imageVector = icon, contentDescription = null)
},
onClick = {
if (isRunning) {
screenModel.pauseDownloads()
} else {
screenModel.startDownloads()
}
},
expanded = fabExpanded,
modifier = Modifier.animateFloatingActionButton(
visible = downloadList.isNotEmpty(),
alignment = Alignment.BottomEnd,
),
)
},
) { contentPadding ->
if (downloadList.isEmpty()) {

View file

@ -1,131 +0,0 @@
package tachiyomi.presentation.core.components.material
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.FloatingActionButtonElevation
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.dp
/**
* ExtendedFloatingActionButton with custom transition between collapsed/expanded state.
*
* @see androidx.compose.material3.ExtendedFloatingActionButton
*/
@Composable
fun ExtendedFloatingActionButton(
text: @Composable () -> Unit,
icon: @Composable () -> Unit,
onClick: () -> Unit,
modifier: Modifier = Modifier,
expanded: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.large,
containerColor: Color = MaterialTheme.colorScheme.primaryContainer,
contentColor: Color = contentColorFor(containerColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
) {
FloatingActionButton(
modifier = modifier,
onClick = onClick,
interactionSource = interactionSource,
shape = shape,
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation,
) {
val minWidth by animateDpAsState(
targetValue = if (expanded) ExtendedFabMinimumWidth else FabContainerWidth,
animationSpec = tween(
durationMillis = 500,
easing = EasingEmphasizedCubicBezier,
),
label = "minWidth",
)
val startPadding by animateDpAsState(
targetValue = if (expanded) ExtendedFabIconSize / 2 else 0.dp,
animationSpec = tween(
durationMillis = if (expanded) 300 else 900,
easing = EasingEmphasizedCubicBezier,
),
label = "startPadding",
)
Row(
modifier = Modifier
.sizeIn(minWidth = minWidth)
.padding(start = startPadding),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
icon()
AnimatedVisibility(
visible = expanded,
enter = ExtendedFabExpandAnimation,
exit = ExtendedFabCollapseAnimation,
) {
Box(modifier = Modifier.padding(start = ExtendedFabIconPadding, end = ExtendedFabTextPadding)) {
text()
}
}
}
}
}
private val EasingLinearCubicBezier = CubicBezierEasing(0.0f, 0.0f, 1.0f, 1.0f)
private val EasingEmphasizedCubicBezier = CubicBezierEasing(0.2f, 0.0f, 0.0f, 1.0f)
private val ExtendedFabMinimumWidth = 80.dp
private val ExtendedFabIconSize = 24.0.dp
private val ExtendedFabIconPadding = 12.dp
private val ExtendedFabTextPadding = 20.dp
private val ExtendedFabCollapseAnimation = fadeOut(
animationSpec = tween(
durationMillis = 100,
easing = EasingLinearCubicBezier,
),
) + shrinkHorizontally(
animationSpec = tween(
durationMillis = 500,
easing = EasingEmphasizedCubicBezier,
),
shrinkTowards = Alignment.Start,
)
private val ExtendedFabExpandAnimation = fadeIn(
animationSpec = tween(
durationMillis = 200,
delayMillis = 100,
easing = EasingLinearCubicBezier,
),
) + expandHorizontally(
animationSpec = tween(
durationMillis = 500,
easing = EasingEmphasizedCubicBezier,
),
expandFrom = Alignment.Start,
)
private val FabContainerWidth = 56.0.dp

View file

@ -12,17 +12,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import kotlinx.coroutines.flow.distinctUntilChanged
@Composable
fun LazyListState.shouldExpandFAB(): Boolean {
return remember {
derivedStateOf {
(firstVisibleItemIndex == 0 && firstVisibleItemScrollOffset == 0) ||
lastScrolledBackward ||
!canScrollForward
}
}
.value
}
fun LazyListState.shouldExpandFAB(): Boolean = lastScrolledBackward || !canScrollForward || !canScrollBackward
@Composable
fun LazyListState.isScrolledToStart(): Boolean {