1 /*
<lambda>null2  * 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.wallpaper.customization.ui.binder
18 
19 import android.content.Context
20 import android.view.View
21 import android.view.ViewGroup
22 import android.widget.ImageView
23 import android.widget.TextView
24 import androidx.constraintlayout.widget.ConstraintSet
25 import androidx.core.content.ContextCompat
26 import androidx.core.view.isVisible
27 import androidx.lifecycle.Lifecycle
28 import androidx.lifecycle.LifecycleOwner
29 import androidx.lifecycle.lifecycleScope
30 import androidx.lifecycle.repeatOnLifecycle
31 import com.android.customization.picker.clock.shared.ClockSize
32 import com.android.customization.picker.clock.ui.view.ClockConstraintLayoutHostView
33 import com.android.customization.picker.clock.ui.view.ClockConstraintLayoutHostView.Companion.addClockViews
34 import com.android.customization.picker.clock.ui.view.ClockViewFactory
35 import com.android.customization.picker.grid.ui.binder.GridIconViewBinder
36 import com.android.systemui.plugins.clocks.ClockFontAxisSetting
37 import com.android.systemui.plugins.clocks.ClockPreviewConfig
38 import com.android.systemui.shared.Flags
39 import com.android.themepicker.R
40 import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption
41 import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerLockCustomizationOption
42 import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
43 import com.android.wallpaper.model.Screen
44 import com.android.wallpaper.picker.common.icon.ui.viewbinder.IconViewBinder
45 import com.android.wallpaper.picker.common.text.ui.viewbinder.TextViewBinder
46 import com.android.wallpaper.picker.customization.ui.binder.CustomizationOptionsBinder
47 import com.android.wallpaper.picker.customization.ui.binder.DefaultCustomizationOptionsBinder
48 import com.android.wallpaper.picker.customization.ui.util.CustomizationOptionUtil.CustomizationOption
49 import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel
50 import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationPickerViewModel2
51 import javax.inject.Inject
52 import javax.inject.Singleton
53 import kotlinx.coroutines.Dispatchers
54 import kotlinx.coroutines.flow.combine
55 import kotlinx.coroutines.launch
56 
57 @Singleton
58 class ThemePickerCustomizationOptionsBinder
59 @Inject
60 constructor(private val defaultCustomizationOptionsBinder: DefaultCustomizationOptionsBinder) :
61     CustomizationOptionsBinder {
62 
63     override fun bind(
64         view: View,
65         lockScreenCustomizationOptionEntries: List<Pair<CustomizationOption, View>>,
66         homeScreenCustomizationOptionEntries: List<Pair<CustomizationOption, View>>,
67         customizationOptionFloatingSheetViewMap: Map<CustomizationOption, View>?,
68         viewModel: CustomizationPickerViewModel2,
69         colorUpdateViewModel: ColorUpdateViewModel,
70         lifecycleOwner: LifecycleOwner,
71         navigateToWallpaperCategoriesScreen: (screen: Screen) -> Unit,
72     ) {
73         defaultCustomizationOptionsBinder.bind(
74             view,
75             lockScreenCustomizationOptionEntries,
76             homeScreenCustomizationOptionEntries,
77             customizationOptionFloatingSheetViewMap,
78             viewModel,
79             colorUpdateViewModel,
80             lifecycleOwner,
81             navigateToWallpaperCategoriesScreen,
82         )
83 
84         val optionClock =
85             lockScreenCustomizationOptionEntries
86                 .find { it.first == ThemePickerLockCustomizationOption.CLOCK }
87                 ?.second
88         val optionClockIcon = optionClock?.findViewById<ImageView>(R.id.option_entry_clock_icon)
89 
90         val optionShortcut =
91             lockScreenCustomizationOptionEntries
92                 .find { it.first == ThemePickerLockCustomizationOption.SHORTCUTS }
93                 ?.second
94         val optionShortcutDescription =
95             optionShortcut?.findViewById<TextView>(
96                 R.id.option_entry_keyguard_quick_affordance_description
97             )
98         val optionShortcutIcon1 =
99             optionShortcut?.findViewById<ImageView>(
100                 R.id.option_entry_keyguard_quick_affordance_icon_1
101             )
102         val optionShortcutIcon2 =
103             optionShortcut?.findViewById<ImageView>(
104                 R.id.option_entry_keyguard_quick_affordance_icon_2
105             )
106 
107         val optionColors =
108             homeScreenCustomizationOptionEntries
109                 .find { it.first == ThemePickerHomeCustomizationOption.COLORS }
110                 ?.second
111 
112         val optionShapeGrid =
113             homeScreenCustomizationOptionEntries
114                 .find { it.first == ThemePickerHomeCustomizationOption.APP_SHAPE_GRID }
115                 ?.second
116         val optionShapeGridDescription =
117             optionShapeGrid?.findViewById<TextView>(R.id.option_entry_app_shape_grid_description)
118         val optionShapeGridIcon =
119             optionShapeGrid?.findViewById<ImageView>(R.id.option_entry_app_shape_grid_icon)
120 
121         val optionsViewModel =
122             viewModel.customizationOptionsViewModel as ThemePickerCustomizationOptionsViewModel
123         lifecycleOwner.lifecycleScope.launch {
124             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
125                 launch {
126                     optionsViewModel.onCustomizeClockClicked.collect {
127                         optionClock?.setOnClickListener { _ -> it?.invoke() }
128                     }
129                 }
130 
131                 launch {
132                     optionsViewModel.clockPickerViewModel.selectedClock.collect {
133                         optionClockIcon?.setImageDrawable(it.thumbnail)
134                     }
135                 }
136 
137                 launch {
138                     optionsViewModel.onCustomizeShortcutClicked.collect {
139                         optionShortcut?.setOnClickListener { _ -> it?.invoke() }
140                     }
141                 }
142 
143                 launch {
144                     optionsViewModel.keyguardQuickAffordancePickerViewModel2.summary.collect {
145                         summary ->
146                         optionShortcutDescription?.let {
147                             TextViewBinder.bind(view = it, viewModel = summary.description)
148                         }
149                         summary.icon1?.let { icon ->
150                             optionShortcutIcon1?.let {
151                                 IconViewBinder.bind(view = it, viewModel = icon)
152                             }
153                         }
154                         optionShortcutIcon1?.isVisible = summary.icon1 != null
155 
156                         summary.icon2?.let { icon ->
157                             optionShortcutIcon2?.let {
158                                 IconViewBinder.bind(view = it, viewModel = icon)
159                             }
160                         }
161                         optionShortcutIcon2?.isVisible = summary.icon2 != null
162                     }
163                 }
164 
165                 launch {
166                     optionsViewModel.onCustomizeColorsClicked.collect {
167                         optionColors?.setOnClickListener { _ -> it?.invoke() }
168                     }
169                 }
170 
171                 launch {
172                     optionsViewModel.onCustomizeShapeGridClicked.collect {
173                         optionShapeGrid?.setOnClickListener { _ -> it?.invoke() }
174                     }
175                 }
176 
177                 launch {
178                     optionsViewModel.shapeGridPickerViewModel.selectedGridOption.collect {
179                         gridOption ->
180                         optionShapeGridDescription?.let { TextViewBinder.bind(it, gridOption.text) }
181                         gridOption.payload?.let { gridIconViewModel ->
182                             optionShapeGridIcon?.let {
183                                 GridIconViewBinder.bind(view = it, viewModel = gridIconViewModel)
184                             }
185                             // TODO(b/363018910): Use ColorUpdateBinder to update color
186                             optionShapeGridIcon?.setColorFilter(
187                                 ContextCompat.getColor(
188                                     view.context,
189                                     com.android.wallpaper.R.color.system_on_surface_variant,
190                                 )
191                             )
192                         }
193                     }
194                 }
195             }
196         }
197 
198         customizationOptionFloatingSheetViewMap
199             ?.get(ThemePickerLockCustomizationOption.CLOCK)
200             ?.let {
201                 ClockFloatingSheetBinder.bind(
202                     it,
203                     optionsViewModel,
204                     colorUpdateViewModel,
205                     lifecycleOwner,
206                 )
207             }
208 
209         customizationOptionFloatingSheetViewMap
210             ?.get(ThemePickerLockCustomizationOption.SHORTCUTS)
211             ?.let {
212                 ShortcutFloatingSheetBinder.bind(
213                     it,
214                     optionsViewModel,
215                     colorUpdateViewModel,
216                     lifecycleOwner,
217                 )
218             }
219 
220         customizationOptionFloatingSheetViewMap
221             ?.get(ThemePickerHomeCustomizationOption.COLORS)
222             ?.let {
223                 ColorsFloatingSheetBinder.bind(
224                     it,
225                     optionsViewModel,
226                     colorUpdateViewModel,
227                     lifecycleOwner,
228                 )
229             }
230 
231         customizationOptionFloatingSheetViewMap
232             ?.get(ThemePickerHomeCustomizationOption.APP_SHAPE_GRID)
233             ?.let {
234                 ShapeGridFloatingSheetBinder.bind(
235                     it,
236                     optionsViewModel,
237                     colorUpdateViewModel,
238                     lifecycleOwner,
239                     Dispatchers.IO,
240                 )
241             }
242     }
243 
244     override fun bindClockPreview(
245         context: Context,
246         clockHostView: View,
247         viewModel: CustomizationPickerViewModel2,
248         colorUpdateViewModel: ColorUpdateViewModel,
249         lifecycleOwner: LifecycleOwner,
250         clockViewFactory: ClockViewFactory,
251     ) {
252         clockHostView as ClockConstraintLayoutHostView
253         val clockPickerViewModel =
254             (viewModel.customizationOptionsViewModel as ThemePickerCustomizationOptionsViewModel)
255                 .clockPickerViewModel
256 
257         lifecycleOwner.lifecycleScope.launch {
258             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
259                 launch {
260                     combine(
261                             clockPickerViewModel.previewingClock,
262                             clockPickerViewModel.previewingClockSize,
263                         ) { clock, size ->
264                             clock to size
265                         }
266                         .collect { (clock, size) ->
267                             clockHostView.removeAllViews()
268                             // For new customization picker, we should get views from clocklayout
269                             if (Flags.newCustomizationPickerUi()) {
270                                 clockViewFactory.getController(clock.clockId).let { clockController
271                                     ->
272                                     addClockViews(clockController, clockHostView, size)
273                                     val cs = ConstraintSet()
274                                     // TODO(b/379348167): get correct isShadeLayoutWide from picker
275                                     clockController.largeClock.layout.applyPreviewConstraints(
276                                         ClockPreviewConfig(
277                                             previewContext = context,
278                                             isShadeLayoutWide = false,
279                                             isSceneContainerFlagEnabled = false,
280                                         ),
281                                         cs,
282                                     )
283                                     clockController.smallClock.layout.applyPreviewConstraints(
284                                         ClockPreviewConfig(
285                                             previewContext = context,
286                                             isShadeLayoutWide = false,
287                                             isSceneContainerFlagEnabled = false,
288                                         ),
289                                         cs,
290                                     )
291                                     cs.applyTo(clockHostView)
292                                 }
293                             } else {
294                                 val clockView =
295                                     when (size) {
296                                         ClockSize.DYNAMIC ->
297                                             clockViewFactory.getLargeView(clock.clockId)
298                                         ClockSize.SMALL ->
299                                             clockViewFactory.getSmallView(clock.clockId)
300                                     }
301                                 // The clock view might still be attached to an existing parent.
302                                 // Detach
303                                 // before adding to another parent.
304                                 (clockView.parent as? ViewGroup)?.removeView(clockView)
305                                 clockHostView.addView(clockView)
306                             }
307                         }
308                 }
309 
310                 launch {
311                     combine(
312                             clockPickerViewModel.previewingSeedColor,
313                             clockPickerViewModel.previewingClock,
314                             clockPickerViewModel.previewingClockFontAxisMap,
315                             colorUpdateViewModel.systemColorsUpdated,
316                             ::Quadruple,
317                         )
318                         .collect { quadruple ->
319                             val (color, clock, axisMap, _) = quadruple
320                             clockViewFactory.updateColor(clock.clockId, color)
321                             val axisList = axisMap.map { ClockFontAxisSetting(it.key, it.value) }
322                             clockViewFactory.updateFontAxes(clock.clockId, axisList)
323                         }
324                 }
325             }
326         }
327     }
328 
329     data class Quadruple<A, B, C, D>(val first: A, val second: B, val third: C, val fourth: D)
330 }
331