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.binder
18 
19 import android.util.TypedValue
20 import android.view.View
21 import android.view.ViewGroup
22 import android.widget.TextView
23 import androidx.lifecycle.Lifecycle
24 import androidx.lifecycle.repeatOnLifecycle
25 import com.android.app.tracing.coroutines.launch
26 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
27 import com.android.systemui.keyguard.MigrateClocksToBlueprint
28 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
29 import com.android.systemui.lifecycle.repeatWhenAttached
30 import com.android.systemui.res.R
31 import com.android.systemui.statusbar.KeyguardIndicationController
32 import kotlinx.coroutines.DisposableHandle
33 import kotlinx.coroutines.ExperimentalCoroutinesApi
34 import kotlinx.coroutines.flow.MutableStateFlow
35 import kotlinx.coroutines.flow.combine
36 import kotlinx.coroutines.flow.flatMapLatest
37 import kotlinx.coroutines.flow.map
38 
39 /**
40  * Binds a keyguard indication area view to its view-model.
41  *
42  * To use this properly, users should maintain a one-to-one relationship between the [View] and the
43  * view-binding, binding each view only once. It is okay and expected for the same instance of the
44  * view-model to be reused for multiple view/view-binder bindings.
45  */
46 @OptIn(ExperimentalCoroutinesApi::class)
47 object KeyguardIndicationAreaBinder {
48 
49     /** Binds the view to the view-model, continuing to update the former based on the latter. */
50     @JvmStatic
51     fun bind(
52         view: ViewGroup,
53         viewModel: KeyguardIndicationAreaViewModel,
54         indicationController: KeyguardIndicationController,
55     ): DisposableHandle {
56         indicationController.setIndicationArea(view)
57 
58         val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
59         val indicationTextBottom: TextView =
60             view.requireViewById(R.id.keyguard_indication_text_bottom)
61 
62         view.clipChildren = false
63         view.clipToPadding = false
64 
65         val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
66         val disposableHandle =
67             view.repeatWhenAttached {
68                 repeatOnLifecycle(Lifecycle.State.STARTED) {
69                     launch("$TAG#viewModel.alpha") {
70                         // Do not independently apply alpha, as [KeyguardRootViewModel] should work
71                         // for this and all its children
72                         if (
73                             !(MigrateClocksToBlueprint.isEnabled ||
74                                 KeyguardBottomAreaRefactor.isEnabled)
75                         ) {
76                             viewModel.alpha.collect { alpha -> view.alpha = alpha }
77                         }
78                     }
79 
80                     launch("$TAG#viewModel.indicationAreaTranslationX") {
81                         viewModel.indicationAreaTranslationX.collect { translationX ->
82                             view.translationX = translationX
83                         }
84                     }
85 
86                     launch("$TAG#viewModel.isIndicationAreaPadded") {
87                         combine(
88                                 viewModel.isIndicationAreaPadded,
89                                 configurationBasedDimensions.map { it.indicationAreaPaddingPx },
90                             ) { isPadded, paddingIfPaddedPx ->
91                                 if (isPadded) {
92                                     paddingIfPaddedPx
93                                 } else {
94                                     0
95                                 }
96                             }
97                             .collect { paddingPx -> view.setPadding(paddingPx, 0, paddingPx, 0) }
98                     }
99 
100                     launch("$TAG#viewModel.indicationAreaTranslationY") {
101                         configurationBasedDimensions
102                             .map { it.defaultBurnInPreventionYOffsetPx }
103                             .flatMapLatest { defaultBurnInOffsetY ->
104                                 viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
105                             }
106                             .collect { translationY -> view.translationY = translationY }
107                     }
108 
109                     launch("$TAG#indicationText.setTextSize") {
110                         configurationBasedDimensions.collect { dimensions ->
111                             indicationText.setTextSize(
112                                 TypedValue.COMPLEX_UNIT_PX,
113                                 dimensions.indicationTextSizePx.toFloat(),
114                             )
115                             indicationTextBottom.setTextSize(
116                                 TypedValue.COMPLEX_UNIT_PX,
117                                 dimensions.indicationTextSizePx.toFloat(),
118                             )
119                         }
120                     }
121 
122                     launch("$TAG#viewModel.configurationChange") {
123                         viewModel.configurationChange.collect {
124                             configurationBasedDimensions.value = loadFromResources(view)
125                         }
126                     }
127                 }
128             }
129         return disposableHandle
130     }
131 
132     private fun loadFromResources(view: View): ConfigurationBasedDimensions {
133         return ConfigurationBasedDimensions(
134             defaultBurnInPreventionYOffsetPx =
135                 view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
136             indicationAreaPaddingPx =
137                 view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
138             indicationTextSizePx =
139                 view.resources.getDimensionPixelSize(
140                     com.android.internal.R.dimen.text_size_small_material,
141                 ),
142         )
143     }
144 
145     private data class ConfigurationBasedDimensions(
146         val defaultBurnInPreventionYOffsetPx: Int,
147         val indicationAreaPaddingPx: Int,
148         val indicationTextSizePx: Int,
149     )
150 
151     private const val TAG = "KeyguardIndicationAreaBinder"
152 }
153