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