diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index 444af9aec..9a6e3f6fd 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -273,10 +273,7 @@ private fun MangaScreenSmallImpl( onBackClicked() } } - BackHandler( - enabled = isAnySelected, - onBack = { onAllChapterSelected(false) }, - ) + BackHandler(onBack = internalOnBackPressed) Scaffold( topBar = { @@ -530,10 +527,7 @@ fun MangaScreenLargeImpl( onBackClicked() } } - BackHandler( - enabled = isAnySelected, - onBack = { onAllChapterSelected(false) }, - ) + BackHandler(onBack = internalOnBackPressed) Scaffold( topBar = { 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 3b5d7e558..b3c06a979 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 @@ -3,10 +3,6 @@ package eu.kanade.presentation.manga.components import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.os.Build -import androidx.activity.compose.PredictiveBackHandler -import androidx.compose.animation.core.LinearOutSlowInEasing -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 @@ -29,18 +25,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,13 +48,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.clickableNoIndication -import kotlin.coroutines.cancellation.CancellationException @Composable fun MangaCoverDialog( @@ -160,32 +151,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, LinearOutSlowInEasing.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 8083bc0af..86311d8e0 100644 --- a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt +++ b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt @@ -1,54 +1,13 @@ package eu.kanade.presentation.util import android.annotation.SuppressLint -import androidx.activity.BackEventCompat -import androidx.activity.compose.PredictiveBackHandler 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.core.FastOutSlowInEasing -import androidx.compose.animation.core.LinearOutSlowInEasing -import androidx.compose.animation.core.animate -import androidx.compose.animation.core.tween -import androidx.compose.animation.togetherWith -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.Stable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.movableContentOf -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithCache -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.BlurEffect -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.ColorMatrix -import androidx.compose.ui.graphics.Paint -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.TransformOrigin -import androidx.compose.ui.graphics.drawscope.drawIntoCanvas -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.toSize -import androidx.compose.ui.util.lerp -import androidx.compose.ui.zIndex import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.ScreenModelStore import cafe.adriel.voyager.core.screen.Screen @@ -57,25 +16,14 @@ 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.async -import kotlinx.coroutines.awaitAll import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.launch import kotlinx.coroutines.plus -import soup.compose.material.motion.MotionConstants import soup.compose.material.motion.animation.materialSharedAxisX import soup.compose.material.motion.animation.rememberSlideDistance -import kotlin.coroutines.cancellation.CancellationException -import kotlin.math.PI -import kotlin.math.sin /** * For invoking back press to the parent activity @@ -109,299 +57,17 @@ interface AssistContentScreen { } @Composable -fun DefaultNavigatorScreenTransition( - navigator: Navigator, - modifier: Modifier = Modifier, -) { - val scope = rememberCoroutineScope() - val view = LocalView.current - val handler = remember { - OnBackHandler( - scope = scope, - windowCornerRadius = view.getWindowRadius(), - onBackPressed = navigator::pop, - ) - } - PredictiveBackHandler(enabled = navigator.canPop) { progress -> - progress - .onStart { handler.reset() } - .onCompletion { e -> - if (e == null) { - handler.onBackConfirmed() - } else { - handler.onBackCancelled() - } - } - .collect(handler::onBackEvent) - } - - Box(modifier = modifier.onSizeChanged { handler.updateContainerSize(it.toSize()) }) { - val currentSceneEntry = navigator.lastItem - val showPrev by remember { - derivedStateOf { handler.scale < 1f || handler.translationY != 0f } - } - val visibleItems = remember(currentSceneEntry, showPrev) { - if (showPrev) { - val prevSceneEntry = navigator.items.getOrNull(navigator.size - 2) - listOfNotNull(currentSceneEntry, prevSceneEntry) - } else { - listOfNotNull(currentSceneEntry) - } - } - - val slideDistance = rememberSlideDistance() - - val screenContent = remember { - movableContentOf { screen -> - navigator.saveableState("transition", screen) { - screen.Content() - } - } - } - - visibleItems.forEachIndexed { index, backStackEntry -> - val isPrev = index == 1 && visibleItems.size > 1 - if (!isPrev) { - AnimatedContent( - targetState = backStackEntry, - transitionSpec = { - val forward = navigator.lastEvent != StackEvent.Pop - if (!forward && !handler.isReady) { - // Pop screen without animation when predictive back is in use - EnterTransition.None togetherWith ExitTransition.None - } else { - materialSharedAxisX( - forward = forward, - slideDistance = slideDistance, - ) - } - }, - modifier = Modifier - .zIndex(1f) - .graphicsLayer { - this.alpha = handler.alpha - this.transformOrigin = TransformOrigin( - pivotFractionX = if (handler.swipeEdge == BackEventCompat.EDGE_LEFT) 0.8f else 0.2f, - pivotFractionY = 0.5f, - ) - this.scaleX = handler.scale - this.scaleY = handler.scale - this.translationY = handler.translationY - this.clip = true - this.shape = if (showPrev) { - RoundedCornerShape(handler.windowCornerRadius.toFloat()) - } else { - RectangleShape - } - } - .then( - if (showPrev) { - Modifier.pointerInput(Unit) { - // Animated content should not be interactive - } - } else { - Modifier - }, - ), - content = { - if (visibleItems.size == 2 && visibleItems.getOrNull(1) == it) { - // Avoid drawing previous screen - return@AnimatedContent - } - screenContent(it) - }, - ) - } else { - Box( - modifier = Modifier - .zIndex(0f) - .drawWithCache { - val bounds = Rect(Offset.Zero, size) - val matrix = ColorMatrix().apply { - // Reduce saturation and brightness - setToSaturation(lerp(1f, 0.95f, handler.alpha)) - set(0, 4, lerp(0f, -25f, handler.alpha)) - set(1, 4, lerp(0f, -25f, handler.alpha)) - set(2, 4, lerp(0f, -25f, handler.alpha)) - } - val paint = Paint().apply { colorFilter = ColorFilter.colorMatrix(matrix) } - onDrawWithContent { - drawIntoCanvas { - it.saveLayer(bounds, paint) - drawContent() - it.restore() - } - } - } - .graphicsLayer { - val blurRadius = 5.dp.toPx() * handler.alpha - renderEffect = if (blurRadius > 0f) { - BlurEffect(blurRadius, blurRadius) - } else { - null - } - } - .pointerInput(Unit) { - // bg content should not be interactive - }, - content = { screenContent(backStackEntry) }, - ) - } - } - - LaunchedEffect(currentSceneEntry) { - // Reset *after* the screen is popped successfully - // so that the correct transition is applied - handler.setReady() - } - } -} - -@Stable -private class OnBackHandler( - private val scope: CoroutineScope, - val windowCornerRadius: Int, - private val onBackPressed: () -> Unit, -) { - - var isReady = true - private set - - var alpha by mutableFloatStateOf(1f) - private set - - var scale by mutableFloatStateOf(1f) - private set - - var translationY by mutableFloatStateOf(0f) - private set - - var swipeEdge by mutableIntStateOf(BackEventCompat.EDGE_LEFT) - private set - - private var containerSize = Size.Zero - private var startPointY = Float.NaN - - var isPredictiveBack by mutableStateOf(false) - private set - - private var animationJob: Job? = null - set(value) { - isReady = false - field = value - } - - fun updateContainerSize(size: Size) { - containerSize = size - } - - fun setReady() { - reset() - animationJob?.cancel() - animationJob = null - isReady = true - isPredictiveBack = false - } - - fun reset() { - startPointY = Float.NaN - } - - fun onBackEvent(backEvent: BackEventCompat) { - if (!isReady) return - isPredictiveBack = true - swipeEdge = backEvent.swipeEdge - - val progress = LinearOutSlowInEasing.transform(backEvent.progress) - scale = lerp(1f, 0.85f, progress) - - if (startPointY.isNaN()) { - startPointY = backEvent.touchY - } - val deltaYRatio = (backEvent.touchY - startPointY) / containerSize.height - val translateYDistance = containerSize.height / 20 - translationY = sin(deltaYRatio * PI * 0.5).toFloat() * translateYDistance * progress - } - - fun onBackConfirmed() { - if (!isReady) return - if (isPredictiveBack) { - // Continue predictive animation and pop the screen - val animationSpec = tween( - durationMillis = MotionConstants.DefaultMotionDuration, - easing = FastOutSlowInEasing, +fun DefaultNavigatorScreenTransition(navigator: Navigator) { + val slideDistance = rememberSlideDistance() + ScreenTransition( + navigator = navigator, + transition = { + materialSharedAxisX( + forward = navigator.lastEvent != StackEvent.Pop, + slideDistance = slideDistance, ) - animationJob = scope.launch { - try { - listOf( - async { - animate( - initialValue = alpha, - targetValue = 0f, - animationSpec = animationSpec, - ) { value, _ -> - alpha = value - } - }, - async { - animate( - initialValue = scale, - targetValue = scale - 0.05f, - animationSpec = animationSpec, - ) { value, _ -> - scale = value - } - }, - ).awaitAll() - } catch (e: CancellationException) { - // no-op - } finally { - onBackPressed() - alpha = 1f - translationY = 0f - scale = 1f - } - } - } else { - // Pop right away and use default transition - onBackPressed() - } - } - - fun onBackCancelled() { - // Reset states - isPredictiveBack = false - animationJob = scope.launch { - listOf( - async { - animate( - initialValue = scale, - targetValue = 1f, - ) { value, _ -> - scale = value - } - }, - async { - animate( - initialValue = alpha, - targetValue = 1f, - ) { value, _ -> - alpha = value - } - }, - async { - animate( - initialValue = translationY, - targetValue = 0f, - ) { value, _ -> - translationY = value - } - }, - ).awaitAll() - - isReady = true - } - } + }, + ) } @Composable 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 7e61985d5..ff2cb7075 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,11 +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.LinearOutSlowInEasing -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 @@ -26,20 +23,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.TransformOrigin -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 @@ -59,7 +49,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 @@ -70,7 +59,6 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.pluralStringResource import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import kotlin.coroutines.cancellation.CancellationException object HomeScreen : Screen() { @@ -92,8 +80,6 @@ object HomeScreen : Screen() { @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow - var scale by remember { mutableFloatStateOf(1f) } - TabNavigator( tab = LibraryTab, key = TabNavigatorKey, @@ -132,11 +118,6 @@ object HomeScreen : Screen() { ) { contentPadding -> Box( modifier = Modifier - .graphicsLayer { - scaleX = scale - scaleY = scale - transformOrigin = TransformOrigin(0.5f, 1f) - } .padding(contentPadding) .consumeWindowInsets(contentPadding), ) { @@ -157,30 +138,10 @@ object HomeScreen : Screen() { } val goToLibraryTab = { tabNavigator.current = LibraryTab } - - var handlingBack by remember { mutableStateOf(false) } - PredictiveBackHandler(enabled = handlingBack || tabNavigator.current != LibraryTab) { progress -> - handlingBack = true - val currentTab = tabNavigator.current - try { - progress.collect { backEvent -> - scale = lerp(1f, 0.92f, LinearOutSlowInEasing.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/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 7184388a8..78c688c2e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -11,6 +11,7 @@ import android.os.Bundle import android.view.View import androidx.activity.ComponentActivity import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.consumeWindowInsets @@ -222,13 +223,14 @@ class MainActivity : BaseActivity() { contentWindowInsets = scaffoldInsets, ) { contentPadding -> // Consume insets already used by app state banners - // Shows current screen - DefaultNavigatorScreenTransition( - navigator = navigator, + Box( modifier = Modifier .padding(contentPadding) .consumeWindowInsets(contentPadding), - ) + ) { + // Shows current screen + DefaultNavigatorScreenTransition(navigator = navigator) + } } // Pop source-related screens when incognito mode is turned off diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt index 3ba5f6dec..3d396ace9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt @@ -40,7 +40,6 @@ class SettingsScreen( Destination.Tracking.id -> SettingsTrackingScreen else -> SettingsMainScreen }, - onBackPressed = null, content = { val pop: () -> Unit = { if (it.canPop) { @@ -62,7 +61,6 @@ class SettingsScreen( Destination.Tracking.id -> SettingsTrackingScreen else -> SettingsAppearanceScreen }, - onBackPressed = null, ) { val insets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal) TwoPanelBox( 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 7e770fb43..d36e2593f 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,11 +1,7 @@ package tachiyomi.presentation.core.components -import androidx.activity.compose.PredictiveBackHandler -import androidx.compose.animation.core.LinearOutSlowInEasing -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.AnchoredDraggableState @@ -30,7 +26,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember @@ -39,11 +34,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 @@ -53,14 +45,14 @@ import androidx.compose.ui.unit.Dp 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 kotlin.coroutines.cancellation.CancellationException import kotlin.math.roundToInt +private val sheetAnimationSpec = tween(durationMillis = 350) + @Composable fun AdaptiveSheet( isTabletUi: Boolean, @@ -99,11 +91,6 @@ fun AdaptiveSheet( ) { Surface( modifier = Modifier - .predictiveBackAnimation( - enabled = remember { derivedStateOf { alpha > 0f } }.value, - transformOrigin = TransformOrigin.Center, - onBack = internalOnDismissRequest, - ) .requiredWidthIn(max = 460.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, @@ -116,6 +103,7 @@ fun AdaptiveSheet( shape = MaterialTheme.shapes.extraLarge, tonalElevation = tonalElevation, content = { + BackHandler(enabled = alpha > 0f, onBack = internalOnDismissRequest) content() }, ) @@ -157,11 +145,6 @@ fun AdaptiveSheet( ) { Surface( modifier = Modifier - .predictiveBackAnimation( - enabled = anchoredDraggableState.targetValue == 0, - transformOrigin = TransformOrigin(0.5f, 1f), - onBack = internalOnDismissRequest, - ) .widthIn(max = 460.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, @@ -201,6 +184,10 @@ fun AdaptiveSheet( shape = MaterialTheme.shapes.extraLarge, tonalElevation = tonalElevation, content = { + BackHandler( + enabled = anchoredDraggableState.targetValue == 0, + onBack = internalOnDismissRequest, + ) content() }, ) @@ -270,37 +257,3 @@ private fun AnchoredDraggableState.preUpPostDownNestedScrollConnection() @JvmName("offsetToFloat") 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, LinearOutSlowInEasing.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)