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

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

Fix regression in SceneGestureHandler

This CL fixes a crash introduced in ag/25994973: if the user drags its
finger by the exact same amount as the touch slop, then the overSlop
will be 0f and we won't be able to compute in which direction the user
is dragging. This CL makes sure that the overSlop is not 0f, unless we
are intercepting a current swipe transition.

Bug: 291053278
Test: SwipeToSceneTest
Flag: N/A
Change-Id: Ia99a388a6d177fa706cc066d4a9223b8efa919dd
parent fb2738a2
Loading
Loading
Loading
Loading
+25 −5
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.util.fastForEach
import kotlin.math.sign

/**
 * Make an element draggable in the given [orientation].
@@ -223,12 +224,31 @@ private suspend fun PointerInputScope.detectDragGestures(

                // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once
                // it is public.
                val drag =
                    when (orientation) {
                        Orientation.Horizontal ->
                            awaitHorizontalTouchSlopOrCancellation(down.id, onSlopReached)
                        Orientation.Vertical ->
                            awaitVerticalTouchSlopOrCancellation(down.id, onSlopReached)
                    }

                // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
                // the touch slop. However, the overSlop we pass to onDragStarted() is used to
                // compute the direction we are dragging in, so overSlop should never be 0f unless
                // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
                // true).
                if (drag != null && overSlop == 0f) {
                    val deltaOffset = drag.position - initialDown.position
                    val delta =
                        when (orientation) {
                            Orientation.Horizontal -> deltaOffset.y
                            Orientation.Vertical -> deltaOffset.y
                        }
                    check(delta != 0f)
                    overSlop = delta.sign
                }

                drag
            }

        if (drag != null) {
+40 −1
Original line number Diff line number Diff line
@@ -74,6 +74,7 @@ class SwipeToSceneTest {
                    mapOf(
                        Swipe.Left to TestScenes.SceneB,
                        Swipe.Down to TestScenes.SceneC,
                        Swipe.Up to TestScenes.SceneB,
                    ),
            ) {
                Box(Modifier.fillMaxSize())
@@ -357,7 +358,7 @@ class SwipeToSceneTest {
        // detected as a drag event.
        var touchSlop = 0f

        val layoutState = MutableSceneTransitionLayoutState(TestScenes.SceneA)
        val layoutState = layoutState()
        val verticalSwipeDistance = 50.dp
        assertThat(verticalSwipeDistance).isNotEqualTo(LayoutHeight)

@@ -392,4 +393,42 @@ class SwipeToSceneTest {
        assertThat(transition).isNotNull()
        assertThat(transition!!.progress).isEqualTo(0.5f)
    }

    @Test
    fun swipeByTouchSlop() {
        val layoutState = layoutState()
        var touchSlop = 0f
        rule.setContent {
            touchSlop = LocalViewConfiguration.current.touchSlop
            TestContent(layoutState)
        }

        // Swipe down by exactly touchSlop, so that the drag overSlop is 0f.
        rule.onRoot().performTouchInput {
            down(middle)
            moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
        }

        // We should still correctly compute that we are swiping down to scene C.
        var transition = layoutState.currentTransition
        assertThat(transition).isNotNull()
        assertThat(transition?.toScene).isEqualTo(TestScenes.SceneC)

        // Release the finger, animating back to scene A.
        rule.onRoot().performTouchInput { up() }
        rule.waitForIdle()
        assertThat(layoutState.currentTransition).isNull()
        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)

        // Swipe up by exactly touchSlop, so that the drag overSlop is 0f.
        rule.onRoot().performTouchInput {
            down(middle)
            moveBy(Offset(0f, -touchSlop), delayMillis = 1_000)
        }

        // We should still correctly compute that we are swiping up to scene B.
        transition = layoutState.currentTransition
        assertThat(transition).isNotNull()
        assertThat(transition?.toScene).isEqualTo(TestScenes.SceneB)
    }
}