diff options
3 files changed, 32 insertions, 167 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 737a89630a8f..bb61a131dac3 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 @@ -108,7 +108,7 @@ internal class DraggableHandlerImpl( swipes.updateSwipesResults(fromContent) val result = - swipes.findUserActionResult(overSlop) + (if (overSlop < 0f) swipes.upOrLeftResult else swipes.downOrRightResult) // As we were unable to locate a valid target scene, the initial SwipeAnimation // cannot be defined. Consequently, a simple NoOp Controller will be returned. ?: return NoOpDragController @@ -448,27 +448,6 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol this.upOrLeftResult = upOrLeftResult this.downOrRightResult = downOrRightResult } - - /** - * Returns the [UserActionResult] in the direction of [directionOffset]. - * - * @param directionOffset signed float that indicates the direction. Positive is down or right - * negative is up or left. - * @return null when there are no targets in either direction. If one direction is null and you - * drag into the null direction this function will return the opposite direction, assuming - * that the users intention is to start the drag into the other direction eventually. If - * [directionOffset] is 0f and both direction are available, it will default to - * [upOrLeftResult]. - */ - fun findUserActionResult(directionOffset: Float): UserActionResult? { - return when { - upOrLeftResult == null && downOrRightResult == null -> null - (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null -> - upOrLeftResult - - else -> downOrRightResult - } - } } internal class NestedScrollHandlerImpl( 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 b20056d54de1..6985644579f6 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 @@ -19,7 +19,9 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.overscroll import androidx.compose.material3.Text +import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput @@ -102,7 +104,7 @@ class DraggableHandlerTest { userActions = mapOf(Swipe.Up to SceneB, Swipe.Up(fromSource = Edge.Bottom) to SceneA), ) { - Text("SceneC") + Text("SceneC", Modifier.overscroll(verticalOverscrollEffect)) } overlay( key = OverlayA, @@ -434,35 +436,12 @@ class DraggableHandlerTest { } @Test - fun onDragIntoNoAction_startTransitionToOppositeDirection() = runGestureTest { + fun onDragIntoNoAction_stayIdle() = runGestureTest { navigateToSceneC() // We are on SceneC which has no action in Down direction - val dragController = onDragStarted(overSlop = 10f) - assertTransition( - currentScene = SceneC, - fromScene = SceneC, - toScene = SceneB, - progress = -0.1f, - ) - - // Reverse drag direction, it will consume the previous drag - dragController.onDragDelta(pixels = -10f) - assertTransition( - currentScene = SceneC, - fromScene = SceneC, - toScene = SceneB, - progress = 0.0f, - ) - - // Continue reverse drag direction, it should record progress to Scene B - dragController.onDragDelta(pixels = -10f) - assertTransition( - currentScene = SceneC, - fromScene = SceneC, - toScene = SceneB, - progress = 0.1f, - ) + onDragStarted(overSlop = 10f, expectedConsumedOverSlop = 0f) + assertIdle(currentScene = SceneC) } @Test @@ -942,30 +921,6 @@ class DraggableHandlerTest { } @Test - fun scrollFromIdleWithNoTargetScene_shouldUseOverscrollSpecIfAvailable() = runGestureTest { - layoutState.transitions = transitions { - overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) } - } - // Start at scene C. - navigateToSceneC() - - val scene = layoutState.transitionState.currentScene - // We should have overscroll spec for scene C - assertThat(layoutState.transitions.overscrollSpec(scene, Orientation.Vertical)).isNotNull() - assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNull() - - val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) - nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f)) - - // We scrolled down, under scene C there is nothing, so we can use the overscroll spec - assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNotNull() - assertThat(layoutState.currentTransition?.currentOverscrollSpec?.content).isEqualTo(SceneC) - val transition = layoutState.currentTransition - assertThat(transition).isNotNull() - assertThat(transition!!.progress).isEqualTo(-0.1f) - } - - @Test fun nestedScrollUseFromSourceInfo() = runGestureTest { // Start at scene C. navigateToSceneC() @@ -1229,72 +1184,6 @@ class DraggableHandlerTest { } @Test - fun overscroll_releaseAtNegativePercent_up() = runGestureTest { - // Make Scene A overscrollable. - layoutState.transitions = transitions { - from(SceneA, to = SceneB) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) } - overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) } - } - - mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB)) - - val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f)) - val transition = assertThat(transitionState).isSceneTransition() - assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(SceneB) - assertThat(transition).hasProgress(-1f) - - // Release to A. - dragController.onDragStoppedAnimateNow( - velocity = 0f, - onAnimationStart = { - assertTransition(fromScene = SceneA, toScene = SceneB, progress = -1f) - }, - expectedConsumedVelocity = 0f, - ) - - // We kept the overscroll at 100% so that the placement logic didn't change at the end of - // the animation. - assertIdle(SceneA) - assertThat(transition).hasProgress(0f) - assertThat(transition).hasOverscrollSpec() - } - - @Test - fun overscroll_releaseAtNegativePercent_down() = runGestureTest { - // Make Scene A overscrollable. - layoutState.transitions = transitions { - from(SceneA, to = SceneC) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) } - overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) } - } - - mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC)) - - val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f)) - val transition = assertThat(transitionState).isSceneTransition() - assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(SceneC) - assertThat(transition).hasProgress(-1f) - - // Release to A. - dragController.onDragStoppedAnimateNow( - velocity = 0f, - onAnimationStart = { - assertTransition(fromScene = SceneA, toScene = SceneC, progress = -1f) - }, - expectedConsumedVelocity = 0f, - ) - - // We kept the overscroll at 100% so that the placement logic didn't change at the end of - // the animation. - assertIdle(SceneA) - assertThat(transition).hasProgress(0f) - assertThat(transition).hasOverscrollSpec() - } - - @Test fun requireFullDistanceSwipe() = runGestureTest { mutableUserActionsA += Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 4410e157b526..1959f5914821 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -1006,77 +1006,74 @@ class ElementTest { @Test fun elementTransitionDuringNestedScrollOverscroll() { + lateinit var density: Density // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is // detected as a drag event. var touchSlop = 0f - val overscrollTranslateY = 10.dp val layoutWidth = 200.dp val layoutHeight = 400.dp val state = rule.runOnUiThread { MutableSceneTransitionLayoutState( - initialScene = SceneB, - transitions = - transitions { - overscroll(SceneB, Orientation.Vertical) { - progressConverter = ProgressConverter.linear() - translate(TestElements.Foo, y = overscrollTranslateY) - } - }, + initialScene = SceneA, + transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }, ) - as MutableSceneTransitionLayoutStateImpl } rule.setContent { + density = LocalDensity.current touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout( state = state, modifier = Modifier.size(layoutWidth, layoutHeight), ) { - scene(SceneA) { Spacer(Modifier.fillMaxSize()) } - scene(SceneB, userActions = mapOf(Swipe.Up to SceneA)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { Box( Modifier // A scrollable that does not consume the scroll gesture .scrollable( - rememberScrollableState(consumeScrollDelta = { 0f }), - Orientation.Vertical, + state = rememberScrollableState(consumeScrollDelta = { 0f }), + orientation = Orientation.Vertical, ) .fillMaxSize() - ) { - Spacer(Modifier.element(TestElements.Foo).fillMaxSize()) - } + ) + } + scene(SceneB) { + Spacer( + Modifier.overscroll(verticalOverscrollEffect) + .element(TestElements.Foo) + .fillMaxSize() + ) } } } assertThat(state.transitionState).isIdle() - val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag) - fooElement.assertTopPositionInRootIsEqualTo(0.dp) + rule.onNodeWithTag(TestElements.Foo.testTag).assertDoesNotExist() // Swipe by half of verticalSwipeDistance. rule.onRoot().performTouchInput { val middleTop = Offset((layoutWidth / 2).toPx(), 0f) down(middleTop) - // Scroll 50% + // Scroll 50%. moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000) } val transition = assertThat(state.transitionState).isSceneTransition() - assertThat(transition).hasOverscrollSpec() - assertThat(transition).hasProgress(-0.5f) - fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f) + assertThat(transition).hasProgress(0.5f) + rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp) rule.onRoot().performTouchInput { - // Scroll another 100% + // Scroll another 100%. moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000) } - // Scroll 150% (Scene B overscroll by 50%) - assertThat(transition).hasProgress(-1.5f) - assertThat(transition).hasOverscrollSpec() - fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f) + // Scroll 150% (Scene B overscroll by 50%). + assertThat(transition).hasProgress(1f) + rule + .onNodeWithTag(TestElements.Foo.testTag) + .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.5f, density)) } @Test |