Remove interception capabilities in STL drag handler
This CL removes the interception capabilities of SceneTransitionLayout:
we won't be able to simply put a finger down during a transition to stop
it, and we instead now have to drag the touch slop to start a new
transition. Moreover, nested scrollables now have priority even during a
transition and will consume scroll events first before sending
unconsumed events to the nested scroll connection/STL draggable.
This capability was a nice-to-have that we added when STL was introduced
from the start. It has a high cost in terms of cognitive overload when
thinking about the participation of STL in the nested scroll chain, and
the gains (if any) are not obvious. Moreover, any complexity added in
the event chaining/interceptions can introduce subtle gesture bugs that
are hard to debug. For instance, b/379847834 seems to be mostly fixed
with this change, *probably* because of the removed pre-scroll
interception. In the worst case, we can still revert back this CL should
we realize that we really need these interception capabilities.
Because we don't intercept transitions, we also don't need to support
the "accelerated swipes" anymore.
Bug: 379281707
Bug: 379847834
Test: atest PlatformSceneTransitionLayouTests
Test: Manual, played with flexiglass and Communal
Flag: com.android.systemui.scene_container
Change-Id: I3128bfd6ec0f960b8b57ffb1b55908b4cc2c2ce7
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index cf0ba51..7a8d20a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:Suppress("NOTHING_TO_INLINE")
-
package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
@@ -97,68 +95,11 @@
internal val positionalThreshold
get() = with(layoutImpl.density) { 56.dp.toPx() }
- /**
- * Whether we should immediately intercept a gesture.
- *
- * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
- * indicating that the transition should be intercepted.
- */
- internal fun shouldImmediatelyIntercept(pointersDown: PointersInfo.PointersDown?): Boolean {
- // We don't intercept the touch if we are not currently driving the transition.
- val dragController = dragController
- if (dragController?.isDrivingTransition != true) {
- return false
- }
-
- val swipeAnimation = dragController.swipeAnimation
-
- // Only intercept the current transition if one of the 2 swipes results is also a transition
- // between the same pair of contents.
- val swipes = computeSwipes(pointersDown)
- val fromContent = layoutImpl.content(swipeAnimation.currentContent)
- val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
- val currentScene = layoutImpl.state.currentScene
- val contentTransition = swipeAnimation.contentTransition
- return (upOrLeft != null &&
- contentTransition.isTransitioningBetween(
- fromContent.key,
- upOrLeft.toContent(currentScene),
- )) ||
- (downOrRight != null &&
- contentTransition.isTransitioningBetween(
- fromContent.key,
- downOrRight.toContent(currentScene),
- ))
- }
-
override fun onDragStarted(
pointersDown: PointersInfo.PointersDown?,
overSlop: Float,
): DragController {
- if (overSlop == 0f) {
- val oldDragController = dragController
- check(oldDragController != null && oldDragController.isDrivingTransition) {
- val isActive = oldDragController?.isDrivingTransition
- "onDragStarted(overSlop=0f) requires an active dragController, but was $isActive"
- }
-
- // This [transition] was already driving the animation: simply take over it.
- // Stop animating and start from the current offset.
- val oldSwipeAnimation = oldDragController.swipeAnimation
-
- // We need to recompute the swipe results since this is a new gesture, and the
- // fromScene.userActions may have changed.
- val swipes = oldDragController.swipes
- swipes.updateSwipesResults(
- fromContent = layoutImpl.content(oldSwipeAnimation.fromContent)
- )
-
- // A new gesture should always create a new SwipeAnimation. This way there cannot be
- // different gestures controlling the same transition.
- val swipeAnimation = createSwipeAnimation(oldSwipeAnimation)
- return updateDragController(swipes, swipeAnimation)
- }
-
+ check(overSlop != 0f)
val swipes = computeSwipes(pointersDown)
val fromContent = layoutImpl.contentForUserActions()
@@ -196,7 +137,7 @@
return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
}
- internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
+ private fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
return layoutImpl.swipeSourceDetector.source(
layoutSize = layoutImpl.lastSize,
position = startedPosition.round(),
@@ -291,71 +232,25 @@
return 0f
}
- val toContent = swipeAnimation.toContent
val distance = swipeAnimation.distance()
val previousOffset = swipeAnimation.dragOffset
val desiredOffset = previousOffset + delta
+ val desiredProgress = swipeAnimation.computeProgress(desiredOffset)
- fun hasReachedToSceneUpOrLeft() =
- distance < 0 &&
- desiredOffset <= distance &&
- swipes.upOrLeftResult?.toContent(layoutState.currentScene) == toContent
+ // Note: the distance could be negative if fromContent is above or to the left of
+ // toContent.
+ val newOffset =
+ when {
+ distance == DistanceUnspecified ||
+ swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
+ desiredOffset
+ distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
+ else -> desiredOffset.fastCoerceIn(distance, 0f)
+ }
- fun hasReachedToSceneDownOrRight() =
- distance > 0 &&
- desiredOffset >= distance &&
- swipes.downOrRightResult?.toContent(layoutState.currentScene) == toContent
+ val consumedDelta = newOffset - previousOffset
- // Considering accelerated swipe: Change fromContent in the case where the user quickly
- // swiped multiple times in the same direction to accelerate the transition from A => B then
- // B => C.
- //
- // TODO(b/290184746): the second drag needs to pass B to work. Add support for flinging
- // twice before B has been reached
- val hasReachedToContent =
- swipeAnimation.currentContent == toContent &&
- (hasReachedToSceneUpOrLeft() || hasReachedToSceneDownOrRight())
-
- val fromContent: ContentKey
- val currentTransitionOffset: Float
- val newOffset: Float
- val consumedDelta: Float
- if (hasReachedToContent) {
- // The new transition will start from the current toContent.
- fromContent = toContent
-
- // The current transition is completed (we have reached the full swipe distance).
- currentTransitionOffset = distance
-
- // The next transition will start with the remaining offset.
- newOffset = desiredOffset - distance
- consumedDelta = delta
- } else {
- fromContent = swipeAnimation.fromContent
- val desiredProgress = swipeAnimation.computeProgress(desiredOffset)
-
- // Note: the distance could be negative if fromContent is above or to the left of
- // toContent.
- currentTransitionOffset =
- when {
- distance == DistanceUnspecified ||
- swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
- desiredOffset
-
- distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
- else -> desiredOffset.fastCoerceIn(distance, 0f)
- }
-
- // If there is a new transition, we will use the same offset
- newOffset = currentTransitionOffset
- consumedDelta = newOffset - previousOffset
- }
-
- swipeAnimation.dragOffset = currentTransitionOffset
-
- if (hasReachedToContent) {
- swipes.updateSwipesResults(draggableHandler.layoutImpl.content(fromContent))
- }
+ swipeAnimation.dragOffset = newOffset
val result = swipes.findUserActionResult(directionOffset = newOffset)
if (result == null) {
@@ -372,13 +267,12 @@
val needNewTransition =
!currentTransitionIrreversible &&
- (hasReachedToContent ||
- result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
+ (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 = fromContent
+ swipeAnimation.currentContent = swipeAnimation.fromContent
val newSwipeAnimation = draggableHandler.createSwipeAnimation(swipes, result)
newSwipeAnimation.dragOffset = newOffset
@@ -499,7 +393,9 @@
var upOrLeftResult: UserActionResult? = null
var downOrRightResult: UserActionResult? = null
- fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> {
+ private fun computeSwipesResults(
+ fromContent: Content
+ ): Pair<UserActionResult?, UserActionResult?> {
val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft)
val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight)
return upOrLeftResult to downOrRightResult
@@ -564,48 +460,9 @@
.shouldEnableSwipes(draggableHandler.orientation)
}
- var isIntercepting = false
-
return PriorityNestedScrollConnection(
orientation = draggableHandler.orientation,
- canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
- val pointersDown: PointersInfo.PointersDown? =
- when (val info = pointersInfoOwner.pointersInfo()) {
- PointersInfo.MouseWheel -> {
- // Do not support mouse wheel interactions
- return@PriorityNestedScrollConnection false
- }
-
- is PointersInfo.PointersDown -> info
- null -> null
- }
-
- canChangeScene =
- if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
-
- val canInterceptSwipeTransition =
- canChangeScene &&
- offsetAvailable != 0f &&
- draggableHandler.shouldImmediatelyIntercept(pointersDown)
- if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
-
- val layoutImpl = draggableHandler.layoutImpl
- val threshold = layoutImpl.transitionInterceptionThreshold
- val hasSnappedToIdle = layoutImpl.state.snapToIdleIfClose(threshold)
- if (hasSnappedToIdle) {
- // If the current swipe transition is closed to 0f or 1f, then we want to
- // interrupt the transition (snapping it to Idle) and scroll the list.
- return@PriorityNestedScrollConnection false
- }
-
- lastPointersDown = pointersDown
-
- // If the current swipe transition is *not* closed to 0f or 1f, then we want the
- // scroll events to intercept the current transition to continue the scene
- // transition.
- isIntercepting = true
- true
- },
+ canStartPreScroll = { _, _, _ -> false },
canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
val behavior: NestedScrollBehavior =
when {
@@ -629,29 +486,22 @@
}
lastPointersDown = pointersDown
- val canStart =
- when (behavior) {
- NestedScrollBehavior.EdgeNoPreview -> {
- canChangeScene = isZeroOffset
- isZeroOffset && shouldEnableSwipes()
- }
-
- NestedScrollBehavior.EdgeWithPreview -> {
- canChangeScene = isZeroOffset
- shouldEnableSwipes()
- }
-
- NestedScrollBehavior.EdgeAlways -> {
- canChangeScene = true
- shouldEnableSwipes()
- }
+ when (behavior) {
+ NestedScrollBehavior.EdgeNoPreview -> {
+ canChangeScene = isZeroOffset
+ isZeroOffset && shouldEnableSwipes()
}
- if (canStart) {
- isIntercepting = false
- }
+ NestedScrollBehavior.EdgeWithPreview -> {
+ canChangeScene = isZeroOffset
+ shouldEnableSwipes()
+ }
- canStart
+ NestedScrollBehavior.EdgeAlways -> {
+ canChangeScene = true
+ shouldEnableSwipes()
+ }
+ }
},
canStartPostFling = { velocityAvailable ->
val behavior: NestedScrollBehavior =
@@ -676,19 +526,14 @@
}
lastPointersDown = pointersDown
- val canStart = behavior.canStartOnPostFling && shouldEnableSwipes()
- if (canStart) {
- isIntercepting = false
- }
-
- canStart
+ behavior.canStartOnPostFling && shouldEnableSwipes()
},
onStart = { firstScroll ->
scrollController(
dragController =
draggableHandler.onDragStarted(
pointersDown = lastPointersDown,
- overSlop = if (isIntercepting) 0f else firstScroll,
+ overSlop = firstScroll,
),
canChangeScene = canChangeScene,
pointersInfoOwner = pointersInfoOwner,
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 59ac68b..dcf199b 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
@@ -80,7 +80,6 @@
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
- startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
onFirstPointerDown: () -> Unit = {},
swipeDetector: SwipeDetector = DefaultSwipeDetector,
@@ -89,7 +88,6 @@
this.then(
MultiPointerDraggableElement(
orientation,
- startDragImmediately,
onDragStarted,
onFirstPointerDown,
swipeDetector,
@@ -99,7 +97,6 @@
private data class MultiPointerDraggableElement(
private val orientation: Orientation,
- private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
private val onDragStarted:
(pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
private val onFirstPointerDown: () -> Unit,
@@ -109,7 +106,6 @@
override fun create(): MultiPointerDraggableNode =
MultiPointerDraggableNode(
orientation = orientation,
- startDragImmediately = startDragImmediately,
onDragStarted = onDragStarted,
onFirstPointerDown = onFirstPointerDown,
swipeDetector = swipeDetector,
@@ -118,7 +114,6 @@
override fun update(node: MultiPointerDraggableNode) {
node.orientation = orientation
- node.startDragImmediately = startDragImmediately
node.onDragStarted = onDragStarted
node.onFirstPointerDown = onFirstPointerDown
node.swipeDetector = swipeDetector
@@ -127,7 +122,6 @@
internal class MultiPointerDraggableNode(
orientation: Orientation,
- var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
var onFirstPointerDown: () -> Unit,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
@@ -297,7 +291,6 @@
try {
detectDragGestures(
orientation = orientation,
- startDragImmediately = startDragImmediately,
onDragStart = { pointersDown, overSlop ->
onDragStarted(pointersDown, overSlop)
},
@@ -438,13 +431,11 @@
* Detect drag gestures in the given [orientation].
*
* This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
- * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for:
- * 1) starting the gesture immediately without requiring a drag >= touch slope;
- * 2) passing the number of pointers down to [onDragStart].
+ * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for passing
+ * the number of pointers down to [onDragStart].
*/
private suspend fun AwaitPointerEventScope.detectDragGestures(
orientation: Orientation,
- startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
onDrag: (controller: DragController, dragAmount: Float) -> Unit,
onDragEnd: (controller: DragController) -> Unit,
@@ -470,71 +461,49 @@
.first()
var overSlop = 0f
- var lastPointersDown: PointersInfo.PointersDown =
+ val onSlopReached = { change: PointerInputChange, over: Float ->
+ if (swipeDetector.detectSwipe(change)) {
+ change.consume()
+ overSlop = over
+ }
+ }
+
+ // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
+ // is public.
+ val drag =
+ when (orientation) {
+ Orientation.Horizontal ->
+ awaitHorizontalTouchSlopOrCancellation(consumablePointer.id, onSlopReached)
+ Orientation.Vertical ->
+ awaitVerticalTouchSlopOrCancellation(consumablePointer.id, onSlopReached)
+ } ?: return
+
+ val lastPointersDown =
checkNotNull(pointersInfo()) {
"We should have pointers down, last event: $currentEvent"
}
as PointersInfo.PointersDown
-
- val drag =
- if (startDragImmediately(lastPointersDown)) {
- consumablePointer.consume()
- consumablePointer
- } else {
- val onSlopReached = { change: PointerInputChange, over: Float ->
- if (swipeDetector.detectSwipe(change)) {
- change.consume()
- overSlop = over
- }
+ // 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.
+ if (overSlop == 0f) {
+ // If the user drags in the opposite direction, the delta becomes zero because
+ // we return to the original point. Therefore, we should use the previous event
+ // to calculate the direction.
+ val delta = (drag.position - drag.previousPosition).toFloat()
+ check(delta != 0f) {
+ buildString {
+ append("delta is equal to 0 ")
+ append("touchSlop ${currentValueOf(LocalViewConfiguration).touchSlop} ")
+ append("consumablePointer.position ${consumablePointer.position} ")
+ append("drag.position ${drag.position} ")
+ append("drag.previousPosition ${drag.previousPosition}")
}
-
- // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
- // is public.
- val drag =
- when (orientation) {
- Orientation.Horizontal ->
- awaitHorizontalTouchSlopOrCancellation(
- consumablePointer.id,
- onSlopReached,
- )
- Orientation.Vertical ->
- awaitVerticalTouchSlopOrCancellation(
- consumablePointer.id,
- onSlopReached,
- )
- } ?: return
-
- lastPointersDown =
- checkNotNull(pointersInfo()) {
- "We should have pointers down, last event: $currentEvent"
- }
- as PointersInfo.PointersDown
- // 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 (overSlop == 0f) {
- // If the user drags in the opposite direction, the delta becomes zero because
- // we return to the original point. Therefore, we should use the previous event
- // to calculate the direction.
- val delta = (drag.position - drag.previousPosition).toFloat()
- check(delta != 0f) {
- buildString {
- append("delta is equal to 0 ")
- append("touchSlop ${currentValueOf(LocalViewConfiguration).touchSlop} ")
- append("consumablePointer.position ${consumablePointer.position} ")
- append("drag.position ${drag.position} ")
- append("drag.previousPosition ${drag.previousPosition}")
- }
- }
- overSlop = delta.sign
- }
- drag
}
+ overSlop = delta.sign
+ }
val controller = onDragStart(lastPointersDown, overSlop)
-
val successful: Boolean
try {
onDrag(controller, overSlop)
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 5ab306a..6ef8b86 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
@@ -149,7 +149,6 @@
delegate(
MultiPointerDraggableNode(
orientation = draggableHandler.orientation,
- startDragImmediately = ::startDragImmediately,
onDragStarted = draggableHandler::onDragStarted,
onFirstPointerDown = ::onFirstPointerDown,
swipeDetector = swipeDetector,
@@ -198,21 +197,6 @@
) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds)
override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
-
- private fun startDragImmediately(pointersDown: PointersInfo.PointersDown): Boolean {
- // Immediately start the drag if the user can't swipe in the other direction and the gesture
- // handler can intercept it.
- return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersDown)
- }
-
- private fun canOppositeSwipe(): Boolean {
- val oppositeOrientation =
- when (draggableHandler.orientation) {
- Orientation.Vertical -> Orientation.Horizontal
- Orientation.Horizontal -> Orientation.Vertical
- }
- return draggableHandler.contentForSwipes().shouldEnableSwipes(oppositeOrientation)
- }
}
/** Find the [ScrollBehaviorOwner] for the current orientation. */
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index a1077cf..394568d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -234,12 +234,6 @@
)
}
- fun onDragStartedImmediately(
- pointersInfo: PointersInfo.PointersDown = pointersDown()
- ): DragController {
- return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f)
- }
-
fun onDragStarted(
draggableHandler: DraggableHandler,
pointersInfo: PointersInfo.PointersDown = pointersDown(),
@@ -602,82 +596,6 @@
}
@Test
- fun onAcceleratedScroll_scrollToThirdScene() = runGestureTest {
- // Drag A -> B with progress 0.2
- val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
- assertTransition(
- currentScene = SceneA,
- fromScene = SceneA,
- toScene = SceneB,
- progress = 0.2f,
- )
-
- // Start animation A -> B with progress 0.2 -> 1.0
- dragController1.onDragStoppedAnimateLater(velocity = -velocityThreshold)
- assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-
- // While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
- // the transition to B -> C with progress 0.2
- val dragController2 = onDragStartedImmediately()
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 1f))
- assertTransition(
- currentScene = SceneB,
- fromScene = SceneB,
- toScene = SceneC,
- progress = 0.2f,
- )
-
- // After the drag stopped scene C should be committed
- dragController2.onDragStoppedAnimateNow(
- velocity = -velocityThreshold,
- onAnimationStart = {
- assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
- },
- expectedConsumedVelocity = -velocityThreshold,
- )
- assertIdle(currentScene = SceneC)
- }
-
- @Test
- fun onAcceleratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
- dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.2f))
- dragController1.onDragStoppedAnimateLater(velocity = -velocityThreshold)
- assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-
- mutableUserActionsA = emptyMap()
- mutableUserActionsB = emptyMap()
-
- // start acceleratedScroll and scroll over to B -> null
- val dragController2 = onDragStartedImmediately()
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
-
- // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
- // still be called. Make sure that they don't crash or change the scene
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
- dragController2.onDragStoppedAnimateNow(
- velocity = 0f,
- onAnimationStart = {
- assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
- },
- expectedConsumedVelocity = 0f,
- )
-
- advanceUntilIdle()
- assertIdle(SceneB)
-
- // These events can still come in after the animation has settled
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
- dragController2.onDragStoppedAnimateNow(
- velocity = 0f,
- onAnimationStart = { assertIdle(SceneB) },
- expectedConsumedVelocity = 0f,
- )
- assertIdle(SceneB)
- }
-
- @Test
fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
@@ -711,9 +629,8 @@
dragController1.onDragStoppedAnimateLater(velocity = down(fractionOfScreen = 0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- val dragController2 = onDragStartedImmediately()
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
- assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
}
@Test
@@ -728,7 +645,7 @@
assertThat(isUserInputOngoing).isFalse()
// Start a new gesture while the offset is animating
- onDragStartedImmediately()
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertThat(isUserInputOngoing).isTrue()
}
@@ -812,36 +729,6 @@
}
@Test
- fun scrollAndFling_scrollLessThanInterceptable_goToIdleOnCurrentScene() = runGestureTest {
- val firstScroll = (transitionInterceptionThreshold - 0.0001f) * SCREEN_SIZE
- val secondScroll = 1f
-
- preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
-
- assertIdle(SceneA)
- }
-
- @Test
- fun scrollAndFling_scrollMinInterceptable_interceptPreScrollEvents() = runGestureTest {
- val firstScroll = (transitionInterceptionThreshold + 0.0001f) * SCREEN_SIZE
- val secondScroll = 1f
-
- preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
-
- assertTransition(progress = (firstScroll + secondScroll) / SCREEN_SIZE)
- }
-
- @Test
- fun scrollAndFling_scrollMaxInterceptable_interceptPreScrollEvents() = runGestureTest {
- val firstScroll = -(1f - transitionInterceptionThreshold - 0.0001f) * SCREEN_SIZE
- val secondScroll = -1f
-
- preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
-
- assertTransition(progress = -(firstScroll + secondScroll) / SCREEN_SIZE)
- }
-
- @Test
fun scrollAndFling_scrollMoreThanInterceptable_goToIdleOnNextScene() = runGestureTest {
val firstScroll = -(1f - transitionInterceptionThreshold + 0.0001f) * SCREEN_SIZE
val secondScroll = -0.01f
@@ -1025,7 +912,7 @@
// now we can intercept the scroll events
nestedScroll.scroll(available = -offsetY10)
- assertThat(progress).isEqualTo(0.2f)
+ assertThat(progress).isEqualTo(0.1f)
// this should be ignored, we are scrolling now!
dragController.onDragStoppedAnimateNow(
@@ -1036,10 +923,10 @@
assertTransition(currentScene = SceneA)
nestedScroll.scroll(available = -offsetY10)
- assertThat(progress).isEqualTo(0.3f)
+ assertThat(progress).isEqualTo(0.2f)
nestedScroll.scroll(available = -offsetY10)
- assertThat(progress).isEqualTo(0.4f)
+ assertThat(progress).isEqualTo(0.3f)
nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
assertTransition(currentScene = SceneB)
@@ -1050,57 +937,6 @@
}
@Test
- fun interceptTransition() = runGestureTest {
- // Start at scene C.
- navigateToSceneC()
-
- // Swipe up from the middle to transition to scene B.
- val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- toScene = SceneB,
- progress = 0.1f,
- isUserInputOngoing = true,
- )
-
- val firstTransition = transitionState
-
- // During the current gesture, start a new gesture, still in the middle of the screen. We
- // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
- // should be 0f.
- assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
- onDragStartedImmediately(pointersInfo = middle)
-
- // We should have intercepted the transition, so the transition should be the same object.
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- toScene = SceneB,
- progress = 0.1f,
- isUserInputOngoing = true,
- )
- // We should have a new transition
- assertThat(transitionState).isNotSameInstanceAs(firstTransition)
-
- // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
- // C leads to scene A (and not B), the previous transitions is *not* intercepted and we
- // instead animate from C to A.
- val bottom = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
- assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
- onDragStarted(pointersInfo = bottom, overSlop = up(0.1f))
-
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- toScene = SceneA,
- isUserInputOngoing = true,
- )
- assertThat(transitionState).isNotSameInstanceAs(firstTransition)
- }
-
- @Test
fun freezeAndAnimateToCurrentState() = runGestureTest {
// Start at scene C.
navigateToSceneC()
@@ -1110,9 +946,6 @@
onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
- // The current transition can be intercepted.
- assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
-
// Freeze the transition.
val transition = transitionState as Transition
transition.freezeAndAnimateToCurrentState()
@@ -1123,19 +956,6 @@
}
@Test
- fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
- onDragStarted(overSlop = up(0.1f))
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
-
- layoutState.startTransitionImmediately(
- animationScope = testScope.backgroundScope,
- transition(SceneA, SceneB),
- )
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
- }
-
- @Test
fun blockTransition() = runGestureTest {
assertIdle(SceneA)
@@ -1154,30 +974,6 @@
}
@Test
- fun blockInterceptedTransition() = runGestureTest {
- assertIdle(SceneA)
-
- // Swipe up to B.
- val dragController1 = onDragStarted(overSlop = up(0.1f))
- assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
- dragController1.onDragStoppedAnimateLater(velocity = -velocityThreshold)
- assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-
- // Intercept the transition and swipe down back to scene A.
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
- val dragController2 = onDragStartedImmediately()
-
- // Block the transition when the user release their finger.
- canChangeScene = { false }
- dragController2.onDragStoppedAnimateNow(
- velocity = velocityThreshold,
- onAnimationStart = { assertTransition(fromScene = SceneA, toScene = SceneB) },
- expectedConsumedVelocity = velocityThreshold,
- )
- assertIdle(SceneB)
- }
-
- @Test
fun scrollFromIdleWithNoTargetScene_shouldUseOverscrollSpecIfAvailable() = runGestureTest {
layoutState.transitions = transitions {
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
@@ -1531,25 +1327,6 @@
}
@Test
- fun interceptingTransitionKeepsDistance() = runGestureTest {
- var swipeDistance = 75f
- layoutState.transitions = transitions {
- from(SceneA, to = SceneB) { distance = UserActionDistance { _, _, _ -> swipeDistance } }
- }
-
- // Start transition.
- val controller = onDragStarted(overSlop = -50f)
- assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f)
-
- // Intercept the transition and change the swipe distance. The original distance and
- // progress should be the same.
- swipeDistance = 50f
- controller.onDragStoppedAnimateLater(0f)
- onDragStartedImmediately()
- assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f)
- }
-
- @Test
fun requireFullDistanceSwipe() = runGestureTest {
mutableUserActionsA +=
Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true)
@@ -1579,19 +1356,6 @@
}
@Test
- fun interceptingTransitionReplacesCurrentTransition() = runGestureTest {
- val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.5f))
- val transition = assertThat(layoutState.transitionState).isSceneTransition()
- controller.onDragStoppedAnimateLater(velocity = 0f)
-
- // Intercept the transition.
- onDragStartedImmediately()
- val newTransition = assertThat(layoutState.transitionState).isSceneTransition()
- assertThat(newTransition).isNotSameInstanceAs(transition)
- assertThat(newTransition.replacedTransition).isSameInstanceAs(transition)
- }
-
- @Test
fun showOverlay() = runGestureTest {
mutableUserActionsA = mapOf(Swipe.Down to UserActionResult.ShowOverlay(OverlayA))
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index cb3e433..4153350 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -101,7 +101,6 @@
.thenIf(enabled) {
Modifier.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
started = true
SimpleDragController(
@@ -169,8 +168,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- // We want to start a drag gesture immediately
- startDragImmediately = { true },
onDragStarted = { _, _ ->
started = true
SimpleDragController(
@@ -242,7 +239,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
started = true
SimpleDragController(
@@ -361,7 +357,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
started = true
SimpleDragController(
@@ -466,7 +461,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
verticalStarted = true
SimpleDragController(
@@ -478,7 +472,6 @@
)
.multiPointerDraggable(
orientation = Orientation.Horizontal,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
horizontalStarted = true
SimpleDragController(
@@ -570,7 +563,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
swipeDetector =
object : SwipeDetector {
override fun detectSwipe(change: PointerInputChange): Boolean {
@@ -636,7 +628,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
swipeDetector =
object : SwipeDetector {
override fun detectSwipe(change: PointerInputChange): Boolean {
@@ -738,7 +729,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
SimpleDragController(
onDrag = { consumedOnDrag = it },
@@ -809,7 +799,6 @@
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
SimpleDragController(
onDrag = { /* do nothing */ },