• 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.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