• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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.viewmodel
18 
19 import com.android.customization.model.grid.ShapeOptionModel
20 import com.android.customization.picker.grid.domain.interactor.AppIconInteractor
21 import com.android.customization.picker.grid.ui.viewmodel.ShapeIconViewModel
22 import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
23 import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel2
24 import dagger.assisted.Assisted
25 import dagger.assisted.AssistedFactory
26 import dagger.assisted.AssistedInject
27 import dagger.hilt.android.scopes.ViewModelScoped
28 import kotlinx.coroutines.CoroutineScope
29 import kotlinx.coroutines.flow.Flow
30 import kotlinx.coroutines.flow.MutableStateFlow
31 import kotlinx.coroutines.flow.SharingStarted
32 import kotlinx.coroutines.flow.combine
33 import kotlinx.coroutines.flow.filterNotNull
34 import kotlinx.coroutines.flow.map
35 import kotlinx.coroutines.flow.shareIn
36 import kotlinx.coroutines.flow.stateIn
37 
38 class AppIconPickerViewModel
39 @AssistedInject
40 constructor(interactor: AppIconInteractor, @Assisted private val viewModelScope: CoroutineScope) {
41     //// Shape
42 
43     // The currently-set system shape option
44     val selectedShapeKey =
45         interactor.selectedShapeOption
46             .filterNotNull()
47             .map { it.key }
48             .shareIn(scope = viewModelScope, started = SharingStarted.Lazily, replay = 1)
49     private val overridingShapeKey = MutableStateFlow<String?>(null)
50     // If the overriding key is null, use the currently-set system shape option
51     val previewingShapeKey =
52         combine(overridingShapeKey, selectedShapeKey) { overridingShapeOptionKey, selectedShapeKey
53             ->
54             overridingShapeOptionKey ?: selectedShapeKey
55         }
56 
57     val shapeOptions: Flow<List<OptionItemViewModel2<ShapeIconViewModel>>> =
58         interactor.shapeOptions
59             .filterNotNull()
60             .map { shapeOptions -> shapeOptions.map { toShapeOptionItemViewModel(it) } }
61             .shareIn(scope = viewModelScope, started = SharingStarted.Lazily, replay = 1)
62 
63     //// Themed icons enabled
64     val isThemedIconAvailable =
65         interactor.isThemedIconAvailable.shareIn(
66             scope = viewModelScope,
67             started = SharingStarted.Lazily,
68             replay = 1,
69         )
70 
71     private val overridingIsThemedIconEnabled = MutableStateFlow<Boolean?>(null)
72     val isThemedIconEnabled =
73         interactor.isThemedIconEnabled.shareIn(
74             scope = viewModelScope,
75             started = SharingStarted.Lazily,
76             replay = 1,
77         )
78     val previewingIsThemeIconEnabled =
79         combine(overridingIsThemedIconEnabled, isThemedIconEnabled) {
80             overridingIsThemeIconEnabled,
81             isThemeIconEnabled ->
82             overridingIsThemeIconEnabled ?: isThemeIconEnabled
83         }
84     val toggleThemedIcon: Flow<suspend () -> Unit> =
85         previewingIsThemeIconEnabled.map {
86             {
87                 val newValue = !it
88                 overridingIsThemedIconEnabled.value = newValue
89             }
90         }
91 
92     val onApply: Flow<(suspend () -> Unit)?> =
93         combine(
94             overridingShapeKey,
95             selectedShapeKey,
96             overridingIsThemedIconEnabled,
97             isThemedIconEnabled,
98         ) { overridingShapeKey, selectedShapeKey, overridingIsThemeIconEnabled, isThemeIconEnabled
99             ->
100             if (
101                 (overridingShapeKey != null && overridingShapeKey != selectedShapeKey) ||
102                     (overridingIsThemeIconEnabled != null &&
103                         overridingIsThemeIconEnabled != isThemeIconEnabled)
104             ) {
105                 {
106                     overridingShapeKey?.let { interactor.applyShape(it) }
107                     overridingIsThemeIconEnabled?.let { interactor.applyThemedIconEnabled(it) }
108                 }
109             } else {
110                 null
111             }
112         }
113 
114     fun resetPreview() {
115         overridingShapeKey.value = null
116         overridingIsThemedIconEnabled.value = null
117     }
118 
119     private fun toShapeOptionItemViewModel(
120         option: ShapeOptionModel
121     ): OptionItemViewModel2<ShapeIconViewModel> {
122         val isSelected =
123             previewingShapeKey
124                 .map { it == option.key }
125                 .stateIn(
126                     scope = viewModelScope,
127                     started = SharingStarted.Lazily,
128                     initialValue = false,
129                 )
130 
131         return OptionItemViewModel2(
132             key = MutableStateFlow(option.key),
133             payload = ShapeIconViewModel(option.key, option.path),
134             text = Text.Loaded(option.title),
135             isSelected = isSelected,
136             onClicked =
137                 isSelected.map {
138                     if (!it) {
139                         { overridingShapeKey.value = option.key }
140                     } else {
141                         null
142                     }
143                 },
144         )
145     }
146 
147     @ViewModelScoped
148     @AssistedFactory
149     interface Factory {
150         fun create(viewModelScope: CoroutineScope): AppIconPickerViewModel
151     }
152 }
153