1 /*
<lambda>null2  * Copyright (C) 2023 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 package com.android.wallpaper.picker.preview.ui.binder
17 
18 import android.app.AlertDialog
19 import android.content.Intent
20 import android.net.Uri
21 import android.view.View
22 import android.widget.Toast
23 import androidx.activity.OnBackPressedCallback
24 import androidx.fragment.app.FragmentActivity
25 import androidx.lifecycle.Lifecycle
26 import androidx.lifecycle.LifecycleOwner
27 import androidx.lifecycle.lifecycleScope
28 import androidx.lifecycle.repeatOnLifecycle
29 import com.android.wallpaper.R
30 import com.android.wallpaper.model.wallpaper.DeviceDisplayType
31 import com.android.wallpaper.module.logging.UserEventLogger
32 import com.android.wallpaper.picker.preview.ui.util.ImageEffectDialogUtil
33 import com.android.wallpaper.picker.preview.ui.view.ImageEffectDialog
34 import com.android.wallpaper.picker.preview.ui.view.PreviewActionFloatingSheet
35 import com.android.wallpaper.picker.preview.ui.view.PreviewActionGroup
36 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.CUSTOMIZE
37 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.DELETE
38 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.DOWNLOAD
39 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.EDIT
40 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.EFFECTS
41 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.INFORMATION
42 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.SHARE
43 import com.android.wallpaper.picker.preview.ui.viewmodel.PreviewActionsViewModel
44 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel
45 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperActionsToggleAdapter
46 import com.google.android.material.bottomsheet.BottomSheetBehavior
47 import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
48 import kotlinx.coroutines.launch
49 
50 /** Binds the action buttons and bottom sheet to [PreviewActionsViewModel] */
51 object PreviewActionsBinder {
52 
53     fun bind(
54         actionGroup: PreviewActionGroup,
55         floatingSheet: PreviewActionFloatingSheet,
56         previewViewModel: WallpaperPreviewViewModel,
57         actionsViewModel: PreviewActionsViewModel,
58         deviceDisplayType: DeviceDisplayType,
59         activity: FragmentActivity,
60         lifecycleOwner: LifecycleOwner,
61         logger: UserEventLogger,
62         imageEffectDialogUtil: ImageEffectDialogUtil,
63         onNavigateToEditScreen: (intent: Intent) -> Unit,
64         onStartShareActivity: (intent: Intent) -> Unit,
65     ) {
66         var deleteDialog: AlertDialog? = null
67         var onDelete: (() -> Unit)?
68         var imageEffectConfirmDownloadDialog: ImageEffectDialog? = null
69         var imageEffectConfirmExitDialog: ImageEffectDialog? = null
70         var onBackPressedCallback: OnBackPressedCallback? = null
71 
72         val floatingSheetCallback =
73             object : BottomSheetBehavior.BottomSheetCallback() {
74                 override fun onStateChanged(view: View, newState: Int) {
75                     if (newState == STATE_HIDDEN) {
76                         actionsViewModel.onFloatingSheetCollapsed()
77                     }
78                 }
79 
80                 override fun onSlide(p0: View, p1: Float) {}
81             }
82         floatingSheet.addFloatingSheetCallback(floatingSheetCallback)
83         lifecycleOwner.lifecycleScope.launch {
84             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
85                 floatingSheet.addFloatingSheetCallback(floatingSheetCallback)
86             }
87             floatingSheet.removeFloatingSheetCallback(floatingSheetCallback)
88         }
89 
90         lifecycleOwner.lifecycleScope.launch {
91             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
92                 /** [INFORMATION] */
93                 launch {
94                     actionsViewModel.isInformationVisible.collect {
95                         actionGroup.setIsVisible(INFORMATION, it)
96                     }
97                 }
98 
99                 launch {
100                     actionsViewModel.isInformationChecked.collect {
101                         actionGroup.setIsChecked(INFORMATION, it)
102                     }
103                 }
104 
105                 launch {
106                     actionsViewModel.onInformationClicked.collect {
107                         actionGroup.setClickListener(INFORMATION, it)
108                     }
109                 }
110 
111                 /** [DOWNLOAD] */
112                 launch {
113                     actionsViewModel.isDownloadVisible.collect {
114                         actionGroup.setIsVisible(DOWNLOAD, it)
115                     }
116                 }
117 
118                 launch {
119                     actionsViewModel.isDownloading.collect { actionGroup.setIsDownloading(it) }
120                 }
121 
122                 launch {
123                     actionsViewModel.isDownloadButtonEnabled.collect {
124                         actionGroup.setClickListener(
125                             DOWNLOAD,
126                             if (it) {
127                                 {
128                                     lifecycleOwner.lifecycleScope.launch {
129                                         actionsViewModel.downloadWallpaper()
130                                     }
131                                 }
132                             } else null,
133                         )
134                     }
135                 }
136 
137                 /** [DELETE] */
138                 launch {
139                     actionsViewModel.isDeleteVisible.collect {
140                         actionGroup.setIsVisible(DELETE, it)
141                     }
142                 }
143 
144                 launch {
145                     actionsViewModel.isDeleteChecked.collect {
146                         actionGroup.setIsChecked(DELETE, it)
147                     }
148                 }
149 
150                 launch {
151                     actionsViewModel.onDeleteClicked.collect {
152                         actionGroup.setClickListener(DELETE, it)
153                     }
154                 }
155 
156                 launch {
157                     actionsViewModel.deleteConfirmationDialogViewModel.collect { viewModel ->
158                         val appContext = activity.applicationContext
159                         if (viewModel != null) {
160                             onDelete = {
161                                 if (viewModel.creativeWallpaperDeleteUri != null) {
162                                     appContext.contentResolver.delete(
163                                         viewModel.creativeWallpaperDeleteUri,
164                                         null,
165                                         null
166                                     )
167                                 } else if (viewModel.liveWallpaperDeleteIntent != null) {
168                                     appContext.startService(viewModel.liveWallpaperDeleteIntent)
169                                 }
170                                 activity.finish()
171                             }
172                             val dialog =
173                                 deleteDialog
174                                     ?: AlertDialog.Builder(activity)
175                                         .setMessage(R.string.delete_wallpaper_confirmation)
176                                         .setOnDismissListener { viewModel.onDismiss.invoke() }
177                                         .setPositiveButton(R.string.delete_live_wallpaper) { _, _ ->
178                                             onDelete?.invoke()
179                                         }
180                                         .setNegativeButton(android.R.string.cancel, null)
181                                         .create()
182                                         .also { deleteDialog = it }
183                             dialog.show()
184                         } else {
185                             deleteDialog?.dismiss()
186                         }
187                     }
188                 }
189 
190                 /** [EDIT] */
191                 launch {
192                     actionsViewModel.isEditVisible.collect { actionGroup.setIsVisible(EDIT, it) }
193                 }
194 
195                 launch {
196                     actionsViewModel.isEditChecked.collect { actionGroup.setIsChecked(EDIT, it) }
197                 }
198 
199                 launch {
200                     actionsViewModel.editIntent.collect {
201                         actionGroup.setClickListener(
202                             EDIT,
203                             if (it != null) {
204                                 {
205                                     // We need to set default wallpaper preview config view model
206                                     // before entering full screen with edit activity overlay.
207                                     previewViewModel.setDefaultFullPreviewConfigViewModel(
208                                         deviceDisplayType
209                                     )
210                                     onNavigateToEditScreen.invoke(it)
211                                 }
212                             } else null
213                         )
214                     }
215                 }
216 
217                 /** [CUSTOMIZE] */
218                 launch {
219                     actionsViewModel.isCustomizeVisible.collect {
220                         actionGroup.setIsVisible(CUSTOMIZE, it)
221                     }
222                 }
223 
224                 launch {
225                     actionsViewModel.isCustomizeChecked.collect {
226                         actionGroup.setIsChecked(CUSTOMIZE, it)
227                     }
228                 }
229 
230                 launch {
231                     actionsViewModel.onCustomizeClicked.collect {
232                         actionGroup.setClickListener(CUSTOMIZE, it)
233                     }
234                 }
235 
236                 /** [EFFECTS] */
237                 launch {
238                     actionsViewModel.isEffectsVisible.collect {
239                         actionGroup.setIsVisible(EFFECTS, it)
240                     }
241                 }
242 
243                 launch {
244                     actionsViewModel.isEffectsChecked.collect {
245                         actionGroup.setIsChecked(EFFECTS, it)
246                     }
247                 }
248 
249                 launch {
250                     actionsViewModel.onEffectsClicked.collect {
251                         actionGroup.setClickListener(EFFECTS, it)
252                     }
253                 }
254 
255                 launch {
256                     actionsViewModel.effectDownloadFailureToastText.collect {
257                         Toast.makeText(floatingSheet.context, it, Toast.LENGTH_LONG).show()
258                     }
259                 }
260 
261                 launch {
262                     actionsViewModel.imageEffectConfirmDownloadDialogViewModel.collect { viewModel
263                         ->
264                         if (viewModel != null) {
265                             val dialog =
266                                 imageEffectConfirmDownloadDialog
267                                     ?: imageEffectDialogUtil
268                                         .createConfirmDownloadDialog(activity)
269                                         .also { imageEffectConfirmDownloadDialog = it }
270                             dialog.onDismiss = viewModel.onDismiss
271                             dialog.onContinue = viewModel.onContinue
272                             dialog.show()
273                         } else {
274                             imageEffectConfirmDownloadDialog?.dismiss()
275                         }
276                     }
277                 }
278 
279                 launch {
280                     actionsViewModel.imageEffectConfirmExitDialogViewModel.collect { viewModel ->
281                         if (viewModel != null) {
282                             val dialog =
283                                 imageEffectConfirmExitDialog
284                                     ?: imageEffectDialogUtil
285                                         .createConfirmExitDialog(activity)
286                                         .also { imageEffectConfirmExitDialog = it }
287                             dialog.onDismiss = viewModel.onDismiss
288                             dialog.onContinue = {
289                                 viewModel.onContinue()
290                                 activity.onBackPressedDispatcher.onBackPressed()
291                             }
292                             dialog.show()
293                         } else {
294                             imageEffectConfirmExitDialog?.dismiss()
295                         }
296                     }
297                 }
298 
299                 launch {
300                     actionsViewModel.handleOnBackPressed.collect { handleOnBackPressed ->
301                         // Reset the callback
302                         onBackPressedCallback?.remove()
303                         onBackPressedCallback = null
304                         if (handleOnBackPressed != null) {
305                             // If handleOnBackPressed is not null, set it to the activity
306                             val callback =
307                                 object : OnBackPressedCallback(true) {
308                                         override fun handleOnBackPressed() {
309                                             val handled = handleOnBackPressed()
310                                             if(!handled) {
311                                                 onBackPressedCallback?.remove()
312                                                 onBackPressedCallback = null
313                                                 activity.onBackPressedDispatcher.onBackPressed()
314                                             }
315                                         }
316                                     }
317                                     .also { onBackPressedCallback = it }
318                             activity.onBackPressedDispatcher.addCallback(lifecycleOwner, callback)
319                         }
320                     }
321                 }
322 
323                 /** [SHARE] */
324                 launch {
325                     actionsViewModel.isShareVisible.collect { actionGroup.setIsVisible(SHARE, it) }
326                 }
327 
328                 launch {
329                     actionsViewModel.shareIntent.collect {
330                         actionGroup.setClickListener(
331                             SHARE,
332                             if (it != null) {
333                                 { onStartShareActivity.invoke(it) }
334                             } else null
335                         )
336                     }
337                 }
338 
339                 /** Floating sheet behavior */
340                 launch {
341                     actionsViewModel.previewFloatingSheetViewModel.collect { floatingSheetViewModel
342                         ->
343                         if (floatingSheetViewModel != null) {
344                             val (
345                                 informationViewModel,
346                                 imageEffectViewModel,
347                                 creativeEffectViewModel,
348                                 customizeViewModel,
349                             ) = floatingSheetViewModel
350                             when {
351                                 informationViewModel != null -> {
352                                     floatingSheet.setInformationContent(
353                                         informationViewModel.attributions,
354                                         informationViewModel.exploreActionUrl?.let { url ->
355                                             {
356                                                 logger.logWallpaperExploreButtonClicked()
357                                                 floatingSheet.context.startActivity(
358                                                     Intent(Intent.ACTION_VIEW, Uri.parse(url))
359                                                 )
360                                             }
361                                         },
362                                     )
363                                 }
364                                 imageEffectViewModel != null ->
365                                     floatingSheet.setImageEffectContent(
366                                         imageEffectViewModel.effectType,
367                                         imageEffectViewModel.myPhotosClickListener,
368                                         imageEffectViewModel.collapseFloatingSheetListener,
369                                         imageEffectViewModel.effectSwitchListener,
370                                         imageEffectViewModel.effectDownloadClickListener,
371                                         imageEffectViewModel.status,
372                                         imageEffectViewModel.resultCode,
373                                         imageEffectViewModel.errorMessage,
374                                         imageEffectViewModel.title,
375                                         imageEffectViewModel.effectTextRes,
376                                     )
377                                 creativeEffectViewModel != null ->
378                                     floatingSheet.setCreativeEffectContent(
379                                         creativeEffectViewModel.title,
380                                         creativeEffectViewModel.subtitle,
381                                         creativeEffectViewModel.wallpaperActions,
382                                         object :
383                                             WallpaperActionsToggleAdapter.WallpaperEffectSwitchListener {
384                                             override fun onEffectSwitchChanged(checkedItem: Int) {
385                                                 launch {
386                                                     creativeEffectViewModel
387                                                         .wallpaperEffectSwitchListener(checkedItem)
388                                                 }
389                                             }
390                                         },
391                                     )
392                                 customizeViewModel != null ->
393                                     floatingSheet.setCustomizeContent(
394                                         customizeViewModel.customizeSliceUri
395                                     )
396                             }
397                             floatingSheet.expand()
398                         } else {
399                             floatingSheet.collapse()
400                         }
401                     }
402                 }
403             }
404         }
405     }
406 }
407