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

Commit 9e2330ef authored by Omar Miatello's avatar Omar Miatello Committed by Android (Google) Code Review
Browse files

Merge changes Ib0dde168,Ia6a610ee into main

* changes:
  Refactor Swipes
  Make SwipeTransition an implementation detail of SceneGestureHandler
parents 50605977 26ec7328
Loading
Loading
Loading
Loading
+251 −220
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ internal class SceneGestureHandler(
    val draggable: DraggableHandler = SceneDraggableHandler(this)

    private var _swipeTransition: SwipeTransition? = null
    internal var swipeTransition: SwipeTransition
    private var swipeTransition: SwipeTransition
        get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
        set(value) {
            _swipeTransition = value
@@ -92,10 +92,6 @@ internal class SceneGestureHandler(
    /** The [Swipes] associated to the current gesture. */
    private var swipes: Swipes? = null

    /** The [UserActionResult] associated to up and down swipes. */
    private var upOrLeftResult: UserActionResult? = null
    private var downOrRightResult: UserActionResult? = null

    /**
     * Whether we should immediately intercept a gesture.
     *
@@ -128,7 +124,7 @@ internal class SceneGestureHandler(
            // This [transition] was already driving the animation: simply take over it.
            // Stop animating and start from where the current offset.
            swipeTransition.cancelOffsetAnimation()
            updateSwipesResults(swipeTransition._fromScene)
            swipes!!.updateSwipesResults(swipeTransition._fromScene)
            return
        }

@@ -144,16 +140,24 @@ internal class SceneGestureHandler(
        }

        val fromScene = layoutImpl.scene(transitionState.currentScene)
        updateSwipes(fromScene, startedPosition, pointersDown)

        val result =
            findUserActionResult(fromScene, directionOffset = overSlop, updateSwipesResults = true)
                ?: return
        updateTransition(SwipeTransition(fromScene, result), force = true)
    }
        val newSwipes = computeSwipes(fromScene, startedPosition, pointersDown)
        swipes = newSwipes
        val result = newSwipes.findUserActionResult(fromScene, overSlop, true)

        // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be
        // defined.
        if (result == null) return

        val newSwipeTransition =
            SwipeTransition(
                fromScene = fromScene,
                result = result,
                swipes = newSwipes,
                layoutImpl = layoutImpl,
                orientation = orientation
            )

    private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
        this.swipes = computeSwipes(fromScene, startedPosition, pointersDown)
        updateTransition(newSwipeTransition, force = true)
    }

    private fun computeSwipes(
@@ -210,13 +214,6 @@ internal class SceneGestureHandler(
        }
    }

    private fun Scene.getAbsoluteDistance(distance: UserActionDistance?): Float {
        val targetSize = this.targetSize
        return with(distance ?: DefaultSwipeDistance) {
            layoutImpl.density.absoluteDistance(targetSize, orientation)
        }
    }

    internal fun onDrag(delta: Float) {
        if (delta == 0f || !isDrivingTransition) return
        swipeTransition.dragOffset += delta
@@ -226,15 +223,17 @@ internal class SceneGestureHandler(

        val isNewFromScene = fromScene.key != swipeTransition.fromScene
        val result =
            findUserActionResult(
                fromScene,
                swipeTransition.dragOffset,
                updateSwipesResults = isNewFromScene,
            swipes!!.findUserActionResult(
                fromScene = fromScene,
                directionOffset = swipeTransition.dragOffset,
                updateSwipesResults = isNewFromScene
            )
                ?: run {
                    onDragStopped(delta, true)

        if (result == null) {
            onDragStopped(velocity = delta, canChangeScene = true)
            return
        }

        swipeTransition.dragOffset += acceleratedOffset

        if (
@@ -242,23 +241,18 @@ internal class SceneGestureHandler(
                result.toScene != swipeTransition.toScene ||
                result.transitionKey != swipeTransition.key
        ) {
            updateTransition(
                SwipeTransition(fromScene, result).apply {
                    this.dragOffset = swipeTransition.dragOffset
                }
            val newSwipeTransition =
                SwipeTransition(
                        fromScene = fromScene,
                        result = result,
                        swipes = swipes!!,
                        layoutImpl = layoutImpl,
                        orientation = orientation
                    )
        }
    }
                    .apply { dragOffset = swipeTransition.dragOffset }

    private fun updateSwipesResults(fromScene: Scene) {
        val (upOrLeftResult, downOrRightResult) =
            computeSwipesResults(
                fromScene,
                this.swipes ?: error("updateSwipes() should be called before updateSwipesResults()")
            )

        this.upOrLeftResult = upOrLeftResult
        this.downOrRightResult = downOrRightResult
            updateTransition(newSwipeTransition)
        }
    }

    private fun computeSwipesResults(
@@ -295,74 +289,20 @@ internal class SceneGestureHandler(

        // If the swipe was not committed, don't do anything.
        if (swipeTransition._currentScene != toScene) {
            return Pair(fromScene, 0f)
            return fromScene to 0f
        }

        // If the offset is past the distance then let's change fromScene so that the user can swipe
        // to the next screen or go back to the previous one.
        val offset = swipeTransition.dragOffset
        return if (offset <= -absoluteDistance && upOrLeftResult?.toScene == toScene.key) {
            Pair(toScene, absoluteDistance)
        } else if (offset >= absoluteDistance && downOrRightResult?.toScene == toScene.key) {
            Pair(toScene, -absoluteDistance)
        } else {
            Pair(fromScene, 0f)
        }
    }

    /**
     * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
     *
     * @param fromScene the scene from which we look for the target
     * @param directionOffset signed float that indicates the direction. Positive is down or right
     *   negative is up or left.
     * @param updateSwipesResults whether the target scenes should be updated to the current values
     *   held in the Scenes map. Usually we don't want to update them while doing a drag, because
     *   this could change the target scene (jump cutting) to a different scene, when some system
     *   state changed the targets the background. However, an update is needed any time we
     *   calculate the targets for a new fromScene.
     * @return null when there are no targets in either direction. If one direction is null and you
     *   drag into the null direction this function will return the opposite direction, assuming
     *   that the users intention is to start the drag into the other direction eventually. If
     *   [directionOffset] is 0f and both direction are available, it will default to
     *   [upOrLeftResult].
     */
    private fun findUserActionResult(
        fromScene: Scene,
        directionOffset: Float,
        updateSwipesResults: Boolean,
    ): UserActionResult? {
        if (updateSwipesResults) updateSwipesResults(fromScene)

        return when {
            upOrLeftResult == null && downOrRightResult == null -> null
            (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
                upOrLeftResult
            else -> downOrRightResult
        }
    }

    /**
     * A strict version of [findUserActionResult] that will return null when there is no Scene in
     * [directionOffset] direction
     */
    private fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
        return when {
            directionOffset > 0f -> upOrLeftResult
            directionOffset < 0f -> downOrRightResult
            else -> null
        }
    }

    private fun computeAbsoluteDistance(
        fromScene: Scene,
        result: UserActionResult,
    ): Float {
        return if (result == upOrLeftResult) {
            -fromScene.getAbsoluteDistance(result.distance)
        return if (offset <= -absoluteDistance && swipes!!.upOrLeftResult?.toScene == toScene.key) {
            toScene to absoluteDistance
        } else if (
            offset >= absoluteDistance && swipes!!.downOrRightResult?.toScene == toScene.key
        ) {
            toScene to -absoluteDistance
        } else {
            check(result == downOrRightResult)
            fromScene.getAbsoluteDistance(result.distance)
            fromScene to 0f
        }
    }

@@ -430,19 +370,24 @@ internal class SceneGestureHandler(

            if (startFromIdlePosition) {
                // If there is a target scene, we start the overscroll animation.
                val result =
                    findUserActionResultStrict(velocity)
                        ?: run {
                val result = swipes!!.findUserActionResultStrict(velocity)
                if (result == null) {
                    // We will not animate
                    layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
                    return
                }

                updateTransition(
                    SwipeTransition(fromScene, result).apply {
                        _currentScene = swipeTransition._currentScene
                    }
                val newSwipeTransition =
                    SwipeTransition(
                            fromScene = fromScene,
                            result = result,
                            swipes = swipes!!,
                            layoutImpl = layoutImpl,
                            orientation = orientation
                        )
                        .apply { _currentScene = swipeTransition._currentScene }

                updateTransition(newSwipeTransition)
                animateTo(targetScene = fromScene, targetOffset = 0f)
            } else {
                // We were between two scenes: animate to the initial scene.
@@ -486,22 +431,46 @@ internal class SceneGestureHandler(
        }
    }

    private fun SwipeTransition(fromScene: Scene, result: UserActionResult): SwipeTransition {
    companion object {
        private const val TAG = "SceneGestureHandler"
    }
}

private fun SwipeTransition(
    fromScene: Scene,
    result: UserActionResult,
    swipes: Swipes,
    layoutImpl: SceneTransitionLayoutImpl,
    orientation: Orientation,
): SwipeTransition {
    val upOrLeftResult = swipes.upOrLeftResult
    val downOrRightResult = swipes.downOrRightResult
    val userActionDistance = result.distance ?: DefaultSwipeDistance
    val absoluteDistance =
        with(userActionDistance) {
            layoutImpl.density.absoluteDistance(fromScene.targetSize, orientation)
        }

    return SwipeTransition(
            result.transitionKey,
            fromScene,
            layoutImpl.scene(result.toScene),
            computeAbsoluteDistance(fromScene, result),
        key = result.transitionKey,
        _fromScene = fromScene,
        _toScene = layoutImpl.scene(result.toScene),
        distance =
            when (result) {
                upOrLeftResult -> -absoluteDistance
                downOrRightResult -> absoluteDistance
                else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)")
            },
    )
}

    internal class SwipeTransition(
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].
     * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
     * or to the left of [toScene]
     */
    val distance: Float,
) : TransitionState.Transition(_fromScene.key, _toScene.key) {
@@ -521,8 +490,7 @@ internal class SceneGestureHandler(
    var dragOffset by mutableFloatStateOf(0f)

    /**
         * Whether the offset is animated (the user lifted their finger) or if it is driven by
         * gesture.
     * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
     */
    var isAnimatingOffset by mutableStateOf(false)

@@ -591,10 +559,6 @@ internal class SceneGestureHandler(
    }
}

    companion object {
        private const val TAG = "SceneGestureHandler"
    }

private object DefaultSwipeDistance : UserActionDistance {
    override fun Density.absoluteDistance(
        fromSceneSize: IntSize,
@@ -613,7 +577,74 @@ internal class SceneGestureHandler(
    val downOrRight: Swipe?,
    val upOrLeftNoSource: Swipe?,
    val downOrRightNoSource: Swipe?,
    )
) {
    /** The [UserActionResult] associated to up and down swipes. */
    var upOrLeftResult: UserActionResult? = null
    var downOrRightResult: UserActionResult? = null

    fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
        val userActions = fromScene.userActions
        fun result(swipe: Swipe?): UserActionResult? {
            return userActions[swipe ?: return null]
        }

        val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource)
        val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource)
        return upOrLeftResult to downOrRightResult
    }

    fun updateSwipesResults(fromScene: Scene) {
        val (upOrLeftResult, downOrRightResult) = computeSwipesResults(fromScene)

        this.upOrLeftResult = upOrLeftResult
        this.downOrRightResult = downOrRightResult
    }

    /**
     * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
     *
     * @param fromScene the scene from which we look for the target
     * @param directionOffset signed float that indicates the direction. Positive is down or right
     *   negative is up or left.
     * @param updateSwipesResults whether the target scenes should be updated to the current values
     *   held in the Scenes map. Usually we don't want to update them while doing a drag, because
     *   this could change the target scene (jump cutting) to a different scene, when some system
     *   state changed the targets the background. However, an update is needed any time we
     *   calculate the targets for a new fromScene.
     * @return null when there are no targets in either direction. If one direction is null and you
     *   drag into the null direction this function will return the opposite direction, assuming
     *   that the users intention is to start the drag into the other direction eventually. If
     *   [directionOffset] is 0f and both direction are available, it will default to
     *   [upOrLeftResult].
     */
    fun findUserActionResult(
        fromScene: Scene,
        directionOffset: Float,
        updateSwipesResults: Boolean,
    ): UserActionResult? {
        if (updateSwipesResults) {
            updateSwipesResults(fromScene)
        }

        return when {
            upOrLeftResult == null && downOrRightResult == null -> null
            (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
                upOrLeftResult
            else -> downOrRightResult
        }
    }

    /**
     * A strict version of [findUserActionResult] that will return null when there is no Scene in
     * [directionOffset] direction
     */
    fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
        return when {
            directionOffset > 0f -> upOrLeftResult
            directionOffset < 0f -> downOrRightResult
            else -> null
        }
    }
}

private class SceneDraggableHandler(
+5 −3
Original line number Diff line number Diff line
@@ -127,6 +127,9 @@ class SceneGestureHandlerTest {
        val progress: Float
            get() = (transitionState as Transition).progress

        val isUserInputOngoing: Boolean
            get() = (transitionState as Transition).isUserInputOngoing

        fun advanceUntilIdle() {
            testScope.testScheduler.advanceUntilIdle()
        }
@@ -538,12 +541,11 @@ class SceneGestureHandlerTest {
        onDragStopped(velocity = velocityThreshold)

        assertTransition(currentScene = SceneC)
        assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
        assertThat(isUserInputOngoing).isFalse()

        // Start a new gesture while the offset is animating
        onDragStartedImmediately()
        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
        assertThat(isUserInputOngoing).isTrue()
    }

    @Test