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