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

Commit 3f9cba65 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Move UserActionDistance to TransitionSpec

This CL moves UserActionDistance from UserActionResult to
TransitionSpec. I realized while adding custom distances to the demo
that this made more sense: usually the distance is the same when going
from A => B and B => A, and the previous API would require us to specify
the distance twice. Moreover, it was impossible to assign a distance to
a generic transition (for example from any scene to Shade or from Shade
to any scene).

Bug: 308961608
Test: SwipeToSceneTest
Flag: N/A
Change-Id: I24f9cf346f37d81dc19f738665b872e2efcd94d9
parent 9c0fbb1a
Loading
Loading
Loading
Loading
+0 −22
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.systemui.scene.ui.composable

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Edge as ComposeAwareEdge
import com.android.compose.animation.scene.SceneKey as ComposeAwareSceneKey
@@ -25,15 +23,12 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.TransitionKey as ComposeAwareTransitionKey
import com.android.compose.animation.scene.UserAction as ComposeAwareUserAction
import com.android.compose.animation.scene.UserActionDistance as ComposeAwareUserActionDistance
import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.compose.animation.scene.UserActionResult as ComposeAwareUserActionResult
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.TransitionKey
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.shared.model.UserActionDistance
import com.android.systemui.scene.shared.model.UserActionResult

// TODO(b/293899074): remove this file once we can use the types from SceneTransitionLayout.
@@ -82,22 +77,5 @@ fun UserActionResult.asComposeAware(): ComposeAwareUserActionResult {
    return ComposeAwareUserActionResult(
        toScene = composeUnaware.toScene.asComposeAware(),
        transitionKey = composeUnaware.transitionKey?.asComposeAware(),
        distance = composeUnaware.distance?.asComposeAware(),
    )
}

