1 /* <lambda>null2 * Copyright (C) 2022 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 18 package com.android.customization.picker.quickaffordance.ui.binder 19 20 import android.app.Dialog 21 import android.content.Context 22 import android.view.View 23 import android.widget.ImageView 24 import androidx.lifecycle.Lifecycle 25 import androidx.lifecycle.LifecycleOwner 26 import androidx.lifecycle.lifecycleScope 27 import androidx.lifecycle.repeatOnLifecycle 28 import androidx.recyclerview.widget.LinearLayoutManager 29 import androidx.recyclerview.widget.RecyclerView 30 import com.android.customization.picker.common.ui.view.ItemSpacing 31 import com.android.customization.picker.quickaffordance.ui.adapter.SlotTabAdapter 32 import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel 33 import com.android.wallpaper.R 34 import com.android.wallpaper.picker.common.dialog.ui.viewbinder.DialogViewBinder 35 import com.android.wallpaper.picker.common.dialog.ui.viewmodel.DialogViewModel 36 import com.android.wallpaper.picker.common.icon.ui.viewbinder.IconViewBinder 37 import com.android.wallpaper.picker.common.icon.ui.viewmodel.Icon 38 import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter 39 import kotlinx.coroutines.ExperimentalCoroutinesApi 40 import kotlinx.coroutines.flow.collectIndexed 41 import kotlinx.coroutines.flow.combine 42 import kotlinx.coroutines.flow.distinctUntilChanged 43 import kotlinx.coroutines.flow.flatMapLatest 44 import kotlinx.coroutines.flow.map 45 import kotlinx.coroutines.launch 46 47 @OptIn(ExperimentalCoroutinesApi::class) 48 object KeyguardQuickAffordancePickerBinder { 49 50 /** Binds view with view-model for a lock screen quick affordance picker experience. */ 51 @JvmStatic 52 fun bind( 53 view: View, 54 viewModel: KeyguardQuickAffordancePickerViewModel, 55 lifecycleOwner: LifecycleOwner, 56 ) { 57 val slotTabView: RecyclerView = view.requireViewById(R.id.slot_tabs) 58 val affordancesView: RecyclerView = view.requireViewById(R.id.affordances) 59 60 val slotTabAdapter = SlotTabAdapter() 61 slotTabView.adapter = slotTabAdapter 62 slotTabView.layoutManager = 63 LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false) 64 slotTabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP)) 65 val affordancesAdapter = 66 OptionItemAdapter( 67 layoutResourceId = R.layout.keyguard_quick_affordance, 68 lifecycleOwner = lifecycleOwner, 69 bindIcon = { foregroundView: View, gridIcon: Icon -> 70 val imageView = foregroundView as? ImageView 71 imageView?.let { IconViewBinder.bind(imageView, gridIcon) } 72 } 73 ) 74 affordancesView.adapter = affordancesAdapter 75 affordancesView.layoutManager = 76 LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false) 77 affordancesView.addItemDecoration(ItemSpacing(ItemSpacing.ITEM_SPACING_DP)) 78 79 var dialog: Dialog? = null 80 81 lifecycleOwner.lifecycleScope.launch { 82 lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 83 launch { 84 viewModel.slots 85 .map { slotById -> slotById.values } 86 .collect { slots -> slotTabAdapter.setItems(slots.toList()) } 87 } 88 89 launch { 90 viewModel.quickAffordances.collect { affordances -> 91 affordancesAdapter.setItems(affordances) 92 } 93 } 94 95 launch { 96 viewModel.quickAffordances 97 .flatMapLatest { affordances -> 98 combine(affordances.map { affordance -> affordance.isSelected }) { 99 selectedFlags -> 100 selectedFlags.indexOfFirst { it } 101 } 102 } 103 .collectIndexed { index, selectedPosition -> 104 // Scroll the view to show the first selected affordance. 105 if (selectedPosition != -1) { 106 // We use "post" because we need to give the adapter item a pass to 107 // update the view. 108 affordancesView.post { 109 if (index == 0) { 110 // don't animate on initial collection 111 affordancesView.scrollToPosition(selectedPosition) 112 } else { 113 affordancesView.smoothScrollToPosition(selectedPosition) 114 } 115 } 116 } 117 } 118 } 119 120 launch { 121 viewModel.dialog.distinctUntilChanged().collect { dialogRequest -> 122 dialog?.dismiss() 123 dialog = 124 if (dialogRequest != null) { 125 showDialog( 126 context = view.context, 127 request = dialogRequest, 128 onDismissed = viewModel::onDialogDismissed 129 ) 130 } else { 131 null 132 } 133 } 134 } 135 136 launch { 137 viewModel.activityStartRequests.collect { intent -> 138 if (intent != null) { 139 view.context.startActivity(intent) 140 viewModel.onActivityStarted() 141 } 142 } 143 } 144 } 145 } 146 } 147 148 private fun showDialog( 149 context: Context, 150 request: DialogViewModel, 151 onDismissed: () -> Unit, 152 ): Dialog { 153 return DialogViewBinder.show( 154 context = context, 155 viewModel = request, 156 onDismissed = onDismissed, 157 ) 158 } 159 } 160