summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt23
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt123
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt53
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