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.keyguard.ui.composable.blueprint
18 
19 import androidx.compose.foundation.layout.Box
20 import androidx.compose.foundation.layout.Column
21 import androidx.compose.foundation.layout.fillMaxHeight
22 import androidx.compose.foundation.layout.fillMaxSize
23 import androidx.compose.foundation.layout.fillMaxWidth
24 import androidx.compose.runtime.Composable
25 import androidx.compose.runtime.getValue
26 import androidx.compose.ui.Alignment
27 import androidx.compose.ui.Modifier
28 import androidx.compose.ui.graphics.graphicsLayer
29 import androidx.compose.ui.layout.Layout
30 import androidx.compose.ui.unit.IntRect
31 import androidx.lifecycle.compose.collectAsStateWithLifecycle
32 import com.android.compose.animation.scene.SceneScope
33 import com.android.compose.modifiers.padding
34 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
35 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
36 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
37 import com.android.systemui.keyguard.ui.composable.section.LockSection
38 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
39 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
40 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
41 import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
42 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
43 import dagger.Binds
44 import dagger.Module
45 import dagger.multibindings.IntoSet
46 import java.util.Optional
47 import javax.inject.Inject
48 import kotlin.math.roundToInt
49 
50 /**
51  * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
52  * factor).
53  */
54 class ShortcutsBesideUdfpsBlueprint
55 @Inject
56 constructor(
57     private val viewModel: LockscreenContentViewModel,
58     private val statusBarSection: StatusBarSection,
59     private val lockSection: LockSection,
60     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
61     private val bottomAreaSection: BottomAreaSection,
62     private val settingsMenuSection: SettingsMenuSection,
63     private val topAreaSection: TopAreaSection,
64     private val notificationSection: NotificationSection,
65 ) : ComposableLockscreenSceneBlueprint {
66 
67     override val id: String = "shortcuts-besides-udfps"
68 
69     @Composable
70     override fun SceneScope.Content(modifier: Modifier) {
71         val isUdfpsVisible = viewModel.isUdfpsVisible
72         val shouldUseSplitNotificationShade by
73             viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
74         val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
75 
76         LockscreenLongPress(
77             viewModel = viewModel.longPress,
78             modifier = modifier,
79         ) { onSettingsMenuPlaced ->
80             Layout(
81                 content = {
82                     // Constrained to above the lock icon.
83                     Column(
84                         modifier = Modifier.fillMaxSize(),
85                     ) {
86                         with(statusBarSection) {
87                             StatusBar(
88                                 modifier =
89                                     Modifier.fillMaxWidth()
90                                         .padding(
91                                             horizontal = { unfoldTranslations.start.roundToInt() },
92                                         )
93                             )
94                         }
95 
96                         Box {
97                             with(topAreaSection) {
98                                 DefaultClockLayout(
99                                     modifier =
100                                         Modifier.graphicsLayer {
101                                             translationX = unfoldTranslations.start
102                                         },
103                                 )
104                             }
105                             if (shouldUseSplitNotificationShade) {
106                                 with(notificationSection) {
107                                     Notifications(
108                                         burnInParams = null,
109                                         modifier =
110                                             Modifier.fillMaxWidth(0.5f)
111                                                 .fillMaxHeight()
112                                                 .align(alignment = Alignment.TopEnd)
113                                     )
114                                 }
115                             }
116                         }
117                         if (!shouldUseSplitNotificationShade) {
118                             with(notificationSection) {
119                                 Notifications(
120                                     burnInParams = null,
121                                     modifier = Modifier.weight(weight = 1f)
122                                 )
123                             }
124                         }
125                         if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
126                             with(ambientIndicationSectionOptional.get()) {
127                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
128                             }
129                         }
130                     }
131 
132                     // Constrained to the left of the lock icon (in left-to-right layouts).
133                     with(bottomAreaSection) {
134                         Shortcut(
135                             isStart = true,
136                             applyPadding = false,
137                             modifier =
138                                 Modifier.graphicsLayer { translationX = unfoldTranslations.start },
139                         )
140                     }
141 
142                     with(lockSection) { LockIcon() }
143 
144                     // Constrained to the right of the lock icon (in left-to-right layouts).
145                     with(bottomAreaSection) {
146                         Shortcut(
147                             isStart = false,
148                             applyPadding = false,
149                             modifier =
150                                 Modifier.graphicsLayer { translationX = unfoldTranslations.end },
151                         )
152                     }
153 
154                     // Aligned to bottom and constrained to below the lock icon.
155                     Column(modifier = Modifier.fillMaxWidth()) {
156                         if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
157                             with(ambientIndicationSectionOptional.get()) {
158                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
159                             }
160                         }
161 
162                         with(bottomAreaSection) {
163                             IndicationArea(modifier = Modifier.fillMaxWidth())
164                         }
165                     }
166 
167                     // Aligned to bottom and NOT constrained by the lock icon.
168                     with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
169                 },
170                 modifier = Modifier.fillMaxSize(),
171             ) { measurables, constraints ->
172                 check(measurables.size == 6)
173                 val aboveLockIconMeasurable = measurables[0]
174                 val startSideShortcutMeasurable = measurables[1]
175                 val lockIconMeasurable = measurables[2]
176                 val endSideShortcutMeasurable = measurables[3]
177                 val belowLockIconMeasurable = measurables[4]
178                 val settingsMenuMeasurable = measurables[5]
179 
180                 val noMinConstraints =
181                     constraints.copy(
182                         minWidth = 0,
183                         minHeight = 0,
184                     )
185 
186                 val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
187                 val lockIconBounds =
188                     IntRect(
189                         left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
190                         top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
191                         right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
192                         bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
193                     )
194 
195                 val aboveLockIconPlaceable =
196                     aboveLockIconMeasurable.measure(
197                         noMinConstraints.copy(maxHeight = lockIconBounds.top)
198                     )
199                 val startSideShortcutPlaceable =
200                     startSideShortcutMeasurable.measure(noMinConstraints)
201                 val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
202                 val belowLockIconPlaceable =
203                     belowLockIconMeasurable.measure(
204                         noMinConstraints.copy(
205                             maxHeight = constraints.maxHeight - lockIconBounds.bottom
206                         )
207                     )
208                 val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
209 
210                 layout(constraints.maxWidth, constraints.maxHeight) {
211                     aboveLockIconPlaceable.place(
212                         x = 0,
213                         y = 0,
214                     )
215                     startSideShortcutPlaceable.placeRelative(
216                         x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
217                         y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
218                     )
219                     lockIconPlaceable.place(
220                         x = lockIconBounds.left,
221                         y = lockIconBounds.top,
222                     )
223                     endSideShortcutPlaceable.placeRelative(
224                         x =
225                             lockIconBounds.right +
226                                 (constraints.maxWidth - lockIconBounds.right) / 2 -
227                                 endSideShortcutPlaceable.width / 2,
228                         y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
229                     )
230                     belowLockIconPlaceable.place(
231                         x = 0,
232                         y = constraints.maxHeight - belowLockIconPlaceable.height,
233                     )
234                     settingsMenuPlaceable.place(
235                         x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
236                         y = constraints.maxHeight - settingsMenuPlaceable.height,
237                     )
238                 }
239             }
240         }
241     }
242 }
243 
244 @Module
245 interface ShortcutsBesideUdfpsBlueprintModule {
246     @Binds
247     @IntoSet
blueprintnull248     fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): ComposableLockscreenSceneBlueprint
249 }
250