Loading packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +5 −1 Original line number Diff line number Diff line Loading @@ -77,7 +77,7 @@ fun SceneContainer( SceneTransitionLayout( currentScene = currentSceneKey.toTransitionSceneKey(), onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) }, onChangeScene = viewModel::onSceneChanged, transitions = transitions {}, state = state, modifier = modifier.fillMaxSize(), Loading Loading @@ -154,3 +154,7 @@ private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction { is UserAction.Back -> Back } } private fun SceneContainerViewModel.onSceneChanged(sceneKey: SceneTransitionSceneKey) { onSceneChanged(sceneKey.toModel()) } packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +10 −12 Original line number Diff line number Diff line Loading @@ -476,18 +476,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { // When the scene framework transitions from bouncer to gone, we dismiss the keyguard. mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( mSceneInteractor.get().getTransitions(), sceneTransitionModel -> { if (sceneTransitionModel != null && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) { mSceneInteractor.get().finishedSceneTransitions( /* from= */ SceneKey.Bouncer.INSTANCE, /* to= */ SceneKey.Gone.INSTANCE), unused -> { final int selectedUserId = mUserInteractor.getSelectedUserId(); showNextSecurityScreenOrFinish( /* authenticated= */ true, selectedUserId, /* bypassSecondaryLockScreen= */ true, mSecurityModel.getSecurityMode(selectedUserId)); } }); } } Loading packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +3 −3 Original line number Diff line number Diff line Loading @@ -116,12 +116,12 @@ constructor( repository.setMessage( message ?: promptMessage(authenticationInteractor.getAuthenticationMethod()) ) sceneInteractor.setCurrentScene( sceneInteractor.changeScene( scene = SceneModel(SceneKey.Bouncer), loggingReason = "request to unlock device while authentication required", ) } else { sceneInteractor.setCurrentScene( sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "request to unlock device while authentication isn't required", ) Loading Loading @@ -169,7 +169,7 @@ constructor( authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null if (isAuthenticated) { sceneInteractor.setCurrentScene( sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "successful authentication", ) Loading packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +24 −46 Original line number Diff line number Diff line Loading @@ -18,50 +18,49 @@ package com.android.systemui.scene.data.repository import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.SceneTransitionModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn /** Source of truth for scene framework application state. */ class SceneContainerRepository @Inject constructor( @Application applicationScope: CoroutineScope, private val config: SceneContainerConfig, ) { private val _desiredScene = MutableStateFlow(SceneModel(config.initialSceneKey)) val desiredScene: StateFlow<SceneModel> = _desiredScene.asStateFlow() private val _isVisible = MutableStateFlow(true) val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow() private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey)) val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow() private val transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) val transitionProgress: Flow<Float> = transitionState.flatMapLatest { observableTransitionStateFlow -> observableTransitionStateFlow?.flatMapLatest { observableTransitionState -> when (observableTransitionState) { is ObservableTransitionState.Idle -> flowOf(1f) is ObservableTransitionState.Transition -> observableTransitionState.progress } } ?: flowOf(1f) } private val _transitions = MutableStateFlow<SceneTransitionModel?>(null) val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow() private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey) private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) val transitionState: StateFlow<ObservableTransitionState> = _transitionState .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, initialValue = defaultTransitionState, ) /** * Returns the keys to all scenes in the container with the given name. * Returns the keys to all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. Loading @@ -70,40 +69,19 @@ constructor( return config.sceneKeys } /** Sets the current scene in the container with the given name. */ fun setCurrentScene(scene: SceneModel) { fun setDesiredScene(scene: SceneModel) { check(allSceneKeys().contains(scene.key)) { """ Cannot set current scene key to "${scene.key}". The configuration does not contain a scene with that key. """ .trimIndent() } _currentScene.value = scene } /** Sets the scene transition in the container with the given name. */ fun setSceneTransition(from: SceneKey, to: SceneKey) { check(allSceneKeys().contains(from)) { """ Cannot set current scene key to "$from". The configuration does not contain a scene with that key. """ .trimIndent() } check(allSceneKeys().contains(to)) { """ Cannot set current scene key to "$to". The configuration does not contain a scene with that key. Cannot set the desired scene key to "${scene.key}". The configuration does not contain a scene with that key. """ .trimIndent() } _transitions.value = SceneTransitionModel(from = from, to = to) _desiredScene.value = scene } /** Sets whether the container with the given name is visible. */ /** Sets whether the container is visible. */ fun setVisible(isVisible: Boolean) { _isVisible.value = isVisible } Loading @@ -114,6 +92,6 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { this.transitionState.value = transitionState _transitionState.value = transitionState } } packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +106 −35 Original line number Diff line number Diff line Loading @@ -23,12 +23,15 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.SceneTransitionModel import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull /** * Generic business logic and app state accessors for the scene framework. Loading @@ -46,7 +49,54 @@ constructor( ) { /** * Returns the keys of all scenes in the container with the given name. * The currently *desired* scene. * * **Important:** this value will _commonly be different_ from what is being rendered in the UI, * by design. * * There are two intended sources for this value: * 1. Programmatic requests to transition to another scene (calls to [changeScene]). * 2. Reports from the UI about completing a transition to another scene (calls to * [onSceneChanged]). * * Both the sources above cause the value of this flow to change; however, they cause mismatches * in different ways. * * **Updates from programmatic transitions** * * When an external bit of code asks the framework to switch to another scene, the value here * will update immediately. Downstream, the UI will detect this change and initiate the * transition animation. As the transition animation progresses, a threshold will be reached, at * which point the UI and the state here will match each other. * * **Updates from the UI** * * When the user interacts with the UI, the UI runs a transition animation that tracks the user * pointer (for example, the user's finger). During this time, the state value here and what the * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the * change, making the value here match the UI again. */ val desiredScene: StateFlow<SceneModel> = repository.desiredScene /** * The current state of the transition. * * Consumers should use this state to know: * 1. Whether there is an ongoing transition or if the system is at rest. * 2. When transitioning, which scenes are being transitioned between. * 3. When transitioning, what the progress of the transition is. */ val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState /** Whether the scene container is visible. */ val isVisible: StateFlow<Boolean> = repository.isVisible private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) /** A flow of motion events originating from outside of the scene framework. */ val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() /** * Returns the keys of all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. Loading @@ -55,26 +105,20 @@ constructor( return repository.allSceneKeys() } /** Sets the scene in the container with the given name. */ fun setCurrentScene(scene: SceneModel, loggingReason: String) { val currentSceneKey = repository.currentScene.value.key if (currentSceneKey == scene.key) { return } logger.logSceneChange( from = currentSceneKey, to = scene.key, reason = loggingReason, ) repository.setCurrentScene(scene) repository.setSceneTransition(from = currentSceneKey, to = scene.key) /** * Requests a scene change to the given scene. * * The change is animated. Therefore, while the value in [desiredScene] will update immediately, * it will be some time before the UI will switch to the desired scene. The scene change * requested is remembered here but served by the UI layer, which will start a transition * animation. Once enough of the transition has occurred, the system will come into agreement * between the [desiredScene] and the UI. */ fun changeScene(scene: SceneModel, loggingReason: String) { updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested) } /** The current scene in the container with the given name. */ val currentScene: StateFlow<SceneModel> = repository.currentScene /** Sets the visibility of the container with the given name. */ /** Sets the visibility of the container. */ fun setVisible(isVisible: Boolean, loggingReason: String) { val wasVisible = repository.isVisible.value if (wasVisible == isVisible) { Loading @@ -89,9 +133,6 @@ constructor( return repository.setVisible(isVisible) } /** Whether the container with the given name is visible. */ val isVisible: StateFlow<Boolean> = repository.isVisible /** * Binds the given flow so the system remembers it. * Loading @@ -101,23 +142,53 @@ constructor( repository.setTransitionState(transitionState) } /** Progress of the transition into the current scene in the container with the given name. */ val transitionProgress: Flow<Float> = repository.transitionProgress /** * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene * transition occurs. The flow begins with a `null` value at first, because the initial scene is * not something that we transition to from another scene. * Returns a stream of events that emits one [Unit] every time the framework transitions from * [from] to [to]. */ val transitions: StateFlow<SceneTransitionModel?> = repository.transitions private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) /** A flow of motion events originating from outside of the scene framework. */ val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> { return transitionState .mapNotNull { it as? ObservableTransitionState.Idle } .map { idleState -> idleState.scene } .distinctUntilChanged() .pairwise() .mapNotNull { (previousSceneKey, currentSceneKey) -> Unit.takeIf { previousSceneKey == from && currentSceneKey == to } } } /** Handles a remote user input. */ fun onRemoteUserInput(input: RemoteUserInput) { _remoteUserInput.value = input } /** * Notifies that the UI has transitioned sufficiently to the given scene. * * *Not intended for external use!* * * Once a transition between one scene and another passes a threshold, the UI invokes this * method to report it, updating the value in [desiredScene] to match what the UI shows. */ internal fun onSceneChanged(scene: SceneModel, loggingReason: String) { updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted) } private fun updateDesiredScene( scene: SceneModel, loggingReason: String, log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit, ) { val currentSceneKey = desiredScene.value.key if (currentSceneKey == scene.key) { return } log( /* from= */ currentSceneKey, /* to= */ scene.key, /* loggingReason= */ loggingReason, ) repository.setDesiredScene(scene) } } Loading
packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +5 −1 Original line number Diff line number Diff line Loading @@ -77,7 +77,7 @@ fun SceneContainer( SceneTransitionLayout( currentScene = currentSceneKey.toTransitionSceneKey(), onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) }, onChangeScene = viewModel::onSceneChanged, transitions = transitions {}, state = state, modifier = modifier.fillMaxSize(), Loading Loading @@ -154,3 +154,7 @@ private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction { is UserAction.Back -> Back } } private fun SceneContainerViewModel.onSceneChanged(sceneKey: SceneTransitionSceneKey) { onSceneChanged(sceneKey.toModel()) }
packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +10 −12 Original line number Diff line number Diff line Loading @@ -476,18 +476,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { // When the scene framework transitions from bouncer to gone, we dismiss the keyguard. mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( mSceneInteractor.get().getTransitions(), sceneTransitionModel -> { if (sceneTransitionModel != null && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) { mSceneInteractor.get().finishedSceneTransitions( /* from= */ SceneKey.Bouncer.INSTANCE, /* to= */ SceneKey.Gone.INSTANCE), unused -> { final int selectedUserId = mUserInteractor.getSelectedUserId(); showNextSecurityScreenOrFinish( /* authenticated= */ true, selectedUserId, /* bypassSecondaryLockScreen= */ true, mSecurityModel.getSecurityMode(selectedUserId)); } }); } } Loading
packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +3 −3 Original line number Diff line number Diff line Loading @@ -116,12 +116,12 @@ constructor( repository.setMessage( message ?: promptMessage(authenticationInteractor.getAuthenticationMethod()) ) sceneInteractor.setCurrentScene( sceneInteractor.changeScene( scene = SceneModel(SceneKey.Bouncer), loggingReason = "request to unlock device while authentication required", ) } else { sceneInteractor.setCurrentScene( sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "request to unlock device while authentication isn't required", ) Loading Loading @@ -169,7 +169,7 @@ constructor( authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null if (isAuthenticated) { sceneInteractor.setCurrentScene( sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "successful authentication", ) Loading
packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +24 −46 Original line number Diff line number Diff line Loading @@ -18,50 +18,49 @@ package com.android.systemui.scene.data.repository import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.SceneTransitionModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn /** Source of truth for scene framework application state. */ class SceneContainerRepository @Inject constructor( @Application applicationScope: CoroutineScope, private val config: SceneContainerConfig, ) { private val _desiredScene = MutableStateFlow(SceneModel(config.initialSceneKey)) val desiredScene: StateFlow<SceneModel> = _desiredScene.asStateFlow() private val _isVisible = MutableStateFlow(true) val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow() private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey)) val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow() private val transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) val transitionProgress: Flow<Float> = transitionState.flatMapLatest { observableTransitionStateFlow -> observableTransitionStateFlow?.flatMapLatest { observableTransitionState -> when (observableTransitionState) { is ObservableTransitionState.Idle -> flowOf(1f) is ObservableTransitionState.Transition -> observableTransitionState.progress } } ?: flowOf(1f) } private val _transitions = MutableStateFlow<SceneTransitionModel?>(null) val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow() private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey) private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) val transitionState: StateFlow<ObservableTransitionState> = _transitionState .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, initialValue = defaultTransitionState, ) /** * Returns the keys to all scenes in the container with the given name. * Returns the keys to all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. Loading @@ -70,40 +69,19 @@ constructor( return config.sceneKeys } /** Sets the current scene in the container with the given name. */ fun setCurrentScene(scene: SceneModel) { fun setDesiredScene(scene: SceneModel) { check(allSceneKeys().contains(scene.key)) { """ Cannot set current scene key to "${scene.key}". The configuration does not contain a scene with that key. """ .trimIndent() } _currentScene.value = scene } /** Sets the scene transition in the container with the given name. */ fun setSceneTransition(from: SceneKey, to: SceneKey) { check(allSceneKeys().contains(from)) { """ Cannot set current scene key to "$from". The configuration does not contain a scene with that key. """ .trimIndent() } check(allSceneKeys().contains(to)) { """ Cannot set current scene key to "$to". The configuration does not contain a scene with that key. Cannot set the desired scene key to "${scene.key}". The configuration does not contain a scene with that key. """ .trimIndent() } _transitions.value = SceneTransitionModel(from = from, to = to) _desiredScene.value = scene } /** Sets whether the container with the given name is visible. */ /** Sets whether the container is visible. */ fun setVisible(isVisible: Boolean) { _isVisible.value = isVisible } Loading @@ -114,6 +92,6 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { this.transitionState.value = transitionState _transitionState.value = transitionState } }
packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +106 −35 Original line number Diff line number Diff line Loading @@ -23,12 +23,15 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.SceneTransitionModel import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull /** * Generic business logic and app state accessors for the scene framework. Loading @@ -46,7 +49,54 @@ constructor( ) { /** * Returns the keys of all scenes in the container with the given name. * The currently *desired* scene. * * **Important:** this value will _commonly be different_ from what is being rendered in the UI, * by design. * * There are two intended sources for this value: * 1. Programmatic requests to transition to another scene (calls to [changeScene]). * 2. Reports from the UI about completing a transition to another scene (calls to * [onSceneChanged]). * * Both the sources above cause the value of this flow to change; however, they cause mismatches * in different ways. * * **Updates from programmatic transitions** * * When an external bit of code asks the framework to switch to another scene, the value here * will update immediately. Downstream, the UI will detect this change and initiate the * transition animation. As the transition animation progresses, a threshold will be reached, at * which point the UI and the state here will match each other. * * **Updates from the UI** * * When the user interacts with the UI, the UI runs a transition animation that tracks the user * pointer (for example, the user's finger). During this time, the state value here and what the * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the * change, making the value here match the UI again. */ val desiredScene: StateFlow<SceneModel> = repository.desiredScene /** * The current state of the transition. * * Consumers should use this state to know: * 1. Whether there is an ongoing transition or if the system is at rest. * 2. When transitioning, which scenes are being transitioned between. * 3. When transitioning, what the progress of the transition is. */ val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState /** Whether the scene container is visible. */ val isVisible: StateFlow<Boolean> = repository.isVisible private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) /** A flow of motion events originating from outside of the scene framework. */ val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() /** * Returns the keys of all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. Loading @@ -55,26 +105,20 @@ constructor( return repository.allSceneKeys() } /** Sets the scene in the container with the given name. */ fun setCurrentScene(scene: SceneModel, loggingReason: String) { val currentSceneKey = repository.currentScene.value.key if (currentSceneKey == scene.key) { return } logger.logSceneChange( from = currentSceneKey, to = scene.key, reason = loggingReason, ) repository.setCurrentScene(scene) repository.setSceneTransition(from = currentSceneKey, to = scene.key) /** * Requests a scene change to the given scene. * * The change is animated. Therefore, while the value in [desiredScene] will update immediately, * it will be some time before the UI will switch to the desired scene. The scene change * requested is remembered here but served by the UI layer, which will start a transition * animation. Once enough of the transition has occurred, the system will come into agreement * between the [desiredScene] and the UI. */ fun changeScene(scene: SceneModel, loggingReason: String) { updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested) } /** The current scene in the container with the given name. */ val currentScene: StateFlow<SceneModel> = repository.currentScene /** Sets the visibility of the container with the given name. */ /** Sets the visibility of the container. */ fun setVisible(isVisible: Boolean, loggingReason: String) { val wasVisible = repository.isVisible.value if (wasVisible == isVisible) { Loading @@ -89,9 +133,6 @@ constructor( return repository.setVisible(isVisible) } /** Whether the container with the given name is visible. */ val isVisible: StateFlow<Boolean> = repository.isVisible /** * Binds the given flow so the system remembers it. * Loading @@ -101,23 +142,53 @@ constructor( repository.setTransitionState(transitionState) } /** Progress of the transition into the current scene in the container with the given name. */ val transitionProgress: Flow<Float> = repository.transitionProgress /** * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene * transition occurs. The flow begins with a `null` value at first, because the initial scene is * not something that we transition to from another scene. * Returns a stream of events that emits one [Unit] every time the framework transitions from * [from] to [to]. */ val transitions: StateFlow<SceneTransitionModel?> = repository.transitions private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) /** A flow of motion events originating from outside of the scene framework. */ val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> { return transitionState .mapNotNull { it as? ObservableTransitionState.Idle } .map { idleState -> idleState.scene } .distinctUntilChanged() .pairwise() .mapNotNull { (previousSceneKey, currentSceneKey) -> Unit.takeIf { previousSceneKey == from && currentSceneKey == to } } } /** Handles a remote user input. */ fun onRemoteUserInput(input: RemoteUserInput) { _remoteUserInput.value = input } /** * Notifies that the UI has transitioned sufficiently to the given scene. * * *Not intended for external use!* * * Once a transition between one scene and another passes a threshold, the UI invokes this * method to report it, updating the value in [desiredScene] to match what the UI shows. */ internal fun onSceneChanged(scene: SceneModel, loggingReason: String) { updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted) } private fun updateDesiredScene( scene: SceneModel, loggingReason: String, log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit, ) { val currentSceneKey = desiredScene.value.key if (currentSceneKey == scene.key) { return } log( /* from= */ currentSceneKey, /* to= */ scene.key, /* loggingReason= */ loggingReason, ) repository.setDesiredScene(scene) } }