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.annotation.SuppressLint 19 import android.content.Context 20 import android.graphics.Point 21 import android.view.View 22 import androidx.core.view.doOnLayout 23 import androidx.core.view.doOnPreDraw 24 import androidx.lifecycle.Lifecycle 25 import androidx.lifecycle.LifecycleOwner 26 import androidx.lifecycle.lifecycleScope 27 import androidx.lifecycle.repeatOnLifecycle 28 import androidx.recyclerview.widget.RecyclerView 29 import androidx.transition.Transition 30 import androidx.viewpager2.widget.ViewPager2 31 import com.android.wallpaper.R 32 import com.android.wallpaper.config.BaseFlags 33 import com.android.wallpaper.model.wallpaper.DeviceDisplayType 34 import com.android.wallpaper.picker.customization.ui.view.transformer.PreviewPagerPageTransformer 35 import com.android.wallpaper.picker.preview.ui.view.adapters.SinglePreviewPagerAdapter 36 import com.android.wallpaper.picker.preview.ui.view.pagetransformers.PreviewCardPageTransformer 37 import com.android.wallpaper.picker.preview.ui.viewmodel.FullPreviewConfigViewModel 38 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel 39 import com.android.wallpaper.util.wallpaperconnection.WallpaperConnectionUtils 40 import kotlinx.coroutines.CompletableDeferred 41 import kotlinx.coroutines.CoroutineScope 42 import kotlinx.coroutines.launch 43 44 /** Binds single preview home screen and lock screen tabs view pager. */ 45 object PreviewPagerBinder { 46 47 @SuppressLint("WrongConstant") 48 fun bind( 49 applicationContext: Context, 50 mainScope: CoroutineScope, 51 viewLifecycleOwner: LifecycleOwner, 52 previewsViewPager: ViewPager2, 53 wallpaperPreviewViewModel: WallpaperPreviewViewModel, 54 previewDisplaySize: Point, 55 transition: Transition?, 56 transitionConfig: FullPreviewConfigViewModel?, 57 wallpaperConnectionUtils: WallpaperConnectionUtils, 58 isFirstBindingDeferred: CompletableDeferred<Boolean>, 59 navigate: (View) -> Unit, 60 ) { 61 previewsViewPager.apply { 62 adapter = SinglePreviewPagerAdapter { viewHolder, position -> 63 PreviewTooltipBinder.bindSmallPreviewTooltip( 64 tooltipStub = 65 viewHolder.itemView.requireViewById(R.id.small_preview_tooltip_stub), 66 viewModel = wallpaperPreviewViewModel.smallTooltipViewModel, 67 lifecycleOwner = viewLifecycleOwner, 68 ) 69 70 SmallPreviewBinder.bind( 71 applicationContext = applicationContext, 72 view = viewHolder.itemView.requireViewById(R.id.preview), 73 viewModel = wallpaperPreviewViewModel, 74 screen = wallpaperPreviewViewModel.smallPreviewTabs[position], 75 displaySize = previewDisplaySize, 76 deviceDisplayType = DeviceDisplayType.SINGLE, 77 mainScope = mainScope, 78 viewLifecycleOwner = viewLifecycleOwner, 79 currentNavDestId = R.id.smallPreviewFragment, 80 transition = transition, 81 transitionConfig = transitionConfig, 82 isFirstBindingDeferred = isFirstBindingDeferred, 83 wallpaperConnectionUtils = wallpaperConnectionUtils, 84 navigate = navigate, 85 ) 86 } 87 offscreenPageLimit = SinglePreviewPagerAdapter.PREVIEW_PAGER_ITEM_COUNT 88 // the over scroll animation needs to be disabled for the RecyclerView that is contained 89 // in the ViewPager2 rather than the ViewPager2 itself 90 val child: View = getChildAt(0) 91 if (child is RecyclerView) { 92 child.overScrollMode = View.OVER_SCROLL_NEVER 93 // Remove clip children to enable child card view to display fully during scaling 94 // shared element transition. 95 child.clipChildren = false 96 } 97 98 // When pager's height changes, request transform to recalculate the preview offset 99 // to make sure correct space between the previews. 100 // TODO (b/348462236): figure out how to scale surface view content with layout change 101 addOnLayoutChangeListener { view, _, _, _, _, _, topWas, _, bottomWas -> 102 val isHeightChanged = (bottomWas - topWas) != view.height 103 if (isHeightChanged) { 104 requestTransform() 105 } 106 } 107 } 108 109 // Only when pager is laid out, we can get the width and set the preview's offset correctly 110 previewsViewPager.doOnLayout { 111 val pageTransformer = 112 if (BaseFlags.get().isNewPickerUi()) PreviewPagerPageTransformer(previewDisplaySize) 113 else PreviewCardPageTransformer(previewDisplaySize) 114 (it as ViewPager2).setPageTransformer(pageTransformer) 115 } 116 117 // Wrap in doOnPreDraw for emoji wallpaper creation case, to make sure recycler view with 118 // previews have finished layout before calling registerOnPageChangeCallback and 119 // setCurrentItem. 120 // TODO (b/339679893): investigate to see if there is a better solution 121 previewsViewPager.doOnPreDraw { 122 previewsViewPager.registerOnPageChangeCallback( 123 object : ViewPager2.OnPageChangeCallback() { 124 override fun onPageSelected(position: Int) { 125 super.onPageSelected(position) 126 wallpaperPreviewViewModel.setSmallPreviewSelectedTabIndex(position) 127 } 128 } 129 ) 130 131 viewLifecycleOwner.lifecycleScope.launch { 132 viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 133 wallpaperPreviewViewModel.smallPreviewSelectedTabIndex.collect { 134 if (previewsViewPager.currentItem != it) { 135 previewsViewPager.setCurrentItem(it, /* smoothScroll= */ true) 136 } 137 } 138 } 139 } 140 } 141 } 142 } 143