Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7b2ada3e authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge "Introduce TransitionKey" into main

parents 71da30a0 aa58dfb9
Loading
Loading
Loading
Loading
+13 −5
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import kotlinx.coroutines.launch
internal fun CoroutineScope.animateToScene(
    layoutState: BaseSceneTransitionLayoutState,
    target: SceneKey,
    transitionKey: TransitionKey?,
): TransitionState.Transition? {
    val transitionState = layoutState.transitionState
    if (transitionState.currentScene == target) {
@@ -45,7 +46,7 @@ internal fun CoroutineScope.animateToScene(
    }

    return when (transitionState) {
        is TransitionState.Idle -> animate(layoutState, target)
        is TransitionState.Idle -> animate(layoutState, target, transitionKey)
        is TransitionState.Transition -> {
            // A transition is currently running: first check whether `transition.toScene` or
            // `transition.fromScene` is the same as our target scene, in which case the transition
@@ -67,7 +68,7 @@ internal fun CoroutineScope.animateToScene(
                    // The transition is in progress: start the canned animation at the same
                    // progress as it was in.
                    // TODO(b/290184746): Also take the current velocity into account.
                    animate(layoutState, target, startProgress = progress)
                    animate(layoutState, target, transitionKey, startProgress = progress)
                }
            } else if (transitionState.fromScene == target) {
                // There is a transition from [target] to another scene: simply animate the same
@@ -82,12 +83,18 @@ internal fun CoroutineScope.animateToScene(
                    null
                } else {
                    // TODO(b/290184746): Also take the current velocity into account.
                    animate(layoutState, target, startProgress = progress, reversed = true)
                    animate(
                        layoutState,
                        target,
                        transitionKey,
                        startProgress = progress,
                        reversed = true,
                    )
                }
            } else {
                // Generic interruption; the current transition is neither from or to [target].
                // TODO(b/290930950): Better handle interruptions here.
                animate(layoutState, target)
                animate(layoutState, target, transitionKey)
            }
        }
    }
@@ -96,6 +103,7 @@ internal fun CoroutineScope.animateToScene(
private fun CoroutineScope.animate(
    layoutState: BaseSceneTransitionLayoutState,
    target: SceneKey,
    transitionKey: TransitionKey?,
    startProgress: Float = 0f,
    reversed: Boolean = false,
): TransitionState.Transition {
@@ -127,7 +135,7 @@ private fun CoroutineScope.animate(
    // Change the current layout state to start this new transition. This will compute the
    // TransformationSpec associated to this transition, which we need to initialize the Animatable
    // that will actually animate it.
    layoutState.startTransition(transition)
    layoutState.startTransition(transition, transitionKey)

    // The transformation now contains the spec that we should use to instantiate the Animatable.
    val animationSpec = layoutState.transformationSpec.progressSpec
+11 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ class SceneKey(

    // Implementation of [UserActionResult].
    override val toScene: SceneKey = this
    override val transitionKey: TransitionKey? = null
    override val distance: UserActionDistance? = null

    override fun toString(): String {
@@ -104,3 +105,13 @@ class ValueKey(debugName: String, identity: Any = Object()) : Key(debugName, ide
        return "ValueKey(debugName=$debugName)"
    }
}

/**
 * Key for a transition. This can be used to specify which transition spec should be used when
 * starting the transition between two scenes.
 */
class TransitionKey(debugName: String, identity: Any = Object()) : Key(debugName, identity) {
    override fun toString(): String {
        return "TransitionKey(debugName=$debugName)"
    }
}
+8 −2
Original line number Diff line number Diff line
@@ -54,7 +54,7 @@ internal class SceneGestureHandler(

    private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
        if (isDrivingTransition || force) {
            layoutState.startTransition(newTransition)
            layoutState.startTransition(newTransition, newTransition.key)

            // Initialize SwipeTransition.swipeSpec. Note that this must be called right after
            // layoutState.startTransition() is called, because it computes the
@@ -237,7 +237,11 @@ internal class SceneGestureHandler(
                }
        swipeTransition.dragOffset += acceleratedOffset

        if (isNewFromScene || result.toScene != swipeTransition.toScene) {
        if (
            isNewFromScene ||
                result.toScene != swipeTransition.toScene ||
                result.transitionKey != swipeTransition.key
        ) {
            updateTransition(
                SwipeTransition(fromScene, result).apply {
                    this.dragOffset = swipeTransition.dragOffset
@@ -484,6 +488,7 @@ internal class SceneGestureHandler(

    private fun SwipeTransition(fromScene: Scene, result: UserActionResult): SwipeTransition {
        return SwipeTransition(
            result.transitionKey,
            fromScene,
            layoutImpl.scene(result.toScene),
            computeAbsoluteDistance(fromScene, result),
@@ -491,6 +496,7 @@ internal class SceneGestureHandler(
    }

    internal class SwipeTransition(
        val key: TransitionKey?,
        val _fromScene: Scene,
        val _toScene: Scene,
        /**
+33 −19
Original line number Diff line number Diff line
@@ -388,8 +388,9 @@ interface SwipeSourceDetector {
/**
 * The result of performing a [UserAction].
 *
 * Note: [UserActionResult] is implemented by [SceneKey], and you can also use [withDistance] to
 * easily create a [UserActionResult] with a fixed distance:
 * Note: [UserActionResult] is implemented by [SceneKey], so you can also use scene keys directly
 * when defining your [UserActionResult]s.
 *
 * ```
 * SceneTransitionLayout(...) {
 *     scene(
@@ -397,7 +398,7 @@ interface SwipeSourceDetector {
 *         userActions =
 *             mapOf(
 *                 Swipe.Right to Scene.Bar,
 *                 Swipe.Down to Scene.Doe withDistance 100.dp,
 *                 Swipe.Down to Scene.Doe,
 *             )
 *         )
 *     ) { ... }
@@ -408,6 +409,9 @@ interface UserActionResult {
    /** The scene we should be transitioning to during the [UserAction]. */
    val toScene: SceneKey

    /** The key of the transition that should be used. */
    val transitionKey: TransitionKey?

    /**
     * The distance the action takes to animate from 0% to 100%.
     *
@@ -416,6 +420,32 @@ interface UserActionResult {
    val distance: UserActionDistance?
}

/** Create a [UserActionResult] to [toScene] with the given [distance] and [transitionKey]. */
fun UserActionResult(
    toScene: SceneKey,
    distance: UserActionDistance? = null,
    transitionKey: TransitionKey? = null,
): UserActionResult {
    return object : UserActionResult {
        override val toScene: SceneKey = toScene
        override val transitionKey: TransitionKey? = transitionKey
        override val distance: UserActionDistance? = distance
    }
}

/** Create a [UserActionResult] to [toScene] with the given fixed [distance] and [transitionKey]. */
fun UserActionResult(
    toScene: SceneKey,
    distance: Dp,
    transitionKey: TransitionKey? = null,
): UserActionResult {
    return UserActionResult(
        toScene = toScene,
        distance = FixedDistance(distance),
        transitionKey = transitionKey,
    )
}

interface UserActionDistance {
    /**
     * Return the **absolute** distance of the user action given the size of the scene we are
@@ -424,22 +454,6 @@ interface UserActionDistance {
    fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float
}

/**
 * A utility function to make it possible to define user actions with a distance using the syntax
 * `Swipe.Up to Scene.foo withDistance 100.dp`
 */
infix fun Pair<UserAction, SceneKey>.withDistance(
    distance: Dp
): Pair<UserAction, UserActionResult> {
    val scene = second
    val distance = FixedDistance(distance)
    return first to
        object : UserActionResult {
            override val toScene: SceneKey = scene
            override val distance: UserActionDistance = distance
        }
}

/** The user action has a fixed [absoluteDistance]. */
private class FixedDistance(private val distance: Dp) : UserActionDistance {
    override fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float {
+18 −10
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState
    fun setTargetScene(
        targetScene: SceneKey,
        coroutineScope: CoroutineScope,
        transitionKey: TransitionKey? = null,
    ): TransitionState.Transition?
}

@@ -213,11 +214,14 @@ internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
    }

    /** Start a new [transition], instantly interrupting any ongoing transition if there was one. */
    internal fun startTransition(transition: TransitionState.Transition) {
    internal fun startTransition(
        transition: TransitionState.Transition,
        transitionKey: TransitionKey?,
    ) {
        // Compute the [TransformationSpec] when the transition starts.
        transformationSpec =
            transitions
                .transitionSpec(transition.fromScene, transition.toScene)
                .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
                .transformationSpec()

        transitionState = transition
@@ -265,7 +269,11 @@ internal class HoistedSceneTransitionLayoutScene(
                // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
                // late.
                val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
                animateToScene(layoutState = this@HoistedSceneTransitionLayoutScene, newKey)
                animateToScene(
                    layoutState = this@HoistedSceneTransitionLayoutScene,
                    target = newKey,
                    transitionKey = null,
                )
            }
        }
    }
@@ -278,15 +286,15 @@ internal class MutableSceneTransitionLayoutStateImpl(
) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
    override fun setTargetScene(
        targetScene: SceneKey,
        coroutineScope: CoroutineScope
        coroutineScope: CoroutineScope,
        transitionKey: TransitionKey?,
    ): TransitionState.Transition? {
        return with(this) {
            coroutineScope.animateToScene(
        return coroutineScope.animateToScene(
            layoutState = this@MutableSceneTransitionLayoutStateImpl,
            target = targetScene,
            transitionKey = transitionKey,
        )
    }
    }

    override fun CoroutineScope.onChangeScene(scene: SceneKey) {
        setTargetScene(scene, coroutineScope = this)
Loading