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