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