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

Commit cc82f4af authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Make all swipe transitions irreversible

This CL makes all transitions irreversible, i.e. it's impossible to
start a swipe transition A => B then reverse to A => C by simply
changing direction without lifting the finger. This feature was mostly
useful for the Shade scene, where going Shade => Gone/LS then reverse to
Shade => QS was nice to have.

Bug: 379281707
Test: Manual, played with flexiglass on
Flag: com.android.systemui.scene_container
Change-Id: I65b91ec31d0b7deaa1c32b3d70ca6ac4df9b5553
parent 70719187
Loading
Loading
Loading
Loading
+2 −32
Original line number Diff line number Diff line
@@ -124,7 +124,7 @@ internal class DraggableHandlerImpl(
        return newDragController
    }

    internal fun createSwipeAnimation(swipes: Swipes, result: UserActionResult): SwipeAnimation<*> {
    private fun createSwipeAnimation(swipes: Swipes, result: UserActionResult): SwipeAnimation<*> {
        val upOrLeftResult = swipes.upOrLeftResult
        val downOrRightResult = swipes.downOrRightResult
        val isUpOrLeft =
@@ -248,38 +248,8 @@ private class DragControllerImpl(
                else -> desiredOffset.fastCoerceIn(distance, 0f)
            }

        val consumedDelta = newOffset - previousOffset

        swipeAnimation.dragOffset = newOffset
        val result = swipes.findUserActionResult(directionOffset = newOffset)

        if (result == null) {
            onCancel(canChangeContent = true)
            return 0f
        }

        val currentTransitionIrreversible =
            if (swipeAnimation.isUpOrLeft) {
                swipes.upOrLeftResult?.isIrreversible ?: false
            } else {
                swipes.downOrRightResult?.isIrreversible ?: false
            }

        val needNewTransition =
            !currentTransitionIrreversible &&
                (result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
                    result.transitionKey != swipeAnimation.contentTransition.key)

        if (needNewTransition) {
            // Make sure the current transition will finish to the right current scene.
            swipeAnimation.currentContent = swipeAnimation.fromContent

            val newSwipeAnimation = draggableHandler.createSwipeAnimation(swipes, result)
            newSwipeAnimation.dragOffset = newOffset
            updateTransition(newSwipeAnimation)
        }

        return consumedDelta
        return newOffset - previousOffset
    }

    override suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float {
+1 −16
Original line number Diff line number Diff line
@@ -554,12 +554,6 @@ sealed class UserActionResult(
     * bigger than 100% when the user released their finger. `
     */
    open val requiresFullDistanceSwipe: Boolean,

    /**
     * Whether swiping back in the opposite direction past the origin point of the swipe can replace
     * the action with the action for the opposite direction.
     */
    open val isIrreversible: Boolean = false,
) {
    internal abstract fun toContent(currentScene: SceneKey): ContentKey

@@ -569,7 +563,6 @@ sealed class UserActionResult(
        val toScene: SceneKey,
        override val transitionKey: TransitionKey? = null,
        override val requiresFullDistanceSwipe: Boolean = false,
        override val isIrreversible: Boolean = false,
    ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
        override fun toContent(currentScene: SceneKey): ContentKey = toScene
    }
@@ -579,7 +572,6 @@ sealed class UserActionResult(
        val overlay: OverlayKey,
        override val transitionKey: TransitionKey? = null,
        override val requiresFullDistanceSwipe: Boolean = false,
        override val isIrreversible: Boolean = false,
    ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
        override fun toContent(currentScene: SceneKey): ContentKey = overlay
    }
@@ -622,14 +614,7 @@ sealed class UserActionResult(
             * the user released their finger.
             */
            requiresFullDistanceSwipe: Boolean = false,

            /**
             * Whether swiping back in the opposite direction past the origin point of the swipe can
             * replace the action with the action for the opposite direction.
             */
            isIrreversible: Boolean = false,
        ): UserActionResult =
            ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe, isIrreversible)
        ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe)

        /** A [UserActionResult] that shows [toOverlay]. */
        operator fun invoke(
+2 −55
Original line number Diff line number Diff line
@@ -417,37 +417,6 @@ class DraggableHandlerTest {
        assertIdle(currentScene = SceneA)
    }

    @Test
    fun onDragReversedDirection_changeToScene() = runGestureTest {
        // Drag A -> B with progress 0.6
        val dragController = onDragStarted(overSlop = -60f)
        assertTransition(
            currentScene = SceneA,
            fromScene = SceneA,
            toScene = SceneB,
            progress = 0.6f,
        )

        // Reverse direction such that A -> C now with 0.4
        dragController.onDragDelta(pixels = 100f)
        assertTransition(
            currentScene = SceneA,
            fromScene = SceneA,
            toScene = SceneC,
            progress = 0.4f,
        )

        // After the drag stopped scene C should be committed
        dragController.onDragStoppedAnimateNow(
            velocity = velocityThreshold,
            onAnimationStart = {
                assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
            },
            expectedConsumedVelocity = velocityThreshold,
        )
        assertIdle(currentScene = SceneC)
    }

    @Test
    fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
        onDragStarted(
@@ -497,32 +466,10 @@ class DraggableHandlerTest {
        )
    }

    @Test
    fun onDragWithActionsInBothDirections_dragToOppositeDirectionReplacesAction() = runGestureTest {
        // We are on SceneA. UP -> B, DOWN-> C.
        val dragController = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
        assertTransition(
            currentScene = SceneA,
            fromScene = SceneA,
            toScene = SceneB,
            progress = 0.2f,
        )

        // Reverse drag direction, it will replace the previous transition
        dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
        assertTransition(
            currentScene = SceneA,
            fromScene = SceneA,
            toScene = SceneC,
            progress = 0.3f,
        )
    }

    @Test
    fun onDragWithActionsInBothDirections_dragToOppositeDirectionNotReplaceable() = runGestureTest {
        // We are on SceneA. UP -> B, DOWN-> C. The up swipe is not replaceable though.
        mutableUserActionsA =
            mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
        mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB), Swipe.Down to SceneC)
        val dragController =
            onDragStarted(
                pointersInfo =
@@ -536,7 +483,7 @@ class DraggableHandlerTest {
            progress = 0.2f,
        )

        // Reverse drag direction, it cannot replace the previous transition
        // Reverse drag direction, it does not replace the previous transition.
        dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
        assertTransition(
            currentScene = SceneA,
+3 −9
Original line number Diff line number Diff line
@@ -664,17 +664,11 @@ class SwipeToSceneTest {
            }
        }

        // Swipe down for the default transition from A to B.
        // Move the pointer up to swipe to scene B using the new transition.
        rule.onRoot().performTouchInput {
            down(middle)
            moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
            down(center)
            moveBy(Offset(0f, -touchSlop - 1.dp.toPx()), delayMillis = 1_000)
        }

        assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
        assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(1)

        // Move the pointer up to swipe to scene B using the new transition.
        rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) }
        assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
        assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
    }
+6 −12
Original line number Diff line number Diff line
@@ -85,8 +85,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
            assertThat(actions).isNotEmpty()
            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
            assertThat(actions?.get(Swipe.Down))
                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))

            setUpState(
                isShadeTouchable = false,
@@ -103,8 +102,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
            assertThat(actions).isNotEmpty()
            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
            assertThat(actions?.get(Swipe.Down))
                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
        }

    @Test
@@ -122,7 +120,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
            assertThat(actions?.get(Swipe.Down))
                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))

            setUpState(
                isShadeTouchable = false,
@@ -140,7 +138,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
            assertThat(actions?.get(Swipe.Down))
                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
        }

    @Test
@@ -158,9 +156,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
            assertThat(actions?.get(Swipe.Down))
                .isEqualTo(
                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
                )
                .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))

            setUpState(
                isShadeTouchable = false,
@@ -174,9 +170,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
            assertThat(actions?.get(Swipe.Down))
                .isEqualTo(
                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
                )
                .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
        }

    private fun TestScope.setUpState(
Loading