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 package com.android.customization.picker.clock.ui.binder 17 18 import android.content.res.Configuration 19 import android.view.LayoutInflater 20 import android.view.View 21 import android.view.ViewGroup 22 import android.widget.LinearLayout 23 import android.widget.SeekBar 24 import androidx.core.view.doOnPreDraw 25 import androidx.core.view.isInvisible 26 import androidx.core.view.isVisible 27 import androidx.lifecycle.Lifecycle 28 import androidx.lifecycle.LifecycleEventObserver 29 import androidx.lifecycle.LifecycleOwner 30 import androidx.lifecycle.lifecycleScope 31 import androidx.lifecycle.repeatOnLifecycle 32 import androidx.recyclerview.widget.LinearLayoutManager 33 import androidx.recyclerview.widget.RecyclerView 34 import com.android.customization.picker.clock.shared.ClockSize 35 import com.android.customization.picker.clock.ui.adapter.ClockSettingsTabAdapter 36 import com.android.customization.picker.clock.ui.view.ClockCarouselView 37 import com.android.customization.picker.clock.ui.view.ClockHostView 38 import com.android.customization.picker.clock.ui.view.ClockSizeRadioButtonGroup 39 import com.android.customization.picker.clock.ui.view.ClockViewFactory 40 import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel 41 import com.android.customization.picker.color.ui.binder.ColorOptionIconBinder 42 import com.android.customization.picker.common.ui.view.ItemSpacing 43 import com.android.wallpaper.R 44 import com.android.wallpaper.picker.option.ui.binder.OptionItemBinder 45 import kotlinx.coroutines.flow.combine 46 import kotlinx.coroutines.flow.mapNotNull 47 import kotlinx.coroutines.launch 48 49 /** Bind between the clock settings screen and its view model. */ 50 object ClockSettingsBinder { 51 private const val SLIDER_ENABLED_ALPHA = 1f 52 private const val SLIDER_DISABLED_ALPHA = .3f 53 private const val COLOR_PICKER_ITEM_PREFIX_ID = 1234 54 55 fun bind( 56 view: View, 57 viewModel: ClockSettingsViewModel, 58 clockViewFactory: ClockViewFactory, 59 lifecycleOwner: LifecycleOwner, 60 ) { 61 val clockHostView: ClockHostView = view.requireViewById(R.id.clock_host_view) 62 val tabView: RecyclerView = view.requireViewById(R.id.tabs) 63 val tabAdapter = ClockSettingsTabAdapter() 64 tabView.adapter = tabAdapter 65 tabView.layoutManager = LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false) 66 tabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP)) 67 val colorOptionContainerListView: LinearLayout = view.requireViewById(R.id.color_options) 68 val slider: SeekBar = view.requireViewById(R.id.slider) 69 slider.setOnSeekBarChangeListener( 70 object : SeekBar.OnSeekBarChangeListener { 71 override fun onProgressChanged(p0: SeekBar?, progress: Int, fromUser: Boolean) { 72 if (fromUser) { 73 viewModel.onSliderProgressChanged(progress) 74 } 75 } 76 77 override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit 78 override fun onStopTrackingTouch(seekBar: SeekBar?) { 79 seekBar?.progress?.let { 80 lifecycleOwner.lifecycleScope.launch { viewModel.onSliderProgressStop(it) } 81 } 82 } 83 } 84 ) 85 86 val sizeOptions = 87 view.requireViewById<ClockSizeRadioButtonGroup>(R.id.clock_size_radio_button_group) 88 sizeOptions.onRadioButtonClickListener = 89 object : ClockSizeRadioButtonGroup.OnRadioButtonClickListener { 90 override fun onClick(size: ClockSize) { 91 viewModel.setClockSize(size) 92 } 93 } 94 95 val colorOptionContainer = view.requireViewById<View>(R.id.color_picker_container) 96 lifecycleOwner.lifecycleScope.launch { 97 lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 98 launch { 99 viewModel.seedColor.collect { seedColor -> 100 viewModel.selectedClockId.value?.let { selectedClockId -> 101 clockViewFactory.updateColor(selectedClockId, seedColor) 102 } 103 } 104 } 105 106 launch { viewModel.tabs.collect { tabAdapter.setItems(it) } } 107 108 launch { 109 viewModel.selectedTab.collect { tab -> 110 when (tab) { 111 ClockSettingsViewModel.Tab.COLOR -> { 112 colorOptionContainer.isVisible = true 113 sizeOptions.isInvisible = true 114 } 115 ClockSettingsViewModel.Tab.SIZE -> { 116 colorOptionContainer.isInvisible = true 117 sizeOptions.isVisible = true 118 } 119 } 120 } 121 } 122 123 launch { 124 viewModel.colorOptions.collect { colorOptions -> 125 colorOptions.forEachIndexed { index, colorOption -> 126 colorOption.payload?.let { payload -> 127 val item = 128 LayoutInflater.from(view.context) 129 .inflate( 130 R.layout.clock_color_option, 131 colorOptionContainerListView, 132 false, 133 ) as LinearLayout 134 val darkMode = 135 (view.resources.configuration.uiMode and 136 Configuration.UI_MODE_NIGHT_MASK == 137 Configuration.UI_MODE_NIGHT_YES) 138 ColorOptionIconBinder.bind( 139 item.requireViewById(R.id.foreground), 140 payload, 141 darkMode 142 ) 143 OptionItemBinder.bind( 144 view = item, 145 viewModel = colorOptions[index], 146 lifecycleOwner = lifecycleOwner, 147 foregroundTintSpec = null, 148 ) 149 150 val id = COLOR_PICKER_ITEM_PREFIX_ID + index 151 item.id = id 152 colorOptionContainerListView.addView(item) 153 } 154 } 155 } 156 } 157 158 launch { 159 viewModel.selectedColorOptionPosition.collect { selectedPosition -> 160 if (selectedPosition != -1) { 161 val colorOptionContainerListView: LinearLayout = 162 view.requireViewById(R.id.color_options) 163 164 val selectedView = 165 colorOptionContainerListView.findViewById<View>( 166 COLOR_PICKER_ITEM_PREFIX_ID + selectedPosition 167 ) 168 selectedView?.parent?.requestChildFocus(selectedView, selectedView) 169 } 170 } 171 } 172 173 launch { 174 combine( 175 viewModel.selectedClockId.mapNotNull { it }, 176 viewModel.selectedClockSize, 177 ::Pair, 178 ) 179 .collect { (clockId, size) -> 180 clockHostView.removeAllViews() 181 val clockView = 182 when (size) { 183 ClockSize.DYNAMIC -> clockViewFactory.getLargeView(clockId) 184 ClockSize.SMALL -> clockViewFactory.getSmallView(clockId) 185 } 186 // The clock view might still be attached to an existing parent. Detach 187 // before adding to another parent. 188 (clockView.parent as? ViewGroup)?.removeView(clockView) 189 clockHostView.addView(clockView) 190 when (size) { 191 ClockSize.DYNAMIC -> { 192 sizeOptions.radioButtonDynamic.isChecked = true 193 sizeOptions.radioButtonSmall.isChecked = false 194 clockHostView.doOnPreDraw { 195 it.pivotX = it.width / 2F 196 it.pivotY = it.height / 2F 197 } 198 } 199 ClockSize.SMALL -> { 200 sizeOptions.radioButtonDynamic.isChecked = false 201 sizeOptions.radioButtonSmall.isChecked = true 202 clockHostView.doOnPreDraw { 203 it.pivotX = ClockCarouselView.getCenteredHostViewPivotX(it) 204 it.pivotY = 0F 205 } 206 } 207 } 208 } 209 } 210 211 launch { 212 viewModel.sliderProgress.collect { progress -> 213 slider.setProgress(progress, true) 214 } 215 } 216 217 launch { 218 viewModel.isSliderEnabled.collect { isEnabled -> 219 slider.isEnabled = isEnabled 220 slider.alpha = 221 if (isEnabled) SLIDER_ENABLED_ALPHA else SLIDER_DISABLED_ALPHA 222 } 223 } 224 } 225 } 226 227 lifecycleOwner.lifecycle.addObserver( 228 LifecycleEventObserver { source, event -> 229 when (event) { 230 Lifecycle.Event.ON_RESUME -> { 231 clockViewFactory.registerTimeTicker(source) 232 } 233 Lifecycle.Event.ON_PAUSE -> { 234 clockViewFactory.unregisterTimeTicker(source) 235 } 236 else -> {} 237 } 238 } 239 ) 240 } 241 } 242