1 /* 2 * 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 */ 17 18 package com.android.customization.picker.preview.ui.section 19 20 import android.app.WallpaperManager 21 import android.content.Context 22 import android.graphics.Rect 23 import android.view.TouchDelegate 24 import android.view.View 25 import android.view.View.OnAttachStateChangeListener 26 import android.view.ViewStub 27 import androidx.activity.ComponentActivity 28 import androidx.constraintlayout.helper.widget.Carousel 29 import androidx.constraintlayout.widget.ConstraintLayout 30 import androidx.constraintlayout.widget.Guideline 31 import androidx.lifecycle.LifecycleOwner 32 import androidx.lifecycle.ViewModelProvider 33 import androidx.lifecycle.get 34 import androidx.lifecycle.lifecycleScope 35 import com.android.customization.model.themedicon.domain.interactor.ThemedIconInteractor 36 import com.android.customization.picker.clock.ui.binder.ClockCarouselViewBinder 37 import com.android.customization.picker.clock.ui.fragment.ClockSettingsFragment 38 import com.android.customization.picker.clock.ui.view.ClockCarouselView 39 import com.android.customization.picker.clock.ui.view.ClockViewFactory 40 import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel 41 import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor 42 import com.android.customization.picker.grid.domain.interactor.GridInteractor 43 import com.android.themepicker.R 44 import com.android.wallpaper.model.CustomizationSectionController 45 import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController 46 import com.android.wallpaper.model.Screen 47 import com.android.wallpaper.model.WallpaperPreviewNavigator 48 import com.android.wallpaper.module.CurrentWallpaperInfoFactory 49 import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository 50 import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor 51 import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewClickView 52 import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController 53 import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewView 54 import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationPickerViewModel 55 import com.android.wallpaper.util.DisplayUtils 56 import kotlinx.coroutines.Job 57 import kotlinx.coroutines.launch 58 59 /** 60 * A ThemePicker version of the [ScreenPreviewSectionController] that adjusts the preview for the 61 * clock carousel, and also updates the preview on theme changes. 62 */ 63 class PreviewWithClockCarouselSectionController( 64 activity: ComponentActivity, 65 private val lifecycleOwner: LifecycleOwner, 66 private val screen: Screen, 67 wallpaperInfoFactory: CurrentWallpaperInfoFactory, 68 wallpaperColorsRepository: WallpaperColorsRepository, 69 displayUtils: DisplayUtils, 70 clockCarouselViewModelFactory: ClockCarouselViewModel.Factory, 71 private val clockViewFactory: ClockViewFactory, 72 wallpaperPreviewNavigator: WallpaperPreviewNavigator, 73 private val navigationController: CustomizationSectionNavigationController, 74 wallpaperInteractor: WallpaperInteractor, 75 themedIconInteractor: ThemedIconInteractor, 76 gridInteractor: GridInteractor, 77 colorPickerInteractor: ColorPickerInteractor, 78 wallpaperManager: WallpaperManager, 79 private val isTwoPaneAndSmallWidth: Boolean, 80 customizationPickerViewModel: CustomizationPickerViewModel, 81 ) : 82 PreviewWithThemeSectionController( 83 activity, 84 lifecycleOwner, 85 screen, 86 wallpaperInfoFactory, 87 wallpaperColorsRepository, 88 displayUtils, 89 wallpaperPreviewNavigator, 90 wallpaperInteractor, 91 themedIconInteractor, 92 gridInteractor, 93 colorPickerInteractor, 94 wallpaperManager, 95 isTwoPaneAndSmallWidth, 96 customizationPickerViewModel, 97 ) { 98 99 private val viewModel = 100 ViewModelProvider(activity, clockCarouselViewModelFactory).get() as ClockCarouselViewModel 101 102 private var clockColorAndSizeButton: View? = null 103 104 override val hideLockScreenClockPreview = true 105 createViewnull106 override fun createView( 107 context: Context, 108 params: CustomizationSectionController.ViewCreationParams, 109 ): ScreenPreviewView { 110 val view = super.createView(context, params) 111 if (screen == Screen.LOCK_SCREEN) { 112 val screenPreviewClickView: ScreenPreviewClickView = 113 view.requireViewById(com.android.wallpaper.R.id.screen_preview_click_view) 114 val clockColorAndSizeButtonStub: ViewStub = 115 view.requireViewById(com.android.wallpaper.R.id.clock_color_and_size_button) 116 clockColorAndSizeButtonStub.layoutResource = R.layout.clock_color_and_size_button 117 clockColorAndSizeButton = clockColorAndSizeButtonStub.inflate() as View 118 clockColorAndSizeButton?.setOnClickListener { 119 navigationController.navigateTo(ClockSettingsFragment()) 120 } 121 // clockColorAndSizeButton's touch target has to be increased programmatically 122 // rather than with padding because this button only appears in the lock screen tab. 123 view.post { 124 val rect = Rect() 125 clockColorAndSizeButton?.getHitRect(rect) 126 val padding = 127 context 128 .getResources() 129 .getDimensionPixelSize( 130 com.android.wallpaper.R.dimen.screen_preview_section_vertical_space 131 ) 132 rect.top -= padding 133 rect.bottom += padding 134 val touchDelegate = TouchDelegate(rect, clockColorAndSizeButton) 135 view.setTouchDelegate(touchDelegate) 136 } 137 138 val carouselViewStub: ViewStub = 139 view.requireViewById(com.android.wallpaper.R.id.clock_carousel_view_stub) 140 carouselViewStub.layoutResource = R.layout.clock_carousel_view 141 val carouselView = carouselViewStub.inflate() as ClockCarouselView 142 143 if (isTwoPaneAndSmallWidth) { 144 val guidelineMargin = 145 context.resources.getDimensionPixelSize( 146 R.dimen.clock_carousel_guideline_margin_for_2_pane_small_width 147 ) 148 149 val guidelineStart = carouselView.requireViewById<Guideline>(R.id.guideline_start) 150 var layoutParams = guidelineStart.layoutParams as ConstraintLayout.LayoutParams 151 layoutParams.guideBegin = guidelineMargin 152 guidelineStart.layoutParams = layoutParams 153 154 val guidelineEnd = carouselView.requireViewById<Guideline>(R.id.guideline_end) 155 layoutParams = guidelineEnd.layoutParams as ConstraintLayout.LayoutParams 156 layoutParams.guideEnd = guidelineMargin 157 guidelineEnd.layoutParams = layoutParams 158 } 159 160 /** 161 * Only bind after [Carousel.onAttachedToWindow]. This is to avoid the race condition 162 * that the flow emits before attached to window where [Carousel.mMotionLayout] is still 163 * null. 164 */ 165 var onAttachStateChangeListener: OnAttachStateChangeListener? = null 166 var bindJob: Job? = null 167 onAttachStateChangeListener = 168 object : OnAttachStateChangeListener { 169 override fun onViewAttachedToWindow(view: View) { 170 bindJob = 171 lifecycleOwner.lifecycleScope.launch { 172 ClockCarouselViewBinder.bind( 173 context = context, 174 carouselView = carouselView, 175 screenPreviewClickView = screenPreviewClickView, 176 viewModel = viewModel, 177 clockViewFactory = clockViewFactory, 178 lifecycleOwner = lifecycleOwner, 179 isTwoPaneAndSmallWidth = isTwoPaneAndSmallWidth, 180 ) 181 if (onAttachStateChangeListener != null) { 182 carouselView.carousel.removeOnAttachStateChangeListener( 183 onAttachStateChangeListener 184 ) 185 } 186 } 187 } 188 189 override fun onViewDetachedFromWindow(view: View) { 190 bindJob?.cancel() 191 } 192 } 193 carouselView.carousel.addOnAttachStateChangeListener(onAttachStateChangeListener) 194 } 195 196 return view 197 } 198 } 199