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.volume.ui.navigation
18 
19 import android.app.Dialog
20 import android.content.Intent
21 import android.provider.Settings
22 import androidx.compose.runtime.LaunchedEffect
23 import androidx.compose.runtime.remember
24 import androidx.compose.runtime.rememberCoroutineScope
25 import com.android.internal.logging.UiEventLogger
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.dagger.qualifiers.Application
28 import com.android.systemui.dagger.qualifiers.Main
29 import com.android.systemui.plugins.ActivityStarter
30 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
31 import com.android.systemui.statusbar.phone.createBottomSheet
32 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
33 import com.android.systemui.volume.VolumePanelFactory
34 import com.android.systemui.volume.domain.model.VolumePanelRoute
35 import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor
36 import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
37 import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
38 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
39 import javax.inject.Inject
40 import kotlin.coroutines.CoroutineContext
41 import kotlinx.coroutines.CoroutineScope
42 import kotlinx.coroutines.ExperimentalCoroutinesApi
43 import kotlinx.coroutines.channels.awaitClose
44 import kotlinx.coroutines.flow.distinctUntilChanged
45 import kotlinx.coroutines.flow.emptyFlow
46 import kotlinx.coroutines.flow.flatMapLatest
47 import kotlinx.coroutines.flow.flowOn
48 import kotlinx.coroutines.flow.launchIn
49 import kotlinx.coroutines.flow.map
50 
51 @OptIn(ExperimentalCoroutinesApi::class)
52 @SysUISingleton
53 class VolumeNavigator
54 @Inject
55 constructor(
56     @Application private val applicationScope: CoroutineScope,
57     @Main private val mainContext: CoroutineContext,
58     private val volumePanelFactory: VolumePanelFactory,
59     private val activityStarter: ActivityStarter,
60     private val viewModelFactory: VolumePanelViewModel.Factory,
61     private val dialogFactory: SystemUIDialogFactory,
62     private val uiEventLogger: UiEventLogger,
63     private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor,
64 ) {
65 
66     init {
67         volumePanelGlobalStateInteractor.globalState
68             .map { it.isVisible }
69             .distinctUntilChanged()
70             .flatMapLatest { isVisible ->
71                 if (isVisible) {
72                     conflatedCallbackFlow<Unit> {
73                             val dialog = createNewVolumePanelDialog()
74                             uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SHOWN)
75                             dialog.show()
76                             awaitClose { dialog.dismiss() }
77                         }
78                         .flowOn(mainContext)
79                 } else {
80                     emptyFlow()
81                 }
82             }
83             .launchIn(applicationScope)
84     }
85 
86     fun openVolumePanel(route: VolumePanelRoute) {
87         when (route) {
88             VolumePanelRoute.COMPOSE_VOLUME_PANEL -> showNewVolumePanel()
89             VolumePanelRoute.SETTINGS_VOLUME_PANEL ->
90                 activityStarter.startActivity(
91                     /* intent= */ Intent(Settings.Panel.ACTION_VOLUME),
92                     /* dismissShade= */ true
93                 )
94             VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL ->
95                 volumePanelFactory.create(aboveStatusBar = true, view = null)
96         }
97     }
98 
99     private fun showNewVolumePanel() {
100         activityStarter.dismissKeyguardThenExecute(
101             /* action = */ {
102                 volumePanelGlobalStateInteractor.setVisible(true)
103                 false
104             },
105             /* cancel = */ {},
106             /* afterKeyguardGone = */ true,
107         )
108     }
109 
110     private fun createNewVolumePanelDialog(): Dialog {
111         return dialogFactory.createBottomSheet(
112             content = { dialog ->
113                 LaunchedEffect(dialog) {
114                     dialog.setOnDismissListener {
115                         uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_GONE)
116                         volumePanelGlobalStateInteractor.setVisible(false)
117                     }
118                 }
119 
120                 val coroutineScope = rememberCoroutineScope()
121                 VolumePanelRoot(
122                     remember(coroutineScope) { viewModelFactory.create(coroutineScope) }
123                 )
124             },
125         )
126     }
127 }
128