• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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