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.model.Screen 33 import com.android.wallpaper.model.wallpaper.DeviceDisplayType 34 import com.android.wallpaper.picker.preview.ui.fragment.SmallPreviewFragment 35 import com.android.wallpaper.picker.preview.ui.viewmodel.FullPreviewConfigViewModel 36 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel 37 import kotlinx.coroutines.DisposableHandle 38 import kotlinx.coroutines.launch 39 40 object SmallPreviewBinder { 41 42 fun bind( 43 applicationContext: Context, 44 view: View, 45 viewModel: WallpaperPreviewViewModel, 46 screen: Screen, 47 displaySize: Point, 48 deviceDisplayType: DeviceDisplayType, 49 viewLifecycleOwner: LifecycleOwner, 50 currentNavDestId: Int, 51 navigate: ((View) -> Unit)? = null, 52 transition: Transition? = null, 53 transitionConfig: FullPreviewConfigViewModel? = null, 54 isFirstBinding: Boolean, 55 ) { 56 57 val previewCard: CardView = view.requireViewById(R.id.preview_card) 58 val foldedStateDescription = 59 when (deviceDisplayType) { 60 DeviceDisplayType.FOLDED -> 61 view.context.getString(R.string.folded_device_state_description) 62 DeviceDisplayType.UNFOLDED -> 63 view.context.getString(R.string.unfolded_device_state_description) 64 else -> "" 65 } 66 previewCard.contentDescription = 67 view.context.getString( 68 R.string.wallpaper_preview_card_content_description_editable, 69 foldedStateDescription 70 ) 71 val wallpaperSurface: SurfaceView = view.requireViewById(R.id.wallpaper_surface) 72 val workspaceSurface: SurfaceView = view.requireViewById(R.id.workspace_surface) 73 var transitionDisposableHandle: DisposableHandle? = null 74 75 // Set transition names to enable the small to full preview enter and return shared 76 // element transitions. 77 val transitionName = 78 when (screen) { 79 Screen.LOCK_SCREEN -> 80 when (deviceDisplayType) { 81 DeviceDisplayType.SINGLE -> 82 SmallPreviewFragment.SMALL_PREVIEW_LOCK_SHARED_ELEMENT_ID 83 DeviceDisplayType.FOLDED -> 84 SmallPreviewFragment.SMALL_PREVIEW_LOCK_FOLDED_SHARED_ELEMENT_ID 85 DeviceDisplayType.UNFOLDED -> 86 SmallPreviewFragment.SMALL_PREVIEW_LOCK_UNFOLDED_SHARED_ELEMENT_ID 87 } 88 Screen.HOME_SCREEN -> 89 when (deviceDisplayType) { 90 DeviceDisplayType.SINGLE -> 91 SmallPreviewFragment.SMALL_PREVIEW_HOME_SHARED_ELEMENT_ID 92 DeviceDisplayType.FOLDED -> 93 SmallPreviewFragment.SMALL_PREVIEW_HOME_FOLDED_SHARED_ELEMENT_ID 94 DeviceDisplayType.UNFOLDED -> 95 SmallPreviewFragment.SMALL_PREVIEW_HOME_UNFOLDED_SHARED_ELEMENT_ID 96 } 97 } 98 ViewCompat.setTransitionName(previewCard, transitionName) 99 100 viewLifecycleOwner.lifecycleScope.launch { 101 viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { 102 // All surface views are initially hidden in the XML to enable smoother 103 // transitions. Only show the surface view used in the shared element transition 104 // until the transition ends to avoid issues with multiple surface views 105 // overlapping. 106 if (transition == null || transitionConfig == null) { 107 // If no enter or re-enter transition, show child surfaces. 108 wallpaperSurface.isVisible = true 109 workspaceSurface.isVisible = true 110 } else { 111 if ( 112 transitionConfig.screen == screen && 113 transitionConfig.deviceDisplayType == deviceDisplayType 114 ) { 115 // If transitioning to the current small preview, show child surfaces when 116 // transition starts. 117 val listener = 118 object : TransitionListenerAdapter() { 119 override fun onTransitionStart(transition: Transition) { 120 super.onTransitionStart(transition) 121 wallpaperSurface.isVisible = true 122 workspaceSurface.isVisible = true 123 transition.removeListener(this) 124 transitionDisposableHandle = null 125 } 126 } 127 transition.addListener(listener) 128 transitionDisposableHandle = DisposableHandle { 129 transition.removeListener(listener) 130 } 131 } else { 132 // If transitioning to another small preview, keep child surfaces hidden 133 // until transition ends. 134 val listener = 135 object : TransitionListenerAdapter() { 136 override fun onTransitionEnd(transition: Transition) { 137 super.onTransitionEnd(transition) 138 wallpaperSurface.isVisible = true 139 workspaceSurface.isVisible = true 140 wallpaperSurface.alpha = 0f 141 workspaceSurface.alpha = 0f 142 143 val mediumAnimTimeMs = 144 view.resources 145 .getInteger(android.R.integer.config_mediumAnimTime) 146 .toLong() 147 wallpaperSurface.startFadeInAnimation(mediumAnimTimeMs) 148 workspaceSurface.startFadeInAnimation(mediumAnimTimeMs) 149 150 transition.removeListener(this) 151 transitionDisposableHandle = null 152 } 153 } 154 transition.addListener(listener) 155 transitionDisposableHandle = DisposableHandle { 156 transition.removeListener(listener) 157 } 158 } 159 } 160 161 if (R.id.smallPreviewFragment == currentNavDestId) { 162 viewModel 163 .onSmallPreviewClicked(screen, deviceDisplayType) { 164 navigate?.invoke(previewCard) 165 } 166 .collect { onClick -> 167 if (onClick != null) { 168 view.setOnClickListener { onClick() } 169 } else { 170 view.setOnClickListener(null) 171 } 172 } 173 } else if (R.id.setWallpaperDialog == currentNavDestId) { 174 previewCard.radius = 175 previewCard.resources.getDimension( 176 R.dimen.set_wallpaper_dialog_preview_corner_radius 177 ) 178 } 179 } 180 // Remove transition listeners on destroy 181 transitionDisposableHandle?.dispose() 182 transitionDisposableHandle = null 183 // Remove on click listener when on destroyed 184 view.setOnClickListener(null) 185 } 186 187 val config = viewModel.getWorkspacePreviewConfig(screen, deviceDisplayType) 188 WorkspacePreviewBinder.bind( 189 workspaceSurface, 190 config, 191 viewModel, 192 viewLifecycleOwner, 193 ) 194 195 SmallWallpaperPreviewBinder.bind( 196 surface = wallpaperSurface, 197 viewModel = viewModel, 198 displaySize = displaySize, 199 applicationContext = applicationContext, 200 viewLifecycleOwner = viewLifecycleOwner, 201 deviceDisplayType = deviceDisplayType, 202 isFirstBinding = isFirstBinding, 203 ) 204 } 205 206 private fun SurfaceView.startFadeInAnimation(duration: Long) { 207 animate().alpha(1f).setDuration(duration).start() 208 } 209 } 210