summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt7
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt10
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt6
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt37
4 files changed, 59 insertions, 1 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index b329534e6e3a..3487730945da 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -81,6 +81,7 @@ internal fun Modifier.multiPointerDraggable(
enabled: () -> Boolean,
startDragImmediately: (startedPosition: Offset) -> Boolean,
onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ onFirstPointerDown: () -> Unit = {},
swipeDetector: SwipeDetector = DefaultSwipeDetector,
dispatcher: NestedScrollDispatcher,
): Modifier =
@@ -90,6 +91,7 @@ internal fun Modifier.multiPointerDraggable(
enabled,
startDragImmediately,
onDragStarted,
+ onFirstPointerDown,
swipeDetector,
dispatcher,
)
@@ -101,6 +103,7 @@ private data class MultiPointerDraggableElement(
private val startDragImmediately: (startedPosition: Offset) -> Boolean,
private val onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ private val onFirstPointerDown: () -> Unit,
private val swipeDetector: SwipeDetector,
private val dispatcher: NestedScrollDispatcher,
) : ModifierNodeElement<MultiPointerDraggableNode>() {
@@ -110,6 +113,7 @@ private data class MultiPointerDraggableElement(
enabled = enabled,
startDragImmediately = startDragImmediately,
onDragStarted = onDragStarted,
+ onFirstPointerDown = onFirstPointerDown,
swipeDetector = swipeDetector,
dispatcher = dispatcher,
)
@@ -119,6 +123,7 @@ private data class MultiPointerDraggableElement(
node.enabled = enabled
node.startDragImmediately = startDragImmediately
node.onDragStarted = onDragStarted
+ node.onFirstPointerDown = onFirstPointerDown
node.swipeDetector = swipeDetector
}
}
@@ -129,6 +134,7 @@ internal class MultiPointerDraggableNode(
var startDragImmediately: (startedPosition: Offset) -> Boolean,
var onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ var onFirstPointerDown: () -> Unit,
var swipeDetector: SwipeDetector = DefaultSwipeDetector,
private val dispatcher: NestedScrollDispatcher,
) :
@@ -225,6 +231,7 @@ internal class MultiPointerDraggableNode(
startedPosition = null
} else if (startedPosition == null) {
startedPosition = pointers.first().position
+ onFirstPointerDown()
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index f06214645144..d1e83bacf40a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -67,6 +67,7 @@ private class SwipeToSceneNode(
enabled = ::enabled,
startDragImmediately = ::startDragImmediately,
onDragStarted = draggableHandler::onDragStarted,
+ onFirstPointerDown = ::onFirstPointerDown,
swipeDetector = swipeDetector,
dispatcher = dispatcher,
)
@@ -101,6 +102,15 @@ private class SwipeToSceneNode(
delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl))
}
+ private fun onFirstPointerDown() {
+ // When we drag our finger across the screen, the NestedScrollConnection keeps track of all
+ // the scroll events until we lift our finger. However, in some cases, the connection might
+ // not receive the "up" event. This can lead to an incorrect initial state for the gesture.
+ // To prevent this issue, we can call the reset() method when the first finger touches the
+ // screen. This ensures that the NestedScrollConnection starts from a correct state.
+ nestedScrollHandlerImpl.connection.reset()
+ }
+
override fun onDetach() {
// Make sure we reset the scroll connection when this modifier is removed from composition
nestedScrollHandlerImpl.connection.reset()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 228f7ba48d3e..16fb533bbe06 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -129,7 +129,11 @@ class PriorityNestedScrollConnection(
return onPriorityStop(available)
}
- /** Method to call before destroying the object or to reset the initial state. */
+ /**
+ * Method to call before destroying the object or to reset the initial state.
+ *
+ * TODO(b/303224944) This method should be removed.
+ */
fun reset() {
// Step 3c: To ensure that an onStop is always called for every onStart.
onPriorityStop(velocity = Velocity.Zero)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
index 9ebc42650d45..d8a06f54e74b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
@@ -23,6 +23,9 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalViewConfiguration
@@ -266,4 +269,38 @@ class NestedScrollToSceneTest {
val transition = assertThat(state.transitionState).isTransition()
assertThat(transition).hasProgress(0.5f)
}
+
+ @Test
+ fun resetScrollTracking_afterMissingPointerUpEvent() {
+ var canScroll = true
+ var hasScrollable by mutableStateOf(true)
+ val state = setup2ScenesAndScrollTouchSlop {
+ if (hasScrollable) {
+ Modifier.scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
+ } else {
+ Modifier
+ }
+ }
+
+ // The gesture is consumed by the component in the scene.
+ scrollUp(percent = 0.2f)
+
+ // STL keeps track of the scroll consumed. The scene remains in Idle.
+ assertThat(state.transitionState).isIdle()
+
+ // The scrollable component disappears, and does not send the signal (pointer up) to reset
+ // the consumed amount.
+ hasScrollable = false
+ pointerUp()
+
+ // A new scrollable component appears and allows the scene to consume the scroll.
+ hasScrollable = true
+ canScroll = false
+ pointerDownAndScrollTouchSlop()
+ scrollUp(percent = 0.2f)
+
+ // STL can only start the transition if it has reset the amount of scroll consumed.
+ val transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasProgress(0.2f)
+ }
}