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