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