Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +11 −4 Original line number Diff line number Diff line Loading @@ -226,7 +226,6 @@ internal class DraggableHandlerImpl( val fromSource = resolveSwipeSource(startedPosition) val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true) val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false) return if (fromSource == null) { Swipes( upOrLeft = null, Loading Loading @@ -366,10 +365,18 @@ private class DragControllerImpl( return 0f } val currentTransitionIrreversible = if (swipeAnimation.isUpOrLeft) { swipes.upOrLeftResult?.isIrreversible ?: false } else { swipes.downOrRightResult?.isIrreversible ?: false } val needNewTransition = hasReachedToContent || !currentTransitionIrreversible && (hasReachedToContent || result.toContent(layoutState.currentScene) != swipeAnimation.toContent || result.transitionKey != swipeAnimation.contentTransition.key result.transitionKey != swipeAnimation.contentTransition.key) if (needNewTransition) { // Make sure the current transition will finish to the right current scene. Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +16 −1 Original line number Diff line number Diff line Loading @@ -498,6 +498,12 @@ sealed class UserActionResult( * bigger than 100% when the user released their finger. ` */ open val requiresFullDistanceSwipe: Boolean, /** * Whether swiping back in the opposite direction past the origin point of the swipe can replace * the action with the action for the opposite direction. */ open val isIrreversible: Boolean = false, ) { internal abstract fun toContent(currentScene: SceneKey): ContentKey Loading @@ -507,6 +513,7 @@ sealed class UserActionResult( val toScene: SceneKey, override val transitionKey: TransitionKey? = null, override val requiresFullDistanceSwipe: Boolean = false, override val isIrreversible: Boolean = false, ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) { override fun toContent(currentScene: SceneKey): ContentKey = toScene } Loading @@ -516,6 +523,7 @@ sealed class UserActionResult( val overlay: OverlayKey, override val transitionKey: TransitionKey? = null, override val requiresFullDistanceSwipe: Boolean = false, override val isIrreversible: Boolean = false, ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) { override fun toContent(currentScene: SceneKey): ContentKey = overlay } Loading Loading @@ -558,7 +566,14 @@ sealed class UserActionResult( * the user released their finger. */ requiresFullDistanceSwipe: Boolean = false, ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe) /** * Whether swiping back in the opposite direction past the origin point of the swipe can * replace the action with the action for the opposite direction. */ isIrreversible: Boolean = false, ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe, isIrreversible) /** A [UserActionResult] that shows [toOverlay]. */ operator fun invoke( Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +48 −0 Original line number Diff line number Diff line Loading @@ -506,6 +506,54 @@ class DraggableHandlerTest { ) } @Test fun onDragWithActionsInBothDirections_dragToOppositeDirectionReplacesAction() = runGestureTest { // We are on SceneA. UP -> B, DOWN-> C. val dragController = onDragStarted(overSlop = up(fractionOfScreen = 0.2f)) assertTransition( currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = 0.2f, ) // Reverse drag direction, it will replace the previous transition dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f)) assertTransition( currentScene = SceneA, fromScene = SceneA, toScene = SceneC, progress = 0.3f, ) } @Test fun onDragWithActionsInBothDirections_dragToOppositeDirectionNotReplaceable() = runGestureTest { // We are on SceneA. UP -> B, DOWN-> C. The up swipe is not replaceable though. mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC) val dragController = onDragStarted( startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f), overSlop = up(fractionOfScreen = 0.2f), ) assertTransition( currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = 0.2f, ) // Reverse drag direction, it cannot replace the previous transition dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f)) assertTransition( currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = -0.3f, ) } @Test fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest { navigateToSceneC() Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt +12 −6 Original line number Diff line number Diff line Loading @@ -85,7 +85,8 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions).isNotEmpty() assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true)) setUpState( isShadeTouchable = false, Loading @@ -102,7 +103,8 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions).isNotEmpty() assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true)) } @Test Loading @@ -120,7 +122,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade)) .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)) setUpState( isShadeTouchable = false, Loading @@ -138,7 +140,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade)) .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)) } @Test Loading @@ -156,7 +158,9 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Overlays.NotificationsShade)) .isEqualTo( UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true) ) setUpState( isShadeTouchable = false, Loading @@ -170,7 +174,9 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Overlays.NotificationsShade)) .isEqualTo( UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true) ) } private fun TestScope.setUpState( Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt +3 −2 Original line number Diff line number Diff line Loading @@ -303,7 +303,8 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { // Top edge is not applicable in dual shade, as well as two-finger swipe. assertThat(downDestination).isNull() } else { assertThat(downDestination).isEqualTo(ShowOverlay(Overlays.NotificationsShade)) assertThat(downDestination) .isEqualTo(ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)) assertThat(downDestination?.transitionKey).isNull() } Loading @@ -320,7 +321,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull() else -> { assertThat(downFromTopRightDestination) .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade)) .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true)) assertThat(downFromTopRightDestination?.transitionKey).isNull() } } Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +11 −4 Original line number Diff line number Diff line Loading @@ -226,7 +226,6 @@ internal class DraggableHandlerImpl( val fromSource = resolveSwipeSource(startedPosition) val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true) val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false) return if (fromSource == null) { Swipes( upOrLeft = null, Loading Loading @@ -366,10 +365,18 @@ private class DragControllerImpl( return 0f } val currentTransitionIrreversible = if (swipeAnimation.isUpOrLeft) { swipes.upOrLeftResult?.isIrreversible ?: false } else { swipes.downOrRightResult?.isIrreversible ?: false } val needNewTransition = hasReachedToContent || !currentTransitionIrreversible && (hasReachedToContent || result.toContent(layoutState.currentScene) != swipeAnimation.toContent || result.transitionKey != swipeAnimation.contentTransition.key result.transitionKey != swipeAnimation.contentTransition.key) if (needNewTransition) { // Make sure the current transition will finish to the right current scene. Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +16 −1 Original line number Diff line number Diff line Loading @@ -498,6 +498,12 @@ sealed class UserActionResult( * bigger than 100% when the user released their finger. ` */ open val requiresFullDistanceSwipe: Boolean, /** * Whether swiping back in the opposite direction past the origin point of the swipe can replace * the action with the action for the opposite direction. */ open val isIrreversible: Boolean = false, ) { internal abstract fun toContent(currentScene: SceneKey): ContentKey Loading @@ -507,6 +513,7 @@ sealed class UserActionResult( val toScene: SceneKey, override val transitionKey: TransitionKey? = null, override val requiresFullDistanceSwipe: Boolean = false, override val isIrreversible: Boolean = false, ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) { override fun toContent(currentScene: SceneKey): ContentKey = toScene } Loading @@ -516,6 +523,7 @@ sealed class UserActionResult( val overlay: OverlayKey, override val transitionKey: TransitionKey? = null, override val requiresFullDistanceSwipe: Boolean = false, override val isIrreversible: Boolean = false, ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) { override fun toContent(currentScene: SceneKey): ContentKey = overlay } Loading Loading @@ -558,7 +566,14 @@ sealed class UserActionResult( * the user released their finger. */ requiresFullDistanceSwipe: Boolean = false, ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe) /** * Whether swiping back in the opposite direction past the origin point of the swipe can * replace the action with the action for the opposite direction. */ isIrreversible: Boolean = false, ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe, isIrreversible) /** A [UserActionResult] that shows [toOverlay]. */ operator fun invoke( Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +48 −0 Original line number Diff line number Diff line Loading @@ -506,6 +506,54 @@ class DraggableHandlerTest { ) } @Test fun onDragWithActionsInBothDirections_dragToOppositeDirectionReplacesAction() = runGestureTest { // We are on SceneA. UP -> B, DOWN-> C. val dragController = onDragStarted(overSlop = up(fractionOfScreen = 0.2f)) assertTransition( currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = 0.2f, ) // Reverse drag direction, it will replace the previous transition dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f)) assertTransition( currentScene = SceneA, fromScene = SceneA, toScene = SceneC, progress = 0.3f, ) } @Test fun onDragWithActionsInBothDirections_dragToOppositeDirectionNotReplaceable() = runGestureTest { // We are on SceneA. UP -> B, DOWN-> C. The up swipe is not replaceable though. mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC) val dragController = onDragStarted( startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f), overSlop = up(fractionOfScreen = 0.2f), ) assertTransition( currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = 0.2f, ) // Reverse drag direction, it cannot replace the previous transition dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f)) assertTransition( currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = -0.3f, ) } @Test fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest { navigateToSceneC() Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt +12 −6 Original line number Diff line number Diff line Loading @@ -85,7 +85,8 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions).isNotEmpty() assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true)) setUpState( isShadeTouchable = false, Loading @@ -102,7 +103,8 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions).isNotEmpty() assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true)) } @Test Loading @@ -120,7 +122,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade)) .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)) setUpState( isShadeTouchable = false, Loading @@ -138,7 +140,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade)) .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)) } @Test Loading @@ -156,7 +158,9 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Overlays.NotificationsShade)) .isEqualTo( UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true) ) setUpState( isShadeTouchable = false, Loading @@ -170,7 +174,9 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() { assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) assertThat(actions?.get(Swipe.Down)) .isEqualTo(UserActionResult(Overlays.NotificationsShade)) .isEqualTo( UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true) ) } private fun TestScope.setUpState( Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt +3 −2 Original line number Diff line number Diff line Loading @@ -303,7 +303,8 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { // Top edge is not applicable in dual shade, as well as two-finger swipe. assertThat(downDestination).isNull() } else { assertThat(downDestination).isEqualTo(ShowOverlay(Overlays.NotificationsShade)) assertThat(downDestination) .isEqualTo(ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)) assertThat(downDestination?.transitionKey).isNull() } Loading @@ -320,7 +321,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull() else -> { assertThat(downFromTopRightDestination) .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade)) .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true)) assertThat(downFromTopRightDestination?.transitionKey).isNull() } } Loading