diff options
5 files changed, 79 insertions, 528 deletions
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 cf0ba5196bad..7a8d20a7b85d 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 class DraggableHandlerImpl( 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 @@ internal class DraggableHandlerImpl( 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 @@ private class DragControllerImpl( return 0f } - val toContent = swipeAnimation.toContent val distance = swipeAnimation.distance() val previousOffset = swipeAnimation.dragOffset val desiredOffset = previousOffset + delta + val desiredProgress = swipeAnimation.computeProgress(desiredOffset) + + // 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 hasReachedToSceneUpOrLeft() = - distance < 0 && - desiredOffset <= distance && - swipes.upOrLeftResult?.toContent(layoutState.currentScene) == toContent - - fun hasReachedToSceneDownOrRight() = - distance > 0 && - desiredOffset >= distance && - swipes.downOrRightResult?.toContent(layoutState.currentScene) == toContent - - // 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 + val consumedDelta = newOffset - previousOffset - if (hasReachedToContent) { - swipes.updateSwipesResults(draggableHandler.layoutImpl.content(fromContent)) - } + swipeAnimation.dragOffset = newOffset val result = swipes.findUserActionResult(directionOffset = newOffset) if (result == null) { @@ -372,13 +267,12 @@ private class DragControllerImpl( 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 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol 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 @@ internal class NestedScrollHandlerImpl( .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 @@ internal class NestedScrollHandlerImpl( } lastPointersDown = pointersDown - val canStart = - when (behavior) { - NestedScrollBehavior.EdgeNoPreview -> { - canChangeScene = isZeroOffset - isZeroOffset && shouldEnableSwipes() - } - - NestedScrollBehavior.EdgeWithPreview -> { - canChangeScene = isZeroOffset - shouldEnableSwipes() - } + when (behavior) { + NestedScrollBehavior.EdgeNoPreview -> { + canChangeScene = isZeroOffset + isZeroOffset && shouldEnableSwipes() + } - NestedScrollBehavior.EdgeAlways -> { - canChangeScene = true - shouldEnableSwipes() - } + NestedScrollBehavior.EdgeWithPreview -> { + canChangeScene = isZeroOffset + shouldEnableSwipes() } - if (canStart) { - isIntercepting = false + NestedScrollBehavior.EdgeAlways -> { + canChangeScene = true + shouldEnableSwipes() + } } - - canStart }, canStartPostFling = { velocityAvailable -> val behavior: NestedScrollBehavior = @@ -676,19 +526,14 @@ internal class NestedScrollHandlerImpl( } 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 501fbb034c51..f5f01d4d1a35 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 @@ import kotlinx.coroutines.launch @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 @@ internal fun Modifier.multiPointerDraggable( this.then( MultiPointerDraggableElement( orientation, - startDragImmediately, onDragStarted, onFirstPointerDown, swipeDetector, @@ -99,7 +97,6 @@ internal fun Modifier.multiPointerDraggable( 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 @@ private data class MultiPointerDraggableElement( override fun create(): MultiPointerDraggableNode = MultiPointerDraggableNode( orientation = orientation, - startDragImmediately = startDragImmediately, onDragStarted = onDragStarted, onFirstPointerDown = onFirstPointerDown, swipeDetector = swipeDetector, @@ -118,7 +114,6 @@ private data class MultiPointerDraggableElement( override fun update(node: MultiPointerDraggableNode) { node.orientation = orientation - node.startDragImmediately = startDragImmediately node.onDragStarted = onDragStarted node.onFirstPointerDown = onFirstPointerDown node.swipeDetector = swipeDetector @@ -127,7 +122,6 @@ private data class MultiPointerDraggableElement( 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, @@ -293,7 +287,6 @@ internal class MultiPointerDraggableNode( try { detectDragGestures( orientation = orientation, - startDragImmediately = startDragImmediately, onDragStart = { pointersDown, overSlop -> onDragStarted(pointersDown, overSlop) }, @@ -434,13 +427,11 @@ internal class MultiPointerDraggableNode( * 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, @@ -466,71 +457,49 @@ internal class MultiPointerDraggableNode( .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 - } - } - - // 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 + // 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}") } - 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 5ab306a63a7e..6ef8b86cf72a 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 @@ private class SwipeToSceneNode( delegate( MultiPointerDraggableNode( orientation = draggableHandler.orientation, - startDragImmediately = ::startDragImmediately, onDragStarted = draggableHandler::onDragStarted, onFirstPointerDown = ::onFirstPointerDown, swipeDetector = swipeDetector, @@ -198,21 +197,6 @@ private class SwipeToSceneNode( ) = 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 a1077cf95133..394568d34fa2 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 @@ class DraggableHandlerTest { ) } - 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 @@ class DraggableHandlerTest { } @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 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { } @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 @@ class DraggableHandlerTest { // 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 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { } @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 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { } @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 @@ class DraggableHandlerTest { } @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 @@ class DraggableHandlerTest { } @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 @@ class DraggableHandlerTest { } @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 cb3e433ec4a5..4153350fce60 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 @@ class MultiPointerDraggableTest { .thenIf(enabled) { Modifier.multiPointerDraggable( orientation = Orientation.Vertical, - startDragImmediately = { false }, onDragStarted = { _, _ -> started = true SimpleDragController( @@ -169,8 +168,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .multiPointerDraggable( orientation = Orientation.Vertical, - // We want to start a drag gesture immediately - startDragImmediately = { true }, onDragStarted = { _, _ -> started = true SimpleDragController( @@ -242,7 +239,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .multiPointerDraggable( orientation = Orientation.Vertical, - startDragImmediately = { false }, onDragStarted = { _, _ -> started = true SimpleDragController( @@ -361,7 +357,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .multiPointerDraggable( orientation = Orientation.Vertical, - startDragImmediately = { false }, onDragStarted = { _, _ -> started = true SimpleDragController( @@ -466,7 +461,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .multiPointerDraggable( orientation = Orientation.Vertical, - startDragImmediately = { false }, onDragStarted = { _, _ -> verticalStarted = true SimpleDragController( @@ -478,7 +472,6 @@ class MultiPointerDraggableTest { ) .multiPointerDraggable( orientation = Orientation.Horizontal, - startDragImmediately = { false }, onDragStarted = { _, _ -> horizontalStarted = true SimpleDragController( @@ -570,7 +563,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .multiPointerDraggable( orientation = Orientation.Vertical, - startDragImmediately = { false }, swipeDetector = object : SwipeDetector { override fun detectSwipe(change: PointerInputChange): Boolean { @@ -636,7 +628,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .multiPointerDraggable( orientation = Orientation.Vertical, - startDragImmediately = { false }, swipeDetector = object : SwipeDetector { override fun detectSwipe(change: PointerInputChange): Boolean { @@ -738,7 +729,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .multiPointerDraggable( orientation = Orientation.Vertical, - startDragImmediately = { false }, onDragStarted = { _, _ -> SimpleDragController( onDrag = { consumedOnDrag = it }, @@ -809,7 +799,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .multiPointerDraggable( orientation = Orientation.Vertical, - startDragImmediately = { false }, onDragStarted = { _, _ -> SimpleDragController( onDrag = { /* do nothing */ }, |