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.volume.panel.ui.viewmodel
18 
19 import android.content.Context
20 import android.content.IntentFilter
21 import android.content.res.Resources
22 import com.android.systemui.broadcast.BroadcastDispatcher
23 import com.android.systemui.dagger.qualifiers.Application
24 import com.android.systemui.res.R
25 import com.android.systemui.statusbar.policy.ConfigurationController
26 import com.android.systemui.statusbar.policy.onConfigChanged
27 import com.android.systemui.volume.VolumePanelDialogReceiver
28 import com.android.systemui.volume.panel.dagger.VolumePanelComponent
29 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
30 import com.android.systemui.volume.panel.domain.VolumePanelStartable
31 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
32 import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor
33 import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
34 import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
35 import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
36 import javax.inject.Inject
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.SharingStarted
40 import kotlinx.coroutines.flow.StateFlow
41 import kotlinx.coroutines.flow.combine
42 import kotlinx.coroutines.flow.launchIn
43 import kotlinx.coroutines.flow.map
44 import kotlinx.coroutines.flow.onEach
45 import kotlinx.coroutines.flow.onStart
46 import kotlinx.coroutines.flow.shareIn
47 import kotlinx.coroutines.flow.stateIn
48 
49 // Can't inject a constructor here because VolumePanelComponent provides this view model for its
50 // components.
51 class VolumePanelViewModel(
52     resources: Resources,
53     coroutineScope: CoroutineScope,
54     daggerComponentFactory: VolumePanelComponentFactory,
55     configurationController: ConfigurationController,
56     broadcastDispatcher: BroadcastDispatcher,
57     private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor,
58 ) {
59 
60     private val volumePanelComponent: VolumePanelComponent =
61         daggerComponentFactory.create(this, coroutineScope)
62 
63     private val scope: CoroutineScope
64         get() = volumePanelComponent.coroutineScope()
65 
66     private val componentsInteractor: ComponentsInteractor
67         get() = volumePanelComponent.componentsInteractor()
68 
69     private val componentsFactory: ComponentsFactory
70         get() = volumePanelComponent.componentsFactory()
71 
72     private val componentsLayoutManager: ComponentsLayoutManager
73         get() = volumePanelComponent.componentsLayoutManager()
74 
75     val volumePanelState: StateFlow<VolumePanelState> =
76         configurationController.onConfigChanged
77             .onStart { emit(resources.configuration) }
78             .map { configuration ->
79                 VolumePanelState(
80                     orientation = configuration.orientation,
81                     isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen),
82                 )
83             }
84             .stateIn(
85                 scope,
86                 SharingStarted.Eagerly,
87                 VolumePanelState(
88                     orientation = resources.configuration.orientation,
89                     isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen)
90                 ),
91             )
92     val componentsLayout: Flow<ComponentsLayout> =
93         combine(
94                 componentsInteractor.components,
95                 volumePanelState,
96             ) { components, scope ->
97                 val componentStates =
98                     components.map { model ->
99                         ComponentState(
100                             model.key,
101                             componentsFactory.createComponent(model.key),
102                             model.isAvailable,
103                         )
104                     }
105                 componentsLayoutManager.layout(scope, componentStates)
106             }
107             .shareIn(
108                 scope,
109                 SharingStarted.Eagerly,
110                 replay = 1,
111             )
112 
113     init {
114         volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start)
115         broadcastDispatcher
116             .broadcastFlow(IntentFilter(VolumePanelDialogReceiver.DISMISS_ACTION))
117             .onEach { dismissPanel() }
118             .launchIn(scope)
119     }
120 
121     fun dismissPanel() {
122         volumePanelGlobalStateInteractor.setVisible(false)
123     }
124 
125     class Factory
126     @Inject
127     constructor(
128         @Application private val context: Context,
129         private val daggerComponentFactory: VolumePanelComponentFactory,
130         private val configurationController: ConfigurationController,
131         private val broadcastDispatcher: BroadcastDispatcher,
132         private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor,
133     ) {
134 
135         fun create(coroutineScope: CoroutineScope): VolumePanelViewModel {
136             return VolumePanelViewModel(
137                 context.resources,
138                 coroutineScope,
139                 daggerComponentFactory,
140                 configurationController,
141                 broadcastDispatcher,
142                 volumePanelGlobalStateInteractor,
143             )
144         }
145     }
146 }
147