diff --git a/CHANGELOG.md b/CHANGELOG.md index c140b5fce..a4fd9ec27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ### Fixes - Fixed scrollbar sometimes not showing during scroll or not reaching the bottom with few items ([@anirudhsnayak](https://github.com/anirudhsnayak)) ([#2304](https://github.com/mihonapp/mihon/pull/2304)) +### Removed +- Predictive back support ([@AntsyLich](https://github.com/AntsyLich)) ([#2362](https://github.com/mihonapp/mihon/pull/2362)) + ## [v0.19.0] - 2025-08-04 ### Added - Add more Kaomoji for empty/error screens ([@ianfhunter](https://github.com/ianfhunter/)) ([#1909](https://github.com/mihonapp/mihon/pull/1909)) diff --git a/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt b/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt index 0b04913d4..a5916d268 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt @@ -1,9 +1,9 @@ package eu.kanade.presentation.components -import androidx.compose.animation.SizeTransform import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.window.Dialog @@ -32,9 +32,10 @@ fun NavigatorAdaptiveSheet( ) { ScreenTransition( navigator = sheetNavigator, - enterTransition = { fadeIn(animationSpec = tween(220, delayMillis = 90)) }, - exitTransition = { fadeOut(animationSpec = tween(90)) }, - sizeTransform = { SizeTransform() }, + transition = { + fadeIn(animationSpec = tween(220, delayMillis = 90)) togetherWith + fadeOut(animationSpec = tween(90)) + }, ) } diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaCoverDialog.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaCoverDialog.kt index 1e647c6b7..fc82c0117 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaCoverDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaCoverDialog.kt @@ -2,9 +2,6 @@ package eu.kanade.presentation.manga.components import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable -import androidx.activity.compose.PredictiveBackHandler -import androidx.compose.animation.core.animate -import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -27,18 +24,15 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.lerp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -55,14 +49,11 @@ import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.manga.EditCoverAction import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView import kotlinx.collections.immutable.persistentListOf -import soup.compose.material.motion.MotionConstants import tachiyomi.domain.manga.model.Manga import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource -import tachiyomi.presentation.core.util.PredictiveBack import tachiyomi.presentation.core.util.clickableNoIndication -import kotlin.coroutines.cancellation.CancellationException @Composable fun MangaCoverDialog( @@ -161,32 +152,10 @@ fun MangaCoverDialog( val statusBarPaddingPx = with(LocalDensity.current) { contentPadding.calculateTopPadding().roundToPx() } val bottomPaddingPx = with(LocalDensity.current) { contentPadding.calculateBottomPadding().roundToPx() } - var scale by remember { mutableFloatStateOf(1f) } - PredictiveBackHandler { progress -> - try { - progress.collect { backEvent -> - scale = lerp(1f, 0.8f, PredictiveBack.transform(backEvent.progress)) - } - onDismissRequest() - } catch (e: CancellationException) { - animate( - initialValue = scale, - targetValue = 1f, - animationSpec = tween(durationMillis = MotionConstants.DefaultMotionDuration), - ) { value, _ -> - scale = value - } - } - } - Box( modifier = Modifier .fillMaxSize() - .clickableNoIndication(onClick = onDismissRequest) - .graphicsLayer { - scaleX = scale - scaleY = scale - }, + .clickableNoIndication(onClick = onDismissRequest), ) { AndroidView( factory = { diff --git a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt index ffb6635e8..eae03ee1c 100644 --- a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt +++ b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt @@ -1,46 +1,13 @@ package eu.kanade.presentation.util -import android.annotation.SuppressLint -import androidx.activity.BackEventCompat -import androidx.activity.compose.PredictiveBackHandler +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.ContentTransform -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.SizeTransform -import androidx.compose.animation.core.AnimationSpec -import androidx.compose.animation.core.SeekableTransitionState -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.animate -import androidx.compose.animation.core.rememberTransition -import androidx.compose.animation.core.spring -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.platform.LocalViewConfiguration -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.lerp -import cafe.adriel.voyager.core.annotation.InternalVoyagerApi import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.ScreenModelStore import cafe.adriel.voyager.core.screen.Screen @@ -49,28 +16,18 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.transitions.ScreenTransitionContent -import eu.kanade.tachiyomi.util.view.getWindowRadius import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.dropWhile -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.launch import kotlinx.coroutines.plus -import soup.compose.material.motion.animation.materialSharedAxisXIn -import soup.compose.material.motion.animation.materialSharedAxisXOut +import soup.compose.material.motion.animation.materialSharedAxisX import soup.compose.material.motion.animation.rememberSlideDistance -import tachiyomi.presentation.core.util.PredictiveBack -import kotlin.coroutines.cancellation.CancellationException -import kotlin.math.absoluteValue /** * For invoking back press to the parent activity */ -@SuppressLint("ComposeCompositionLocalUsage") val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } interface Tab : cafe.adriel.voyager.navigator.tab.Tab { @@ -98,278 +55,41 @@ interface AssistContentScreen { fun onProvideAssistUrl(): String? } -@OptIn(InternalVoyagerApi::class) @Composable fun DefaultNavigatorScreenTransition( navigator: Navigator, modifier: Modifier = Modifier, ) { - val screenCandidatesToDispose = rememberSaveable(saver = screenCandidatesToDisposeSaver()) { - mutableStateOf(emptySet()) - } - val currentScreens = navigator.items - DisposableEffect(currentScreens) { - onDispose { - val newScreenKeys = navigator.items.map { it.key } - screenCandidatesToDispose.value += currentScreens.filter { it.key !in newScreenKeys } - } - } - - val slideDistance = rememberSlideDistance(slideDistance = 30.dp) + val slideDistance = rememberSlideDistance() ScreenTransition( navigator = navigator, + transition = { + materialSharedAxisX( + forward = navigator.lastEvent != StackEvent.Pop, + slideDistance = slideDistance, + ) + }, modifier = modifier, - enterTransition = { - if (it == SwipeEdge.Right) { - materialSharedAxisXIn(forward = false, slideDistance = slideDistance) - } else { - materialSharedAxisXIn(forward = true, slideDistance = slideDistance) - } - }, - exitTransition = { - if (it == SwipeEdge.Right) { - materialSharedAxisXOut(forward = false, slideDistance = slideDistance) - } else { - materialSharedAxisXOut(forward = true, slideDistance = slideDistance) - } - }, - popEnterTransition = { - if (it == SwipeEdge.Right) { - materialSharedAxisXIn(forward = true, slideDistance = slideDistance) - } else { - materialSharedAxisXIn(forward = false, slideDistance = slideDistance) - } - }, - popExitTransition = { - if (it == SwipeEdge.Right) { - materialSharedAxisXOut(forward = true, slideDistance = slideDistance) - } else { - materialSharedAxisXOut(forward = false, slideDistance = slideDistance) - } - }, - content = { screen -> - if (this.transition.targetState == this.transition.currentState) { - LaunchedEffect(Unit) { - val newScreens = navigator.items.map { it.key } - val screensToDispose = screenCandidatesToDispose.value.filterNot { it.key in newScreens } - if (screensToDispose.isNotEmpty()) { - screensToDispose.forEach { navigator.dispose(it) } - navigator.clearEvent() - } - screenCandidatesToDispose.value = emptySet() - } - } - screen.Content() - }, ) } -enum class SwipeEdge { - Unknown, - Left, - Right, -} - -private enum class AnimationType { - Pop, - Cancel, -} - @Composable fun ScreenTransition( navigator: Navigator, + transition: AnimatedContentTransitionScope.() -> ContentTransform, modifier: Modifier = Modifier, - enterTransition: AnimatedContentTransitionScope.(SwipeEdge) -> EnterTransition = { fadeIn() }, - exitTransition: AnimatedContentTransitionScope.(SwipeEdge) -> ExitTransition = { fadeOut() }, - popEnterTransition: AnimatedContentTransitionScope.(SwipeEdge) -> EnterTransition = enterTransition, - popExitTransition: AnimatedContentTransitionScope.(SwipeEdge) -> ExitTransition = exitTransition, - sizeTransform: (AnimatedContentTransitionScope.() -> SizeTransform?)? = null, - flingAnimationSpec: () -> AnimationSpec = { spring(stiffness = Spring.StiffnessLow) }, content: ScreenTransitionContent = { it.Content() }, ) { - val view = LocalView.current - val viewConfig = LocalViewConfiguration.current - val scope = rememberCoroutineScope() - val state = remember { - ScreenTransitionState( - navigator = navigator, - scope = scope, - flingAnimationSpec = flingAnimationSpec(), - windowCornerRadius = view.getWindowRadius().toFloat(), - ) - } - val transitionState = remember { SeekableTransitionState(navigator.lastItem) } - val transition = rememberTransition(transitionState = transitionState) - - if (state.isPredictiveBack || state.isAnimating) { - LaunchedEffect(state.progress) { - if (!state.isPredictiveBack) return@LaunchedEffect - val previousEntry = navigator.items.getOrNull(navigator.size - 2) - if (previousEntry != null) { - transitionState.seekTo(fraction = state.progress, targetState = previousEntry) - } - } - } else { - LaunchedEffect(navigator) { - snapshotFlow { navigator.lastItem } - .collect { - state.cancelCancelAnimation() - if (it != transitionState.currentState) { - transitionState.animateTo(it) - } else { - transitionState.snapTo(it) - } - } - } - } - - PredictiveBackHandler(enabled = navigator.canPop) { backEvent -> - state.cancelCancelAnimation() - var startOffset: Offset? = null - backEvent - .dropWhile { - if (startOffset == null) startOffset = Offset(it.touchX, it.touchY) - if (state.isAnimating) return@dropWhile true - // Touch slop check - val diff = Offset(it.touchX, it.touchY) - startOffset!! - diff.x.absoluteValue < viewConfig.touchSlop && diff.y.absoluteValue < viewConfig.touchSlop - } - .onCompletion { - if (it == null) { - state.finish() - } else { - state.cancel() - } - } - .collect { - state.setPredictiveBackProgress( - progress = it.progress, - swipeEdge = when (it.swipeEdge) { - BackEventCompat.EDGE_LEFT -> SwipeEdge.Left - BackEventCompat.EDGE_RIGHT -> SwipeEdge.Right - else -> SwipeEdge.Unknown - }, - ) - } - } - - transition.AnimatedContent( + AnimatedContent( + targetState = navigator.lastItem, + transitionSpec = transition, modifier = modifier, - transitionSpec = { - val pop = navigator.lastEvent == StackEvent.Pop || state.isPredictiveBack - ContentTransform( - targetContentEnter = if (pop) { - popEnterTransition(state.swipeEdge) - } else { - enterTransition(state.swipeEdge) - }, - initialContentExit = if (pop) { - popExitTransition(state.swipeEdge) - } else { - exitTransition(state.swipeEdge) - }, - targetContentZIndex = if (pop) 0f else 1f, - sizeTransform = sizeTransform?.invoke(this), - ) - }, - contentKey = { it.key }, - ) { - navigator.saveableState("transition", it) { - content(it) + label = "transition", + ) { screen -> + navigator.saveableState("transition", screen) { + content(screen) } } -} - -@Stable -private class ScreenTransitionState( - private val navigator: Navigator, - private val scope: CoroutineScope, - private val flingAnimationSpec: AnimationSpec, - windowCornerRadius: Float, -) { - var isPredictiveBack: Boolean by mutableStateOf(false) - private set - - var progress: Float by mutableFloatStateOf(0f) - private set - - var swipeEdge: SwipeEdge by mutableStateOf(SwipeEdge.Unknown) - private set - - private var animationJob: Pair? by mutableStateOf(null) - - val isAnimating: Boolean - get() = animationJob?.first?.isActive == true - - val windowCornerShape = RoundedCornerShape(windowCornerRadius) - - private fun reset() { - this.isPredictiveBack = false - this.swipeEdge = SwipeEdge.Unknown - this.animationJob = null - } - - fun setPredictiveBackProgress(progress: Float, swipeEdge: SwipeEdge) { - this.progress = lerp(0f, 0.65f, PredictiveBack.transform(progress)) - this.swipeEdge = swipeEdge - this.isPredictiveBack = true - } - - fun finish() { - if (!isPredictiveBack) { - navigator.pop() - return - } - animationJob = scope.launch { - try { - animate( - initialValue = progress, - targetValue = 1f, - animationSpec = flingAnimationSpec, - block = { i, _ -> progress = i }, - ) - navigator.pop() - } catch (e: CancellationException) { - // Cancelled - progress = 0f - } finally { - reset() - } - } to AnimationType.Pop - } - - fun cancel() { - if (!isPredictiveBack) { - return - } - animationJob = scope.launch { - try { - animate( - initialValue = progress, - targetValue = 0f, - animationSpec = flingAnimationSpec, - block = { i, _ -> progress = i }, - ) - } catch (e: CancellationException) { - // Cancelled - progress = 1f - } finally { - reset() - } - } to AnimationType.Cancel - } - - fun cancelCancelAnimation() { - if (animationJob?.second == AnimationType.Cancel) { - animationJob?.first?.cancel() - animationJob = null - } - } -} - -private fun screenCandidatesToDisposeSaver(): Saver>, List> { - return Saver( - save = { it.value.toList() }, - restore = { mutableStateOf(it.toSet()) }, - ) + + BackHandler(enabled = navigator.canPop, onBack = navigator::pop) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt index f69c11121..a713989c1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt @@ -1,10 +1,8 @@ package eu.kanade.tachiyomi.ui.home -import androidx.activity.compose.PredictiveBackHandler +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.animate -import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.animation.togetherWith @@ -24,19 +22,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.util.fastForEach -import androidx.compose.ui.util.lerp import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.tab.LocalTabNavigator @@ -56,7 +48,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -import soup.compose.material.motion.MotionConstants import soup.compose.material.motion.animation.materialFadeThroughIn import soup.compose.material.motion.animation.materialFadeThroughOut import tachiyomi.domain.library.service.LibraryPreferences @@ -65,10 +56,8 @@ import tachiyomi.presentation.core.components.material.NavigationBar import tachiyomi.presentation.core.components.material.NavigationRail import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.pluralStringResource -import tachiyomi.presentation.core.util.PredictiveBack import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import kotlin.coroutines.cancellation.CancellationException object HomeScreen : Screen() { @@ -93,8 +82,6 @@ object HomeScreen : Screen() { @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow - var scale by remember { mutableFloatStateOf(1f) } - TabNavigator( tab = LibraryTab, key = TabNavigatorKey, @@ -134,11 +121,7 @@ object HomeScreen : Screen() { Box( modifier = Modifier .padding(contentPadding) - .consumeWindowInsets(contentPadding) - .graphicsLayer { - scaleX = scale - scaleY = scale - }, + .consumeWindowInsets(contentPadding), ) { AnimatedContent( targetState = tabNavigator.current, @@ -158,31 +141,7 @@ object HomeScreen : Screen() { val goToLibraryTab = { tabNavigator.current = LibraryTab } - var handlingBack by remember { mutableStateOf(false) } - PredictiveBackHandler( - enabled = handlingBack || tabNavigator.current::class != LibraryTab::class, - ) { progress -> - handlingBack = true - val currentTab = tabNavigator.current - try { - progress.collect { backEvent -> - scale = lerp(1f, 0.92f, PredictiveBack.transform(backEvent.progress)) - tabNavigator.current = if (backEvent.progress > 0.25f) TABS[0] else currentTab - } - goToLibraryTab() - } catch (e: CancellationException) { - tabNavigator.current = currentTab - } finally { - animate( - initialValue = scale, - targetValue = 1f, - animationSpec = tween(durationMillis = MotionConstants.DefaultMotionDuration), - ) { value, _ -> - scale = value - } - handlingBack = false - } - } + BackHandler(enabled = tabNavigator.current != LibraryTab, onBack = goToLibraryTab) LaunchedEffect(Unit) { launch { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 7e8651111..60d357a53 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -4,11 +4,9 @@ package eu.kanade.tachiyomi.util.view import android.content.res.Resources import android.graphics.Rect -import android.os.Build import android.view.Gravity import android.view.Menu import android.view.MenuItem -import android.view.RoundedCorner import android.view.View import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -97,22 +95,3 @@ fun View?.isVisibleOnScreen(): Boolean { Rect(0, 0, Resources.getSystem().displayMetrics.widthPixels, Resources.getSystem().displayMetrics.heightPixels) return actualPosition.intersect(screen) } - -/** - * Returns window radius (in pixel) applied to this view - */ -fun View.getWindowRadius(): Int { - val rad = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val windowInsets = rootWindowInsets - listOfNotNull( - windowInsets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT), - windowInsets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT), - windowInsets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT), - windowInsets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT), - ) - .minOfOrNull { it.radius } - } else { - null - } - return rad ?: 0 -} diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt index 932af9deb..3d2fcd73a 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt @@ -1,10 +1,7 @@ package tachiyomi.presentation.core.components -import androidx.activity.compose.PredictiveBackHandler -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.animate +import androidx.activity.compose.BackHandler import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.AnchoredDraggableDefaults @@ -36,11 +33,8 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.composed import androidx.compose.ui.draw.alpha import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.TransformOrigin -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -49,13 +43,10 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.lerp import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch -import tachiyomi.presentation.core.util.PredictiveBack -import kotlin.coroutines.cancellation.CancellationException import kotlin.math.roundToInt @Composable @@ -84,7 +75,6 @@ fun AdaptiveSheet( Box( modifier = Modifier .clickable( - enabled = true, interactionSource = null, indication = null, onClick = internalOnDismissRequest, @@ -95,11 +85,6 @@ fun AdaptiveSheet( ) { Surface( modifier = Modifier - .predictiveBackAnimation( - enabled = remember { derivedStateOf { alpha > 0f } }.value, - transformOrigin = TransformOrigin.Center, - onBack = internalOnDismissRequest, - ) .requiredWidthIn(max = 460.dp) .clickable( interactionSource = null, @@ -112,6 +97,10 @@ fun AdaptiveSheet( shape = MaterialTheme.shapes.extraLarge, color = MaterialTheme.colorScheme.surfaceContainerHigh, content = { + BackHandler( + enabled = remember { derivedStateOf { alpha > 0f } }.value, + onBack = internalOnDismissRequest, + ) content() }, ) @@ -153,11 +142,6 @@ fun AdaptiveSheet( ) { Surface( modifier = Modifier - .predictiveBackAnimation( - enabled = anchoredDraggableState.targetValue == 0, - transformOrigin = TransformOrigin(0.5f, 1f), - onBack = internalOnDismissRequest, - ) .widthIn(max = 460.dp) .clickable( interactionSource = null, @@ -198,6 +182,10 @@ fun AdaptiveSheet( shape = MaterialTheme.shapes.extraLarge, color = MaterialTheme.colorScheme.surfaceContainerHigh, content = { + BackHandler( + enabled = anchoredDraggableState.targetValue == 0, + onBack = internalOnDismissRequest, + ) content() }, ) @@ -268,36 +256,4 @@ private fun AnchoredDraggableState.preUpPostDownNestedScrollConnection( private fun Offset.toFloat(): Float = this.y } -private fun Modifier.predictiveBackAnimation( - enabled: Boolean, - transformOrigin: TransformOrigin, - onBack: () -> Unit, -) = composed { - var scale by remember { mutableFloatStateOf(1f) } - PredictiveBackHandler(enabled = enabled) { progress -> - try { - progress.collect { backEvent -> - scale = lerp(1f, 0.85f, PredictiveBack.transform(backEvent.progress)) - } - // Completion - onBack() - } catch (e: CancellationException) { - // Cancellation - } finally { - animate( - initialValue = scale, - targetValue = 1f, - animationSpec = spring(stiffness = Spring.StiffnessLow), - ) { value, _ -> - scale = value - } - } - } - Modifier.graphicsLayer { - this.scaleX = scale - this.scaleY = scale - this.transformOrigin = transformOrigin - } -} - private val sheetAnimationSpec = tween(durationMillis = 350) diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/util/PredictiveBack.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/util/PredictiveBack.kt deleted file mode 100644 index b4280dbf1..000000000 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/util/PredictiveBack.kt +++ /dev/null @@ -1,9 +0,0 @@ -package tachiyomi.presentation.core.util - -import androidx.compose.animation.core.CubicBezierEasing - -private val PredictiveBackEasing = CubicBezierEasing(0.1f, 0.1f, 0f, 1f) - -object PredictiveBack { - fun transform(progress: Float): Float = PredictiveBackEasing.transform(progress) -}