fun UserActionDistance.asComposeAware(): ComposeAwareUserActionDistance {
    val composeUnware = this
    return object : ComposeAwareUserActionDistance {
        override fun UserActionDistanceScope.absoluteDistance(
            fromSceneSize: IntSize,
            orientation: Orientation,
        ): Float {
            return composeUnware.absoluteDistance(
                fromSceneWidth = fromSceneSize.width,
                fromSceneHeight = fromSceneSize.height,
                isHorizontal = orientation == Orientation.Horizontal,
            )
        }
    }
}
+61 −46
Original line number Diff line number Diff line
@@ -55,14 +55,17 @@ internal class SceneGestureHandler(
        if (isDrivingTransition || force) {
            layoutState.startTransition(newTransition, newTransition.key)

            // Initialize SwipeTransition.swipeSpec. Note that this must be called right after
            // layoutState.startTransition() is called, because it computes the
            // layoutState.transformationSpec().
            // Initialize SwipeTransition.transformationSpec and .swipeSpec. Note that this must be
            // called right after layoutState.startTransition() is called, because it computes the
            // current layoutState.transformationSpec().
            val transformationSpec = layoutState.transformationSpec
            newTransition.transformationSpec = transformationSpec
            newTransition.swipeSpec =
                layoutState.transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec
                transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec
        } else {
            // We were not driving the transition and we don't force the update, so the spec won't
            // be used and it doesn't matter which one we set here.
            // We were not driving the transition and we don't force the update, so the specs won't
            // be used and it doesn't matter which ones we set here.
            newTransition.transformationSpec = TransformationSpec.Empty
            newTransition.swipeSpec = SceneTransitions.DefaultSwipeSpec
        }

@@ -472,43 +475,20 @@ private fun SwipeTransition(
): SwipeTransition {
    val upOrLeftResult = swipes.upOrLeftResult
    val downOrRightResult = swipes.downOrRightResult
    val userActionDistance = result.distance ?: DefaultSwipeDistance

    // The absolute distance of the gesture. Note that the UserActionDistance might return 0f or a
    // negative value at first if it needs the size or offset of an element that is not composed yet
    // when computing the distance. We call UserActionDistance.absoluteDistance() until it returns a
    // value different than 0.
    var lastAbsoluteDistance = 0f
    val absoluteDistance: () -> Float = {
        if (lastAbsoluteDistance > 0f) {
            lastAbsoluteDistance
        } else {
            with(userActionDistance) {
                    layoutImpl.userActionDistanceScope.absoluteDistance(
                        fromScene.targetSize,
                        orientation,
                    )
                }
                .also { lastAbsoluteDistance = it }
        }
    }

    // The signed distance of the gesture.
    val distance: () -> Float = {
        val absoluteDistance = absoluteDistance()
        when {
            absoluteDistance <= 0f -> SwipeTransition.DistanceUnspecified
            result == upOrLeftResult -> -absoluteDistance
            result == downOrRightResult -> absoluteDistance
    val isUpOrLeft =
        when (result) {
            upOrLeftResult -> true
            downOrRightResult -> false
            else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)")
        }
    }

    return SwipeTransition(
        key = result.transitionKey,
        _fromScene = fromScene,
        _toScene = layoutImpl.scene(result.toScene),
        distance = distance,
        userActionDistanceScope = layoutImpl.userActionDistanceScope,
        orientation = orientation,
        isUpOrLeft = isUpOrLeft,
    )
}

@@ -516,16 +496,9 @@ private class SwipeTransition(
    val key: TransitionKey?,
    val _fromScene: Scene,
    val _toScene: Scene,

    /**
     * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
     * or to the left of [toScene].
     *
     * Note that this distance can be equal to [DistanceUnspecified] during the first frame of a
     * transition when the distance depends on the size or position of an element that is composed
     * in the scene we are going to.
     */
    val distance: () -> Float,
    private val userActionDistanceScope: UserActionDistanceScope,
    private val orientation: Orientation,
    private val isUpOrLeft: Boolean,
) : TransitionState.Transition(_fromScene.key, _toScene.key) {
    var _currentScene by mutableStateOf(_fromScene)
    override val currentScene: SceneKey
@@ -566,9 +539,50 @@ private class SwipeTransition(
    /** Job to check that there is at most one offset animation in progress. */
    private var offsetAnimationJob: Job? = null

    /**
     * The [TransformationSpecImpl] associated to this transition.
     *
     * Note: This is lateinit because this [SwipeTransition] is needed by
     * [BaseSceneTransitionLayoutState] to compute the [TransitionSpec], and it will be set right
     * after [BaseSceneTransitionLayoutState.startTransition] is called with this transition.
     */
    lateinit var transformationSpec: TransformationSpecImpl

    /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
    lateinit var swipeSpec: SpringSpec<Float>

    private var lastDistance = DistanceUnspecified

    /**
     * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
     * or to the left of [toScene].
     *
     * Note that this distance can be equal to [DistanceUnspecified] during the first frame of a
     * transition when the distance depends on the size or position of an element that is composed
     * in the scene we are going to.
     */
    fun distance(): Float {
        if (lastDistance != DistanceUnspecified) {
            return lastDistance
        }

        val absoluteDistance =
            with(transformationSpec.distance ?: DefaultSwipeDistance) {
                userActionDistanceScope.absoluteDistance(
                    _fromScene.targetSize,
                    orientation,
                )
            }

        if (absoluteDistance <= 0f) {
            return DistanceUnspecified
        }

        val distance = if (isUpOrLeft) -absoluteDistance else absoluteDistance
        lastDistance = distance
        return distance
    }

    /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
    private fun startOffsetAnimation(job: () -> Job) {
        cancelOffsetAnimation()
@@ -611,6 +625,7 @@ private class SwipeTransition(
        }
        isAnimatingOffset = true

        val animationSpec = transformationSpec
        offsetAnimatable.animateTo(
            targetValue = targetOffset,
            animationSpec = swipeSpec,
+2 −15
Original line number Diff line number Diff line
@@ -395,22 +395,9 @@ class UserActionResult(
    /** The scene we should be transitioning to during the [UserAction]. */
    val toScene: SceneKey,

    /**
     * The distance the action takes to animate from 0% to 100%.
     *
     * If `null`, a default distance will be used that depends on the [UserAction] performed.
     */
    val distance: UserActionDistance? = null,

    /** The key of the transition that should be used. */
    val transitionKey: TransitionKey? = null,
) {
    constructor(
        toScene: SceneKey,
        distance: Dp,
        transitionKey: TransitionKey? = null,
    ) : this(toScene, FixedDistance(distance), transitionKey)
}
)

interface UserActionDistance {
    /**
@@ -449,7 +436,7 @@ interface UserActionDistanceScope : Density {
}

/** The user action has a fixed [absoluteDistance]. */
private class FixedDistance(private val distance: Dp) : UserActionDistance {
class FixedDistance(private val distance: Dp) : UserActionDistance {
    override fun UserActionDistanceScope.absoluteDistance(
        fromSceneSize: IntSize,
        orientation: Orientation,
+11 −0
Original line number Diff line number Diff line
@@ -163,6 +163,14 @@ interface TransformationSpec {
     */
    val swipeSpec: SpringSpec<Float>?

    /**
     * The distance it takes for this transition to animate from 0% to 100% when it is driven by a
     * [UserAction].
     *
     * If `null`, a default distance will be used that depends on the [UserAction] performed.
     */
    val distance: UserActionDistance?

    /** The list of [Transformation] applied to elements during this transition. */
    val transformations: List<Transformation>

@@ -171,6 +179,7 @@ interface TransformationSpec {
            TransformationSpecImpl(
                progressSpec = snap(),
                swipeSpec = null,
                distance = null,
                transformations = emptyList(),
            )
        internal val EmptyProvider = { Empty }
@@ -193,6 +202,7 @@ internal class TransitionSpecImpl(
                TransformationSpecImpl(
                    progressSpec = reverse.progressSpec,
                    swipeSpec = reverse.swipeSpec,
                    distance = reverse.distance,
                    transformations = reverse.transformations.map { it.reversed() }
                )
            }
@@ -209,6 +219,7 @@ internal class TransitionSpecImpl(
internal class TransformationSpecImpl(
    override val progressSpec: AnimationSpec<Float>,
    override val swipeSpec: SpringSpec<Float>?,
    override val distance: UserActionDistance?,
    override val transformations: List<Transformation>,
) : TransformationSpec {
    private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
+8 −0
Original line number Diff line number Diff line
@@ -90,6 +90,14 @@ interface TransitionBuilder : PropertyTransformationBuilder {
     */
    var swipeSpec: SpringSpec<Float>?

    /**
     * The distance it takes for this transition to animate from 0% to 100% when it is driven by a
     * [UserAction].
     *
     * If `null`, a default distance will be used that depends on the [UserAction] performed.
     */
    var distance: UserActionDistance?

    /**
     * Define a progress-based range for the transformations inside [builder].
     *
Loading