Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +36 −191 Original line number Original line Diff line number Diff line Loading @@ -14,8 +14,6 @@ * limitations under the License. * limitations under the License. */ */ @file:Suppress("NOTHING_TO_INLINE") package com.android.compose.animation.scene package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation Loading Loading @@ -97,68 +95,11 @@ internal class DraggableHandlerImpl( internal val positionalThreshold internal val positionalThreshold get() = with(layoutImpl.density) { 56.dp.toPx() } 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( override fun onDragStarted( pointersDown: PointersInfo.PointersDown?, pointersDown: PointersInfo.PointersDown?, overSlop: Float, overSlop: Float, ): DragController { ): DragController { if (overSlop == 0f) { check(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) } val swipes = computeSwipes(pointersDown) val swipes = computeSwipes(pointersDown) val fromContent = layoutImpl.contentForUserActions() val fromContent = layoutImpl.contentForUserActions() Loading Loading @@ -196,7 +137,7 @@ internal class DraggableHandlerImpl( return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation) return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation) } } internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? { private fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? { return layoutImpl.swipeSourceDetector.source( return layoutImpl.swipeSourceDetector.source( layoutSize = layoutImpl.lastSize, layoutSize = layoutImpl.lastSize, position = startedPosition.round(), position = startedPosition.round(), Loading Loading @@ -291,71 +232,25 @@ private class DragControllerImpl( return 0f return 0f } } val toContent = swipeAnimation.toContent val distance = swipeAnimation.distance() val distance = swipeAnimation.distance() val previousOffset = swipeAnimation.dragOffset val previousOffset = swipeAnimation.dragOffset val desiredOffset = previousOffset + delta val desiredOffset = previousOffset + delta 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) val desiredProgress = swipeAnimation.computeProgress(desiredOffset) // Note: the distance could be negative if fromContent is above or to the left of // Note: the distance could be negative if fromContent is above or to the left of // toContent. // toContent. currentTransitionOffset = val newOffset = when { when { distance == DistanceUnspecified || distance == DistanceUnspecified || swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) -> swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) -> desiredOffset desiredOffset distance > 0f -> desiredOffset.fastCoerceIn(0f, distance) distance > 0f -> desiredOffset.fastCoerceIn(0f, distance) else -> desiredOffset.fastCoerceIn(distance, 0f) else -> desiredOffset.fastCoerceIn(distance, 0f) } } // If there is a new transition, we will use the same offset val consumedDelta = newOffset - previousOffset newOffset = currentTransitionOffset consumedDelta = newOffset - previousOffset } swipeAnimation.dragOffset = currentTransitionOffset if (hasReachedToContent) { swipeAnimation.dragOffset = newOffset swipes.updateSwipesResults(draggableHandler.layoutImpl.content(fromContent)) } val result = swipes.findUserActionResult(directionOffset = newOffset) val result = swipes.findUserActionResult(directionOffset = newOffset) if (result == null) { if (result == null) { Loading @@ -372,13 +267,12 @@ private class DragControllerImpl( val needNewTransition = val needNewTransition = !currentTransitionIrreversible && !currentTransitionIrreversible && (hasReachedToContent || (result.toContent(layoutState.currentScene) != swipeAnimation.toContent || result.toContent(layoutState.currentScene) != swipeAnimation.toContent || result.transitionKey != swipeAnimation.contentTransition.key) result.transitionKey != swipeAnimation.contentTransition.key) if (needNewTransition) { if (needNewTransition) { // Make sure the current transition will finish to the right current scene. // 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) val newSwipeAnimation = draggableHandler.createSwipeAnimation(swipes, result) newSwipeAnimation.dragOffset = newOffset newSwipeAnimation.dragOffset = newOffset Loading Loading @@ -499,7 +393,9 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol var upOrLeftResult: UserActionResult? = null var upOrLeftResult: UserActionResult? = null var downOrRightResult: 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 upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft) val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight) val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight) return upOrLeftResult to downOrRightResult return upOrLeftResult to downOrRightResult Loading Loading @@ -564,48 +460,9 @@ internal class NestedScrollHandlerImpl( .shouldEnableSwipes(draggableHandler.orientation) .shouldEnableSwipes(draggableHandler.orientation) } } var isIntercepting = false return PriorityNestedScrollConnection( return PriorityNestedScrollConnection( orientation = draggableHandler.orientation, orientation = draggableHandler.orientation, canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> canStartPreScroll = { _, _, _ -> false }, 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 }, canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ -> canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ -> val behavior: NestedScrollBehavior = val behavior: NestedScrollBehavior = when { when { Loading @@ -629,7 +486,6 @@ internal class NestedScrollHandlerImpl( } } lastPointersDown = pointersDown lastPointersDown = pointersDown val canStart = when (behavior) { when (behavior) { NestedScrollBehavior.EdgeNoPreview -> { NestedScrollBehavior.EdgeNoPreview -> { canChangeScene = isZeroOffset canChangeScene = isZeroOffset Loading @@ -646,12 +502,6 @@ internal class NestedScrollHandlerImpl( shouldEnableSwipes() shouldEnableSwipes() } } } } if (canStart) { isIntercepting = false } canStart }, }, canStartPostFling = { velocityAvailable -> canStartPostFling = { velocityAvailable -> val behavior: NestedScrollBehavior = val behavior: NestedScrollBehavior = Loading @@ -676,19 +526,14 @@ internal class NestedScrollHandlerImpl( } } lastPointersDown = pointersDown lastPointersDown = pointersDown val canStart = behavior.canStartOnPostFling && shouldEnableSwipes() behavior.canStartOnPostFling && shouldEnableSwipes() if (canStart) { isIntercepting = false } canStart }, }, onStart = { firstScroll -> onStart = { firstScroll -> scrollController( scrollController( dragController = dragController = draggableHandler.onDragStarted( draggableHandler.onDragStarted( pointersDown = lastPointersDown, pointersDown = lastPointersDown, overSlop = if (isIntercepting) 0f else firstScroll, overSlop = firstScroll, ), ), canChangeScene = canChangeScene, canChangeScene = canChangeScene, pointersInfoOwner = pointersInfoOwner, pointersInfoOwner = pointersInfoOwner, Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +37 −68 Original line number Original line Diff line number Diff line Loading @@ -80,7 +80,6 @@ import kotlinx.coroutines.launch @Stable @Stable internal fun Modifier.multiPointerDraggable( internal fun Modifier.multiPointerDraggable( orientation: Orientation, orientation: Orientation, startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onFirstPointerDown: () -> Unit = {}, onFirstPointerDown: () -> Unit = {}, swipeDetector: SwipeDetector = DefaultSwipeDetector, swipeDetector: SwipeDetector = DefaultSwipeDetector, Loading @@ -89,7 +88,6 @@ internal fun Modifier.multiPointerDraggable( this.then( this.then( MultiPointerDraggableElement( MultiPointerDraggableElement( orientation, orientation, startDragImmediately, onDragStarted, onDragStarted, onFirstPointerDown, onFirstPointerDown, swipeDetector, swipeDetector, Loading @@ -99,7 +97,6 @@ internal fun Modifier.multiPointerDraggable( private data class MultiPointerDraggableElement( private data class MultiPointerDraggableElement( private val orientation: Orientation, private val orientation: Orientation, private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, private val onDragStarted: private val onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, private val onFirstPointerDown: () -> Unit, private val onFirstPointerDown: () -> Unit, Loading @@ -109,7 +106,6 @@ private data class MultiPointerDraggableElement( override fun create(): MultiPointerDraggableNode = override fun create(): MultiPointerDraggableNode = MultiPointerDraggableNode( MultiPointerDraggableNode( orientation = orientation, orientation = orientation, startDragImmediately = startDragImmediately, onDragStarted = onDragStarted, onDragStarted = onDragStarted, onFirstPointerDown = onFirstPointerDown, onFirstPointerDown = onFirstPointerDown, swipeDetector = swipeDetector, swipeDetector = swipeDetector, Loading @@ -118,7 +114,6 @@ private data class MultiPointerDraggableElement( override fun update(node: MultiPointerDraggableNode) { override fun update(node: MultiPointerDraggableNode) { node.orientation = orientation node.orientation = orientation node.startDragImmediately = startDragImmediately node.onDragStarted = onDragStarted node.onDragStarted = onDragStarted node.onFirstPointerDown = onFirstPointerDown node.onFirstPointerDown = onFirstPointerDown node.swipeDetector = swipeDetector node.swipeDetector = swipeDetector Loading @@ -127,7 +122,6 @@ private data class MultiPointerDraggableElement( internal class MultiPointerDraggableNode( internal class MultiPointerDraggableNode( orientation: Orientation, orientation: Orientation, var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, var onFirstPointerDown: () -> Unit, var onFirstPointerDown: () -> Unit, swipeDetector: SwipeDetector = DefaultSwipeDetector, swipeDetector: SwipeDetector = DefaultSwipeDetector, Loading Loading @@ -293,7 +287,6 @@ internal class MultiPointerDraggableNode( try { try { detectDragGestures( detectDragGestures( orientation = orientation, orientation = orientation, startDragImmediately = startDragImmediately, onDragStart = { pointersDown, overSlop -> onDragStart = { pointersDown, overSlop -> onDragStarted(pointersDown, overSlop) onDragStarted(pointersDown, overSlop) }, }, Loading Loading @@ -434,13 +427,11 @@ internal class MultiPointerDraggableNode( * Detect drag gestures in the given [orientation]. * Detect drag gestures in the given [orientation]. * * * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for: * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for passing * 1) starting the gesture immediately without requiring a drag >= touch slope; * the number of pointers down to [onDragStart]. * 2) passing the number of pointers down to [onDragStart]. */ */ private suspend fun AwaitPointerEventScope.detectDragGestures( private suspend fun AwaitPointerEventScope.detectDragGestures( orientation: Orientation, orientation: Orientation, startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onDrag: (controller: DragController, dragAmount: Float) -> Unit, onDrag: (controller: DragController, dragAmount: Float) -> Unit, onDragEnd: (controller: DragController) -> Unit, onDragEnd: (controller: DragController) -> Unit, Loading @@ -466,17 +457,6 @@ internal class MultiPointerDraggableNode( .first() .first() var overSlop = 0f var overSlop = 0f var lastPointersDown: PointersInfo.PointersDown = 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 -> val onSlopReached = { change: PointerInputChange, over: Float -> if (swipeDetector.detectSwipe(change)) { if (swipeDetector.detectSwipe(change)) { change.consume() change.consume() Loading @@ -489,27 +469,19 @@ internal class MultiPointerDraggableNode( val drag = val drag = when (orientation) { when (orientation) { Orientation.Horizontal -> Orientation.Horizontal -> awaitHorizontalTouchSlopOrCancellation( awaitHorizontalTouchSlopOrCancellation(consumablePointer.id, onSlopReached) consumablePointer.id, onSlopReached, ) Orientation.Vertical -> Orientation.Vertical -> awaitVerticalTouchSlopOrCancellation( awaitVerticalTouchSlopOrCancellation(consumablePointer.id, onSlopReached) consumablePointer.id, onSlopReached, ) } ?: return } ?: return lastPointersDown = val lastPointersDown = checkNotNull(pointersInfo()) { checkNotNull(pointersInfo()) { "We should have pointers down, last event: $currentEvent" "We should have pointers down, last event: $currentEvent" } } as PointersInfo.PointersDown as PointersInfo.PointersDown // Make sure that overSlop is not 0f. This can happen when the user drags by exactly // 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 // 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 // compute the direction we are dragging in, so overSlop should never be 0f. // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned // true). if (overSlop == 0f) { if (overSlop == 0f) { // If the user drags in the opposite direction, the delta becomes zero because // 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 // we return to the original point. Therefore, we should use the previous event Loading @@ -526,11 +498,8 @@ internal class MultiPointerDraggableNode( } } overSlop = delta.sign overSlop = delta.sign } } drag } val controller = onDragStart(lastPointersDown, overSlop) val controller = onDragStart(lastPointersDown, overSlop) val successful: Boolean val successful: Boolean try { try { onDrag(controller, overSlop) onDrag(controller, overSlop) Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +0 −16 Original line number Original line Diff line number Diff line Loading @@ -149,7 +149,6 @@ private class SwipeToSceneNode( delegate( delegate( MultiPointerDraggableNode( MultiPointerDraggableNode( orientation = draggableHandler.orientation, orientation = draggableHandler.orientation, startDragImmediately = ::startDragImmediately, onDragStarted = draggableHandler::onDragStarted, onDragStarted = draggableHandler::onDragStarted, onFirstPointerDown = ::onFirstPointerDown, onFirstPointerDown = ::onFirstPointerDown, swipeDetector = swipeDetector, swipeDetector = swipeDetector, Loading Loading @@ -198,21 +197,6 @@ private class SwipeToSceneNode( ) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds) ) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds) override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput() 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. */ /** Find the [ScrollBehaviorOwner] for the current orientation. */ Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +6 −242 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +0 −11 Original line number Original line Diff line number Diff line Loading @@ -101,7 +101,6 @@ class MultiPointerDraggableTest { .thenIf(enabled) { .thenIf(enabled) { Modifier.multiPointerDraggable( Modifier.multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> started = true started = true SimpleDragController( SimpleDragController( Loading Loading @@ -169,8 +168,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, // We want to start a drag gesture immediately startDragImmediately = { true }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> started = true started = true SimpleDragController( SimpleDragController( Loading Loading @@ -242,7 +239,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> started = true started = true SimpleDragController( SimpleDragController( Loading Loading @@ -361,7 +357,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> started = true started = true SimpleDragController( SimpleDragController( Loading Loading @@ -466,7 +461,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> verticalStarted = true verticalStarted = true SimpleDragController( SimpleDragController( Loading @@ -478,7 +472,6 @@ class MultiPointerDraggableTest { ) ) .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Horizontal, orientation = Orientation.Horizontal, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> horizontalStarted = true horizontalStarted = true SimpleDragController( SimpleDragController( Loading Loading @@ -570,7 +563,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, swipeDetector = swipeDetector = object : SwipeDetector { object : SwipeDetector { override fun detectSwipe(change: PointerInputChange): Boolean { override fun detectSwipe(change: PointerInputChange): Boolean { Loading Loading @@ -636,7 +628,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, swipeDetector = swipeDetector = object : SwipeDetector { object : SwipeDetector { override fun detectSwipe(change: PointerInputChange): Boolean { override fun detectSwipe(change: PointerInputChange): Boolean { Loading Loading @@ -738,7 +729,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> SimpleDragController( SimpleDragController( onDrag = { consumedOnDrag = it }, onDrag = { consumedOnDrag = it }, Loading Loading @@ -809,7 +799,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> SimpleDragController( SimpleDragController( onDrag = { /* do nothing */ }, onDrag = { /* do nothing */ }, Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +36 −191 Original line number Original line Diff line number Diff line Loading @@ -14,8 +14,6 @@ * limitations under the License. * limitations under the License. */ */ @file:Suppress("NOTHING_TO_INLINE") package com.android.compose.animation.scene package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation Loading Loading @@ -97,68 +95,11 @@ internal class DraggableHandlerImpl( internal val positionalThreshold internal val positionalThreshold get() = with(layoutImpl.density) { 56.dp.toPx() } 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( override fun onDragStarted( pointersDown: PointersInfo.PointersDown?, pointersDown: PointersInfo.PointersDown?, overSlop: Float, overSlop: Float, ): DragController { ): DragController { if (overSlop == 0f) { check(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) } val swipes = computeSwipes(pointersDown) val swipes = computeSwipes(pointersDown) val fromContent = layoutImpl.contentForUserActions() val fromContent = layoutImpl.contentForUserActions() Loading Loading @@ -196,7 +137,7 @@ internal class DraggableHandlerImpl( return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation) return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation) } } internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? { private fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? { return layoutImpl.swipeSourceDetector.source( return layoutImpl.swipeSourceDetector.source( layoutSize = layoutImpl.lastSize, layoutSize = layoutImpl.lastSize, position = startedPosition.round(), position = startedPosition.round(), Loading Loading @@ -291,71 +232,25 @@ private class DragControllerImpl( return 0f return 0f } } val toContent = swipeAnimation.toContent val distance = swipeAnimation.distance() val distance = swipeAnimation.distance() val previousOffset = swipeAnimation.dragOffset val previousOffset = swipeAnimation.dragOffset val desiredOffset = previousOffset + delta val desiredOffset = previousOffset + delta 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) val desiredProgress = swipeAnimation.computeProgress(desiredOffset) // Note: the distance could be negative if fromContent is above or to the left of // Note: the distance could be negative if fromContent is above or to the left of // toContent. // toContent. currentTransitionOffset = val newOffset = when { when { distance == DistanceUnspecified || distance == DistanceUnspecified || swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) -> swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) -> desiredOffset desiredOffset distance > 0f -> desiredOffset.fastCoerceIn(0f, distance) distance > 0f -> desiredOffset.fastCoerceIn(0f, distance) else -> desiredOffset.fastCoerceIn(distance, 0f) else -> desiredOffset.fastCoerceIn(distance, 0f) } } // If there is a new transition, we will use the same offset val consumedDelta = newOffset - previousOffset newOffset = currentTransitionOffset consumedDelta = newOffset - previousOffset } swipeAnimation.dragOffset = currentTransitionOffset if (hasReachedToContent) { swipeAnimation.dragOffset = newOffset swipes.updateSwipesResults(draggableHandler.layoutImpl.content(fromContent)) } val result = swipes.findUserActionResult(directionOffset = newOffset) val result = swipes.findUserActionResult(directionOffset = newOffset) if (result == null) { if (result == null) { Loading @@ -372,13 +267,12 @@ private class DragControllerImpl( val needNewTransition = val needNewTransition = !currentTransitionIrreversible && !currentTransitionIrreversible && (hasReachedToContent || (result.toContent(layoutState.currentScene) != swipeAnimation.toContent || result.toContent(layoutState.currentScene) != swipeAnimation.toContent || result.transitionKey != swipeAnimation.contentTransition.key) result.transitionKey != swipeAnimation.contentTransition.key) if (needNewTransition) { if (needNewTransition) { // Make sure the current transition will finish to the right current scene. // 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) val newSwipeAnimation = draggableHandler.createSwipeAnimation(swipes, result) newSwipeAnimation.dragOffset = newOffset newSwipeAnimation.dragOffset = newOffset Loading Loading @@ -499,7 +393,9 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol var upOrLeftResult: UserActionResult? = null var upOrLeftResult: UserActionResult? = null var downOrRightResult: 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 upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft) val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight) val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight) return upOrLeftResult to downOrRightResult return upOrLeftResult to downOrRightResult Loading Loading @@ -564,48 +460,9 @@ internal class NestedScrollHandlerImpl( .shouldEnableSwipes(draggableHandler.orientation) .shouldEnableSwipes(draggableHandler.orientation) } } var isIntercepting = false return PriorityNestedScrollConnection( return PriorityNestedScrollConnection( orientation = draggableHandler.orientation, orientation = draggableHandler.orientation, canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> canStartPreScroll = { _, _, _ -> false }, 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 }, canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ -> canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ -> val behavior: NestedScrollBehavior = val behavior: NestedScrollBehavior = when { when { Loading @@ -629,7 +486,6 @@ internal class NestedScrollHandlerImpl( } } lastPointersDown = pointersDown lastPointersDown = pointersDown val canStart = when (behavior) { when (behavior) { NestedScrollBehavior.EdgeNoPreview -> { NestedScrollBehavior.EdgeNoPreview -> { canChangeScene = isZeroOffset canChangeScene = isZeroOffset Loading @@ -646,12 +502,6 @@ internal class NestedScrollHandlerImpl( shouldEnableSwipes() shouldEnableSwipes() } } } } if (canStart) { isIntercepting = false } canStart }, }, canStartPostFling = { velocityAvailable -> canStartPostFling = { velocityAvailable -> val behavior: NestedScrollBehavior = val behavior: NestedScrollBehavior = Loading @@ -676,19 +526,14 @@ internal class NestedScrollHandlerImpl( } } lastPointersDown = pointersDown lastPointersDown = pointersDown val canStart = behavior.canStartOnPostFling && shouldEnableSwipes() behavior.canStartOnPostFling && shouldEnableSwipes() if (canStart) { isIntercepting = false } canStart }, }, onStart = { firstScroll -> onStart = { firstScroll -> scrollController( scrollController( dragController = dragController = draggableHandler.onDragStarted( draggableHandler.onDragStarted( pointersDown = lastPointersDown, pointersDown = lastPointersDown, overSlop = if (isIntercepting) 0f else firstScroll, overSlop = firstScroll, ), ), canChangeScene = canChangeScene, canChangeScene = canChangeScene, pointersInfoOwner = pointersInfoOwner, pointersInfoOwner = pointersInfoOwner, Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +37 −68 Original line number Original line Diff line number Diff line Loading @@ -80,7 +80,6 @@ import kotlinx.coroutines.launch @Stable @Stable internal fun Modifier.multiPointerDraggable( internal fun Modifier.multiPointerDraggable( orientation: Orientation, orientation: Orientation, startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onFirstPointerDown: () -> Unit = {}, onFirstPointerDown: () -> Unit = {}, swipeDetector: SwipeDetector = DefaultSwipeDetector, swipeDetector: SwipeDetector = DefaultSwipeDetector, Loading @@ -89,7 +88,6 @@ internal fun Modifier.multiPointerDraggable( this.then( this.then( MultiPointerDraggableElement( MultiPointerDraggableElement( orientation, orientation, startDragImmediately, onDragStarted, onDragStarted, onFirstPointerDown, onFirstPointerDown, swipeDetector, swipeDetector, Loading @@ -99,7 +97,6 @@ internal fun Modifier.multiPointerDraggable( private data class MultiPointerDraggableElement( private data class MultiPointerDraggableElement( private val orientation: Orientation, private val orientation: Orientation, private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, private val onDragStarted: private val onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, private val onFirstPointerDown: () -> Unit, private val onFirstPointerDown: () -> Unit, Loading @@ -109,7 +106,6 @@ private data class MultiPointerDraggableElement( override fun create(): MultiPointerDraggableNode = override fun create(): MultiPointerDraggableNode = MultiPointerDraggableNode( MultiPointerDraggableNode( orientation = orientation, orientation = orientation, startDragImmediately = startDragImmediately, onDragStarted = onDragStarted, onDragStarted = onDragStarted, onFirstPointerDown = onFirstPointerDown, onFirstPointerDown = onFirstPointerDown, swipeDetector = swipeDetector, swipeDetector = swipeDetector, Loading @@ -118,7 +114,6 @@ private data class MultiPointerDraggableElement( override fun update(node: MultiPointerDraggableNode) { override fun update(node: MultiPointerDraggableNode) { node.orientation = orientation node.orientation = orientation node.startDragImmediately = startDragImmediately node.onDragStarted = onDragStarted node.onDragStarted = onDragStarted node.onFirstPointerDown = onFirstPointerDown node.onFirstPointerDown = onFirstPointerDown node.swipeDetector = swipeDetector node.swipeDetector = swipeDetector Loading @@ -127,7 +122,6 @@ private data class MultiPointerDraggableElement( internal class MultiPointerDraggableNode( internal class MultiPointerDraggableNode( orientation: Orientation, orientation: Orientation, var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, var onFirstPointerDown: () -> Unit, var onFirstPointerDown: () -> Unit, swipeDetector: SwipeDetector = DefaultSwipeDetector, swipeDetector: SwipeDetector = DefaultSwipeDetector, Loading Loading @@ -293,7 +287,6 @@ internal class MultiPointerDraggableNode( try { try { detectDragGestures( detectDragGestures( orientation = orientation, orientation = orientation, startDragImmediately = startDragImmediately, onDragStart = { pointersDown, overSlop -> onDragStart = { pointersDown, overSlop -> onDragStarted(pointersDown, overSlop) onDragStarted(pointersDown, overSlop) }, }, Loading Loading @@ -434,13 +427,11 @@ internal class MultiPointerDraggableNode( * Detect drag gestures in the given [orientation]. * Detect drag gestures in the given [orientation]. * * * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for: * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for passing * 1) starting the gesture immediately without requiring a drag >= touch slope; * the number of pointers down to [onDragStart]. * 2) passing the number of pointers down to [onDragStart]. */ */ private suspend fun AwaitPointerEventScope.detectDragGestures( private suspend fun AwaitPointerEventScope.detectDragGestures( orientation: Orientation, orientation: Orientation, startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onDrag: (controller: DragController, dragAmount: Float) -> Unit, onDrag: (controller: DragController, dragAmount: Float) -> Unit, onDragEnd: (controller: DragController) -> Unit, onDragEnd: (controller: DragController) -> Unit, Loading @@ -466,17 +457,6 @@ internal class MultiPointerDraggableNode( .first() .first() var overSlop = 0f var overSlop = 0f var lastPointersDown: PointersInfo.PointersDown = 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 -> val onSlopReached = { change: PointerInputChange, over: Float -> if (swipeDetector.detectSwipe(change)) { if (swipeDetector.detectSwipe(change)) { change.consume() change.consume() Loading @@ -489,27 +469,19 @@ internal class MultiPointerDraggableNode( val drag = val drag = when (orientation) { when (orientation) { Orientation.Horizontal -> Orientation.Horizontal -> awaitHorizontalTouchSlopOrCancellation( awaitHorizontalTouchSlopOrCancellation(consumablePointer.id, onSlopReached) consumablePointer.id, onSlopReached, ) Orientation.Vertical -> Orientation.Vertical -> awaitVerticalTouchSlopOrCancellation( awaitVerticalTouchSlopOrCancellation(consumablePointer.id, onSlopReached) consumablePointer.id, onSlopReached, ) } ?: return } ?: return lastPointersDown = val lastPointersDown = checkNotNull(pointersInfo()) { checkNotNull(pointersInfo()) { "We should have pointers down, last event: $currentEvent" "We should have pointers down, last event: $currentEvent" } } as PointersInfo.PointersDown as PointersInfo.PointersDown // Make sure that overSlop is not 0f. This can happen when the user drags by exactly // 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 // 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 // compute the direction we are dragging in, so overSlop should never be 0f. // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned // true). if (overSlop == 0f) { if (overSlop == 0f) { // If the user drags in the opposite direction, the delta becomes zero because // 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 // we return to the original point. Therefore, we should use the previous event Loading @@ -526,11 +498,8 @@ internal class MultiPointerDraggableNode( } } overSlop = delta.sign overSlop = delta.sign } } drag } val controller = onDragStart(lastPointersDown, overSlop) val controller = onDragStart(lastPointersDown, overSlop) val successful: Boolean val successful: Boolean try { try { onDrag(controller, overSlop) onDrag(controller, overSlop) Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +0 −16 Original line number Original line Diff line number Diff line Loading @@ -149,7 +149,6 @@ private class SwipeToSceneNode( delegate( delegate( MultiPointerDraggableNode( MultiPointerDraggableNode( orientation = draggableHandler.orientation, orientation = draggableHandler.orientation, startDragImmediately = ::startDragImmediately, onDragStarted = draggableHandler::onDragStarted, onDragStarted = draggableHandler::onDragStarted, onFirstPointerDown = ::onFirstPointerDown, onFirstPointerDown = ::onFirstPointerDown, swipeDetector = swipeDetector, swipeDetector = swipeDetector, Loading Loading @@ -198,21 +197,6 @@ private class SwipeToSceneNode( ) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds) ) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds) override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput() 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. */ /** Find the [ScrollBehaviorOwner] for the current orientation. */ Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +6 −242 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +0 −11 Original line number Original line Diff line number Diff line Loading @@ -101,7 +101,6 @@ class MultiPointerDraggableTest { .thenIf(enabled) { .thenIf(enabled) { Modifier.multiPointerDraggable( Modifier.multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> started = true started = true SimpleDragController( SimpleDragController( Loading Loading @@ -169,8 +168,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, // We want to start a drag gesture immediately startDragImmediately = { true }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> started = true started = true SimpleDragController( SimpleDragController( Loading Loading @@ -242,7 +239,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> started = true started = true SimpleDragController( SimpleDragController( Loading Loading @@ -361,7 +357,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> started = true started = true SimpleDragController( SimpleDragController( Loading Loading @@ -466,7 +461,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> verticalStarted = true verticalStarted = true SimpleDragController( SimpleDragController( Loading @@ -478,7 +472,6 @@ class MultiPointerDraggableTest { ) ) .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Horizontal, orientation = Orientation.Horizontal, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> horizontalStarted = true horizontalStarted = true SimpleDragController( SimpleDragController( Loading Loading @@ -570,7 +563,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, swipeDetector = swipeDetector = object : SwipeDetector { object : SwipeDetector { override fun detectSwipe(change: PointerInputChange): Boolean { override fun detectSwipe(change: PointerInputChange): Boolean { Loading Loading @@ -636,7 +628,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, swipeDetector = swipeDetector = object : SwipeDetector { object : SwipeDetector { override fun detectSwipe(change: PointerInputChange): Boolean { override fun detectSwipe(change: PointerInputChange): Boolean { Loading Loading @@ -738,7 +729,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> SimpleDragController( SimpleDragController( onDrag = { consumedOnDrag = it }, onDrag = { consumedOnDrag = it }, Loading Loading @@ -809,7 +799,6 @@ class MultiPointerDraggableTest { .nestedScrollDispatcher() .nestedScrollDispatcher() .multiPointerDraggable( .multiPointerDraggable( orientation = Orientation.Vertical, orientation = Orientation.Vertical, startDragImmediately = { false }, onDragStarted = { _, _ -> onDragStarted = { _, _ -> SimpleDragController( SimpleDragController( onDrag = { /* do nothing */ }, onDrag = { /* do nothing */ }, Loading