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.section
18 
19 import android.content.res.Resources
20 import android.view.View
21 import android.view.ViewGroup
22 import android.widget.FrameLayout
23 import androidx.compose.foundation.layout.fillMaxSize
24 import androidx.compose.foundation.layout.height
25 import androidx.compose.foundation.layout.padding
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.res.dimensionResource
32 import androidx.compose.ui.viewinterop.AndroidView
33 import androidx.core.view.contains
34 import androidx.lifecycle.compose.collectAsStateWithLifecycle
35 import com.android.compose.animation.scene.SceneScope
36 import com.android.compose.modifiers.padding
37 import com.android.systemui.customization.R
38 import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
39 import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
40 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
41 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene
42 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
43 import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
44 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
45 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
46 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
47 import javax.inject.Inject
48 
49 /** Provides small clock and large clock composables for the default clock face. */
50 class DefaultClockSection
51 @Inject
52 constructor(
53     private val viewModel: KeyguardClockViewModel,
54     private val aodBurnInViewModel: AodBurnInViewModel,
55 ) {
56     @Composable
57     fun SceneScope.SmallClock(
58         burnInParams: BurnInParameters,
59         onTopChanged: (top: Float?) -> Unit,
60         modifier: Modifier = Modifier,
61     ) {
62         val currentClock by viewModel.currentClock.collectAsStateWithLifecycle()
63         val smallTopMargin by
64             viewModel.smallClockTopMargin.collectAsStateWithLifecycle(
65                 viewModel.getSmallClockTopMargin()
66             )
67         if (currentClock?.smallClock?.view == null) {
68             return
69         }
70         val context = LocalContext.current
71         AndroidView(
72             factory = { context ->
73                 FrameLayout(context).apply {
74                     ensureClockViewExists(checkNotNull(currentClock).smallClock.view)
75                 }
76             },
77             update = { it.ensureClockViewExists(checkNotNull(currentClock).smallClock.view) },
78             modifier =
79                 modifier
80                     .height(dimensionResource(R.dimen.small_clock_height))
81                     .padding(horizontal = dimensionResource(R.dimen.clock_padding_start))
82                     .padding(top = { smallTopMargin })
83                     .onTopPlacementChanged(onTopChanged)
84                     .burnInAware(
85                         viewModel = aodBurnInViewModel,
86                         params = burnInParams,
87                     )
88                     .element(smallClockElementKey),
89         )
90     }
91 
92     @Composable
93     fun SceneScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) {
94         val currentClock by viewModel.currentClock.collectAsStateWithLifecycle()
95         if (currentClock?.largeClock?.view == null) {
96             return
97         }
98 
99         // Centering animation for clocks that have custom position animations.
100         LaunchedEffect(layoutState.currentTransition?.progress) {
101             val transition = layoutState.currentTransition ?: return@LaunchedEffect
102             if (currentClock?.largeClock?.config?.hasCustomPositionUpdatedAnimation != true) {
103                 return@LaunchedEffect
104             }
105 
106             // If we are not doing the centering animation, do not animate.
107             val progress =
108                 if (transition.isTransitioningBetween(largeClockScene, splitShadeLargeClockScene)) {
109                     transition.progress
110                 } else {
111                     1f
112                 }
113 
114             val dir = if (transition.toScene == splitShadeLargeClockScene) -1f else 1f
115             val distance = dir * getClockCenteringDistance()
116             val largeClock = checkNotNull(currentClock).largeClock
117             largeClock.animations.onPositionUpdated(
118                 distance = distance,
119                 fraction = progress,
120             )
121         }
122 
123         MovableElement(key = largeClockElementKey, modifier = modifier) {
124             content {
125                 AndroidView(
126                     factory = { context ->
127                         FrameLayout(context).apply {
128                             ensureClockViewExists(checkNotNull(currentClock).largeClock.view)
129                         }
130                     },
131                     update = {
132                         it.ensureClockViewExists(checkNotNull(currentClock).largeClock.view)
133                     },
134                     modifier =
135                         Modifier.fillMaxSize()
136                             .burnInAware(
137                                 viewModel = aodBurnInViewModel,
138                                 params = burnInParams,
139                                 isClock = true
140                             )
141                 )
142             }
143         }
144     }
145 
146     private fun FrameLayout.ensureClockViewExists(clockView: View) {
147         if (contains(clockView)) {
148             return
149         }
150         removeAllViews()
151         (clockView.parent as? ViewGroup)?.removeView(clockView)
152         addView(clockView)
153     }
154 
155     fun getClockCenteringDistance(): Float {
156         return Resources.getSystem().displayMetrics.widthPixels / 4f
157     }
158 }
159