• 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.wallpaper.R
43 import com.android.wallpaper.model.CustomizationSectionController
44 import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController
45 import com.android.wallpaper.model.WallpaperColorsViewModel
46 import com.android.wallpaper.model.WallpaperPreviewNavigator
47 import com.android.wallpaper.module.CurrentWallpaperInfoFactory
48 import com.android.wallpaper.module.CustomizationSections
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: CustomizationSections.Screen,
66     wallpaperInfoFactory: CurrentWallpaperInfoFactory,
67     colorViewModel: WallpaperColorsViewModel,
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         colorViewModel,
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 == CustomizationSections.Screen.LOCK_SCREEN) {
113             val screenPreviewClickView: ScreenPreviewClickView =
114                 view.findViewById(R.id.screen_preview_click_view)
115             val clockColorAndSizeButtonStub: ViewStub =
116                 view.requireViewById(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(R.dimen.screen_preview_section_vertical_space)
131                 rect.top -= padding
132                 rect.bottom += padding
133                 val touchDelegate = TouchDelegate(rect, clockColorAndSizeButton)
134                 view.setTouchDelegate(touchDelegate)
135             }
136 
137             val carouselViewStub: ViewStub = view.requireViewById(R.id.clock_carousel_view_stub)
138             carouselViewStub.layoutResource = R.layout.clock_carousel_view
139             val carouselView = carouselViewStub.inflate() as ClockCarouselView
140 
141             if (isTwoPaneAndSmallWidth) {
142                 val guidelineMargin =
143                     context.resources.getDimensionPixelSize(
144                         R.dimen.clock_carousel_guideline_margin_for_2_pane_small_width
145                     )
146 
147                 val guidelineStart = carouselView.requireViewById<Guideline>(R.id.guideline_start)
148                 var layoutParams = guidelineStart.layoutParams as ConstraintLayout.LayoutParams
149                 layoutParams.guideBegin = guidelineMargin
150                 guidelineStart.layoutParams = layoutParams
151 
152                 val guidelineEnd = carouselView.requireViewById<Guideline>(R.id.guideline_end)
153                 layoutParams = guidelineEnd.layoutParams as ConstraintLayout.LayoutParams
154                 layoutParams.guideEnd = guidelineMargin
155                 guidelineEnd.layoutParams = layoutParams
156             }
157 
158             /**
159              * Only bind after [Carousel.onAttachedToWindow]. This is to avoid the race condition
160              * that the flow emits before attached to window where [Carousel.mMotionLayout] is still
161              * null.
162              */
163             var onAttachStateChangeListener: OnAttachStateChangeListener? = null
164             var bindJob: Job? = null
165             onAttachStateChangeListener =
166                 object : OnAttachStateChangeListener {
167                     override fun onViewAttachedToWindow(view: View?) {
168                         bindJob =
169                             lifecycleOwner.lifecycleScope.launch {
170                                 ClockCarouselViewBinder.bind(
171                                     context = context,
172                                     carouselView = carouselView,
173                                     screenPreviewClickView = screenPreviewClickView,
174                                     viewModel = viewModel,
175                                     clockViewFactory = clockViewFactory,
176                                     lifecycleOwner = lifecycleOwner,
177                                     isTwoPaneAndSmallWidth = isTwoPaneAndSmallWidth,
178                                 )
179                                 if (onAttachStateChangeListener != null) {
180                                     carouselView.carousel.removeOnAttachStateChangeListener(
181                                         onAttachStateChangeListener,
182                                     )
183                                 }
184                             }
185                     }
186 
187                     override fun onViewDetachedFromWindow(view: View?) {
188                         bindJob?.cancel()
189                     }
190                 }
191             carouselView.carousel.addOnAttachStateChangeListener(onAttachStateChangeListener)
192         }
193 
194         return view
195     }
196 }
197