|  | @@ -1,13 +1,54 @@
 | 
	
		
			
				|  |  |  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
 | 
	
	
		
			
				|  | @@ -16,14 +57,25 @@ 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
 | 
	
	
		
			
				|  | @@ -57,17 +109,299 @@ interface AssistContentScreen {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @Composable
 | 
	
		
			
				|  |  | -fun DefaultNavigatorScreenTransition(navigator: Navigator) {
 | 
	
		
			
				|  |  | -    val slideDistance = rememberSlideDistance()
 | 
	
		
			
				|  |  | -    ScreenTransition(
 | 
	
		
			
				|  |  | -        navigator = navigator,
 | 
	
		
			
				|  |  | -        transition = {
 | 
	
		
			
				|  |  | -            materialSharedAxisX(
 | 
	
		
			
				|  |  | -                forward = navigator.lastEvent != StackEvent.Pop,
 | 
	
		
			
				|  |  | -                slideDistance = slideDistance,
 | 
	
		
			
				|  |  | +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> { 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<Float>(
 | 
	
		
			
				|  |  | +                durationMillis = MotionConstants.DefaultMotionDuration,
 | 
	
		
			
				|  |  | +                easing = FastOutSlowInEasing,
 | 
	
		
			
				|  |  |              )
 | 
	
		
			
				|  |  | -        },
 | 
	
		
			
				|  |  | -    )
 | 
	
		
			
				|  |  | +            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
 |