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.fragment 17 18 import android.content.Context 19 import android.content.Intent 20 import android.os.Bundle 21 import android.view.LayoutInflater 22 import android.view.View 23 import android.view.ViewGroup 24 import androidx.activity.result.ActivityResultLauncher 25 import androidx.activity.result.contract.ActivityResultContract 26 import androidx.core.content.ContextCompat 27 import androidx.core.view.doOnPreDraw 28 import androidx.fragment.app.activityViewModels 29 import androidx.lifecycle.Lifecycle 30 import androidx.lifecycle.lifecycleScope 31 import androidx.lifecycle.repeatOnLifecycle 32 import androidx.navigation.fragment.FragmentNavigatorExtras 33 import androidx.navigation.fragment.findNavController 34 import androidx.transition.Transition 35 import com.android.wallpaper.R 36 import com.android.wallpaper.R.id.preview_tabs_container 37 import com.android.wallpaper.module.logging.UserEventLogger 38 import com.android.wallpaper.picker.AppbarFragment 39 import com.android.wallpaper.picker.preview.ui.binder.DualPreviewSelectorBinder 40 import com.android.wallpaper.picker.preview.ui.binder.PreviewActionsBinder 41 import com.android.wallpaper.picker.preview.ui.binder.PreviewSelectorBinder 42 import com.android.wallpaper.picker.preview.ui.binder.SetWallpaperButtonBinder 43 import com.android.wallpaper.picker.preview.ui.binder.SetWallpaperProgressDialogBinder 44 import com.android.wallpaper.picker.preview.ui.util.AnimationUtil 45 import com.android.wallpaper.picker.preview.ui.util.ImageEffectDialogUtil 46 import com.android.wallpaper.picker.preview.ui.view.DualPreviewViewPager 47 import com.android.wallpaper.picker.preview.ui.view.PreviewActionGroup 48 import com.android.wallpaper.picker.preview.ui.view.PreviewTabs 49 import com.android.wallpaper.picker.preview.ui.viewmodel.Action 50 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel 51 import com.android.wallpaper.util.DisplayUtils 52 import dagger.hilt.android.AndroidEntryPoint 53 import dagger.hilt.android.qualifiers.ApplicationContext 54 import javax.inject.Inject 55 import kotlinx.coroutines.launch 56 57 /** 58 * This fragment displays the preview of the selected wallpaper on all available workspaces and 59 * device displays. 60 */ 61 @AndroidEntryPoint(AppbarFragment::class) 62 class SmallPreviewFragment : Hilt_SmallPreviewFragment() { 63 64 @Inject @ApplicationContext lateinit var appContext: Context 65 @Inject lateinit var displayUtils: DisplayUtils 66 @Inject lateinit var logger: UserEventLogger 67 @Inject lateinit var imageEffectDialogUtil: ImageEffectDialogUtil 68 69 private lateinit var currentView: View 70 private lateinit var shareActivityResult: ActivityResultLauncher<Intent> 71 72 private val wallpaperPreviewViewModel by activityViewModels<WallpaperPreviewViewModel>() 73 74 /** 75 * True if the view of this fragment is destroyed from the current or previous lifecycle. 76 * 77 * Null if it's the first life cycle, and false if the view has not been destroyed. 78 * 79 * Read-only during the first half of the lifecycle (when starting a fragment). 80 */ 81 private var isViewDestroyed: Boolean? = null 82 83 override fun onCreate(savedInstanceState: Bundle?) { 84 super.onCreate(savedInstanceState) 85 exitTransition = AnimationUtil.getFastFadeOutTransition() 86 reenterTransition = AnimationUtil.getFastFadeInTransition() 87 } 88 89 override fun onCreateView( 90 inflater: LayoutInflater, 91 container: ViewGroup?, 92 savedInstanceState: Bundle?, 93 ): View { 94 postponeEnterTransition() 95 currentView = 96 inflater.inflate( 97 if (displayUtils.hasMultiInternalDisplays()) 98 R.layout.fragment_small_preview_foldable 99 else R.layout.fragment_small_preview_handheld, 100 container, 101 false, 102 ) 103 setUpToolbar(currentView, /* upArrow= */ true, /* transparentToolbar= */ true) 104 bindPreviewActions(currentView) 105 106 SetWallpaperButtonBinder.bind( 107 button = currentView.requireViewById(R.id.button_set_wallpaper), 108 viewModel = wallpaperPreviewViewModel, 109 lifecycleOwner = viewLifecycleOwner, 110 ) { 111 findNavController().navigate(R.id.setWallpaperDialog) 112 } 113 114 SetWallpaperProgressDialogBinder.bind( 115 viewModel = wallpaperPreviewViewModel, 116 activity = requireActivity(), 117 lifecycleOwner = viewLifecycleOwner, 118 ) 119 120 currentView.doOnPreDraw { 121 // FullPreviewConfigViewModel not being null indicates that we are navigated to small 122 // preview from the full preview, and therefore should play the shared element re-enter 123 // animation. Reset it after views are finished binding. 124 wallpaperPreviewViewModel.resetFullPreviewConfigViewModel() 125 startPostponedEnterTransition() 126 } 127 128 shareActivityResult = 129 registerForActivityResult( 130 object : ActivityResultContract<Intent, Int>() { 131 override fun createIntent(context: Context, input: Intent): Intent { 132 return input 133 } 134 135 override fun parseResult(resultCode: Int, intent: Intent?): Int { 136 return resultCode 137 } 138 }, 139 ) { 140 currentView 141 .findViewById<PreviewActionGroup>(R.id.action_button_group) 142 ?.setIsChecked(Action.SHARE, false) 143 } 144 145 return currentView 146 } 147 148 override fun onViewStateRestored(savedInstanceState: Bundle?) { 149 super.onViewStateRestored(savedInstanceState) 150 bindScreenPreview(currentView, isFirstBinding = savedInstanceState == null) 151 } 152 153 override fun onStart() { 154 super.onStart() 155 // Reinitialize the preview tab motion. If navigating up back to this fragment happened 156 // before the transition finished, the lifecycle begins at onStart without recreating the 157 // preview tabs, 158 isViewDestroyed?.let { 159 if (!it) { 160 currentView 161 .requireViewById<PreviewTabs>(preview_tabs_container) 162 .resetTransition(wallpaperPreviewViewModel.getSmallPreviewTabIndex()) 163 } 164 } 165 } 166 167 override fun onStop() { 168 super.onStop() 169 // onStop won't destroy view 170 isViewDestroyed = false 171 } 172 173 override fun onDestroyView() { 174 super.onDestroyView() 175 isViewDestroyed = true 176 } 177 178 override fun getDefaultTitle(): CharSequence { 179 return getString(R.string.preview) 180 } 181 182 override fun getToolbarTextColor(): Int { 183 return ContextCompat.getColor(requireContext(), R.color.system_on_surface) 184 } 185 186 private fun bindScreenPreview(view: View, isFirstBinding: Boolean) { 187 val currentNavDestId = checkNotNull(findNavController().currentDestination?.id) 188 val tabs = view.requireViewById<PreviewTabs>(preview_tabs_container) 189 if (displayUtils.hasMultiInternalDisplays()) { 190 val dualPreviewView: DualPreviewViewPager = 191 view.requireViewById(R.id.dual_preview_pager) 192 193 DualPreviewSelectorBinder.bind( 194 tabs, 195 dualPreviewView, 196 wallpaperPreviewViewModel, 197 appContext, 198 viewLifecycleOwner, 199 currentNavDestId, 200 (reenterTransition as Transition?), 201 wallpaperPreviewViewModel.fullPreviewConfigViewModel.value, 202 isFirstBinding, 203 ) { sharedElement -> 204 val extras = 205 FragmentNavigatorExtras(sharedElement to FULL_PREVIEW_SHARED_ELEMENT_ID) 206 // Set to false on small-to-full preview transition to remove surfaceView jank. 207 (view as ViewGroup).isTransitionGroup = false 208 findNavController() 209 .navigate( 210 resId = R.id.action_smallPreviewFragment_to_fullPreviewFragment, 211 args = null, 212 navOptions = null, 213 navigatorExtras = extras 214 ) 215 } 216 } else { 217 PreviewSelectorBinder.bind( 218 tabs, 219 view.requireViewById(R.id.pager_previews), 220 displayUtils.getRealSize(displayUtils.getWallpaperDisplay()), 221 wallpaperPreviewViewModel, 222 appContext, 223 viewLifecycleOwner, 224 currentNavDestId, 225 (reenterTransition as Transition?), 226 wallpaperPreviewViewModel.fullPreviewConfigViewModel.value, 227 isFirstBinding, 228 ) { sharedElement -> 229 val extras = 230 FragmentNavigatorExtras(sharedElement to FULL_PREVIEW_SHARED_ELEMENT_ID) 231 // Set to false on small-to-full preview transition to remove surfaceView jank. 232 (view as ViewGroup).isTransitionGroup = false 233 findNavController() 234 .navigate( 235 resId = R.id.action_smallPreviewFragment_to_fullPreviewFragment, 236 args = null, 237 navOptions = null, 238 navigatorExtras = extras 239 ) 240 } 241 } 242 243 viewLifecycleOwner.lifecycleScope.launch { 244 viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 245 // Always reset isTransitionGroup value on start for the edge case that the 246 // navigation is cancelled and the fragment resumes. 247 (view as ViewGroup).isTransitionGroup = true 248 } 249 } 250 } 251 252 private fun bindPreviewActions(view: View) { 253 PreviewActionsBinder.bind( 254 actionGroup = view.requireViewById(R.id.action_button_group), 255 floatingSheet = view.requireViewById(R.id.floating_sheet), 256 previewViewModel = wallpaperPreviewViewModel, 257 actionsViewModel = wallpaperPreviewViewModel.previewActionsViewModel, 258 deviceDisplayType = displayUtils.getCurrentDisplayType(requireActivity()), 259 activity = requireActivity(), 260 lifecycleOwner = viewLifecycleOwner, 261 logger = logger, 262 imageEffectDialogUtil = imageEffectDialogUtil, 263 onNavigateToEditScreen = { navigateToEditScreen(it) }, 264 onStartShareActivity = { shareActivityResult.launch(it) }, 265 ) 266 } 267 268 private fun navigateToEditScreen(intent: Intent) { 269 findNavController() 270 .navigate( 271 resId = R.id.action_smallPreviewFragment_to_creativeEditPreviewFragment, 272 args = Bundle().apply { putParcelable(ARG_EDIT_INTENT, intent) }, 273 navOptions = null, 274 navigatorExtras = null, 275 ) 276 } 277 278 companion object { 279 const val SMALL_PREVIEW_HOME_SHARED_ELEMENT_ID = "small_preview_home" 280 const val SMALL_PREVIEW_LOCK_SHARED_ELEMENT_ID = "small_preview_lock" 281 const val SMALL_PREVIEW_HOME_FOLDED_SHARED_ELEMENT_ID = "small_preview_home_folded" 282 const val SMALL_PREVIEW_HOME_UNFOLDED_SHARED_ELEMENT_ID = "small_preview_home_unfolded" 283 const val SMALL_PREVIEW_LOCK_FOLDED_SHARED_ELEMENT_ID = "small_preview_lock_folded" 284 const val SMALL_PREVIEW_LOCK_UNFOLDED_SHARED_ELEMENT_ID = "small_preview_lock_unfolded" 285 const val FULL_PREVIEW_SHARED_ELEMENT_ID = "full_preview" 286 const val ARG_EDIT_INTENT = "arg_edit_intent" 287 } 288 } 289