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