diff options
3 files changed, 58 insertions, 4 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 61332b61ed1b..ffd95643c122 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -614,8 +614,8 @@ internal class MutableSceneTransitionLayoutStateImpl( } val shouldSnap = - (isProgressCloseTo(0f) && transition.currentScene == transition.fromContent) || - (isProgressCloseTo(1f) && transition.currentScene == transition.toContent) + (isProgressCloseTo(0f) && transition.isFromCurrentContent()) || + (isProgressCloseTo(1f) && transition.isToCurrentContent()) return if (shouldSnap) { finishAllTransitions() true diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt index 7d29a68e3a8d..ccabe3d01d26 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt @@ -129,7 +129,7 @@ sealed interface TransitionState { * starting a swipe transition to show [overlay] and will be `true` only once the swipe * transition is committed. */ - protected abstract val isEffectivelyShown: Boolean + abstract val isEffectivelyShown: Boolean init { check( @@ -164,7 +164,7 @@ sealed interface TransitionState { * [fromOverlay] by [toOverlay] and will [toOverlay] once the swipe transition is * committed. */ - protected abstract val effectivelyShownOverlay: OverlayKey + abstract val effectivelyShownOverlay: OverlayKey init { check(fromOverlay != toOverlay) @@ -327,6 +327,21 @@ sealed interface TransitionState { } } + /** Whether [fromContent] is effectively the current content of the transition. */ + internal fun isFromCurrentContent() = isCurrentContent(expectedFrom = true) + + /** Whether [toContent] is effectively the current content of the transition. */ + internal fun isToCurrentContent() = isCurrentContent(expectedFrom = false) + + private fun isCurrentContent(expectedFrom: Boolean): Boolean { + val expectedContent = if (expectedFrom) fromContent else toContent + return when (this) { + is ChangeScene -> currentScene == expectedContent + is ReplaceOverlay -> effectivelyShownOverlay == expectedContent + is ShowOrHideOverlay -> isEffectivelyShown == (expectedContent == overlay) + } + } + /** Run this transition and return once it is finished. */ abstract suspend fun run() diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index a2b263b9f9ed..75339a8a2a73 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestOverlays.OverlayA import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC @@ -327,6 +328,25 @@ class SceneTransitionLayoutStateTest { } @Test + fun snapToIdleIfClose_snapToStart_overlays() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) + state.startTransitionImmediately( + animationScope = backgroundScope, + transition(SceneA, OverlayA, isEffectivelyShown = { false }, progress = { 0.2f }), + ) + assertThat(state.isTransitioning()).isTrue() + + // Ignore the request if the progress is not close to 0 or 1, using the threshold. + assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse() + assertThat(state.isTransitioning()).isTrue() + + // Go to the initial scene if it is close to 0. + assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue() + assertThat(state.isTransitioning()).isFalse() + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA)) + } + + @Test fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) state.startTransitionImmediately( @@ -346,6 +366,25 @@ class SceneTransitionLayoutStateTest { } @Test + fun snapToIdleIfClose_snapToEnd_overlays() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) + state.startTransitionImmediately( + animationScope = backgroundScope, + transition(SceneA, OverlayA, isEffectivelyShown = { true }, progress = { 0.8f }), + ) + assertThat(state.isTransitioning()).isTrue() + + // Ignore the request if the progress is not close to 0 or 1, using the threshold. + assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse() + assertThat(state.isTransitioning()).isTrue() + + // Go to the final scene if it is close to 1. + assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue() + assertThat(state.isTransitioning()).isFalse() + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA, setOf(OverlayA))) + } + + @Test fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) |