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 17 package com.android.wallpaper.picker.preview.ui.viewmodel 18 19 import android.content.ClipData 20 import android.content.ComponentName 21 import android.content.Context 22 import android.content.Intent 23 import android.net.ConnectivityManager 24 import android.net.Uri 25 import android.net.wifi.WifiManager 26 import android.service.wallpaper.WallpaperSettingsActivity 27 import com.android.wallpaper.effects.Effect 28 import com.android.wallpaper.effects.EffectsController.EffectEnumInterface 29 import com.android.wallpaper.picker.data.CreativeWallpaperData 30 import com.android.wallpaper.picker.data.DownloadableWallpaperData 31 import com.android.wallpaper.picker.data.LiveWallpaperData 32 import com.android.wallpaper.picker.data.WallpaperModel 33 import com.android.wallpaper.picker.data.WallpaperModel.LiveWallpaperModel 34 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_APPLIED 35 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_APPLY_FAILED 36 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_APPLY_IN_PROGRESS 37 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_DISABLE 38 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_DOWNLOAD_FAILED 39 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_DOWNLOAD_IN_PROGRESS 40 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_DOWNLOAD_READY 41 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_READY 42 import com.android.wallpaper.picker.preview.domain.interactor.PreviewActionsInteractor 43 import com.android.wallpaper.picker.preview.shared.model.ImageEffectsModel 44 import com.android.wallpaper.picker.preview.ui.util.LiveWallpaperDeleteUtil 45 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.CUSTOMIZE 46 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.DELETE 47 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.EDIT 48 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.EFFECTS 49 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.INFORMATION 50 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.SHARE 51 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.CreativeEffectFloatingSheetViewModel 52 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.CustomizeFloatingSheetViewModel 53 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.ImageEffectFloatingSheetViewModel 54 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.InformationFloatingSheetViewModel 55 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.PreviewFloatingSheetViewModel 56 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.EffectDownloadClickListener 57 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.EffectSwitchListener 58 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.DOWNLOADING 59 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.FAILED 60 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.IDLE 61 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.PROCESSING 62 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.SHOW_DOWNLOAD_BUTTON 63 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.SUCCESS 64 import dagger.hilt.android.qualifiers.ApplicationContext 65 import dagger.hilt.android.scopes.ViewModelScoped 66 import javax.inject.Inject 67 import kotlinx.coroutines.flow.Flow 68 import kotlinx.coroutines.flow.MutableStateFlow 69 import kotlinx.coroutines.flow.asStateFlow 70 import kotlinx.coroutines.flow.combine 71 import kotlinx.coroutines.flow.filterNotNull 72 import kotlinx.coroutines.flow.map 73 74 /** View model for the preview action buttons */ 75 @ViewModelScoped 76 class PreviewActionsViewModel 77 @Inject 78 constructor( 79 private val interactor: PreviewActionsInteractor, 80 liveWallpaperDeleteUtil: LiveWallpaperDeleteUtil, 81 @ApplicationContext private val context: Context, 82 ) { 83 /** [INFORMATION] */ 84 private val informationFloatingSheetViewModel: Flow<InformationFloatingSheetViewModel?> = 85 interactor.wallpaperModel.map { wallpaperModel -> 86 if (wallpaperModel == null || !wallpaperModel.shouldShowInformationFloatingSheet()) { 87 null 88 } else { 89 InformationFloatingSheetViewModel( 90 wallpaperModel.commonWallpaperData.attributions, 91 if (wallpaperModel.commonWallpaperData.exploreActionUrl.isNullOrEmpty()) { 92 null 93 } else { 94 wallpaperModel.commonWallpaperData.exploreActionUrl 95 } 96 ) 97 } 98 } 99 100 val isInformationVisible: Flow<Boolean> = informationFloatingSheetViewModel.map { it != null } 101 102 private val _isInformationChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 103 val isInformationChecked: Flow<Boolean> = _isInformationChecked.asStateFlow() 104 105 val onInformationClicked: Flow<(() -> Unit)?> = 106 combine(isInformationVisible, isInformationChecked) { show, isChecked -> 107 if (show) { 108 { 109 if (!isChecked) { 110 uncheckAllOthersExcept(INFORMATION) 111 } 112 _isInformationChecked.value = !isChecked 113 } 114 } else { 115 null 116 } 117 } 118 119 /** [DOWNLOAD] */ 120 private val downloadableWallpaperData: Flow<DownloadableWallpaperData?> = 121 interactor.wallpaperModel.map { 122 (it as? WallpaperModel.StaticWallpaperModel)?.downloadableWallpaperData 123 } 124 val isDownloadVisible: Flow<Boolean> = downloadableWallpaperData.map { it != null } 125 126 val isDownloading: Flow<Boolean> = interactor.isDownloadingWallpaper 127 128 val isDownloadButtonEnabled: Flow<Boolean> = 129 combine(downloadableWallpaperData, isDownloading) { downloadableData, isDownloading -> 130 downloadableData != null && !isDownloading 131 } 132 133 suspend fun downloadWallpaper() { 134 interactor.downloadWallpaper() 135 } 136 137 /** [DELETE] */ 138 private val liveWallpaperDeleteIntent: Flow<Intent?> = 139 interactor.wallpaperModel.map { 140 if (it is LiveWallpaperModel && it.creativeWallpaperData == null && it.canBeDeleted()) { 141 liveWallpaperDeleteUtil.getDeleteActionIntent( 142 it.liveWallpaperData.systemWallpaperInfo 143 ) 144 } else { 145 null 146 } 147 } 148 private val creativeWallpaperDeleteUri: Flow<Uri?> = 149 interactor.wallpaperModel.map { 150 val deleteUri = (it as? LiveWallpaperModel)?.creativeWallpaperData?.deleteUri 151 if (deleteUri != null && it.canBeDeleted()) { 152 deleteUri 153 } else { 154 null 155 } 156 } 157 val isDeleteVisible: Flow<Boolean> = 158 combine(liveWallpaperDeleteIntent, creativeWallpaperDeleteUri) { intent, uri -> 159 intent != null || uri != null 160 } 161 162 private val _isDeleteChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 163 val isDeleteChecked: Flow<Boolean> = _isDeleteChecked.asStateFlow() 164 165 // View model for delete confirmation dialog. Note that null means the dialog should show; 166 // otherwise, the dialog should hide. 167 val deleteConfirmationDialogViewModel: Flow<DeleteConfirmationDialogViewModel?> = 168 combine(isDeleteChecked, liveWallpaperDeleteIntent, creativeWallpaperDeleteUri) { 169 isChecked, 170 intent, 171 uri -> 172 if (isChecked && (intent != null || uri != null)) { 173 DeleteConfirmationDialogViewModel( 174 onDismiss = { _isDeleteChecked.value = false }, 175 liveWallpaperDeleteIntent = intent, 176 creativeWallpaperDeleteUri = uri, 177 ) 178 } else { 179 null 180 } 181 } 182 183 val onDeleteClicked: Flow<(() -> Unit)?> = 184 combine(isDeleteVisible, isDeleteChecked) { show, isChecked -> 185 if (show) { 186 { 187 if (!isChecked) { 188 uncheckAllOthersExcept(DELETE) 189 } 190 _isDeleteChecked.value = !isChecked 191 } 192 } else { 193 null 194 } 195 } 196 197 /** [EDIT] */ 198 val editIntent: Flow<Intent?> = 199 interactor.wallpaperModel.map { model -> 200 (model as? LiveWallpaperModel)?.liveWallpaperData?.getEditActivityIntent(false)?.let { 201 intent -> 202 if (intent.resolveActivityInfo(context.packageManager, 0) != null) intent else null 203 } 204 } 205 val isEditVisible: Flow<Boolean> = editIntent.map { it != null } 206 207 private val _isEditChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 208 val isEditChecked: Flow<Boolean> = _isEditChecked.asStateFlow() 209 210 /** [CUSTOMIZE] */ 211 private val customizeFloatingSheetViewModel: Flow<CustomizeFloatingSheetViewModel?> = 212 interactor.wallpaperModel.map { 213 (it as? LiveWallpaperModel) 214 ?.liveWallpaperData 215 ?.systemWallpaperInfo 216 ?.settingsSliceUri 217 ?.let { CustomizeFloatingSheetViewModel(it) } 218 } 219 val isCustomizeVisible: Flow<Boolean> = customizeFloatingSheetViewModel.map { it != null } 220 221 private val _isCustomizeChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 222 val isCustomizeChecked: Flow<Boolean> = _isCustomizeChecked.asStateFlow() 223 224 val onCustomizeClicked: Flow<(() -> Unit)?> = 225 combine(isCustomizeVisible, isCustomizeChecked) { show, isChecked -> 226 if (show) { 227 { 228 if (!isChecked) { 229 uncheckAllOthersExcept(CUSTOMIZE) 230 } 231 _isCustomizeChecked.value = !isChecked 232 } 233 } else { 234 null 235 } 236 } 237 238 /** [EFFECTS] */ 239 private val _imageEffectConfirmDownloadDialogViewModel: 240 MutableStateFlow<ImageEffectDialogViewModel?> = 241 MutableStateFlow(null) 242 // View model for the dialog that confirms downloading the effect ML model. 243 val imageEffectConfirmDownloadDialogViewModel = 244 _imageEffectConfirmDownloadDialogViewModel.asStateFlow() 245 246 private val imageEffectFloatingSheetViewModel: Flow<ImageEffectFloatingSheetViewModel?> = 247 combine(interactor.imageEffectsModel, interactor.imageEffect) { 248 imageEffectsModel, 249 imageEffect -> 250 imageEffect?.let { 251 when (imageEffectsModel.status) { 252 EFFECT_DISABLE -> { 253 null 254 } 255 else -> { 256 getImageEffectFloatingSheetViewModel( 257 imageEffect, 258 imageEffectsModel, 259 ) 260 } 261 } 262 } 263 } 264 private val _imageEffectConfirmExitDialogViewModel: 265 MutableStateFlow<ImageEffectDialogViewModel?> = 266 MutableStateFlow(null) 267 val imageEffectConfirmExitDialogViewModel = _imageEffectConfirmExitDialogViewModel.asStateFlow() 268 val handleOnBackPressed: Flow<(() -> Boolean)?> = 269 combine(imageEffectFloatingSheetViewModel, interactor.imageEffect, isDownloading) { 270 viewModel, 271 effect, 272 isDownloading -> 273 when { 274 viewModel?.status == DOWNLOADING -> { -> 275 _imageEffectConfirmExitDialogViewModel.value = 276 ImageEffectDialogViewModel( 277 onDismiss = { _imageEffectConfirmExitDialogViewModel.value = null }, 278 onContinue = { 279 // Continue to exit the screen. We should stop downloading. 280 effect?.let { interactor.interruptEffectsModelDownload(it) } 281 }, 282 ) 283 true 284 } 285 isDownloading -> { -> interactor.cancelDownloadWallpaper() } 286 else -> null 287 } 288 } 289 290 private val creativeEffectFloatingSheetViewModel: Flow<CreativeEffectFloatingSheetViewModel?> = 291 interactor.creativeEffectsModel.map { creativeEffectsModel -> 292 creativeEffectsModel?.let { 293 CreativeEffectFloatingSheetViewModel( 294 title = it.title, 295 subtitle = it.subtitle, 296 wallpaperActions = it.actions, 297 wallpaperEffectSwitchListener = { interactor.turnOnCreativeEffect(it) }, 298 ) 299 } 300 } 301 302 private fun getImageEffectFloatingSheetViewModel( 303 effect: Effect, 304 imageEffectsModel: ImageEffectsModel, 305 ): ImageEffectFloatingSheetViewModel { 306 val floatingSheetViewStatus = 307 when (imageEffectsModel.status) { 308 EFFECT_DISABLE -> { 309 FAILED 310 } 311 EFFECT_READY -> { 312 IDLE 313 } 314 EFFECT_DOWNLOAD_READY -> { 315 SHOW_DOWNLOAD_BUTTON 316 } 317 EFFECT_DOWNLOAD_IN_PROGRESS -> { 318 DOWNLOADING 319 } 320 EFFECT_APPLY_IN_PROGRESS -> { 321 PROCESSING 322 } 323 EFFECT_APPLIED -> { 324 SUCCESS 325 } 326 EFFECT_DOWNLOAD_FAILED -> { 327 SHOW_DOWNLOAD_BUTTON 328 } 329 EFFECT_APPLY_FAILED -> { 330 FAILED 331 } 332 } 333 return ImageEffectFloatingSheetViewModel( 334 myPhotosClickListener = {}, 335 collapseFloatingSheetListener = {}, 336 object : EffectSwitchListener { 337 override fun onEffectSwitchChanged( 338 effect: EffectEnumInterface, 339 isChecked: Boolean 340 ) { 341 if (interactor.isTargetEffect(effect)) { 342 if (isChecked) { 343 interactor.enableImageEffect(effect) 344 } else { 345 interactor.disableImageEffect() 346 } 347 } 348 } 349 }, 350 object : EffectDownloadClickListener { 351 override fun onEffectDownloadClick() { 352 if (isWifiOnAndConnected()) { 353 interactor.startEffectsModelDownload(effect) 354 } else { 355 _imageEffectConfirmDownloadDialogViewModel.value = 356 ImageEffectDialogViewModel( 357 onDismiss = { 358 _imageEffectConfirmDownloadDialogViewModel.value = null 359 }, 360 onContinue = { 361 // Continue to download the ML model 362 interactor.startEffectsModelDownload(effect) 363 }, 364 ) 365 } 366 } 367 }, 368 floatingSheetViewStatus, 369 imageEffectsModel.resultCode, 370 imageEffectsModel.errorMessage, 371 effect.title, 372 effect.type, 373 interactor.getEffectTextRes(), 374 ) 375 } 376 377 private fun isWifiOnAndConnected(): Boolean { 378 val wifiMgr = context.getSystemService(Context.WIFI_SERVICE) as WifiManager 379 return if (wifiMgr.isWifiEnabled) { // Wi-Fi adapter is ON 380 val connMgr = 381 context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 382 val wifiInfo = wifiMgr.connectionInfo 383 val wifi = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI) 384 val signalLevel = wifiMgr.calculateSignalLevel(wifiInfo.rssi) 385 signalLevel > 0 && wifi!!.isConnectedOrConnecting 386 } else { 387 false 388 } 389 } 390 391 val isEffectsVisible: Flow<Boolean> = 392 combine(imageEffectFloatingSheetViewModel, creativeEffectFloatingSheetViewModel) { 393 imageEffect, 394 creativeEffect -> 395 imageEffect != null || creativeEffect != null 396 } 397 398 private val _isEffectsChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 399 val isEffectsChecked: Flow<Boolean> = _isEffectsChecked.asStateFlow() 400 401 val onEffectsClicked: Flow<(() -> Unit)?> = 402 combine(isEffectsVisible, isEffectsChecked) { show, isChecked -> 403 if (show) { 404 { 405 if (!isChecked) { 406 uncheckAllOthersExcept(EFFECTS) 407 } 408 _isEffectsChecked.value = !isChecked 409 } 410 } else { 411 null 412 } 413 } 414 415 val effectDownloadFailureToastText: Flow<String> = 416 interactor.imageEffectsModel 417 .map { if (it.status == EFFECT_DOWNLOAD_FAILED) it.errorMessage else null } 418 .filterNotNull() 419 420 /** [SHARE] */ 421 val shareIntent: Flow<Intent?> = 422 interactor.wallpaperModel.map { model -> 423 (model as? LiveWallpaperModel)?.creativeWallpaperData?.let { data -> 424 if (data.shareUri == null || data.shareUri == Uri.EMPTY) null 425 else data.getShareIntent() 426 } 427 } 428 val isShareVisible: Flow<Boolean> = shareIntent.map { it != null } 429 430 // Floating sheet contents for the bottom sheet dialog. If content is null, the bottom sheet 431 // should collapse, otherwise, expended. 432 val previewFloatingSheetViewModel: Flow<PreviewFloatingSheetViewModel?> = 433 combine7( 434 isInformationChecked, 435 isEffectsChecked, 436 isCustomizeChecked, 437 informationFloatingSheetViewModel, 438 imageEffectFloatingSheetViewModel, 439 creativeEffectFloatingSheetViewModel, 440 customizeFloatingSheetViewModel, 441 ) { 442 isInformationChecked, 443 isEffectsChecked, 444 isCustomizeChecked, 445 informationFloatingSheetViewModel, 446 imageEffectFloatingSheetViewModel, 447 creativeEffectFloatingSheetViewModel, 448 customizeFloatingSheetViewModel -> 449 if (isInformationChecked && informationFloatingSheetViewModel != null) { 450 PreviewFloatingSheetViewModel( 451 informationFloatingSheetViewModel = informationFloatingSheetViewModel 452 ) 453 } else if (isEffectsChecked && imageEffectFloatingSheetViewModel != null) { 454 PreviewFloatingSheetViewModel( 455 imageEffectFloatingSheetViewModel = imageEffectFloatingSheetViewModel 456 ) 457 } else if (isEffectsChecked && creativeEffectFloatingSheetViewModel != null) { 458 PreviewFloatingSheetViewModel( 459 creativeEffectFloatingSheetViewModel = creativeEffectFloatingSheetViewModel 460 ) 461 } else if (isCustomizeChecked && customizeFloatingSheetViewModel != null) { 462 PreviewFloatingSheetViewModel( 463 customizeFloatingSheetViewModel = customizeFloatingSheetViewModel 464 ) 465 } else { 466 null 467 } 468 } 469 470 fun onFloatingSheetCollapsed() { 471 // When floating collapsed, we should look for those actions that expand the floating sheet 472 // and see which is checked, and uncheck it. 473 if (_isInformationChecked.value) { 474 _isInformationChecked.value = false 475 } 476 477 if (_isEffectsChecked.value) { 478 _isEffectsChecked.value = false 479 } 480 481 if (_isCustomizeChecked.value) { 482 _isCustomizeChecked.value = false 483 } 484 } 485 486 private fun uncheckAllOthersExcept(action: Action) { 487 if (action != INFORMATION) { 488 _isInformationChecked.value = false 489 } 490 if (action != DELETE) { 491 _isDeleteChecked.value = false 492 } 493 if (action != EDIT) { 494 _isEditChecked.value = false 495 } 496 if (action != CUSTOMIZE) { 497 _isCustomizeChecked.value = false 498 } 499 if (action != EFFECTS) { 500 _isEffectsChecked.value = false 501 } 502 } 503 504 companion object { 505 const val EXTRA_KEY_IS_CREATE_NEW = "is_create_new" 506 507 private fun WallpaperModel.shouldShowInformationFloatingSheet(): Boolean { 508 if ( 509 this is LiveWallpaperModel && 510 !liveWallpaperData.systemWallpaperInfo.showMetadataInPreview 511 ) { 512 // If the live wallpaper's flag of showMetadataInPreview is false, do not show the 513 // information floating sheet. 514 return false 515 } 516 val attributions = commonWallpaperData.attributions 517 // Show information floating sheet when any of the following contents exists 518 // 1. Attributions: Any of the list values is not null nor empty 519 // 2. Explore action URL 520 return (!attributions.isNullOrEmpty() && attributions.any { !it.isNullOrEmpty() }) || 521 !commonWallpaperData.exploreActionUrl.isNullOrEmpty() 522 } 523 524 private fun CreativeWallpaperData.getShareIntent(): Intent { 525 val shareIntent = Intent(Intent.ACTION_SEND) 526 shareIntent.putExtra(Intent.EXTRA_STREAM, shareUri) 527 shareIntent.setType("image/*") 528 shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 529 shareIntent.clipData = ClipData.newRawUri(null, shareUri) 530 return Intent.createChooser(shareIntent, null) 531 } 532 533 private fun LiveWallpaperModel.canBeDeleted(): Boolean { 534 return if (creativeWallpaperData != null) { 535 !liveWallpaperData.isApplied && 536 !creativeWallpaperData.isCurrent && 537 creativeWallpaperData.deleteUri.toString().isNotEmpty() 538 } else { 539 !liveWallpaperData.isApplied 540 } 541 } 542 543 /** 544 * @param isCreateNew: True means creating a new creative wallpaper. False means editing an 545 * existing wallpaper. 546 */ 547 fun LiveWallpaperData.getEditActivityIntent(isCreateNew: Boolean): Intent? { 548 val settingsActivity = systemWallpaperInfo.settingsActivity 549 if (settingsActivity.isNullOrEmpty()) { 550 return null 551 } 552 val intent = 553 Intent().apply { 554 component = ComponentName(systemWallpaperInfo.packageName, settingsActivity) 555 putExtra(WallpaperSettingsActivity.EXTRA_PREVIEW_MODE, true) 556 putExtra(EXTRA_KEY_IS_CREATE_NEW, isCreateNew) 557 } 558 return intent 559 } 560 561 fun LiveWallpaperModel.isNewCreativeWallpaper(): Boolean { 562 return creativeWallpaperData?.deleteUri?.toString()?.isEmpty() == true 563 } 564 565 /** The original combine function can only take up to 5 flows. */ 566 inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine7( 567 flow: Flow<T1>, 568 flow2: Flow<T2>, 569 flow3: Flow<T3>, 570 flow4: Flow<T4>, 571 flow5: Flow<T5>, 572 flow6: Flow<T6>, 573 flow7: Flow<T7>, 574 crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R 575 ): Flow<R> { 576 return combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> -> 577 @Suppress("UNCHECKED_CAST") 578 transform( 579 args[0] as T1, 580 args[1] as T2, 581 args[2] as T3, 582 args[3] as T4, 583 args[4] as T5, 584 args[5] as T6, 585 args[6] as T7, 586 ) 587 } 588 } 589 } 590 } 591 592 enum class Action { 593 INFORMATION, 594 DOWNLOAD, 595 DELETE, 596 EDIT, 597 CUSTOMIZE, 598 EFFECTS, 599 SHARE, 600 } 601