• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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 package com.android.customization.picker.color.ui.viewmodel
18 
19 import android.content.Context
20 import androidx.lifecycle.ViewModel
21 import androidx.lifecycle.ViewModelProvider
22 import androidx.lifecycle.viewModelScope
23 import com.android.customization.model.color.ColorOptionImpl
24 import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
25 import com.android.customization.picker.color.shared.model.ColorType
26 import com.android.wallpaper.R
27 import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
28 import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
29 import kotlin.math.max
30 import kotlin.math.min
31 import kotlinx.coroutines.flow.Flow
32 import kotlinx.coroutines.flow.MutableStateFlow
33 import kotlinx.coroutines.flow.SharingStarted
34 import kotlinx.coroutines.flow.StateFlow
35 import kotlinx.coroutines.flow.combine
36 import kotlinx.coroutines.flow.map
37 import kotlinx.coroutines.flow.shareIn
38 import kotlinx.coroutines.flow.stateIn
39 import kotlinx.coroutines.launch
40 
41 /** Models UI state for a color picker experience. */
42 class ColorPickerViewModel
43 private constructor(
44     context: Context,
45     private val interactor: ColorPickerInteractor,
46 ) : ViewModel() {
47 
48     private val selectedColorTypeTabId = MutableStateFlow<ColorType?>(null)
49 
50     /** View-models for each color tab. */
51     val colorTypeTabs: Flow<Map<ColorType, ColorTypeTabViewModel>> =
52         combine(
53             interactor.colorOptions,
54             selectedColorTypeTabId,
55         ) { colorOptions, selectedColorTypeIdOrNull ->
56             colorOptions.keys
57                 .mapIndexed { index, colorType ->
58                     val isSelected =
59                         (selectedColorTypeIdOrNull == null && index == 0) ||
60                             selectedColorTypeIdOrNull == colorType
61                     colorType to
62                         ColorTypeTabViewModel(
63                             name =
64                                 when (colorType) {
65                                     ColorType.WALLPAPER_COLOR ->
66                                         context.resources.getString(R.string.wallpaper_color_tab)
67                                     ColorType.PRESET_COLOR ->
68                                         context.resources.getString(R.string.preset_color_tab_2)
69                                 },
70                             isSelected = isSelected,
71                             onClick =
72                                 if (isSelected) {
73                                     null
74                                 } else {
75                                     { this.selectedColorTypeTabId.value = colorType }
76                                 },
77                         )
78                 }
79                 .toMap()
80         }
81 
82     /** View-models for each color tab subheader */
83     val colorTypeTabSubheader: Flow<String> =
84         selectedColorTypeTabId.map { selectedColorTypeIdOrNull ->
85             when (selectedColorTypeIdOrNull ?: ColorType.WALLPAPER_COLOR) {
86                 ColorType.WALLPAPER_COLOR ->
87                     context.resources.getString(R.string.wallpaper_color_subheader)
88                 ColorType.PRESET_COLOR ->
89                     context.resources.getString(R.string.preset_color_subheader)
90             }
91         }
92 
93     /** The list of all color options mapped by their color type */
94     private val allColorOptions:
95         Flow<Map<ColorType, List<OptionItemViewModel<ColorOptionIconViewModel>>>> =
96         interactor.colorOptions
97             .map { colorOptions ->
98                 colorOptions
99                     .map { colorOptionEntry ->
100                         colorOptionEntry.key to
101                             colorOptionEntry.value.map { colorOptionModel ->
102                                 val colorOption: ColorOptionImpl =
103                                     colorOptionModel.colorOption as ColorOptionImpl
104                                 val lightThemeColors =
105                                     colorOption.previewInfo.resolveColors(/* darkTheme= */ false)
106                                 val darkThemeColors =
107                                     colorOption.previewInfo.resolveColors(/* darkTheme= */ true)
108                                 val isSelectedFlow: StateFlow<Boolean> =
109                                     interactor.activeColorOption
110                                         .map {
111                                             it?.colorOption?.isEquivalent(
112                                                 colorOptionModel.colorOption
113                                             )
114                                                 ?: colorOptionModel.isSelected
115                                         }
116                                         .stateIn(viewModelScope)
117                                 OptionItemViewModel<ColorOptionIconViewModel>(
118                                     key =
119                                         MutableStateFlow(colorOptionModel.key) as StateFlow<String>,
120                                     payload =
121                                         ColorOptionIconViewModel(
122                                             lightThemeColor0 = lightThemeColors[0],
123                                             lightThemeColor1 = lightThemeColors[1],
124                                             lightThemeColor2 = lightThemeColors[2],
125                                             lightThemeColor3 = lightThemeColors[3],
126                                             darkThemeColor0 = darkThemeColors[0],
127                                             darkThemeColor1 = darkThemeColors[1],
128                                             darkThemeColor2 = darkThemeColors[2],
129                                             darkThemeColor3 = darkThemeColors[3],
130                                         ),
131                                     text =
132                                         Text.Loaded(
133                                             colorOption.getContentDescription(context).toString()
134                                         ),
135                                     isTextUserVisible = false,
136                                     isSelected = isSelectedFlow,
137                                     onClicked =
138                                         isSelectedFlow.map { isSelected ->
139                                             if (isSelected) {
140                                                 null
141                                             } else {
142                                                 {
143                                                     viewModelScope.launch {
144                                                         interactor.select(colorOptionModel)
145                                                     }
146                                                 }
147                                             }
148                                         },
149                                 )
150                             }
151                     }
152                     .toMap()
153             }
154             .shareIn(
155                 scope = viewModelScope,
156                 started = SharingStarted.WhileSubscribed(),
157                 replay = 1,
158             )
159 
160     /** The list of all available color options for the selected Color Type. */
161     val colorOptions: Flow<List<OptionItemViewModel<ColorOptionIconViewModel>>> =
162         combine(allColorOptions, selectedColorTypeTabId) {
163                 allColorOptions:
164                     Map<ColorType, List<OptionItemViewModel<ColorOptionIconViewModel>>>,
165                 selectedColorTypeIdOrNull ->
166                 val selectedColorTypeId = selectedColorTypeIdOrNull ?: ColorType.WALLPAPER_COLOR
167                 allColorOptions[selectedColorTypeId]!!
168             }
169             .shareIn(
170                 scope = viewModelScope,
171                 started = SharingStarted.Eagerly,
172                 replay = 1,
173             )
174 
175     /** The list of color options for the color section */
176     val colorSectionOptions: Flow<List<OptionItemViewModel<ColorOptionIconViewModel>>> =
177         allColorOptions
178             .map { allColorOptions ->
179                 val wallpaperOptions = allColorOptions[ColorType.WALLPAPER_COLOR]
180                 val presetOptions = allColorOptions[ColorType.PRESET_COLOR]
181                 val subOptions =
182                     wallpaperOptions!!.subList(
183                         0,
184                         min(COLOR_SECTION_OPTION_SIZE, wallpaperOptions.size)
185                     )
186                 // Add additional options based on preset colors if size of wallpaper color options
187                 // is
188                 // less than COLOR_SECTION_OPTION_SIZE
189                 val additionalSubOptions =
190                     presetOptions!!.subList(
191                         0,
192                         min(
193                             max(0, COLOR_SECTION_OPTION_SIZE - wallpaperOptions.size),
194                             presetOptions.size,
195                         )
196                     )
197                 subOptions + additionalSubOptions
198             }
199             .shareIn(
200                 scope = viewModelScope,
201                 started = SharingStarted.WhileSubscribed(),
202                 replay = 1,
203             )
204 
205     class Factory(
206         private val context: Context,
207         private val interactor: ColorPickerInteractor,
208     ) : ViewModelProvider.Factory {
209         override fun <T : ViewModel> create(modelClass: Class<T>): T {
210             @Suppress("UNCHECKED_CAST")
211             return ColorPickerViewModel(
212                 context = context,
213                 interactor = interactor,
214             )
215                 as T
216         }
217     }
218 
219     companion object {
220         private const val COLOR_SECTION_OPTION_SIZE = 5
221     }
222 }
223