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