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.keyguard.ui.composable.section
18 
19 import android.content.Context
20 import androidx.compose.foundation.layout.Column
21 import androidx.compose.foundation.layout.fillMaxSize
22 import androidx.compose.foundation.layout.fillMaxWidth
23 import androidx.compose.foundation.layout.heightIn
24 import androidx.compose.foundation.layout.offset
25 import androidx.compose.foundation.layout.wrapContentSize
26 import androidx.compose.runtime.Composable
27 import androidx.compose.runtime.LaunchedEffect
28 import androidx.compose.runtime.getValue
29 import androidx.compose.ui.Modifier
30 import androidx.compose.ui.platform.LocalContext
31 import androidx.compose.ui.platform.LocalDensity
32 import androidx.compose.ui.unit.Density
33 import androidx.compose.ui.unit.Dp
34 import androidx.compose.ui.unit.IntOffset
35 import androidx.lifecycle.compose.collectAsStateWithLifecycle
36 import com.android.compose.animation.scene.SceneScope
37 import com.android.compose.animation.scene.SceneTransitionLayout
38 import com.android.compose.modifiers.thenIf
39 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
40 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
41 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
42 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene
43 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeSmallClockScene
44 import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition
45 import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockScenes
46 import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
47 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
48 import javax.inject.Inject
49 
50 class TopAreaSection
51 @Inject
52 constructor(
53     private val clockViewModel: KeyguardClockViewModel,
54     private val smartSpaceSection: SmartSpaceSection,
55     private val mediaCarouselSection: MediaCarouselSection,
56     private val clockSection: DefaultClockSection,
57     private val weatherClockSection: WeatherClockSection,
58     private val clockInteractor: KeyguardClockInteractor,
59 ) {
60     @Composable
DefaultClockLayoutnull61     fun DefaultClockLayout(
62         modifier: Modifier = Modifier,
63     ) {
64         val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle()
65         val hasCustomPositionUpdatedAnimation by
66             clockViewModel.hasCustomPositionUpdatedAnimation.collectAsStateWithLifecycle()
67         val currentScene =
68             when (currentClockLayout) {
69                 KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK ->
70                     splitShadeLargeClockScene
71                 KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_SMALL_CLOCK ->
72                     splitShadeSmallClockScene
73                 KeyguardClockViewModel.ClockLayout.LARGE_CLOCK -> largeClockScene
74                 KeyguardClockViewModel.ClockLayout.SMALL_CLOCK -> smallClockScene
75                 KeyguardClockViewModel.ClockLayout.WEATHER_LARGE_CLOCK ->
76                     WeatherClockScenes.largeClockScene
77                 KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_WEATHER_LARGE_CLOCK ->
78                     WeatherClockScenes.splitShadeLargeClockScene
79             }
80 
81         SceneTransitionLayout(
82             modifier = modifier,
83             currentScene = currentScene,
84             onChangeScene = {},
85             transitions = ClockTransition.defaultClockTransitions,
86             enableInterruptions = false,
87         ) {
88             scene(splitShadeLargeClockScene) {
89                 LargeClockWithSmartSpace(
90                     shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
91                 )
92             }
93 
94             scene(splitShadeSmallClockScene) {
95                 SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
96             }
97 
98             scene(smallClockScene) { SmallClockWithSmartSpace() }
99 
100             scene(largeClockScene) { LargeClockWithSmartSpace() }
101 
102             scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() }
103 
104             scene(WeatherClockScenes.splitShadeLargeClockScene) {
105                 WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
106             }
107         }
108     }
109 
110     @Composable
SmallClockWithSmartSpacenull111     private fun SceneScope.SmallClockWithSmartSpace(modifier: Modifier = Modifier) {
112         val burnIn = rememberBurnIn(clockInteractor)
113 
114         Column(modifier = modifier) {
115             with(clockSection) {
116                 SmallClock(
117                     burnInParams = burnIn.parameters,
118                     onTopChanged = burnIn.onSmallClockTopChanged,
119                     modifier = Modifier.wrapContentSize()
120                 )
121             }
122             with(smartSpaceSection) {
123                 SmartSpace(
124                     burnInParams = burnIn.parameters,
125                     onTopChanged = burnIn.onSmartspaceTopChanged,
126                 )
127             }
128             with(mediaCarouselSection) { KeyguardMediaCarousel() }
129         }
130     }
131 
132     @Composable
LargeClockWithSmartSpacenull133     private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) {
134         val burnIn = rememberBurnIn(clockInteractor)
135         val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
136 
137         LaunchedEffect(isLargeClockVisible) {
138             if (isLargeClockVisible) {
139                 burnIn.onSmallClockTopChanged(null)
140             }
141         }
142 
143         Column {
144             with(smartSpaceSection) {
145                 SmartSpace(
146                     burnInParams = burnIn.parameters,
147                     onTopChanged = burnIn.onSmartspaceTopChanged,
148                 )
149             }
150             with(clockSection) {
151                 LargeClock(
152                     burnInParams = burnIn.parameters,
153                     modifier =
154                         Modifier.fillMaxSize().thenIf(shouldOffSetClockToOneHalf) {
155                             // If we do not have a custom position animation, we want
156                             // the clock to be on one half of the screen.
157                             Modifier.offset {
158                                 IntOffset(
159                                     x = -clockSection.getClockCenteringDistance().toInt(),
160                                     y = 0,
161                                 )
162                             }
163                         }
164                 )
165             }
166         }
167     }
168 
169     @Composable
WeatherLargeClockWithSmartSpacenull170     private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) {
171         val burnIn = rememberBurnIn(clockInteractor)
172         val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
173         val currentClockState = clockViewModel.currentClock.collectAsStateWithLifecycle()
174 
175         LaunchedEffect(isLargeClockVisible) {
176             if (isLargeClockVisible) {
177                 burnIn.onSmallClockTopChanged(null)
178             }
179         }
180 
181         Column(modifier = modifier) {
182             val currentClock = currentClockState.value ?: return@Column
183             with(weatherClockSection) {
184                 Time(
185                     clock = currentClock,
186                     burnInParams = burnIn.parameters,
187                 )
188             }
189             val density = LocalDensity.current
190             val context = LocalContext.current
191 
192             with(smartSpaceSection) {
193                 SmartSpace(
194                     burnInParams = burnIn.parameters,
195                     onTopChanged = burnIn.onSmartspaceTopChanged,
196                     modifier =
197                         Modifier.heightIn(
198                             min = getDimen(context, "enhanced_smartspace_height", density)
199                         )
200                 )
201             }
202             with(weatherClockSection) {
203                 LargeClockSectionBelowSmartspace(
204                     burnInParams = burnIn.parameters,
205                     clock = currentClock,
206                 )
207             }
208         }
209     }
210 
211     /*
212      * Use this function to access dimen which cannot be access by R.dimen directly
213      * Currently use to access dimen from BcSmartspace
214      * @param name Name of resources
215      * @param density Density required to convert dimen from Int To Dp
216      */
getDimennull217     private fun getDimen(context: Context, name: String, density: Density): Dp {
218         val res = context.packageManager.getResourcesForApplication(context.packageName)
219         val id = res.getIdentifier(name, "dimen", context.packageName)
220         var dimen: Dp
221         with(density) { dimen = (if (id == 0) 0 else res.getDimensionPixelSize(id)).toDp() }
222         return dimen
223     }
224 }
225