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