• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.Dialog
20 import android.content.Context
21 import android.view.View
22 import android.view.ViewGroup
23 import android.widget.ImageView
24 import androidx.core.graphics.drawable.DrawableCompat
25 import androidx.lifecycle.Lifecycle
26 import androidx.lifecycle.LifecycleOwner
27 import androidx.lifecycle.lifecycleScope
28 import androidx.lifecycle.repeatOnLifecycle
29 import androidx.recyclerview.widget.GridLayoutManager
30 import androidx.recyclerview.widget.RecyclerView
31 import com.android.customization.picker.common.ui.view.DoubleRowListItemSpacing
32 import com.android.themepicker.R
33 import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerLockCustomizationOption.SHORTCUTS
34 import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
35 import com.android.wallpaper.picker.common.dialog.ui.viewbinder.DialogViewBinder
36 import com.android.wallpaper.picker.common.dialog.ui.viewmodel.DialogViewModel
37 import com.android.wallpaper.picker.common.icon.ui.viewbinder.IconViewBinder
38 import com.android.wallpaper.picker.common.icon.ui.viewmodel.Icon
39 import com.android.wallpaper.picker.customization.ui.binder.ColorUpdateBinder
40 import com.android.wallpaper.picker.customization.ui.view.FloatingToolbar
41 import com.android.wallpaper.picker.customization.ui.view.adapter.FloatingToolbarTabAdapter
42 import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel
43 import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter2
44 import java.lang.ref.WeakReference
45 import kotlinx.coroutines.ExperimentalCoroutinesApi
46 import kotlinx.coroutines.flow.collectIndexed
47 import kotlinx.coroutines.flow.combine
48 import kotlinx.coroutines.flow.distinctUntilChanged
49 import kotlinx.coroutines.flow.flatMapLatest
50 import kotlinx.coroutines.launch
51 
52 @OptIn(ExperimentalCoroutinesApi::class)
53 object ShortcutFloatingSheetBinder {
54 
55     fun bind(
56         view: View,
57         optionsViewModel: ThemePickerCustomizationOptionsViewModel,
58         colorUpdateViewModel: ColorUpdateViewModel,
59         lifecycleOwner: LifecycleOwner,
60     ) {
61         val viewModel = optionsViewModel.keyguardQuickAffordancePickerViewModel2
62         val isFloatingSheetActive = { optionsViewModel.selectedOption.value == SHORTCUTS }
63 
64         val tabs = view.requireViewById<FloatingToolbar>(R.id.floating_toolbar)
65         val tabContainer =
66             tabs.findViewById<ViewGroup>(com.android.wallpaper.R.id.floating_toolbar_tab_container)
67         ColorUpdateBinder.bind(
68             setColor = { color ->
69                 DrawableCompat.setTint(DrawableCompat.wrap(tabContainer.background), color)
70             },
71             color = colorUpdateViewModel.floatingToolbarBackground,
72             shouldAnimate = isFloatingSheetActive,
73             lifecycleOwner = lifecycleOwner,
74         )
75         val tabAdapter =
76             FloatingToolbarTabAdapter(
77                     colorUpdateViewModel = WeakReference(colorUpdateViewModel),
78                     shouldAnimateColor = isFloatingSheetActive,
79                 )
80                 .also { tabs.setAdapter(it) }
81 
82         val floatingSheetContainer =
83             view.requireViewById<ViewGroup>(R.id.floating_sheet_content_container)
84         ColorUpdateBinder.bind(
85             setColor = { color ->
86                 DrawableCompat.setTint(
87                     DrawableCompat.wrap(floatingSheetContainer.background),
88                     color,
89                 )
90             },
91             color = colorUpdateViewModel.colorSurfaceBright,
92             shouldAnimate = isFloatingSheetActive,
93             lifecycleOwner = lifecycleOwner,
94         )
95 
96         val quickAffordanceAdapter =
97             createOptionItemAdapter(
98                 colorUpdateViewModel = colorUpdateViewModel,
99                 shouldAnimateColor = isFloatingSheetActive,
100                 lifecycleOwner = lifecycleOwner,
101             )
102         val quickAffordanceList =
103             view.requireViewById<RecyclerView>(R.id.quick_affordance_horizontal_list).also {
104                 it.initQuickAffordanceList(view.context.applicationContext, quickAffordanceAdapter)
105             }
106 
107         var dialog: Dialog? = null
108 
109         lifecycleOwner.lifecycleScope.launch {
110             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
111                 launch { viewModel.tabs.collect { tabAdapter.submitList(it) } }
112 
113                 launch {
114                     viewModel.quickAffordances.collect { affordances ->
115                         quickAffordanceAdapter.setItems(affordances)
116                     }
117                 }
118 
119                 launch {
120                     viewModel.quickAffordances
121                         .flatMapLatest { affordances ->
122                             combine(affordances.map { affordance -> affordance.isSelected }) {
123                                 selectedFlags ->
124                                 selectedFlags.indexOfFirst { it }
125                             }
126                         }
127                         .collectIndexed { index, selectedPosition ->
128                             // Scroll the view to show the first selected affordance.
129                             if (selectedPosition != -1) {
130                                 // We use "post" because we need to give the adapter item a pass to
131                                 // update the view.
132                                 quickAffordanceList.post {
133                                     if (index == 0) {
134                                         // don't animate on initial collection
135                                         quickAffordanceList.scrollToPosition(selectedPosition)
136                                     } else {
137                                         quickAffordanceList.smoothScrollToPosition(selectedPosition)
138                                     }
139                                 }
140                             }
141                         }
142                 }
143 
144                 launch {
145                     viewModel.dialog.distinctUntilChanged().collect { dialogRequest ->
146                         dialog?.dismiss()
147                         dialog =
148                             if (dialogRequest != null) {
149                                 showDialog(
150                                     context = view.context,
151                                     request = dialogRequest,
152                                     onDismissed = viewModel::onDialogDismissed,
153                                 )
154                             } else {
155                                 null
156                             }
157                     }
158                 }
159 
160                 launch {
161                     viewModel.activityStartRequests.collect { intent ->
162                         if (intent != null) {
163                             view.context.startActivity(intent)
164                             viewModel.onActivityStarted()
165                         }
166                     }
167                 }
168             }
169         }
170     }
171 
172     private fun showDialog(
173         context: Context,
174         request: DialogViewModel,
175         onDismissed: () -> Unit,
176     ): Dialog {
177         return DialogViewBinder.show(
178             context = context,
179             viewModel = request,
180             onDismissed = onDismissed,
181         )
182     }
183 
184     private fun createOptionItemAdapter(
185         colorUpdateViewModel: ColorUpdateViewModel,
186         shouldAnimateColor: () -> Boolean,
187         lifecycleOwner: LifecycleOwner,
188     ): OptionItemAdapter2<Icon> =
189         OptionItemAdapter2(
190             layoutResourceId = R.layout.quick_affordance_list_item2,
191             lifecycleOwner = lifecycleOwner,
192             bindPayload = { itemView: View, gridIcon: Icon ->
193                 val imageView =
194                     itemView.requireViewById<ImageView>(com.android.wallpaper.R.id.foreground)
195                 IconViewBinder.bind(imageView, gridIcon)
196                 // Return null since it does not need the lifecycleOwner to launch any job for later
197                 // disposal when rebind.
198                 return@OptionItemAdapter2 null
199             },
200             colorUpdateViewModel = WeakReference(colorUpdateViewModel),
201             shouldAnimateColor = shouldAnimateColor,
202         )
203 
204     private fun RecyclerView.initQuickAffordanceList(
205         context: Context,
206         adapter: OptionItemAdapter2<Icon>,
207     ) {
208         apply {
209             this.adapter = adapter
210             layoutManager = GridLayoutManager(context, 2, GridLayoutManager.HORIZONTAL, false)
211             addItemDecoration(
212                 DoubleRowListItemSpacing(
213                     context.resources.getDimensionPixelSize(
214                         R.dimen.floating_sheet_content_horizontal_padding
215                     ),
216                     context.resources.getDimensionPixelSize(
217                         R.dimen.floating_sheet_list_item_horizontal_space
218                     ),
219                     context.resources.getDimensionPixelSize(
220                         R.dimen.floating_sheet_list_item_vertical_space
221                     ),
222                 )
223             )
224         }
225     }
226 }
227