1 /*
<lambda>null2  * Copyright (C) 2024 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 package com.android.wallpaper.customization.ui.binder
18 
19 import android.animation.ValueAnimator
20 import android.view.ViewTreeObserver.OnGlobalLayoutListener
21 import android.widget.Button
22 import android.widget.FrameLayout
23 import android.widget.Toolbar
24 import androidx.core.graphics.ColorUtils
25 import androidx.core.graphics.drawable.DrawableCompat
26 import androidx.core.view.isInvisible
27 import androidx.lifecycle.Lifecycle
28 import androidx.lifecycle.LifecycleOwner
29 import androidx.lifecycle.lifecycleScope
30 import androidx.lifecycle.repeatOnLifecycle
31 import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
32 import com.android.wallpaper.customization.ui.viewmodel.ToolbarHeightsViewModel
33 import com.android.wallpaper.picker.customization.ui.binder.ColorUpdateBinder
34 import com.android.wallpaper.picker.customization.ui.binder.DefaultToolbarBinder
35 import com.android.wallpaper.picker.customization.ui.binder.ToolbarBinder
36 import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel
37 import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationOptionsViewModel
38 import javax.inject.Inject
39 import javax.inject.Singleton
40 import kotlinx.coroutines.flow.MutableStateFlow
41 import kotlinx.coroutines.flow.asStateFlow
42 import kotlinx.coroutines.flow.combine
43 import kotlinx.coroutines.flow.filterNotNull
44 import kotlinx.coroutines.flow.map
45 import kotlinx.coroutines.launch
46 
47 @Singleton
48 class ThemePickerToolbarBinder
49 @Inject
50 constructor(private val defaultToolbarBinder: DefaultToolbarBinder) : ToolbarBinder {
51 
52     private val _toolbarHeights: MutableStateFlow<ToolbarHeightsViewModel?> = MutableStateFlow(null)
53     private val toolbarHeights = _toolbarHeights.asStateFlow().filterNotNull()
54 
55     override fun bind(
56         navButton: FrameLayout,
57         toolbar: Toolbar,
58         applyButton: Button,
59         viewModel: CustomizationOptionsViewModel,
60         colorUpdateViewModel: ColorUpdateViewModel,
61         lifecycleOwner: LifecycleOwner,
62         onNavBack: () -> Unit,
63     ) {
64         defaultToolbarBinder.bind(
65             navButton,
66             toolbar,
67             applyButton,
68             viewModel,
69             colorUpdateViewModel,
70             lifecycleOwner,
71             onNavBack,
72         )
73 
74         if (viewModel !is ThemePickerCustomizationOptionsViewModel) {
75             throw IllegalArgumentException(
76                 "viewModel $viewModel is not a ThemePickerCustomizationOptionsViewModel."
77             )
78         }
79 
80         navButton.viewTreeObserver.addOnGlobalLayoutListener(
81             object : OnGlobalLayoutListener {
82                 override fun onGlobalLayout() {
83                     if (navButton.height != 0) {
84                         _toolbarHeights.value =
85                             _toolbarHeights.value?.copy(navButtonHeight = navButton.height)
86                                 ?: ToolbarHeightsViewModel(navButtonHeight = navButton.height)
87                     }
88                     navButton.viewTreeObserver.removeOnGlobalLayoutListener(this)
89                 }
90             }
91         )
92 
93         toolbar.viewTreeObserver.addOnGlobalLayoutListener(
94             object : OnGlobalLayoutListener {
95                 override fun onGlobalLayout() {
96                     if (toolbar.height != 0) {
97                         _toolbarHeights.value =
98                             _toolbarHeights.value?.copy(toolbarHeight = toolbar.height)
99                                 ?: ToolbarHeightsViewModel(toolbarHeight = toolbar.height)
100                     }
101                     navButton.viewTreeObserver.removeOnGlobalLayoutListener(this)
102                 }
103             }
104         )
105 
106         applyButton.viewTreeObserver.addOnGlobalLayoutListener(
107             object : OnGlobalLayoutListener {
108                 override fun onGlobalLayout() {
109                     if (applyButton.height != 0) {
110                         _toolbarHeights.value =
111                             _toolbarHeights.value?.copy(applyButtonHeight = applyButton.height)
112                                 ?: ToolbarHeightsViewModel(applyButtonHeight = applyButton.height)
113                     }
114                     applyButton.viewTreeObserver.removeOnGlobalLayoutListener(this)
115                 }
116             }
117         )
118 
119         ColorUpdateBinder.bind(
120             setColor = { color ->
121                 DrawableCompat.setTint(DrawableCompat.wrap(applyButton.background), color)
122             },
123             color = colorUpdateViewModel.colorPrimary,
124             shouldAnimate = { true },
125             lifecycleOwner = lifecycleOwner,
126         )
127 
128         lifecycleOwner.lifecycleScope.launch {
129             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
130                 launch {
131                     viewModel.onApplyButtonClicked.collect { onApplyButtonClicked ->
132                         applyButton.setOnClickListener { onApplyButtonClicked?.invoke(onNavBack) }
133                     }
134                 }
135 
136                 launch { viewModel.isApplyButtonVisible.collect { applyButton.isInvisible = !it } }
137 
138                 launch {
139                     viewModel.isApplyButtonEnabled.collect {
140                         applyButton.isEnabled = it
141                         applyButton.background.alpha =
142                             if (it) 255 else 31 // 255 for 100%, 31 for 12% transparent
143                         ColorUpdateBinder.bind(
144                             setColor = { color -> applyButton.setTextColor(color) },
145                             color =
146                                 if (it) {
147                                     colorUpdateViewModel.colorOnPrimary
148                                 } else {
149                                     colorUpdateViewModel.colorOnSurface.map { color: Int ->
150                                         ColorUtils.setAlphaComponent(
151                                             color,
152                                             97,
153                                         ) // 97 for 38% transparent
154                                     }
155                                 },
156                             shouldAnimate = { true },
157                             lifecycleOwner = lifecycleOwner,
158                         )
159                     }
160                 }
161 
162                 launch {
163                     combine(toolbarHeights, viewModel.isToolbarCollapsed, ::Pair).collect {
164                         (toolbarHeights, isToolbarCollapsed) ->
165                         val (navButtonHeight, toolbarHeight, applyButtonHeight) = toolbarHeights
166                         navButtonHeight ?: return@collect
167                         toolbarHeight ?: return@collect
168                         applyButtonHeight ?: return@collect
169 
170                         val navButtonToHeight = if (isToolbarCollapsed) 0 else navButtonHeight
171                         val toolbarToHeight = if (isToolbarCollapsed) 0 else toolbarHeight
172                         val applyButtonToHeight = if (isToolbarCollapsed) 0 else applyButtonHeight
173                         ValueAnimator.ofInt(navButton.height, navButtonToHeight)
174                             .apply {
175                                 addUpdateListener { valueAnimator ->
176                                     val value = valueAnimator.animatedValue as Int
177                                     navButton.layoutParams =
178                                         navButton.layoutParams.apply { height = value }
179                                 }
180                                 duration = ANIMATION_DURATION
181                             }
182                             .start()
183 
184                         ValueAnimator.ofInt(toolbar.height, toolbarToHeight)
185                             .apply {
186                                 addUpdateListener { valueAnimator ->
187                                     val value = valueAnimator.animatedValue as Int
188                                     toolbar.layoutParams =
189                                         toolbar.layoutParams.apply { height = value }
190                                 }
191                                 duration = ANIMATION_DURATION
192                             }
193                             .start()
194 
195                         ValueAnimator.ofInt(applyButton.height, applyButtonToHeight)
196                             .apply {
197                                 addUpdateListener { valueAnimator ->
198                                     val value = valueAnimator.animatedValue as Int
199                                     applyButton.layoutParams =
200                                         applyButton.layoutParams.apply { height = value }
201                                 }
202                                 duration = ANIMATION_DURATION
203                             }
204                             .start()
205                     }
206                 }
207             }
208         }
209     }
210 
211     companion object {
212         private const val ANIMATION_DURATION = 200L
213     }
214 }
215