1 /* <lambda>null2 * Copyright (C) 2022 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 */ 17 18 package com.android.wallpaper.picker.customization.ui.binder 19 20 import android.content.Context 21 import android.view.View 22 import android.view.ViewGroup 23 import android.view.WindowInsets 24 import android.widget.FrameLayout 25 import androidx.annotation.IdRes 26 import androidx.core.view.children 27 import androidx.core.view.isInvisible 28 import androidx.core.view.updateLayoutParams 29 import androidx.core.widget.NestedScrollView 30 import androidx.lifecycle.Lifecycle 31 import androidx.lifecycle.LifecycleOwner 32 import androidx.lifecycle.lifecycleScope 33 import androidx.lifecycle.repeatOnLifecycle 34 import com.android.wallpaper.R 35 import com.android.wallpaper.model.CustomizationSectionController 36 import com.android.wallpaper.module.InjectorProvider 37 import com.android.wallpaper.picker.SectionView 38 import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationPickerViewModel 39 import com.android.wallpaper.picker.undo.ui.binder.RevertToolbarButtonBinder 40 import kotlinx.coroutines.DisposableHandle 41 import kotlinx.coroutines.launch 42 43 typealias SectionController = CustomizationSectionController<*> 44 45 /** Binds view to view-model for the customization picker. */ 46 object CustomizationPickerBinder { 47 48 /** 49 * Binds the given view and view-model, keeping the UI up-to-date and listening to user input. 50 * 51 * @param view The root of the UI to keep up-to-date and observe for user input. 52 * @param toolbarViewId The view ID of the toolbar view. 53 * @param viewModel The view-model to observe UI state from and report user input to. 54 * @param lifecycleOwner An owner of the lifecycle, so we can stop doing work when the lifecycle 55 * cleans up. 56 * @param sectionControllerProvider A function that can provide the list of [SectionController] 57 * instances to show, based on the given passed-in value of "isOnLockScreen". 58 * @return A [DisposableHandle] to use to dispose of the binding before another binding is about 59 * to be created by a subsequent call to this function. 60 */ 61 @JvmStatic 62 fun bind( 63 view: View, 64 @IdRes toolbarViewId: Int, 65 viewModel: CustomizationPickerViewModel, 66 lifecycleOwner: LifecycleOwner, 67 sectionControllerProvider: (isOnLockScreen: Boolean) -> List<SectionController>, 68 ): DisposableHandle { 69 RevertToolbarButtonBinder.bind( 70 view = view.requireViewById(toolbarViewId), 71 viewModel = viewModel.undo, 72 lifecycleOwner = lifecycleOwner, 73 ) 74 75 CustomizationPickerTabsBinder.bind( 76 view = view, 77 viewModel = viewModel, 78 lifecycleOwner = lifecycleOwner, 79 ) 80 81 val lockScrollContainer = view.requireViewById<NestedScrollView>(R.id.lock_scroll_container) 82 val homeScrollContainer = view.requireViewById<NestedScrollView>(R.id.home_scroll_container) 83 84 val lockSectionContainer = view.requireViewById<ViewGroup>(R.id.lock_section_container) 85 lockSectionContainer.setOnApplyWindowInsetsListener { v: View, windowInsets: WindowInsets -> 86 v.setPadding( 87 v.paddingLeft, 88 v.paddingTop, 89 v.paddingRight, 90 windowInsets.systemWindowInsetBottom 91 ) 92 windowInsets.consumeSystemWindowInsets() 93 } 94 lockSectionContainer.updateLayoutParams<FrameLayout.LayoutParams> { 95 // We don't want the top margin from the XML because our tabs have that as padding so 96 // they can be collapsed into the toolbar with spacing from the actual title text. 97 topMargin = 0 98 } 99 100 val homeSectionContainer = view.requireViewById<ViewGroup>(R.id.home_section_container) 101 homeSectionContainer.setOnApplyWindowInsetsListener { v: View, windowInsets: WindowInsets -> 102 v.setPadding( 103 v.paddingLeft, 104 v.paddingTop, 105 v.paddingRight, 106 windowInsets.systemWindowInsetBottom 107 ) 108 windowInsets.consumeSystemWindowInsets() 109 } 110 homeSectionContainer.updateLayoutParams<FrameLayout.LayoutParams> { 111 // We don't want the top margin from the XML because our tabs have that as padding so 112 // they can be collapsed into the toolbar with spacing from the actual title text. 113 topMargin = 0 114 } 115 116 // create and add sections to both the lock and home screen tabs ahead of time, since 117 // the lock and home screen preview sections are both needed to load initial wallpaper 118 // colors for the correct functioning of the color picker 119 createAndAddSections( 120 view.context, 121 homeSectionContainer, 122 isOnLockScreen = false, 123 sectionControllerProvider 124 ) 125 createAndAddSections( 126 view.context, 127 lockSectionContainer, 128 isOnLockScreen = true, 129 sectionControllerProvider 130 ) 131 132 val job = 133 lifecycleOwner.lifecycleScope.launch { 134 lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 135 launch { 136 viewModel.isOnLockScreen.collect { isOnLockScreen -> 137 InjectorProvider.getInjector() 138 .getPreviewActivityIntentFactory() 139 .setViewAsHome(!isOnLockScreen) 140 InjectorProvider.getInjector() 141 .getViewOnlyPreviewActivityIntentFactory() 142 .setViewAsHome(!isOnLockScreen) 143 // Offset the scroll position of both tabs 144 lockScrollContainer.scrollTo(0, 0) 145 homeScrollContainer.scrollTo(0, 0) 146 147 lockScrollContainer.isInvisible = !isOnLockScreen 148 homeScrollContainer.isInvisible = isOnLockScreen 149 } 150 } 151 } 152 153 // This happens when the lifecycle is stopped. 154 lockSectionContainer.children 155 .map { Pair(it, it.tag as? CustomizationSectionController<out SectionView>) } 156 .forEach { 157 it.second?.release() 158 it.first?.tag = null 159 } 160 lockSectionContainer.removeAllViews() 161 homeSectionContainer.children 162 .map { Pair(it, it.tag as? CustomizationSectionController<out SectionView>) } 163 .forEach { 164 it.second?.release() 165 it.first?.tag = null 166 } 167 homeSectionContainer.removeAllViews() 168 } 169 return DisposableHandle { job.cancel() } 170 } 171 172 private fun createAndAddSections( 173 context: Context, 174 container: ViewGroup, 175 isOnLockScreen: Boolean, 176 sectionControllerProvider: (isOnLockScreen: Boolean) -> List<SectionController>, 177 ) { 178 sectionControllerProvider 179 .invoke(isOnLockScreen) 180 .filter { it.isAvailable(context) } 181 .forEach { controller -> 182 val viewToAdd = 183 controller.createView( 184 context, 185 CustomizationSectionController.ViewCreationParams( 186 isOnLockScreen = isOnLockScreen, 187 isWallpaperVisibilityControlledByTab = true, 188 ) 189 ) 190 viewToAdd.tag = controller 191 container.addView(viewToAdd) 192 } 193 } 194 } 195