1 /*
<lambda>null2  * Copyright (C) 2023 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.shade.domain.interactor
18 
19 import com.android.compose.animation.scene.ObservableTransitionState
20 import com.android.compose.animation.scene.SceneKey
21 import com.android.systemui.dagger.SysUISingleton
22 import com.android.systemui.dagger.qualifiers.Application
23 import com.android.systemui.scene.domain.interactor.SceneInteractor
24 import com.android.systemui.scene.shared.model.SceneFamilies
25 import com.android.systemui.shade.data.repository.ShadeRepository
26 import com.android.systemui.shade.shared.model.ShadeMode
27 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
28 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
29 import javax.inject.Inject
30 import kotlinx.coroutines.CoroutineScope
31 import kotlinx.coroutines.flow.Flow
32 import kotlinx.coroutines.flow.SharingStarted
33 import kotlinx.coroutines.flow.StateFlow
34 import kotlinx.coroutines.flow.combine
35 import kotlinx.coroutines.flow.distinctUntilChanged
36 import kotlinx.coroutines.flow.flowOf
37 import kotlinx.coroutines.flow.map
38 import kotlinx.coroutines.flow.stateIn
39 
40 /** ShadeInteractor implementation for Scene Container. */
41 @SysUISingleton
42 class ShadeInteractorSceneContainerImpl
43 @Inject
44 constructor(
45     @Application scope: CoroutineScope,
46     sceneInteractor: SceneInteractor,
47     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
48     shadeRepository: ShadeRepository,
49 ) : BaseShadeInteractor {
50     override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
51 
52     override val shadeExpansion: StateFlow<Float> =
53         sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade)
54             .stateIn(scope, SharingStarted.Eagerly, 0f)
55 
56     private val sceneBasedQsExpansion =
57         sceneBasedExpansion(sceneInteractor, SceneFamilies.QuickSettings)
58 
59     override val qsExpansion: StateFlow<Float> =
60         combine(
61                 sharedNotificationContainerInteractor.isSplitShadeEnabled,
62                 shadeExpansion,
63                 sceneBasedQsExpansion,
64             ) { isSplitShadeEnabled, shadeExpansion, qsExpansion ->
65                 if (isSplitShadeEnabled) {
66                     shadeExpansion
67                 } else {
68                     qsExpansion
69                 }
70             }
71             .stateIn(scope, SharingStarted.Eagerly, 0f)
72 
73     override val isQsExpanded: StateFlow<Boolean> =
74         qsExpansion
75             .map { it > 0 }
76             .distinctUntilChanged()
77             .stateIn(scope, SharingStarted.Eagerly, false)
78 
79     override val isQsBypassingShade: Flow<Boolean> =
80         combine(
81                 sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
82                 sceneInteractor.resolveSceneFamily(SceneFamilies.NotifShade),
83                 ::Pair
84             )
85             .flatMapLatestConflated { (quickSettingsScene, notificationsScene) ->
86                 sceneInteractor.transitionState
87                     .map { state ->
88                         when (state) {
89                             is ObservableTransitionState.Idle -> false
90                             is ObservableTransitionState.Transition ->
91                                 state.toScene == quickSettingsScene &&
92                                     state.fromScene != notificationsScene
93                         }
94                     }
95                     .distinctUntilChanged()
96             }
97             .distinctUntilChanged()
98 
99     override val isQsFullscreen: Flow<Boolean> =
100         sceneInteractor
101             .resolveSceneFamily(SceneFamilies.QuickSettings)
102             .flatMapLatestConflated { quickSettingsScene ->
103                 sceneInteractor.transitionState
104                     .map { state ->
105                         when (state) {
106                             is ObservableTransitionState.Idle ->
107                                 state.currentScene == quickSettingsScene
108                             is ObservableTransitionState.Transition -> false
109                         }
110                     }
111                     .distinctUntilChanged()
112             }
113             .distinctUntilChanged()
114 
115     override val anyExpansion: StateFlow<Float> =
116         createAnyExpansionFlow(scope, shadeExpansion, qsExpansion)
117 
118     override val isAnyExpanded =
119         anyExpansion
120             .map { it > 0f }
121             .distinctUntilChanged()
122             .stateIn(scope, SharingStarted.Eagerly, false)
123 
124     override val isUserInteractingWithShade: Flow<Boolean> =
125         sceneBasedInteracting(sceneInteractor, SceneFamilies.NotifShade)
126 
127     override val isUserInteractingWithQs: Flow<Boolean> =
128         sceneBasedInteracting(sceneInteractor, SceneFamilies.QuickSettings)
129 
130     /**
131      * Returns a flow that uses scene transition progress to and from a scene that is pulled down
132      * from the top of the screen to a 0-1 expansion amount float.
133      */
134     fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
135         sceneInteractor
136             .resolveSceneFamily(sceneKey)
137             .flatMapLatestConflated { resolvedSceneKey ->
138                 sceneInteractor.transitionState
139                     .flatMapLatestConflated { state ->
140                         when (state) {
141                             is ObservableTransitionState.Idle ->
142                                 if (state.currentScene == resolvedSceneKey) {
143                                     flowOf(1f)
144                                 } else {
145                                     flowOf(0f)
146                                 }
147                             is ObservableTransitionState.Transition ->
148                                 if (state.toScene == resolvedSceneKey) {
149                                     state.progress
150                                 } else if (state.fromScene == resolvedSceneKey) {
151                                     state.progress.map { progress -> 1 - progress }
152                                 } else {
153                                     flowOf(0f)
154                                 }
155                         }
156                     }
157                     .distinctUntilChanged()
158             }
159             .distinctUntilChanged()
160 
161     /**
162      * Returns a flow that uses scene transition data to determine whether the user is interacting
163      * with a scene that is pulled down from the top of the screen.
164      */
165     fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
166         sceneInteractor.transitionState
167             .flatMapLatestConflated { state ->
168                 when (state) {
169                     is ObservableTransitionState.Idle -> flowOf(false)
170                     is ObservableTransitionState.Transition ->
171                         sceneInteractor.resolveSceneFamily(sceneKey).map { resolvedSceneKey ->
172                             state.isInitiatedByUserInput &&
173                                 (state.toScene == resolvedSceneKey ||
174                                     state.fromScene == resolvedSceneKey)
175                         }
176                 }
177             }
178             .distinctUntilChanged()
179 }
180