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.view.View 20 import android.view.View.OVER_SCROLL_NEVER 21 import androidx.lifecycle.Lifecycle 22 import androidx.lifecycle.LifecycleOwner 23 import androidx.lifecycle.lifecycleScope 24 import androidx.lifecycle.repeatOnLifecycle 25 import androidx.transition.Transition 26 import androidx.transition.TransitionListenerAdapter 27 import androidx.viewpager.widget.ViewPager 28 import com.android.wallpaper.R 29 import com.android.wallpaper.model.wallpaper.DeviceDisplayType 30 import com.android.wallpaper.picker.preview.ui.view.DualDisplayAspectRatioLayout 31 import com.android.wallpaper.picker.preview.ui.view.DualDisplayAspectRatioLayout.Companion.getViewId 32 import com.android.wallpaper.picker.preview.ui.view.DualPreviewViewPager 33 import com.android.wallpaper.picker.preview.ui.view.adapters.DualPreviewPagerAdapter 34 import com.android.wallpaper.picker.preview.ui.viewmodel.FullPreviewConfigViewModel 35 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel 36 import com.android.wallpaper.util.RtlUtils 37 import kotlinx.coroutines.DisposableHandle 38 import kotlinx.coroutines.launch 39 40 /** Binds dual preview home screen and lock screen view pager. */ 41 object DualPreviewPagerBinder { 42 43 fun bind( 44 dualPreviewView: DualPreviewViewPager, 45 wallpaperPreviewViewModel: WallpaperPreviewViewModel, 46 applicationContext: Context, 47 viewLifecycleOwner: LifecycleOwner, 48 currentNavDestId: Int, 49 transition: Transition?, 50 transitionConfig: FullPreviewConfigViewModel?, 51 isFirstBinding: Boolean, 52 navigate: (View) -> Unit, 53 ) { 54 // ViewPager & PagerAdapter do not support RTL. Enable RTL compatibility by converting all 55 // indices to LTR using this function. 56 val convertToLTR = { position: Int -> 57 if (RtlUtils.isRtl(dualPreviewView.context)) { 58 wallpaperPreviewViewModel.smallPreviewTabs.size - position - 1 59 } else position 60 } 61 62 var transitionDisposableHandle: DisposableHandle? = null 63 viewLifecycleOwner.lifecycleScope.launch { 64 viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { 65 if (transitionConfig != null && transition != null) { 66 val listener = 67 object : TransitionListenerAdapter() { 68 override fun onTransitionStart(transition: Transition) { 69 super.onTransitionStart(transition) 70 // Full to small preview return transition is handled by small 71 // preview. Temporarily remove clip to padding to enable the scaled 72 // shared element to display fully. 73 dualPreviewView.clipToPadding = false 74 } 75 76 override fun onTransitionEnd(transition: Transition) { 77 super.onTransitionEnd(transition) 78 dualPreviewView.clipToPadding = true 79 transition.removeListener(this) 80 transitionDisposableHandle = null 81 } 82 } 83 transition.addListener(listener) 84 transitionDisposableHandle = DisposableHandle { 85 transition.removeListener(listener) 86 } 87 } 88 } 89 // Remove transition listeners on destroy 90 transitionDisposableHandle?.dispose() 91 transitionDisposableHandle = null 92 } 93 94 // implement adapter for the dual preview pager 95 dualPreviewView.adapter = DualPreviewPagerAdapter { view, position -> 96 val positionLTR = convertToLTR(position) 97 // Set tag to allow small to full preview transition to accurately identify view 98 view.tag = positionLTR 99 100 PreviewTooltipBinder.bindSmallPreviewTooltip( 101 tooltipStub = view.requireViewById(R.id.tooltip_stub), 102 viewModel = wallpaperPreviewViewModel.smallTooltipViewModel, 103 lifecycleOwner = viewLifecycleOwner, 104 ) 105 106 val dualDisplayAspectRatioLayout: DualDisplayAspectRatioLayout = 107 view.requireViewById(R.id.dual_preview) 108 109 val displaySizes = 110 mapOf( 111 DeviceDisplayType.FOLDED to wallpaperPreviewViewModel.smallerDisplaySize, 112 DeviceDisplayType.UNFOLDED to 113 wallpaperPreviewViewModel.wallpaperDisplaySize.value, 114 ) 115 dualDisplayAspectRatioLayout.setDisplaySizes(displaySizes) 116 dualPreviewView.setDisplaySizes(displaySizes) 117 118 DeviceDisplayType.FOLDABLE_DISPLAY_TYPES.forEach { display -> 119 val previewDisplaySize = dualDisplayAspectRatioLayout.getPreviewDisplaySize(display) 120 previewDisplaySize?.let { 121 SmallPreviewBinder.bind( 122 applicationContext = applicationContext, 123 view = dualDisplayAspectRatioLayout.requireViewById(display.getViewId()), 124 viewModel = wallpaperPreviewViewModel, 125 viewLifecycleOwner = viewLifecycleOwner, 126 screen = wallpaperPreviewViewModel.smallPreviewTabs[positionLTR], 127 displaySize = it, 128 deviceDisplayType = display, 129 currentNavDestId = currentNavDestId, 130 transition = transition, 131 transitionConfig = transitionConfig, 132 isFirstBinding = isFirstBinding, 133 navigate = navigate, 134 ) 135 } 136 } 137 138 dualPreviewView.overScrollMode = OVER_SCROLL_NEVER 139 } 140 141 val onPageChangeListenerPreviews = 142 object : ViewPager.OnPageChangeListener { 143 override fun onPageSelected(position: Int) { 144 val positionLTR = convertToLTR(position) 145 wallpaperPreviewViewModel.setSmallPreviewSelectedTabIndex(positionLTR) 146 } 147 148 override fun onPageScrolled( 149 position: Int, 150 positionOffset: Float, 151 positionOffsetPixels: Int 152 ) {} 153 154 override fun onPageScrollStateChanged(state: Int) {} 155 } 156 dualPreviewView.addOnPageChangeListener(onPageChangeListenerPreviews) 157 158 viewLifecycleOwner.lifecycleScope.launch { 159 viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 160 wallpaperPreviewViewModel.smallPreviewSelectedTabIndex.collect { position -> 161 val positionLTR = convertToLTR(position) 162 if (dualPreviewView.currentItem != positionLTR) { 163 dualPreviewView.setCurrentItem(positionLTR, /* smoothScroll= */ true) 164 } 165 } 166 } 167 } 168 } 169 } 170