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