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

Commit 5111d79c authored by omarmt's avatar omarmt
Browse files

Forced to max one offsetAnimationJob in progress

Previously, it was possible for a new job to start before the previous
one was canceled. This could lead to an inconsistent state because some
properties are initialized at the start of a job and reset when it ends.

To avoid this, the new job is now required to wait for the previous job
to finish before it can start. This ensures that all properties are in a
 consistent state before the new job begins.

Test: mp :SystemUIComposeGallery
Bug: 291025415
Change-Id: I6d3b0cc00df13da25c711e42e713f4b13e4611b9
parent b06a5995
Loading
Loading
Loading
Loading
+41 −39
Original line number Diff line number Diff line
@@ -148,14 +148,18 @@ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition
    /** The animatable used to animate the offset once the user lifted its finger. */
    val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold)

    /**
     * The job currently animating [offsetAnimatable], if it is animating. Note that setting this to
     * a new job will automatically cancel the previous one.
     */
    var offsetAnimationJob: Job? = null
        set(value) {
            field?.cancel()
            field = value
    /** Job to check that there is at most one offset animation in progress. */
    private var offsetAnimationJob: Job? = null

    /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
    fun startOffsetAnimation(job: () -> Job) {
        stopOffsetAnimation()
        offsetAnimationJob = job()
    }

    /** Stops any ongoing offset animation. */
    fun stopOffsetAnimation() {
        offsetAnimationJob?.cancel()
    }

    /** The absolute distance between [fromScene] and [toScene]. */
@@ -208,8 +212,7 @@ private fun onDragStarted(
        if (transition.isAnimatingOffset) {
            // Stop animating and start from where the current offset. Setting the animation job to
            // `null` will effectively cancel the animation.
            transition.isAnimatingOffset = false
            transition.offsetAnimationJob = null
            transition.stopOffsetAnimation()
            transition.dragOffset = transition.offsetAnimatable.value
        }

@@ -227,9 +230,8 @@ private fun onDragStarted(
    // to fromScene, which will effectively be treated the same as Idle(fromScene).
    transition._toScene = fromScene

    transition.stopOffsetAnimation()
    transition.dragOffset = 0f
    transition.isAnimatingOffset = false
    transition.offsetAnimationJob = null

    // Use the layout size in the swipe orientation for swipe distance.
    // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
@@ -450,7 +452,8 @@ private fun CoroutineScope.animateOffset(
    targetOffset: Float,
    targetScene: SceneKey,
) {
    transition.offsetAnimationJob = launch {
    transition.startOffsetAnimation {
        launch {
                if (!transition.isAnimatingOffset) {
                    transition.offsetAnimatable.snapTo(transition.dragOffset)
                }
@@ -466,15 +469,15 @@ private fun CoroutineScope.animateOffset(
                    initialVelocity = initialVelocity,
                )

        // Now that the animation is done, the state should be idle. Note that if the state was
        // changed since this animation started, some external code changed it and we shouldn't do
        // anything here. Note also that this job will be cancelled in the case where the user
        // intercepts this swipe.
                // Now that the animation is done, the state should be idle. Note that if the state
                // was changed since this animation started, some external code changed it and we
                // shouldn't do anything here. Note also that this job will be cancelled in the case
                // where the user intercepts this swipe.
                if (layoutImpl.state.transitionState == transition) {
                    layoutImpl.state.transitionState = TransitionState.Idle(targetScene)
                }

        transition.offsetAnimationJob = null
            }
            .also { it.invokeOnCompletion { transition.isAnimatingOffset = false } }
    }
}

@@ -509,9 +512,8 @@ private fun CoroutineScope.animateOverscroll(
    transition._toScene = layoutImpl.scene(target.sceneKey)
    transition._distance = target.distance
    transition.absoluteDistance = target.distance.absoluteValue
    transition.stopOffsetAnimation()
    transition.dragOffset = 0f
    transition.isAnimatingOffset = false
    transition.offsetAnimationJob = null

    layoutImpl.state.transitionState = transition