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 18 package com.android.wallpaper.picker.customization.ui.viewmodel 19 20 import com.android.wallpaper.R 21 import com.android.wallpaper.picker.common.text.ui.viewmodel.Text 22 import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor 23 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination 24 import kotlinx.coroutines.CoroutineScope 25 import kotlinx.coroutines.flow.Flow 26 import kotlinx.coroutines.flow.SharingStarted 27 import kotlinx.coroutines.flow.combine 28 import kotlinx.coroutines.flow.distinctUntilChanged 29 import kotlinx.coroutines.flow.distinctUntilChangedBy 30 import kotlinx.coroutines.flow.map 31 import kotlinx.coroutines.flow.shareIn 32 import kotlinx.coroutines.launch 33 34 /** Models UI state for views that can render wallpaper quick switching. */ 35 class WallpaperQuickSwitchViewModel 36 constructor( 37 private val interactor: WallpaperInteractor, 38 private val destination: WallpaperDestination, 39 private val coroutineScope: CoroutineScope, 40 maxOptions: Int = interactor.maxOptions, 41 ) { 42 43 private val selectedWallpaperId: Flow<String> = 44 interactor 45 .selectedWallpaperId(destination) 46 .shareIn( 47 scope = coroutineScope, 48 started = SharingStarted.WhileSubscribed(), 49 replay = 1, 50 ) 51 private val selectingWallpaperId: Flow<String?> = 52 interactor 53 .selectingWallpaperId(destination) 54 .shareIn( 55 scope = coroutineScope, 56 started = SharingStarted.WhileSubscribed(), 57 replay = 1, 58 ) 59 60 val options: Flow<List<WallpaperQuickSwitchOptionViewModel>> = 61 interactor 62 .previews( 63 destination = destination, 64 maxResults = maxOptions, 65 ) 66 .distinctUntilChangedBy { previews -> 67 // Produce a key that's the same if the same set of wallpapers is available, 68 // even if in a different order. This is so that the view can keep from 69 // moving the wallpaper options around when the sort order changes as the 70 // user selects different wallpapers. 71 previews 72 .map { preview -> preview.wallpaperId + preview.lastUpdated } 73 .sorted() 74 .joinToString(",") 75 } 76 .map { previews -> 77 // True if any option is becoming selected following user click. 78 val isSomethingBecomingSelectedFlow: Flow<Boolean> = 79 selectingWallpaperId.distinctUntilChanged().map { it != null } 80 81 previews.map { preview -> 82 // True if this option is currently selected. 83 val isSelectedFlow: Flow<Boolean> = 84 selectedWallpaperId.distinctUntilChanged().map { it == preview.wallpaperId } 85 // True if this option is becoming the selected one following user click. 86 val isBecomingSelectedFlow: Flow<Boolean> = 87 selectingWallpaperId.distinctUntilChanged().map { 88 it == preview.wallpaperId 89 } 90 91 WallpaperQuickSwitchOptionViewModel( 92 wallpaperId = preview.wallpaperId, 93 placeholderColor = preview.placeholderColor, 94 thumbnail = { 95 interactor.loadThumbnail( 96 wallpaperId = preview.wallpaperId, 97 lastUpdatedTimestamp = preview.lastUpdated 98 ) 99 }, 100 isLarge = 101 combine( 102 isSelectedFlow, 103 isBecomingSelectedFlow, 104 isSomethingBecomingSelectedFlow, 105 ) { isSelected, isBecomingSelected, isSomethingBecomingSelected, 106 -> 107 // The large option is the one that's currently selected or 108 // the one that is becoming the selected one following user 109 // click. 110 (isSelected && !isSomethingBecomingSelected) || isBecomingSelected 111 }, 112 // We show the progress indicator if the option is in the process of 113 // becoming the selected one following user click. 114 isProgressIndicatorVisible = isBecomingSelectedFlow, 115 isSelectionBorderVisible = 116 combine( 117 isSelectedFlow, 118 isBecomingSelectedFlow, 119 isSomethingBecomingSelectedFlow, 120 ) { isSelected, isBeingSelected, isSomethingBecomingSelected -> 121 // The selection border is shown for the option that is the 122 // one that's currently selected or the one that is becoming 123 // the selected one following user click. 124 (isSelected && !isSomethingBecomingSelected) || isBeingSelected 125 }, 126 isSelectionIconVisible = 127 combine( 128 isSelectedFlow, 129 isSomethingBecomingSelectedFlow, 130 ) { isSelected, isSomethingBecomingSelected -> 131 // The selection icon is shown for the option that is 132 // currently selected but only if nothing else is becoming 133 // selected. If anything is being selected following user 134 // click, the selection icon is not shown on any option. 135 isSelected && !isSomethingBecomingSelected 136 }, 137 onSelected = 138 combine( 139 isSelectedFlow, 140 isBecomingSelectedFlow, 141 isSomethingBecomingSelectedFlow, 142 ) { isSelected, isBeingSelected, isSomethingBecomingSelected, 143 -> 144 // An option is selectable if it is not itself becoming 145 // selected following user click or if nothing else is 146 // becoming selected but this option is not the selected 147 // one. 148 (isSomethingBecomingSelected && !isBeingSelected) || 149 (!isSomethingBecomingSelected && !isSelected) 150 } 151 .distinctUntilChanged() 152 .map { isSelectable -> 153 if (isSelectable) { 154 { 155 // A selectable option can become selected. 156 coroutineScope.launch { 157 interactor.setWallpaper( 158 destination = destination, 159 wallpaperId = preview.wallpaperId, 160 ) 161 } 162 } 163 } else { 164 // A non-selectable option cannot become selected. 165 null 166 } 167 } 168 ) 169 } 170 } 171 .shareIn( 172 scope = coroutineScope, 173 started = SharingStarted.Lazily, 174 replay = 1, 175 ) 176 177 /** Whether recent wallpapers are available */ 178 val areRecentsAvailable: Boolean = interactor.areRecentsAvailable 179 180 /** Text to show to prompt the user to browse more wallpapers */ 181 val actionText: Text = 182 if (areRecentsAvailable) { 183 Text.Resource(R.string.more_wallpapers) 184 } else { 185 Text.Resource(R.string.wallpaper_picker_entry_title) 186 } 187 188 companion object { 189 /** The maximum number of options to show, including the currently-selected one. */ 190 private const val MAX_OPTIONS = 5 191 } 192 } 193