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