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