• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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