1 /*
2  * 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.qs.ui.composable
18 
19 import androidx.compose.animation.AnimatedVisibility
20 import androidx.compose.animation.EnterTransition
21 import androidx.compose.animation.ExitTransition
22 import androidx.compose.animation.core.tween
23 import androidx.compose.animation.fadeIn
24 import androidx.compose.animation.fadeOut
25 import androidx.compose.foundation.layout.Arrangement
26 import androidx.compose.foundation.layout.Box
27 import androidx.compose.foundation.layout.Column
28 import androidx.compose.foundation.layout.fillMaxWidth
29 import androidx.compose.foundation.layout.height
30 import androidx.compose.foundation.layout.heightIn
31 import androidx.compose.foundation.layout.padding
32 import androidx.compose.material3.Button
33 import androidx.compose.material3.Text
34 import androidx.compose.runtime.Composable
35 import androidx.compose.runtime.getValue
36 import androidx.compose.ui.Alignment
37 import androidx.compose.ui.Modifier
38 import androidx.compose.ui.unit.dp
39 import androidx.lifecycle.compose.collectAsStateWithLifecycle
40 import com.android.compose.animation.scene.SceneScope
41 import com.android.compose.animation.scene.UserAction
42 import com.android.compose.animation.scene.UserActionResult
43 import com.android.systemui.battery.BatteryMeterViewController
44 import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
45 import com.android.systemui.dagger.SysUISingleton
46 import com.android.systemui.keyguard.ui.composable.LockscreenContent
47 import com.android.systemui.qs.panels.ui.compose.EditMode
48 import com.android.systemui.qs.panels.ui.compose.TileGrid
49 import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
50 import com.android.systemui.scene.shared.model.Scenes
51 import com.android.systemui.scene.ui.composable.ComposableScene
52 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
53 import com.android.systemui.shade.ui.composable.OverlayShade
54 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
55 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
56 import com.android.systemui.statusbar.phone.ui.TintedIconManager
57 import dagger.Lazy
58 import java.util.Optional
59 import javax.inject.Inject
60 import kotlinx.coroutines.flow.StateFlow
61 
62 @SysUISingleton
63 class QuickSettingsShadeScene
64 @Inject
65 constructor(
66     private val viewModel: QuickSettingsShadeSceneViewModel,
67     private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
68     private val shadeHeaderViewModel: ShadeHeaderViewModel,
69     private val tintedIconManagerFactory: TintedIconManager.Factory,
70     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
71     private val statusBarIconController: StatusBarIconController,
72 ) : ComposableScene {
73 
74     override val key = Scenes.QuickSettingsShade
75 
76     override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
77         viewModel.destinationScenes
78 
79     @Composable
Contentnull80     override fun SceneScope.Content(
81         modifier: Modifier,
82     ) {
83         OverlayShade(
84             viewModel = viewModel.overlayShadeViewModel,
85             panelAlignment = Alignment.TopEnd,
86             lockscreenContent = lockscreenContent,
87             modifier = modifier,
88         ) {
89             Column {
90                 ExpandedShadeHeader(
91                     viewModel = shadeHeaderViewModel,
92                     createTintedIconManager = tintedIconManagerFactory::create,
93                     createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
94                     statusBarIconController = statusBarIconController,
95                     modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding),
96                 )
97 
98                 ShadeBody(
99                     viewModel = viewModel,
100                 )
101             }
102         }
103     }
104 }
105 
106 @Composable
ShadeBodynull107 private fun ShadeBody(
108     viewModel: QuickSettingsShadeSceneViewModel,
109 ) {
110     val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
111 
112     Box {
113         // The main Quick Settings grid layout.
114         AnimatedVisibility(
115             visible = !isEditing,
116             enter = QuickSettingsShade.Transitions.QuickSettingsLayoutEnter,
117             exit = QuickSettingsShade.Transitions.QuickSettingsLayoutExit,
118         ) {
119             QuickSettingsLayout(
120                 viewModel = viewModel,
121             )
122         }
123 
124         // The Quick Settings Editor layout.
125         AnimatedVisibility(
126             visible = isEditing,
127             enter = QuickSettingsShade.Transitions.QuickSettingsEditorEnter,
128             exit = QuickSettingsShade.Transitions.QuickSettingsEditorExit,
129         ) {
130             EditMode(
131                 viewModel = viewModel.editModeViewModel,
132                 modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding)
133             )
134         }
135     }
136 }
137 
138 @Composable
QuickSettingsLayoutnull139 private fun QuickSettingsLayout(
140     viewModel: QuickSettingsShadeSceneViewModel,
141     modifier: Modifier = Modifier,
142 ) {
143     Column(
144         verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding),
145         horizontalAlignment = Alignment.CenterHorizontally,
146         modifier = modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
147     ) {
148         BrightnessSliderContainer(
149             viewModel = viewModel.brightnessSliderViewModel,
150             modifier =
151                 Modifier.fillMaxWidth()
152                     .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
153         )
154         TileGrid(
155             viewModel = viewModel.tileGridViewModel,
156             modifier =
157                 Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
158         )
159         Button(
160             onClick = { viewModel.editModeViewModel.startEditing() },
161         ) {
162             Text("Edit mode")
163         }
164     }
165 }
166 
167 object QuickSettingsShade {
168 
169     object Dimensions {
170         val Padding = 16.dp
171         val BrightnessSliderHeight = 64.dp
172         val GridMaxHeight = 400.dp
173     }
174 
175     object Transitions {
176         val QuickSettingsLayoutEnter: EnterTransition = fadeIn(tween(500))
177         val QuickSettingsLayoutExit: ExitTransition = fadeOut(tween(500))
178         val QuickSettingsEditorEnter: EnterTransition = fadeIn(tween(500))
179         val QuickSettingsEditorExit: ExitTransition = fadeOut(tween(500))
180     }
181 }
182