1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.communal.domain.interactor 18 19 import com.android.compose.animation.scene.ObservableTransitionState 20 import com.android.compose.animation.scene.SceneKey 21 import com.android.compose.animation.scene.TransitionKey 22 import com.android.systemui.communal.data.repository.CommunalSceneRepository 23 import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel 24 import com.android.systemui.communal.shared.model.CommunalScenes 25 import com.android.systemui.communal.shared.model.EditModeState 26 import com.android.systemui.dagger.SysUISingleton 27 import com.android.systemui.dagger.qualifiers.Application 28 import javax.inject.Inject 29 import kotlinx.coroutines.CoroutineScope 30 import kotlinx.coroutines.ExperimentalCoroutinesApi 31 import kotlinx.coroutines.flow.Flow 32 import kotlinx.coroutines.flow.MutableStateFlow 33 import kotlinx.coroutines.flow.SharingStarted 34 import kotlinx.coroutines.flow.StateFlow 35 import kotlinx.coroutines.flow.asStateFlow 36 import kotlinx.coroutines.flow.distinctUntilChanged 37 import kotlinx.coroutines.flow.flatMapLatest 38 import kotlinx.coroutines.flow.flowOf 39 import kotlinx.coroutines.flow.map 40 import kotlinx.coroutines.flow.stateIn 41 42 @OptIn(ExperimentalCoroutinesApi::class) 43 @SysUISingleton 44 class CommunalSceneInteractor 45 @Inject 46 constructor( 47 @Application private val applicationScope: CoroutineScope, 48 private val communalSceneRepository: CommunalSceneRepository, 49 ) { 50 /** 51 * Asks for an asynchronous scene witch to [newScene], which will use the corresponding 52 * installed transition or the one specified by [transitionKey], if provided. 53 */ 54 fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) { 55 communalSceneRepository.changeScene(newScene, transitionKey) 56 } 57 58 /** Immediately snaps to the new scene. */ 59 fun snapToScene(newScene: SceneKey, delayMillis: Long = 0) { 60 communalSceneRepository.snapToScene(newScene, delayMillis) 61 } 62 63 /** Immediately snaps to the new scene when activity is started. */ 64 fun snapToSceneForActivityStart(newScene: SceneKey, delayMillis: Long = 0) { 65 // skip if we're starting edit mode activity, as it will be handled later by changeScene 66 // with transition key [CommunalTransitionKeys.ToEditMode]. 67 if (_editModeState.value == EditModeState.STARTING) { 68 return 69 } 70 snapToScene(newScene, delayMillis) 71 } 72 73 /** 74 * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. 75 */ 76 val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene 77 78 private val _editModeState = MutableStateFlow<EditModeState?>(null) 79 /** 80 * Current state for glanceable hub edit mode, used to chain the animations when transitioning 81 * between communal scene and the edit mode activity. 82 */ 83 val editModeState = _editModeState.asStateFlow() 84 85 fun setEditModeState(value: EditModeState?) { 86 _editModeState.value = value 87 } 88 89 /** Transition state of the hub mode. */ 90 val transitionState: StateFlow<ObservableTransitionState> = 91 communalSceneRepository.transitionState 92 93 /** 94 * Updates the transition state of the hub [SceneTransitionLayout]. 95 * 96 * Note that you must call is with `null` when the UI is done or risk a memory leak. 97 */ 98 fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { 99 communalSceneRepository.setTransitionState(transitionState) 100 } 101 102 /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */ 103 fun transitionProgressToScene(targetScene: SceneKey) = 104 transitionState 105 .flatMapLatest { state -> 106 when (state) { 107 is ObservableTransitionState.Idle -> 108 flowOf(CommunalTransitionProgressModel.Idle(state.currentScene)) 109 is ObservableTransitionState.Transition -> 110 if (state.toScene == targetScene) { 111 state.progress.map { 112 CommunalTransitionProgressModel.Transition( 113 // Clamp the progress values between 0 and 1 as actual progress 114 // values can be higher than 0 or lower than 1 due to a fling. 115 progress = it.coerceIn(0.0f, 1.0f) 116 ) 117 } 118 } else { 119 flowOf(CommunalTransitionProgressModel.OtherTransition) 120 } 121 } 122 } 123 .distinctUntilChanged() 124 125 /** 126 * Flow that emits a boolean if the communal UI is fully visible and not in transition. 127 * 128 * This will not be true while transitioning to the hub and will turn false immediately when a 129 * swipe to exit the hub starts. 130 */ 131 val isIdleOnCommunal: StateFlow<Boolean> = 132 transitionState 133 .map { 134 it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Communal 135 } 136 .stateIn( 137 scope = applicationScope, 138 started = SharingStarted.Eagerly, 139 initialValue = false, 140 ) 141 142 /** 143 * Flow that emits a boolean if any portion of the communal UI is visible at all. 144 * 145 * This flow will be true during any transition and when idle on the communal scene. 146 */ 147 val isCommunalVisible: Flow<Boolean> = 148 transitionState.map { 149 !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) 150 } 151 } 152