Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 48e592c2 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "Fix SceneTransitionLayoutDataSource.currentScene" into main

parents b330e100 5a3b2b09
Loading
Loading
Loading
Loading
+1 −16
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@
package com.android.systemui.scene.ui.composable

import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.observableTransitionState
@@ -29,8 +28,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/**
@@ -48,19 +45,7 @@ class SceneTransitionLayoutDataSource(
    override val currentScene: StateFlow<SceneKey> =
        state
            .observableTransitionState()
            .flatMapLatest { observableTransitionState ->
                when (observableTransitionState) {
                    is ObservableTransitionState.Idle -> flowOf(observableTransitionState.scene)
                    is ObservableTransitionState.Transition ->
                        observableTransitionState.isUserInputOngoing.map { isUserInputOngoing ->
                            if (isUserInputOngoing) {
                                observableTransitionState.fromScene
                            } else {
                                observableTransitionState.toScene
                            }
                        }
                }
            }
            .flatMapLatest { it.currentScene() }
            .stateIn(
                scope = coroutineScope,
                started = SharingStarted.WhileSubscribed(),
+18 −4
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.compose.animation.scene
import androidx.compose.runtime.snapshotFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf

/**
 * A scene transition state.
@@ -33,14 +34,26 @@ import kotlinx.coroutines.flow.distinctUntilChanged
 *    [ObservableTransitionState.Transition.toScene] will never be equal, while
 *    [TransitionState.Transition.fromScene] and [TransitionState.Transition.toScene] can be equal.
 */
sealed class ObservableTransitionState {
sealed interface ObservableTransitionState {
    /**
     * The current effective scene. If a new transition was triggered, it would start from this
     * scene.
     */
    fun currentScene(): Flow<SceneKey> {
        return when (this) {
            is Idle -> flowOf(currentScene)
            is Transition -> currentScene
        }
    }

    /** No transition/animation is currently running. */
    data class Idle(val scene: SceneKey) : ObservableTransitionState()
    data class Idle(val currentScene: SceneKey) : ObservableTransitionState

