Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +0 −105 Original line number Diff line number Diff line Loading @@ -137,13 +137,6 @@ class DraggableHandlerTest { var pointerInfoOwner: () -> PointersInfo = { pointersDown() } fun nestedScrollConnection() = NestedScrollHandlerImpl( draggableHandler = draggableHandler, pointersInfoOwner = { pointerInfoOwner() }, ) .connection val velocityThreshold = draggableHandler.velocityThreshold fun down(fractionOfScreen: Float) = Loading Loading @@ -606,57 +599,6 @@ class DraggableHandlerTest { assertIdle(SceneA) } @Test fun nestedScrollUseFromSourceInfo() = runGestureTest { // Start at scene C. navigateToSceneC() val nestedScroll = nestedScrollConnection() // Drag from the **top** of the screen pointerInfoOwner = { pointersDown() } 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 = { pointersDown(startedPosition = Offset(0f, SCREEN_SIZE)) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertTransition( currentScene = SceneC, fromScene = SceneC, // userAction: Swipe.Up(fromSource = Edge.Bottom) to SceneA toScene = SceneA, progress = 0.1f, ) } @Test fun ignoreMouseWheel() = runGestureTest { // Start at scene C. navigateToSceneC() val nestedScroll = nestedScrollConnection() // Use mouse wheel pointerInfoOwner = { PointersInfo.MouseWheel } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertIdle(currentScene = SceneC) } @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. Loading Loading @@ -688,24 +630,6 @@ class DraggableHandlerTest { ) } @Test fun scrollKeepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() = runGestureTest { val nestedScroll = nestedScrollConnection() // Overscroll is disabled, it will scroll up to 100% nestedScroll.scroll(available = upOffset(fractionOfScreen = 2f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 1f) // We need to maintain scroll priority even if the scene transition can no longer consume // the scroll gesture. nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 1f) // A scroll gesture in the opposite direction allows us to return to the previous scene. nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.5f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.5f) } @Test fun overscroll_releaseBetween0And100Percent_up() = runGestureTest { // Make scene B overscrollable. Loading Loading @@ -944,33 +868,4 @@ class DraggableHandlerTest { assertThat(layoutState.transitionState).hasCurrentScene(SceneA) assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB) } @Test fun replaceOverlayNestedScroll() = runGestureTest { layoutState.showOverlay(OverlayA, animationScope = testScope) advanceUntilIdle() // Initial state. assertThat(layoutState.transitionState).isIdle() assertThat(layoutState.transitionState).hasCurrentScene(SceneA) assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayA) // Swipe down to replace overlay A by overlay B. val nestedScroll = nestedScrollConnection() nestedScroll.scroll(downOffset(0.1f)) val transition = assertThat(layoutState.transitionState).isReplaceOverlayTransition() assertThat(transition).hasCurrentScene(SceneA) assertThat(transition).hasFromOverlay(OverlayA) assertThat(transition).hasToOverlay(OverlayB) assertThat(transition).hasCurrentOverlays(OverlayA) assertThat(transition).hasProgress(0.1f) nestedScroll.preFling(Velocity(0f, velocityThreshold)) advanceUntilIdle() // Commit the gesture. The overlays are instantly swapped in the set of current overlays. assertThat(layoutState.transitionState).isIdle() assertThat(layoutState.transitionState).hasCurrentScene(SceneA) assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB) } } packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +163 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.ScrollWheel import androidx.compose.ui.test.TouchInjectionScope import androidx.compose.ui.test.assertPositionInRootIsEqualTo import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.junit4.createComposeRule Loading @@ -55,6 +56,8 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestOverlays.OverlayA import com.android.compose.animation.scene.TestOverlays.OverlayB import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC Loading Loading @@ -977,4 +980,164 @@ class SwipeToSceneTest { rule.waitForIdle() assertThat(state.transitionState).isSceneTransition() } @Test fun nestedScroll_useFromSourceInfo() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) } var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state) { scene( SceneA, userActions = mapOf(Swipe.Down to SceneB, Swipe.Down(fromSource = Edge.Top) to SceneC), ) { // Use a fullscreen nested scrollable to use the nested scroll connection. Box( Modifier.fillMaxSize() .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) ) } scene(SceneB) { Box(Modifier.fillMaxSize()) } scene(SceneC) { Box(Modifier.fillMaxSize()) } } } // Swiping down from the middle of the screen leads to B. rule.onRoot().performTouchInput { down(center) moveBy(Offset(0f, touchSlop + 1f)) } var transition = assertThat(state.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneB) // Release finger and wait to settle back to A. rule.onRoot().performTouchInput { up() } rule.waitForIdle() assertThat(state.transitionState).isIdle() assertThat(state.transitionState).hasCurrentScene(SceneA) // Swiping down from the top of the screen leads to B. rule.onRoot().performTouchInput { down(center.copy(y = 0f)) moveBy(Offset(0f, touchSlop + 1f)) } transition = assertThat(state.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneC) } @Test fun nestedScroll_ignoreMouseWheel() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) } var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state) { scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { // Use a fullscreen nested scrollable to use the nested scroll connection. Box( Modifier.fillMaxSize() .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) ) } scene(SceneB) { Box(Modifier.fillMaxSize()) } } } rule.onRoot().performMouseInput { scroll(-touchSlop - 1f, scrollWheel = ScrollWheel.Vertical) } assertThat(state.transitionState).isIdle() } @Test fun nestedScroll_keepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) } var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state) { scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { // Use a fullscreen nested scrollable to use the nested scroll connection. Box( Modifier.fillMaxSize() .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) ) } scene(SceneB) { Box(Modifier.fillMaxSize()) } } } fun TouchInjectionScope.height() = bottom fun TouchInjectionScope.halfHeight() = height() / 2f rule.onRoot().performTouchInput { down(center.copy(y = 0f)) moveBy(Offset(0f, touchSlop + halfHeight())) } val transition = assertThat(state.transitionState).isSceneTransition() assertThat(transition).hasProgress(0.5f, tolerance = 0.01f) // The progress should never go above 100%. rule.onRoot().performTouchInput { moveBy(Offset(0f, height())) } assertThat(transition).hasProgress(1f, tolerance = 0.01f) // Because the overscroll effect of scene B is not attached, swiping in the opposite // direction will directly decrease the progress. rule.onRoot().performTouchInput { moveBy(Offset(0f, -halfHeight())) } assertThat(transition).hasProgress(0.5f, tolerance = 0.01f) } @Test fun nestedScroll_replaceOverlay() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA)) } var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state) { scene(SceneA) { Box(Modifier.fillMaxSize()) } overlay( OverlayA, mapOf(Swipe.Down to UserActionResult.ReplaceByOverlay(OverlayB)), ) { Box( Modifier.fillMaxSize() .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) ) } overlay(OverlayB) { Box(Modifier.fillMaxSize()) } } } // Swipe down 100% to replace A by B. rule.onRoot().performTouchInput { down(center.copy(y = 0f)) moveBy(Offset(0f, touchSlop + bottom)) } val transition = assertThat(state.transitionState).isReplaceOverlayTransition() assertThat(transition).hasCurrentScene(SceneA) assertThat(transition).hasFromOverlay(OverlayA) assertThat(transition).hasToOverlay(OverlayB) assertThat(transition).hasCurrentOverlays(OverlayA) assertThat(transition).hasProgress(1f, tolerance = 0.01f) // Commit the gesture. The overlays are instantly swapped in the set of current overlays. rule.onRoot().performTouchInput { up() } assertThat(transition).hasCurrentScene(SceneA) assertThat(transition).hasCurrentOverlays(OverlayB) rule.waitForIdle() assertThat(state.transitionState).isIdle() assertThat(state.transitionState).hasCurrentScene(SceneA) assertThat(state.transitionState).hasCurrentOverlays(OverlayB) } } Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +0 −105 Original line number Diff line number Diff line Loading @@ -137,13 +137,6 @@ class DraggableHandlerTest { var pointerInfoOwner: () -> PointersInfo = { pointersDown() } fun nestedScrollConnection() = NestedScrollHandlerImpl( draggableHandler = draggableHandler, pointersInfoOwner = { pointerInfoOwner() }, ) .connection val velocityThreshold = draggableHandler.velocityThreshold fun down(fractionOfScreen: Float) = Loading Loading @@ -606,57 +599,6 @@ class DraggableHandlerTest { assertIdle(SceneA) } @Test fun nestedScrollUseFromSourceInfo() = runGestureTest { // Start at scene C. navigateToSceneC() val nestedScroll = nestedScrollConnection() // Drag from the **top** of the screen pointerInfoOwner = { pointersDown() } 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 = { pointersDown(startedPosition = Offset(0f, SCREEN_SIZE)) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertTransition( currentScene = SceneC, fromScene = SceneC, // userAction: Swipe.Up(fromSource = Edge.Bottom) to SceneA toScene = SceneA, progress = 0.1f, ) } @Test fun ignoreMouseWheel() = runGestureTest { // Start at scene C. navigateToSceneC() val nestedScroll = nestedScrollConnection() // Use mouse wheel pointerInfoOwner = { PointersInfo.MouseWheel } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertIdle(currentScene = SceneC) } @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. Loading Loading @@ -688,24 +630,6 @@ class DraggableHandlerTest { ) } @Test fun scrollKeepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() = runGestureTest { val nestedScroll = nestedScrollConnection() // Overscroll is disabled, it will scroll up to 100% nestedScroll.scroll(available = upOffset(fractionOfScreen = 2f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 1f) // We need to maintain scroll priority even if the scene transition can no longer consume // the scroll gesture. nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 1f) // A scroll gesture in the opposite direction allows us to return to the previous scene. nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.5f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.5f) } @Test fun overscroll_releaseBetween0And100Percent_up() = runGestureTest { // Make scene B overscrollable. Loading Loading @@ -944,33 +868,4 @@ class DraggableHandlerTest { assertThat(layoutState.transitionState).hasCurrentScene(SceneA) assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB) } @Test fun replaceOverlayNestedScroll() = runGestureTest { layoutState.showOverlay(OverlayA, animationScope = testScope) advanceUntilIdle() // Initial state. assertThat(layoutState.transitionState).isIdle() assertThat(layoutState.transitionState).hasCurrentScene(SceneA) assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayA) // Swipe down to replace overlay A by overlay B. val nestedScroll = nestedScrollConnection() nestedScroll.scroll(downOffset(0.1f)) val transition = assertThat(layoutState.transitionState).isReplaceOverlayTransition() assertThat(transition).hasCurrentScene(SceneA) assertThat(transition).hasFromOverlay(OverlayA) assertThat(transition).hasToOverlay(OverlayB) assertThat(transition).hasCurrentOverlays(OverlayA) assertThat(transition).hasProgress(0.1f) nestedScroll.preFling(Velocity(0f, velocityThreshold)) advanceUntilIdle() // Commit the gesture. The overlays are instantly swapped in the set of current overlays. assertThat(layoutState.transitionState).isIdle() assertThat(layoutState.transitionState).hasCurrentScene(SceneA) assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB) } }
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +163 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.ScrollWheel import androidx.compose.ui.test.TouchInjectionScope import androidx.compose.ui.test.assertPositionInRootIsEqualTo import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.junit4.createComposeRule Loading @@ -55,6 +56,8 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestOverlays.OverlayA import com.android.compose.animation.scene.TestOverlays.OverlayB import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC Loading Loading @@ -977,4 +980,164 @@ class SwipeToSceneTest { rule.waitForIdle() assertThat(state.transitionState).isSceneTransition() } @Test fun nestedScroll_useFromSourceInfo() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) } var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state) { scene( SceneA, userActions = mapOf(Swipe.Down to SceneB, Swipe.Down(fromSource = Edge.Top) to SceneC), ) { // Use a fullscreen nested scrollable to use the nested scroll connection. Box( Modifier.fillMaxSize() .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) ) } scene(SceneB) { Box(Modifier.fillMaxSize()) } scene(SceneC) { Box(Modifier.fillMaxSize()) } } } // Swiping down from the middle of the screen leads to B. rule.onRoot().performTouchInput { down(center) moveBy(Offset(0f, touchSlop + 1f)) } var transition = assertThat(state.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneB) // Release finger and wait to settle back to A. rule.onRoot().performTouchInput { up() } rule.waitForIdle() assertThat(state.transitionState).isIdle() assertThat(state.transitionState).hasCurrentScene(SceneA) // Swiping down from the top of the screen leads to B. rule.onRoot().performTouchInput { down(center.copy(y = 0f)) moveBy(Offset(0f, touchSlop + 1f)) } transition = assertThat(state.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneC) } @Test fun nestedScroll_ignoreMouseWheel() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) } var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state) { scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { // Use a fullscreen nested scrollable to use the nested scroll connection. Box( Modifier.fillMaxSize() .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) ) } scene(SceneB) { Box(Modifier.fillMaxSize()) } } } rule.onRoot().performMouseInput { scroll(-touchSlop - 1f, scrollWheel = ScrollWheel.Vertical) } assertThat(state.transitionState).isIdle() } @Test fun nestedScroll_keepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) } var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state) { scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { // Use a fullscreen nested scrollable to use the nested scroll connection. Box( Modifier.fillMaxSize() .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) ) } scene(SceneB) { Box(Modifier.fillMaxSize()) } } } fun TouchInjectionScope.height() = bottom fun TouchInjectionScope.halfHeight() = height() / 2f rule.onRoot().performTouchInput { down(center.copy(y = 0f)) moveBy(Offset(0f, touchSlop + halfHeight())) } val transition = assertThat(state.transitionState).isSceneTransition() assertThat(transition).hasProgress(0.5f, tolerance = 0.01f) // The progress should never go above 100%. rule.onRoot().performTouchInput { moveBy(Offset(0f, height())) } assertThat(transition).hasProgress(1f, tolerance = 0.01f) // Because the overscroll effect of scene B is not attached, swiping in the opposite // direction will directly decrease the progress. rule.onRoot().performTouchInput { moveBy(Offset(0f, -halfHeight())) } assertThat(transition).hasProgress(0.5f, tolerance = 0.01f) } @Test fun nestedScroll_replaceOverlay() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA)) } var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state) { scene(SceneA) { Box(Modifier.fillMaxSize()) } overlay( OverlayA, mapOf(Swipe.Down to UserActionResult.ReplaceByOverlay(OverlayB)), ) { Box( Modifier.fillMaxSize() .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) ) } overlay(OverlayB) { Box(Modifier.fillMaxSize()) } } } // Swipe down 100% to replace A by B. rule.onRoot().performTouchInput { down(center.copy(y = 0f)) moveBy(Offset(0f, touchSlop + bottom)) } val transition = assertThat(state.transitionState).isReplaceOverlayTransition() assertThat(transition).hasCurrentScene(SceneA) assertThat(transition).hasFromOverlay(OverlayA) assertThat(transition).hasToOverlay(OverlayB) assertThat(transition).hasCurrentOverlays(OverlayA) assertThat(transition).hasProgress(1f, tolerance = 0.01f) // Commit the gesture. The overlays are instantly swapped in the set of current overlays. rule.onRoot().performTouchInput { up() } assertThat(transition).hasCurrentScene(SceneA) assertThat(transition).hasCurrentOverlays(OverlayB) rule.waitForIdle() assertThat(state.transitionState).isIdle() assertThat(state.transitionState).hasCurrentScene(SceneA) assertThat(state.transitionState).hasCurrentOverlays(OverlayB) } }