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