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