Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +20 −29 Original line number Diff line number Diff line Loading @@ -189,7 +189,7 @@ internal class DraggableHandlerImpl( return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation) } private fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? { internal fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? { if (startedPosition == null) return null return layoutImpl.swipeSourceDetector.source( layoutSize = layoutImpl.lastSize, Loading @@ -199,7 +199,7 @@ internal class DraggableHandlerImpl( ) } private fun resolveSwipe( internal fun resolveSwipe( pointersDown: Int, fromSource: SwipeSource.Resolved?, isUpOrLeft: Boolean, Loading Loading @@ -559,6 +559,14 @@ internal class NestedScrollHandlerImpl( val connection: PriorityNestedScrollConnection = nestedScrollConnection() private fun PointersInfo.resolveSwipe(isUpOrLeft: Boolean): Swipe.Resolved { return draggableHandler.resolveSwipe( pointersDown = pointersDown, fromSource = draggableHandler.resolveSwipeSource(startedPosition), isUpOrLeft = isUpOrLeft, ) } private fun nestedScrollConnection(): PriorityNestedScrollConnection { // If we performed a long gesture before entering priority mode, we would have to avoid // moving on to the next scene. Loading @@ -575,36 +583,19 @@ internal class NestedScrollHandlerImpl( val transitionState = layoutState.transitionState val scene = transitionState.currentScene val fromScene = layoutImpl.scene(scene) val nextScene = val resolvedSwipe = when { amount < 0f -> { val actionUpOrLeft = Swipe.Resolved( direction = when (orientation) { Orientation.Horizontal -> SwipeDirection.Resolved.Left Orientation.Vertical -> SwipeDirection.Resolved.Up }, pointerCount = pointersInfo().pointersDown, fromSource = null, ) fromScene.userActions[actionUpOrLeft] } amount > 0f -> { val actionDownOrRight = Swipe.Resolved( direction = when (orientation) { Orientation.Horizontal -> SwipeDirection.Resolved.Right Orientation.Vertical -> SwipeDirection.Resolved.Down }, pointerCount = pointersInfo().pointersDown, fromSource = null, ) fromScene.userActions[actionDownOrRight] } amount < 0f -> pointersInfo().resolveSwipe(isUpOrLeft = true) amount > 0f -> pointersInfo().resolveSwipe(isUpOrLeft = false) else -> null } val nextScene = resolvedSwipe?.let { fromScene.userActions[it] ?: if (it.fromSource != null) { fromScene.userActions[it.copy(fromSource = null)] } else null } if (nextScene != null) return true if (transitionState !is TransitionState.Idle) return false Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +2 −1 Original line number Diff line number Diff line Loading @@ -213,8 +213,9 @@ internal class MultiPointerDraggableNode( internal fun pointersInfo(): PointersInfo { return PointersInfo( // This may be null, i.e. when the user uses TalkBack startedPosition = startedPosition, // Note: We could have 0 pointers during fling or for other reasons. // We could have 0 pointers during fling or for other reasons. pointersDown = pointersDown.coerceAtLeast(1), ) } Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +52 −4 Original line number Diff line number Diff line Loading @@ -130,6 +130,10 @@ class DraggableHandlerTest { val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical) val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal) var pointerInfoOwner: () -> PointersInfo = { PointersInfo(startedPosition = Offset.Zero, pointersDown = 1) } fun nestedScrollConnection( nestedScrollBehavior: NestedScrollBehavior, isExternalOverscrollGesture: Boolean = false, Loading @@ -140,9 +144,7 @@ class DraggableHandlerTest { topOrLeftBehavior = nestedScrollBehavior, bottomOrRightBehavior = nestedScrollBehavior, isExternalOverscrollGesture = { isExternalOverscrollGesture }, pointersInfoOwner = { PointersInfo(startedPosition = Offset.Zero, pointersDown = 1) }, pointersInfoOwner = { pointerInfoOwner() }, ) .connection Loading @@ -156,11 +158,18 @@ class DraggableHandlerTest { fun downOffset(fractionOfScreen: Float) = if (fractionOfScreen < 0f) { error("upOffset() is required, not implemented yet") error("use upOffset()") } else { Offset(x = 0f, y = down(fractionOfScreen)) } fun upOffset(fractionOfScreen: Float) = if (fractionOfScreen < 0f) { error("use downOffset()") } else { Offset(x = 0f, y = up(fractionOfScreen)) } val transitionState: TransitionState get() = layoutState.transitionState Loading Loading @@ -1134,6 +1143,45 @@ class DraggableHandlerTest { assertThat(transition!!.progress).isEqualTo(-0.1f) } @Test fun nestedScrollUseFromSourceInfo() = runGestureTest { // Start at scene C. navigateToSceneC() val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Drag from the **top** of the screen pointerInfoOwner = { PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertTransition( currentScene = SceneC, fromScene = SceneC, // userAction: Swipe.Up to SceneB toScene = SceneB, progress = 0.1f, ) // Reset to SceneC nestedScroll.preFling(Velocity.Zero) advanceUntilIdle() // Drag from the **bottom** of the screen pointerInfoOwner = { PointersInfo(startedPosition = Offset(0f, SCREEN_SIZE), pointersDown = 1) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertTransition( currentScene = SceneC, fromScene = SceneC, // userAction: Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA toScene = SceneA, progress = 0.1f, ) } @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +20 −29 Original line number Diff line number Diff line Loading @@ -189,7 +189,7 @@ internal class DraggableHandlerImpl( return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation) } private fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? { internal fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? { if (startedPosition == null) return null return layoutImpl.swipeSourceDetector.source( layoutSize = layoutImpl.lastSize, Loading @@ -199,7 +199,7 @@ internal class DraggableHandlerImpl( ) } private fun resolveSwipe( internal fun resolveSwipe( pointersDown: Int, fromSource: SwipeSource.Resolved?, isUpOrLeft: Boolean, Loading Loading @@ -559,6 +559,14 @@ internal class NestedScrollHandlerImpl( val connection: PriorityNestedScrollConnection = nestedScrollConnection() private fun PointersInfo.resolveSwipe(isUpOrLeft: Boolean): Swipe.Resolved { return draggableHandler.resolveSwipe( pointersDown = pointersDown, fromSource = draggableHandler.resolveSwipeSource(startedPosition), isUpOrLeft = isUpOrLeft, ) } private fun nestedScrollConnection(): PriorityNestedScrollConnection { // If we performed a long gesture before entering priority mode, we would have to avoid // moving on to the next scene. Loading @@ -575,36 +583,19 @@ internal class NestedScrollHandlerImpl( val transitionState = layoutState.transitionState val scene = transitionState.currentScene val fromScene = layoutImpl.scene(scene) val nextScene = val resolvedSwipe = when { amount < 0f -> { val actionUpOrLeft = Swipe.Resolved( direction = when (orientation) { Orientation.Horizontal -> SwipeDirection.Resolved.Left Orientation.Vertical -> SwipeDirection.Resolved.Up }, pointerCount = pointersInfo().pointersDown, fromSource = null, ) fromScene.userActions[actionUpOrLeft] } amount > 0f -> { val actionDownOrRight = Swipe.Resolved( direction = when (orientation) { Orientation.Horizontal -> SwipeDirection.Resolved.Right Orientation.Vertical -> SwipeDirection.Resolved.Down }, pointerCount = pointersInfo().pointersDown, fromSource = null, ) fromScene.userActions[actionDownOrRight] } amount < 0f -> pointersInfo().resolveSwipe(isUpOrLeft = true) amount > 0f -> pointersInfo().resolveSwipe(isUpOrLeft = false) else -> null } val nextScene = resolvedSwipe?.let { fromScene.userActions[it] ?: if (it.fromSource != null) { fromScene.userActions[it.copy(fromSource = null)] } else null } if (nextScene != null) return true if (transitionState !is TransitionState.Idle) return false Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +2 −1 Original line number Diff line number Diff line Loading @@ -213,8 +213,9 @@ internal class MultiPointerDraggableNode( internal fun pointersInfo(): PointersInfo { return PointersInfo( // This may be null, i.e. when the user uses TalkBack startedPosition = startedPosition, // Note: We could have 0 pointers during fling or for other reasons. // We could have 0 pointers during fling or for other reasons. pointersDown = pointersDown.coerceAtLeast(1), ) } Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +52 −4 Original line number Diff line number Diff line Loading @@ -130,6 +130,10 @@ class DraggableHandlerTest { val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical) val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal) var pointerInfoOwner: () -> PointersInfo = { PointersInfo(startedPosition = Offset.Zero, pointersDown = 1) } fun nestedScrollConnection( nestedScrollBehavior: NestedScrollBehavior, isExternalOverscrollGesture: Boolean = false, Loading @@ -140,9 +144,7 @@ class DraggableHandlerTest { topOrLeftBehavior = nestedScrollBehavior, bottomOrRightBehavior = nestedScrollBehavior, isExternalOverscrollGesture = { isExternalOverscrollGesture }, pointersInfoOwner = { PointersInfo(startedPosition = Offset.Zero, pointersDown = 1) }, pointersInfoOwner = { pointerInfoOwner() }, ) .connection Loading @@ -156,11 +158,18 @@ class DraggableHandlerTest { fun downOffset(fractionOfScreen: Float) = if (fractionOfScreen < 0f) { error("upOffset() is required, not implemented yet") error("use upOffset()") } else { Offset(x = 0f, y = down(fractionOfScreen)) } fun upOffset(fractionOfScreen: Float) = if (fractionOfScreen < 0f) { error("use downOffset()") } else { Offset(x = 0f, y = up(fractionOfScreen)) } val transitionState: TransitionState get() = layoutState.transitionState Loading Loading @@ -1134,6 +1143,45 @@ class DraggableHandlerTest { assertThat(transition!!.progress).isEqualTo(-0.1f) } @Test fun nestedScrollUseFromSourceInfo() = runGestureTest { // Start at scene C. navigateToSceneC() val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Drag from the **top** of the screen pointerInfoOwner = { PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertTransition( currentScene = SceneC, fromScene = SceneC, // userAction: Swipe.Up to SceneB toScene = SceneB, progress = 0.1f, ) // Reset to SceneC nestedScroll.preFling(Velocity.Zero) advanceUntilIdle() // Drag from the **bottom** of the screen pointerInfoOwner = { PointersInfo(startedPosition = Offset(0f, SCREEN_SIZE), pointersDown = 1) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertTransition( currentScene = SceneC, fromScene = SceneC, // userAction: Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA toScene = SceneA, progress = 0.1f, ) } @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. Loading