diff options
7 files changed, 56 insertions, 316 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt index ae7d8f599b91..d005413fcbcf 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt @@ -2,6 +2,7 @@ package com.android.compose.animation.scene import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import kotlinx.coroutines.CoroutineScope interface GestureHandler { val draggable: DraggableHandler @@ -9,9 +10,9 @@ interface GestureHandler { } interface DraggableHandler { - fun onDragStarted(startedPosition: Offset, pointersDown: Int = 1) + suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) fun onDelta(pixels: Float) - fun onDragStopped(velocity: Float) + suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) } interface NestedScrollHandler { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt deleted file mode 100644 index 97d3fff48b23..000000000000 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.compose.animation.scene - -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.awaitEachGesture -import androidx.compose.foundation.gestures.awaitFirstDown -import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation -import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation -import androidx.compose.foundation.gestures.horizontalDrag -import androidx.compose.foundation.gestures.verticalDrag -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.pointer.PointerEventPass -import androidx.compose.ui.input.pointer.PointerId -import androidx.compose.ui.input.pointer.PointerInputChange -import androidx.compose.ui.input.pointer.PointerInputScope -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.input.pointer.positionChange -import androidx.compose.ui.input.pointer.util.VelocityTracker -import androidx.compose.ui.input.pointer.util.addPointerInputChange -import androidx.compose.ui.platform.LocalViewConfiguration -import androidx.compose.ui.unit.Velocity -import androidx.compose.ui.util.fastForEach - -/** - * Make an element draggable in the given [orientation]. - * - * The main difference with [multiPointerDraggable] and - * [androidx.compose.foundation.gestures.draggable] is that [onDragStarted] also receives the number - * of pointers that are down when the drag is started. If you don't need this information, you - * should use `draggable` instead. - * - * Note that the current implementation is trivial: we wait for the touch slope on the *first* down - * pointer, then we count the number of distinct pointers that are down right before calling - * [onDragStarted]. This means that the drag won't start when a first pointer is down (but not - * dragged) and a second pointer is down and dragged. This is an implementation detail that might - * change in the future. - */ -// TODO(b/291055080): Migrate to the Modifier.Node API. -@Composable -internal fun Modifier.multiPointerDraggable( - orientation: Orientation, - enabled: Boolean, - startDragImmediately: Boolean, - onDragStarted: (startedPosition: Offset, pointersDown: Int) -> Unit, - onDragDelta: (Float) -> Unit, - onDragStopped: (velocity: Float) -> Unit, -): Modifier { - val onDragStarted by rememberUpdatedState(onDragStarted) - val onDragStopped by rememberUpdatedState(onDragStopped) - val onDragDelta by rememberUpdatedState(onDragDelta) - val startDragImmediately by rememberUpdatedState(startDragImmediately) - - val velocityTracker = remember { VelocityTracker() } - val maxFlingVelocity = - LocalViewConfiguration.current.maximumFlingVelocity.let { max -> - val maxF = max.toFloat() - Velocity(maxF, maxF) - } - - return this.pointerInput(enabled, orientation, maxFlingVelocity) { - if (!enabled) { - return@pointerInput - } - - val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown -> - velocityTracker.resetTracking() - onDragStarted(startedPosition, pointersDown) - } - - val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) } - - val onDragEnd: () -> Unit = { - val velocity = velocityTracker.calculateVelocity(maxFlingVelocity) - onDragStopped( - when (orientation) { - Orientation.Horizontal -> velocity.x - Orientation.Vertical -> velocity.y - } - ) - } - - val onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit = { change, amount -> - velocityTracker.addPointerInputChange(change) - onDragDelta(amount) - } - - detectDragGestures( - orientation = orientation, - startDragImmediately = { startDragImmediately }, - onDragStart = onDragStart, - onDragEnd = onDragEnd, - onDragCancel = onDragCancel, - onDrag = onDrag, - ) - } -} - -/** - * Detect drag gestures in the given [orientation]. - * - * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and - * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for: - * 1) starting the gesture immediately without requiring a drag >= touch slope; - * 2) passing the number of pointers down to [onDragStart]. - */ -private suspend fun PointerInputScope.detectDragGestures( - orientation: Orientation, - startDragImmediately: () -> Boolean, - onDragStart: (startedPosition: Offset, pointersDown: Int) -> Unit, - onDragEnd: () -> Unit, - onDragCancel: () -> Unit, - onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit, -) { - awaitEachGesture { - val initialDown = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial) - var overSlop = 0f - val drag = - if (startDragImmediately()) { - initialDown.consume() - initialDown - } else { - val down = awaitFirstDown(requireUnconsumed = false) - val onSlopReached = { change: PointerInputChange, over: Float -> - change.consume() - overSlop = over - } - - // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once - // it is public. - when (orientation) { - Orientation.Horizontal -> - awaitHorizontalTouchSlopOrCancellation(down.id, onSlopReached) - Orientation.Vertical -> - awaitVerticalTouchSlopOrCancellation(down.id, onSlopReached) - } - } - - if (drag != null) { - // Count the number of pressed pointers. - val pressed = mutableSetOf<PointerId>() - currentEvent.changes.fastForEach { change -> - if (change.pressed) { - pressed.add(change.id) - } - } - - onDragStart(drag.position, pressed.size) - onDrag(drag, overSlop) - - val successful = - when (orientation) { - Orientation.Horizontal -> - horizontalDrag(drag.id) { - onDrag(it, it.positionChange().x) - it.consume() - } - Orientation.Vertical -> - verticalDrag(drag.id) { - onDrag(it, it.positionChange().y) - it.consume() - } - } - - if (successful) { - onDragEnd() - } else { - onDragCancel() - } - } - } -} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 3fd6828fca6b..9c799b282571 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.State @@ -100,3 +101,19 @@ private class SceneScopeImpl( MovableElement(layoutImpl, scene, key, modifier, content) } } + +/** The destination scene when swiping up or left from [upOrLeft]. */ +internal fun Scene.upOrLeft(orientation: Orientation): SceneKey? { + return when (orientation) { + Orientation.Vertical -> userActions[Swipe.Up] + Orientation.Horizontal -> userActions[Swipe.Left] + } +} + +/** The destination scene when swiping down or right from [downOrRight]. */ +internal fun Scene.downOrRight(orientation: Orientation): SceneKey? { + return when (orientation) { + Orientation.Vertical -> userActions[Swipe.Down] + Orientation.Horizontal -> userActions[Swipe.Right] + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 4a1d73dbfcb1..74e66d2a9949 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -16,7 +16,6 @@ package com.android.compose.animation.scene -import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.remember @@ -192,9 +191,9 @@ data class Swipe( } } -enum class SwipeDirection(val orientation: Orientation) { - Up(Orientation.Vertical), - Down(Orientation.Vertical), - Left(Orientation.Horizontal), - Right(Orientation.Horizontal), +enum class SwipeDirection { + Up, + Down, + Left, + Right, } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index 25b5db5c7506..2dc53ab8bf76 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -22,6 +22,8 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -53,7 +55,7 @@ internal fun Modifier.swipeToScene( /** Whether swipe should be enabled in the given [orientation]. */ fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean = - userActions.keys.any { it is Swipe && it.direction.orientation == orientation } + upOrLeft(orientation) != null || downOrRight(orientation) != null val currentScene = gestureHandler.currentScene val canSwipe = currentScene.shouldEnableSwipes(orientation) @@ -66,7 +68,8 @@ internal fun Modifier.swipeToScene( ) return nestedScroll(connection = gestureHandler.nestedScroll.connection) - .multiPointerDraggable( + .draggable( + state = rememberDraggableState(onDelta = gestureHandler.draggable::onDelta), orientation = orientation, enabled = gestureHandler.isDrivingTransition || canSwipe, // Immediately start the drag if this our [transition] is currently animating to a scene @@ -77,7 +80,6 @@ internal fun Modifier.swipeToScene( gestureHandler.isAnimatingOffset && !canOppositeSwipe, onDragStarted = gestureHandler.draggable::onDragStarted, - onDragDelta = gestureHandler.draggable::onDelta, onDragStopped = gestureHandler.draggable::onDragStopped, ) } @@ -157,7 +159,7 @@ class SceneGestureHandler( internal var gestureWithPriority: Any? = null - internal fun onDragStarted(pointersDown: Int) { + internal fun onDragStarted() { if (isDrivingTransition) { // This [transition] was already driving the animation: simply take over it. // Stop animating and start from where the current offset. @@ -197,26 +199,6 @@ class SceneGestureHandler( Orientation.Vertical -> layoutImpl.size.height }.toFloat() - swipeTransition.actionUpOrLeft = - Swipe( - direction = - when (orientation) { - Orientation.Horizontal -> SwipeDirection.Left - Orientation.Vertical -> SwipeDirection.Up - }, - pointerCount = pointersDown, - ) - - swipeTransition.actionDownOrRight = - Swipe( - direction = - when (orientation) { - Orientation.Horizontal -> SwipeDirection.Right - Orientation.Vertical -> SwipeDirection.Down - }, - pointerCount = pointersDown, - ) - if (swipeTransition.absoluteDistance > 0f) { transitionState = swipeTransition } @@ -264,15 +246,11 @@ class SceneGestureHandler( // to the next screen or go back to the previous one. val offset = swipeTransition.dragOffset val absoluteDistance = swipeTransition.absoluteDistance - if ( - offset <= -absoluteDistance && - fromScene.userActions[swipeTransition.actionUpOrLeft] == toScene.key - ) { + if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) { swipeTransition.dragOffset += absoluteDistance swipeTransition._fromScene = toScene } else if ( - offset >= absoluteDistance && - fromScene.userActions[swipeTransition.actionDownOrRight] == toScene.key + offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key ) { swipeTransition.dragOffset -= absoluteDistance swipeTransition._fromScene = toScene @@ -294,8 +272,8 @@ class SceneGestureHandler( Orientation.Vertical -> layoutImpl.size.height }.toFloat() - val upOrLeft = userActions[swipeTransition.actionUpOrLeft] - val downOrRight = userActions[swipeTransition.actionDownOrRight] + val upOrLeft = upOrLeft(orientation) + val downOrRight = downOrRight(orientation) // Compute the target scene depending on the current offset. return when { @@ -538,10 +516,6 @@ class SceneGestureHandler( var _distance by mutableFloatStateOf(0f) val distance: Float get() = _distance - - /** The [UserAction]s associated to this swipe. */ - var actionUpOrLeft: UserAction = Back - var actionDownOrRight: UserAction = Back } companion object { @@ -552,9 +526,9 @@ class SceneGestureHandler( private class SceneDraggableHandler( private val gestureHandler: SceneGestureHandler, ) : DraggableHandler { - override fun onDragStarted(startedPosition: Offset, pointersDown: Int) { + override suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) { gestureHandler.gestureWithPriority = this - gestureHandler.onDragStarted(pointersDown) + gestureHandler.onDragStarted() } override fun onDelta(pixels: Float) { @@ -563,7 +537,7 @@ private class SceneDraggableHandler( } } - override fun onDragStopped(velocity: Float) { + override suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) { if (gestureHandler.gestureWithPriority == this) { gestureHandler.gestureWithPriority = null gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true) @@ -606,31 +580,11 @@ class SceneNestedScrollHandler( // moving on to the next scene. var gestureStartedOnNestedChild = false - val actionUpOrLeft = - Swipe( - direction = - when (gestureHandler.orientation) { - Orientation.Horizontal -> SwipeDirection.Left - Orientation.Vertical -> SwipeDirection.Up - }, - pointerCount = 1, - ) - - val actionDownOrRight = - Swipe( - direction = - when (gestureHandler.orientation) { - Orientation.Horizontal -> SwipeDirection.Right - Orientation.Vertical -> SwipeDirection.Down - }, - pointerCount = 1, - ) - fun findNextScene(amount: Float): SceneKey? { val fromScene = gestureHandler.currentScene return when { - amount < 0f -> fromScene.userActions[actionUpOrLeft] - amount > 0f -> fromScene.userActions[actionDownOrRight] + amount < 0f -> fromScene.upOrLeft(gestureHandler.orientation) + amount > 0f -> fromScene.downOrRight(gestureHandler.orientation) else -> null } } @@ -671,7 +625,7 @@ class SceneNestedScrollHandler( onStart = { gestureHandler.gestureWithPriority = this priorityScene = nextScene - gestureHandler.onDragStarted(pointersDown = 1) + gestureHandler.onDragStarted() }, onScroll = { offsetAvailable -> if (gestureHandler.gestureWithPriority != this) { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index 4b3fbdf77274..6791a85ff21c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -104,13 +104,13 @@ class SceneGestureHandlerTest { @Test fun onDragStarted_shouldStartATransition() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) assertScene(currentScene = SceneA, isIdle = false) } @Test fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) assertScene(currentScene = SceneA, isIdle = false) val transition = transitionState as Transition @@ -123,13 +123,14 @@ class SceneGestureHandlerTest { @Test fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) assertScene(currentScene = SceneA, isIdle = false) draggable.onDelta(pixels = deltaInPixels10) assertScene(currentScene = SceneA, isIdle = false) draggable.onDragStopped( + coroutineScope = coroutineScope, velocity = velocityThreshold - 0.01f, ) assertScene(currentScene = SceneA, isIdle = false) @@ -141,13 +142,14 @@ class SceneGestureHandlerTest { @Test fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) assertScene(currentScene = SceneA, isIdle = false) draggable.onDelta(pixels = deltaInPixels10) assertScene(currentScene = SceneA, isIdle = false) draggable.onDragStopped( + coroutineScope = coroutineScope, velocity = velocityThreshold, ) assertScene(currentScene = SceneC, isIdle = false) @@ -159,22 +161,23 @@ class SceneGestureHandlerTest { @Test fun onDragStoppedAfterStarted_returnImmediatelyToIdle() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) assertScene(currentScene = SceneA, isIdle = false) - draggable.onDragStopped(velocity = 0f) + draggable.onDragStopped(coroutineScope = coroutineScope, velocity = 0f) assertScene(currentScene = SceneA, isIdle = true) } @Test fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) assertScene(currentScene = SceneA, isIdle = false) draggable.onDelta(pixels = deltaInPixels10) assertScene(currentScene = SceneA, isIdle = false) draggable.onDragStopped( + coroutineScope = coroutineScope, velocity = velocityThreshold, ) @@ -188,7 +191,7 @@ class SceneGestureHandlerTest { assertScene(currentScene = SceneC, isIdle = false) // Start a new gesture while the offset is animating - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) assertThat(sceneGestureHandler.isAnimatingOffset).isFalse() } @@ -317,7 +320,7 @@ class SceneGestureHandlerTest { } @Test fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest { - draggable.onDragStopped(velocityThreshold) + draggable.onDragStopped(coroutineScope, velocityThreshold) assertScene(currentScene = SceneA, isIdle = true) } @@ -329,7 +332,7 @@ class SceneGestureHandlerTest { @Test fun startNestedScrollWhileDragging() = runGestureTest { - draggable.onDragStarted(Offset.Zero) + draggable.onDragStarted(coroutineScope, Offset.Zero) assertScene(currentScene = SceneA, isIdle = false) val transition = transitionState as Transition @@ -341,7 +344,7 @@ class SceneGestureHandlerTest { assertThat(transition.progress).isEqualTo(0.2f) // this should be ignored, we are scrolling now! - draggable.onDragStopped(velocityThreshold) + draggable.onDragStopped(coroutineScope, velocityThreshold) assertScene(currentScene = SceneA, isIdle = false) nestedScrollEvents(available = offsetY10) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 6ad2108c05a5..df3b72aa5533 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -83,11 +83,7 @@ class SwipeToSceneTest { } scene( TestScenes.SceneC, - userActions = - mapOf( - Swipe.Down to TestScenes.SceneA, - Swipe(SwipeDirection.Down, pointerCount = 2) to TestScenes.SceneB, - ), + userActions = mapOf(Swipe.Down to TestScenes.SceneA), ) { Box(Modifier.fillMaxSize()) } @@ -246,43 +242,4 @@ class SwipeToSceneTest { assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) } - - @Test - fun multiPointerSwipe() { - // Start at scene C. - currentScene = TestScenes.SceneC - - // 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 - rule.setContent { - touchSlop = LocalViewConfiguration.current.touchSlop - TestContent() - } - - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) - - // Swipe down with two fingers. - rule.onRoot().performTouchInput { - repeat(2) { i -> down(pointerId = i, middle) } - repeat(2) { i -> - moveBy(pointerId = i, Offset(0f, touchSlop + 10.dp.toPx()), delayMillis = 1_000) - } - } - - // We are transitioning to B because we used 2 fingers. - val transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneC) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) - - // Release the fingers and wait for the animation to end. We are back to C because we only - // swiped 10dp. - rule.onRoot().performTouchInput { repeat(2) { i -> up(pointerId = i) } } - rule.waitForIdle() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) - } } |