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