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 @file:OptIn(ExperimentalCoroutinesApi::class) 18 19 package com.android.systemui.shade.ui.viewmodel 20 21 import androidx.lifecycle.LifecycleOwner 22 import com.android.compose.animation.scene.SceneKey 23 import com.android.compose.animation.scene.Swipe 24 import com.android.compose.animation.scene.SwipeDirection 25 import com.android.compose.animation.scene.UserAction 26 import com.android.compose.animation.scene.UserActionResult 27 import com.android.systemui.dagger.SysUISingleton 28 import com.android.systemui.dagger.qualifiers.Application 29 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor 30 import com.android.systemui.qs.FooterActionsController 31 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel 32 import com.android.systemui.qs.ui.adapter.QSSceneAdapter 33 import com.android.systemui.scene.domain.interactor.SceneInteractor 34 import com.android.systemui.scene.shared.model.SceneFamilies 35 import com.android.systemui.scene.shared.model.Scenes 36 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade 37 import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel 38 import com.android.systemui.shade.domain.interactor.ShadeInteractor 39 import com.android.systemui.shade.shared.model.ShadeMode 40 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel 41 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor 42 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated 43 import java.util.concurrent.atomic.AtomicBoolean 44 import javax.inject.Inject 45 import kotlinx.coroutines.CoroutineScope 46 import kotlinx.coroutines.ExperimentalCoroutinesApi 47 import kotlinx.coroutines.flow.Flow 48 import kotlinx.coroutines.flow.SharingStarted 49 import kotlinx.coroutines.flow.StateFlow 50 import kotlinx.coroutines.flow.combine 51 import kotlinx.coroutines.flow.flowOf 52 import kotlinx.coroutines.flow.map 53 import kotlinx.coroutines.flow.stateIn 54 55 /** Models UI state and handles user input for the shade scene. */ 56 @SysUISingleton 57 class ShadeSceneViewModel 58 @Inject 59 constructor( 60 @Application private val applicationScope: CoroutineScope, 61 val qsSceneAdapter: QSSceneAdapter, 62 val shadeHeaderViewModel: ShadeHeaderViewModel, 63 val notifications: NotificationsPlaceholderViewModel, 64 val brightnessMirrorViewModel: BrightnessMirrorViewModel, 65 val mediaCarouselInteractor: MediaCarouselInteractor, 66 shadeInteractor: ShadeInteractor, 67 private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, 68 private val footerActionsController: FooterActionsController, 69 private val sceneInteractor: SceneInteractor, 70 private val unfoldTransitionInteractor: UnfoldTransitionInteractor, 71 ) { 72 val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = 73 combine( 74 shadeInteractor.shadeMode, 75 qsSceneAdapter.isCustomizerShowing, 76 ) { shadeMode, isCustomizerShowing -> 77 destinationScenes( 78 shadeMode = shadeMode, 79 isCustomizing = isCustomizerShowing, 80 ) 81 } 82 .stateIn( 83 scope = applicationScope, 84 started = SharingStarted.WhileSubscribed(), 85 initialValue = 86 destinationScenes( 87 shadeMode = shadeInteractor.shadeMode.value, 88 isCustomizing = qsSceneAdapter.isCustomizerShowing.value, 89 ), 90 ) 91 92 private val upDestinationSceneKey: Flow<SceneKey?> = 93 destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene } 94 95 /** Whether or not the shade container should be clickable. */ 96 val isClickable: StateFlow<Boolean> = 97 upDestinationSceneKey 98 .flatMapLatestConflated { key -> 99 key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null) 100 } 101 .map { it == Scenes.Lockscreen } 102 .stateIn( 103 scope = applicationScope, 104 started = SharingStarted.WhileSubscribed(), 105 initialValue = false 106 ) 107 108 val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode 109 110 val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation 111 112 /** 113 * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded 114 * slightly, in pixels. 115 */ 116 fun unfoldTranslationX(isOnStartSide: Boolean): Flow<Float> { 117 return unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide) 118 } 119 120 /** Notifies that some content in the shade was clicked. */ 121 fun onContentClicked() { 122 if (!isClickable.value) { 123 return 124 } 125 126 sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty content clicked") 127 } 128 129 private val footerActionsControllerInitialized = AtomicBoolean(false) 130 131 fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { 132 if (footerActionsControllerInitialized.compareAndSet(false, true)) { 133 footerActionsController.init() 134 } 135 return footerActionsViewModelFactory.create(lifecycleOwner) 136 } 137 138 private fun destinationScenes( 139 shadeMode: ShadeMode, 140 isCustomizing: Boolean, 141 ): Map<UserAction, UserActionResult> { 142 return buildMap { 143 if (!isCustomizing) { 144 set( 145 Swipe(SwipeDirection.Up), 146 UserActionResult( 147 SceneFamilies.Home, 148 ToSplitShade.takeIf { shadeMode is ShadeMode.Split } 149 ) 150 ) 151 } // TODO(b/330200163) Add an else to be able to collapse the shade while customizing 152 if (shadeMode is ShadeMode.Single) { 153 set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings)) 154 } 155 } 156 } 157 } 158