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

Commit e0b825dd authored by omarmt's avatar omarmt
Browse files

Support for multiple gestures at once

To ensure that there is only one source in control of the scene at a
time (using the takePriority method).
In this case, the drag and stop methods called by other sources will be
ignored.

For example, this could happen when using the draggable modifier (with
startDragImmediately set to true) and nestedScroll.

In this case, the sequence of events received by our
SceneGestureHandler is as follows:
- draggable: start
- draggable: drag
- nestedScroll: start
- nestedScroll: drag
- draggable: stop // <-- this event should be ignored
- nestedScroll: drag
- nestedScroll: drag
- nestedScroll: drag
- ...
- nestedScroll: stop

Test: atest SceneGestureHandlerTest
Bug: 291025415
Change-Id: I74fdc27e6b7786cd425254f84df07615afa4e6a1
parent 20f85cba
Loading
Loading
Loading
Loading
+19 −2
Original line number Diff line number Diff line
@@ -157,6 +157,8 @@ class SceneGestureHandler(
     */
    private val positionalThreshold = with(layoutImpl.density) { 56.dp.toPx() }

    internal var gestureWithPriority: Any? = null

    internal fun onDragStarted() {
        if (isDrivingTransition) {
            // This [transition] was already driving the animation: simply take over it.
@@ -525,17 +527,23 @@ private class SceneDraggableHandler(
    private val gestureHandler: SceneGestureHandler,
) : DraggableHandler {
    override suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) {
        gestureHandler.gestureWithPriority = this
        gestureHandler.onDragStarted()
    }

    override fun onDelta(pixels: Float) {
        if (gestureHandler.gestureWithPriority == this) {
            gestureHandler.onDrag(delta = pixels)
        }
    }

    override suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) {
        if (gestureHandler.gestureWithPriority == this) {
            gestureHandler.gestureWithPriority = null
            gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
        }
    }
}

@VisibleForTesting
class SceneNestedScrollHandler(
@@ -615,10 +623,15 @@ class SceneNestedScrollHandler(
            },
            canContinueScroll = { priorityScene == gestureHandler.swipeTransitionToScene.key },
            onStart = {
                gestureHandler.gestureWithPriority = this
                priorityScene = nextScene
                gestureHandler.onDragStarted()
            },
            onScroll = { offsetAvailable ->
                if (gestureHandler.gestureWithPriority != this) {
                    return@PriorityNestedScrollConnection Offset.Zero
                }

                val amount = offsetAvailable.toAmount()

                // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
@@ -628,6 +641,10 @@ class SceneNestedScrollHandler(
                amount.toOffset()
            },
            onStop = { velocityAvailable ->
                if (gestureHandler.gestureWithPriority != this) {
                    return@PriorityNestedScrollConnection Velocity.Zero
                }

                priorityScene = null

                gestureHandler.onDragStopped(
+48 −0
Original line number Diff line number Diff line
@@ -312,4 +312,52 @@ class SceneGestureHandlerTest {
        advanceUntilIdle()
        assertScene(currentScene = SceneA, isIdle = true)
    }

    @Test
    fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
        draggable.onDelta(deltaInPixels10)
        assertScene(currentScene = SceneA, isIdle = true)
    }
    @Test
    fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
        draggable.onDragStopped(coroutineScope, velocityThreshold)
        assertScene(currentScene = SceneA, isIdle = true)
    }

    @Test
    fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
        nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
        assertScene(currentScene = SceneA, isIdle = true)
    }

    @Test
    fun startNestedScrollWhileDragging() = runGestureTest {
        draggable.onDragStarted(coroutineScope, Offset.Zero)
        assertScene(currentScene = SceneA, isIdle = false)
        val transition = transitionState as Transition

        draggable.onDelta(deltaInPixels10)
        assertThat(transition.progress).isEqualTo(0.1f)

        // now we can intercept the scroll events
        nestedScrollEvents(available = offsetY10)
        assertThat(transition.progress).isEqualTo(0.2f)

        // this should be ignored, we are scrolling now!
        draggable.onDragStopped(coroutineScope, velocityThreshold)
        assertScene(currentScene = SceneA, isIdle = false)

        nestedScrollEvents(available = offsetY10)
        assertThat(transition.progress).isEqualTo(0.3f)

        nestedScrollEvents(available = offsetY10)
        assertThat(transition.progress).isEqualTo(0.4f)

        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
        assertScene(currentScene = SceneC, isIdle = false)

        // wait for the stop animation
        advanceUntilIdle()
        assertScene(currentScene = SceneC, isIdle = true)
    }
}