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.content.Context 19 import android.graphics.Point 20 import android.view.SurfaceView 21 import android.view.View 22 import androidx.cardview.widget.CardView 23 import androidx.core.view.ViewCompat 24 import androidx.core.view.isVisible 25 import androidx.lifecycle.Lifecycle 26 import androidx.lifecycle.LifecycleOwner 27 import androidx.lifecycle.lifecycleScope 28 import androidx.lifecycle.repeatOnLifecycle 29 import androidx.transition.Transition 30 import androidx.transition.TransitionListenerAdapter 31 import com.android.wallpaper.R 32 import com.android.wallpaper.config.BaseFlags 33 import com.android.wallpaper.model.Screen 34 import com.android.wallpaper.model.wallpaper.DeviceDisplayType 35 import com.android.wallpaper.picker.preview.ui.fragment.SmallPreviewFragment 36 import com.android.wallpaper.picker.preview.ui.viewmodel.FullPreviewConfigViewModel 37 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel 38 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel.Companion.PreviewScreen 39 import com.android.wallpaper.util.wallpaperconnection.WallpaperConnectionUtils 40 import kotlinx.coroutines.CompletableDeferred 41 import kotlinx.coroutines.CoroutineScope 42 import kotlinx.coroutines.DisposableHandle 43 import kotlinx.coroutines.flow.combine 44 import kotlinx.coroutines.launch 45 46 object SmallPreviewBinder { 47 48 fun bind( 49 applicationContext: Context, 50 view: View, 51 viewModel: WallpaperPreviewViewModel, 52 screen: Screen, 53 displaySize: Point, 54 deviceDisplayType: DeviceDisplayType, 55 mainScope: CoroutineScope, 56 viewLifecycleOwner: LifecycleOwner, 57 currentNavDestId: Int, 58 navigate: ((View) -> Unit)? = null, 59 transition: Transition? = null, 60 transitionConfig: FullPreviewConfigViewModel? = null, 61 wallpaperConnectionUtils: WallpaperConnectionUtils, 62 isFirstBindingDeferred: CompletableDeferred<Boolean>, 63 ) { 64 65 val previewCard: CardView = view.requireViewById(R.id.preview_card) 66 val foldedStateDescription = 67 when (deviceDisplayType) { 68 DeviceDisplayType.FOLDED -> 69 view.context.getString(R.string.folded_device_state_description) 70 DeviceDisplayType.UNFOLDED -> 71 view.context.getString(R.string.unfolded_device_state_description) 72 else -> "" 73 } 74 previewCard.contentDescription = 75 view.context.getString( 76 R.string.wallpaper_preview_card_content_description_editable, 77 foldedStateDescription, 78 ) 79 val wallpaperSurface = view.requireViewById<SurfaceView>(R.id.wallpaper_surface) 80 81 // Don't set radius for set wallpaper dialog 82 if (!viewModel.showSetWallpaperDialog.value) { 83 // When putting the surface on top for full transition, the card view is behind the 84 // surface view so we need to apply radius on surface view instead 85 wallpaperSurface.cornerRadius = previewCard.radius 86 } 87 val workspaceSurface: SurfaceView = view.requireViewById(R.id.workspace_surface) 88 89 // Set transition names to enable the small to full preview enter and return shared 90 // element transitions. 91 val transitionName = 92 when (screen) { 93 Screen.LOCK_SCREEN -> 94 when (deviceDisplayType) { 95 DeviceDisplayType.SINGLE -> 96 SmallPreviewFragment.SMALL_PREVIEW_LOCK_SHARED_ELEMENT_ID 97 DeviceDisplayType.FOLDED -> 98 SmallPreviewFragment.SMALL_PREVIEW_LOCK_FOLDED_SHARED_ELEMENT_ID 99 DeviceDisplayType.UNFOLDED -> 100 SmallPreviewFragment.SMALL_PREVIEW_LOCK_UNFOLDED_SHARED_ELEMENT_ID 101 } 102 Screen.HOME_SCREEN -> 103 when (deviceDisplayType) { 104 DeviceDisplayType.SINGLE -> 105 SmallPreviewFragment.SMALL_PREVIEW_HOME_SHARED_ELEMENT_ID 106 DeviceDisplayType.FOLDED -> 107 SmallPreviewFragment.SMALL_PREVIEW_HOME_FOLDED_SHARED_ELEMENT_ID 108 DeviceDisplayType.UNFOLDED -> 109 SmallPreviewFragment.SMALL_PREVIEW_HOME_UNFOLDED_SHARED_ELEMENT_ID 110 } 111 } 112 ViewCompat.setTransitionName(previewCard, transitionName) 113 114 var transitionDisposableHandle: DisposableHandle? = null 115 val transitionListener = 116 if (transition == null || transitionConfig == null) null 117 else 118 object : TransitionListenerAdapter() { 119 // All surface views are initially visible in the XML to enable smoother 120 // transitions. Only hide the surface views not used in the shared element 121 // transition until the transition ends to avoid issues with multiple surface 122 // views 123 // overlapping. 124 override fun onTransitionStart(transition: Transition) { 125 super.onTransitionStart(transition) 126 if ( 127 transitionConfig.screen == screen && 128 transitionConfig.deviceDisplayType == deviceDisplayType 129 ) { 130 wallpaperSurface.setZOrderOnTop(true) 131 workspaceSurface.setZOrderOnTop(true) 132 } else { 133 // If transitioning to another small preview, keep child surfaces hidden 134 // until transition ends. 135 wallpaperSurface.isVisible = false 136 workspaceSurface.isVisible = false 137 } 138 } 139 140 override fun onTransitionEnd(transition: Transition) { 141 super.onTransitionEnd(transition) 142 if ( 143 transitionConfig.screen == screen && 144 transitionConfig.deviceDisplayType == deviceDisplayType 145 ) { 146 wallpaperSurface.setZOrderMediaOverlay(true) 147 workspaceSurface.setZOrderMediaOverlay(true) 148 } else { 149 wallpaperSurface.isVisible = true 150 workspaceSurface.isVisible = true 151 wallpaperSurface.alpha = 0f 152 workspaceSurface.alpha = 0f 153 154 val mediumAnimTimeMs = 155 view.resources 156 .getInteger(android.R.integer.config_mediumAnimTime) 157 .toLong() 158 wallpaperSurface.startFadeInAnimation(mediumAnimTimeMs) 159 workspaceSurface.startFadeInAnimation(mediumAnimTimeMs) 160 } 161 162 transition.removeListener(this) 163 transitionDisposableHandle = null 164 } 165 } 166 167 viewLifecycleOwner.lifecycleScope.launch { 168 viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { 169 transitionListener?.let { 170 // If transitionListener is not null so do transition and transitionConfig 171 transition!!.addListener(it) 172 transitionDisposableHandle = DisposableHandle { transition.removeListener(it) } 173 } 174 175 if (R.id.smallPreviewFragment == currentNavDestId) { 176 combine( 177 viewModel.onSmallPreviewClicked(screen, deviceDisplayType) { 178 navigate?.invoke(previewCard) 179 }, 180 viewModel.currentPreviewScreen, 181 viewModel.smallPreviewSelectedTab, 182 ) { onClick, previewScreen, tab -> 183 Triple(onClick, previewScreen, tab) 184 } 185 .collect { (onClick, previewScreen, tab) -> 186 if ( 187 BaseFlags.get().isNewPickerUi() && 188 previewScreen != PreviewScreen.SMALL_PREVIEW 189 ) { 190 view.setOnClickListener(null) 191 } else { 192 onClick?.let { view.setOnClickListener { it() } } 193 ?: view.setOnClickListener(null) 194 } 195 } 196 } else if (R.id.setWallpaperDialog == currentNavDestId) { 197 previewCard.radius = 198 previewCard.resources.getDimension( 199 R.dimen.set_wallpaper_dialog_preview_corner_radius 200 ) 201 } 202 } 203 // Remove transition listeners on destroy 204 transitionDisposableHandle?.dispose() 205 transitionDisposableHandle = null 206 // Remove on click listener when on destroyed 207 view.setOnClickListener(null) 208 } 209 210 val config = viewModel.getWorkspacePreviewConfig(screen, deviceDisplayType) 211 WorkspacePreviewBinder.bind(workspaceSurface, config, viewModel, viewLifecycleOwner) 212 213 SmallWallpaperPreviewBinder.bind( 214 surface = wallpaperSurface, 215 viewModel = viewModel, 216 displaySize = displaySize, 217 applicationContext = applicationContext, 218 mainScope = mainScope, 219 viewLifecycleOwner = viewLifecycleOwner, 220 deviceDisplayType = deviceDisplayType, 221 wallpaperConnectionUtils = wallpaperConnectionUtils, 222 isFirstBindingDeferred = isFirstBindingDeferred, 223 ) 224 } 225 226 private fun SurfaceView.startFadeInAnimation(duration: Long) { 227 animate().alpha(1f).setDuration(duration).start() 228 } 229 } 230