    /** There is a transition animating between two scenes. */
    data class Transition(
    class Transition(
        val fromScene: SceneKey,
        val toScene: SceneKey,
        val currentScene: Flow<SceneKey>,
        val progress: Flow<Float>,

        /**
@@ -60,7 +73,7 @@ sealed class ObservableTransitionState {
         * the transition completes/settles.
         */
        val isUserInputOngoing: Flow<Boolean>,
    ) : ObservableTransitionState()
    ) : ObservableTransitionState
}

/**
@@ -76,6 +89,7 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans
                    ObservableTransitionState.Transition(
                        fromScene = state.fromScene,
                        toScene = state.toScene,
                        currentScene = snapshotFlow { state.currentScene },
                        progress = snapshotFlow { state.progress },
                        isInitiatedByUserInput = state.isInitiatedByUserInput,
                        isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+76 −12
Original line number Diff line number Diff line
@@ -16,10 +16,18 @@

package com.android.compose.animation.scene

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@@ -55,8 +63,8 @@ class ObservableTransitionStateTest {
        }

        rule.testTransition(
            from = TestScenes.SceneA,
            to = TestScenes.SceneB,
            from = SceneA,
            to = SceneB,
            transitionLayout = { currentScene, onChangeScene ->
                state =
                    updateSceneTransitionLayoutState(
@@ -66,34 +74,90 @@ class ObservableTransitionStateTest {
                    )

                SceneTransitionLayout(state = state) {
                    scene(TestScenes.SceneA) {}
                    scene(TestScenes.SceneB) {}
                    scene(SceneA) {}
                    scene(SceneB) {}
                }
            }
        ) {
            before {
                assertThat(observableState())
                    .isEqualTo(ObservableTransitionState.Idle(TestScenes.SceneA))
                assertThat(observableState()).isEqualTo(ObservableTransitionState.Idle(SceneA))
            }
            at(0) {
                val state = observableState()
                assertThat(state).isInstanceOf(ObservableTransitionState.Transition::class.java)
                assertThat((state as ObservableTransitionState.Transition).fromScene)
                    .isEqualTo(TestScenes.SceneA)
                assertThat(state.toScene).isEqualTo(TestScenes.SceneB)
                    .isEqualTo(SceneA)
                assertThat(state.toScene).isEqualTo(SceneB)
                assertThat(state.progress()).isEqualTo(0f)
            }
            at(TestTransitionDuration / 2) {
                val state = observableState()
                assertThat((state as ObservableTransitionState.Transition).fromScene)
                    .isEqualTo(TestScenes.SceneA)
                assertThat(state.toScene).isEqualTo(TestScenes.SceneB)
                    .isEqualTo(SceneA)
                assertThat(state.toScene).isEqualTo(SceneB)
                assertThat(state.progress()).isEqualTo(0.5f)
            }
            after {
                assertThat(observableState())
                    .isEqualTo(ObservableTransitionState.Idle(TestScenes.SceneB))
                assertThat(observableState()).isEqualTo(ObservableTransitionState.Idle(SceneB))
            }
        }
    }

    @Test
    fun observableCurrentScene() = runTestWithSnapshots {
        val state =
            MutableSceneTransitionLayoutStateImpl(
                initialScene = SceneA,
                transitions = transitions {},
            )
        val observableCurrentScene =
            state.observableTransitionState().flatMapLatest { it.currentScene() }

        // Collect observableCurrentScene into currentScene (unfortunately we can't use
        // collectValues in this test target).
        val currentScene =
            object {
                private var _value: SceneKey? = null
                val value: SceneKey
                    get() {
                        runCurrent()
                        return _value ?: error("observableCurrentScene has no value")
                    }

                init {
                    backgroundScope.launch { observableCurrentScene.collect { _value = it } }
                }
            }

        assertThat(currentScene.value).isEqualTo(SceneA)

        // Start a transition to Scene B.
        var transitionCurrentScene by mutableStateOf(SceneA)
        val transition =
            transition(from = SceneA, to = SceneB, current = { transitionCurrentScene })
        state.startTransition(transition, transitionKey = null)
        assertThat(currentScene.value).isEqualTo(SceneA)

        // Change the transition current scene.
        transitionCurrentScene = SceneB
        assertThat(currentScene.value).isEqualTo(SceneB)

        transitionCurrentScene = SceneA
        assertThat(currentScene.value).isEqualTo(SceneA)
    }

    // See http://shortn/_hj4Mhikmos for inspiration.
    private fun runTestWithSnapshots(testBody: suspend TestScope.() -> Unit) {
        val globalWriteObserverHandle =
            Snapshot.registerGlobalWriteObserver {
                // This is normally done by the compose runtime.
                Snapshot.sendApplyNotifications()
            }

        try {
            runTest(testBody = testBody)
        } finally {
            globalWriteObserverHandle.dispose()
        }
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import kotlinx.coroutines.test.TestScope
fun transition(
    from: SceneKey,
    to: SceneKey,
    current: () -> SceneKey = { from },
    progress: () -> Float = { 0f },
    interruptionProgress: () -> Float = { 100f },
    isInitiatedByUserInput: Boolean = false,
@@ -37,7 +38,8 @@ fun transition(
    onFinish: ((TransitionState.Transition) -> Job)? = null,
): TransitionState.Transition {
    return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties {
        override val currentScene: SceneKey = from
        override val currentScene: SceneKey
            get() = current()
        override val progress: Float
            get() = progress()

+6 −0
Original line number Diff line number Diff line
@@ -795,6 +795,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
                ObservableTransitionState.Transition(
                    Scenes.Lockscreen,
                    Scenes.Bouncer,
                    flowOf(Scenes.Bouncer),
                    flowOf(.5f),
                    false,
                    isUserInputOngoing = flowOf(false),
@@ -818,6 +819,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
                ObservableTransitionState.Transition(
                    Scenes.Bouncer,
                    Scenes.Gone,
                    flowOf(Scenes.Gone),
                    flowOf(.5f),
                    false,
                    isUserInputOngoing = flowOf(false),
@@ -837,6 +839,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
                ObservableTransitionState.Transition(
                    Scenes.Gone,
                    Scenes.Bouncer,
                    flowOf(Scenes.Bouncer),
                    flowOf(.5f),
                    false,
                    isUserInputOngoing = flowOf(false),
@@ -858,6 +861,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
                ObservableTransitionState.Transition(
                    Scenes.Bouncer,
                    Scenes.Gone,
                    flowOf(Scenes.Gone),
                    flowOf(.5f),
                    false,
                    isUserInputOngoing = flowOf(false),
@@ -876,6 +880,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
                ObservableTransitionState.Transition(
                    Scenes.Gone,
                    Scenes.Lockscreen,
                    flowOf(Scenes.Lockscreen),
                    flowOf(.5f),
                    false,
                    isUserInputOngoing = flowOf(false),
@@ -896,6 +901,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
                ObservableTransitionState.Transition(
                    Scenes.Lockscreen,
                    Scenes.Gone,
                    flowOf(Scenes.Gone),
                    flowOf(.5f),
                    false,
                    isUserInputOngoing = flowOf(false),
Loading