1 /*
2 * Copyright (C) 2022 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.systemui.people.ui.viewmodel
18
19 import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
20 import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
21 import android.content.Context
22 import android.content.Intent
23 import android.util.Log
24 import androidx.lifecycle.ViewModel
25 import androidx.lifecycle.ViewModelProvider
26 import com.android.systemui.dagger.qualifiers.Application
27 import com.android.systemui.people.PeopleSpaceUtils
28 import com.android.systemui.people.PeopleTileViewHelper
29 import com.android.systemui.people.data.model.PeopleTileModel
30 import com.android.systemui.people.data.repository.PeopleTileRepository
31 import com.android.systemui.people.data.repository.PeopleWidgetRepository
32 import com.android.systemui.res.R
33 import javax.inject.Inject
34 import kotlinx.coroutines.flow.MutableStateFlow
35 import kotlinx.coroutines.flow.StateFlow
36 import kotlinx.coroutines.flow.asStateFlow
37
38 private const val TAG = "PeopleViewModel"
39
40 /**
41 * Models UI state for the people space, allowing the user to select which conversation should be
42 * associated to a new or existing Conversation widget.
43 */
44 class PeopleViewModel(
45 /**
46 * The list of the priority tiles/conversations.
47 *
48 * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
49 * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
50 */
51 val priorityTiles: StateFlow<List<PeopleTileViewModel>>,
52
53 /**
54 * The list of the priority tiles/conversations.
55 *
56 * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
57 * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
58 */
59 val recentTiles: StateFlow<List<PeopleTileViewModel>>,
60
61 /** The ID of the widget currently being edited/added. */
62 val appWidgetId: StateFlow<Int>,
63
64 /** The result of this user journey. */
65 val result: StateFlow<Result?>,
66
67 /** Refresh the [priorityTiles] and [recentTiles]. */
68 val onTileRefreshRequested: () -> Unit,
69
70 /** Called when the [appWidgetId] should be changed to [widgetId]. */
71 val onWidgetIdChanged: (widgetId: Int) -> Unit,
72
73 /** Clear [result], setting it to null. */
74 val clearResult: () -> Unit,
75
76 /** Called when a tile is clicked. */
77 val onTileClicked: (tile: PeopleTileViewModel) -> Unit,
78
79 /** Called when this user journey is cancelled. */
80 val onUserJourneyCancelled: () -> Unit,
81 ) : ViewModel() {
82 /** The Factory that should be used to create a [PeopleViewModel]. */
83 class Factory
84 @Inject
85 constructor(
86 @Application private val context: Context,
87 private val tileRepository: PeopleTileRepository,
88 private val widgetRepository: PeopleWidgetRepository,
89 ) : ViewModelProvider.Factory {
createnull90 override fun <T : ViewModel> create(modelClass: Class<T>): T {
91 check(modelClass == PeopleViewModel::class.java)
92 return PeopleViewModel(context, tileRepository, widgetRepository) as T
93 }
94 }
95
96 sealed class Result {
97 class Success(val data: Intent) : Result()
98
99 object Cancelled : Result()
100 }
101 }
102
PeopleViewModelnull103 private fun PeopleViewModel(
104 @Application context: Context,
105 tileRepository: PeopleTileRepository,
106 widgetRepository: PeopleWidgetRepository,
107 ): PeopleViewModel {
108 fun priorityTiles(): List<PeopleTileViewModel> {
109 return try {
110 tileRepository.priorityTiles().map { it.toViewModel(context) }
111 } catch (e: Exception) {
112 Log.e(TAG, "Couldn't retrieve priority conversations", e)
113 emptyList()
114 }
115 }
116
117 fun recentTiles(): List<PeopleTileViewModel> {
118 return try {
119 tileRepository.recentTiles().map { it.toViewModel(context) }
120 } catch (e: Exception) {
121 Log.e(TAG, "Couldn't retrieve recent conversations", e)
122 emptyList()
123 }
124 }
125
126 val priorityTiles = MutableStateFlow(priorityTiles())
127 val recentTiles = MutableStateFlow(recentTiles())
128 val appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
129 val result = MutableStateFlow<PeopleViewModel.Result?>(null)
130
131 fun onTileRefreshRequested() {
132 priorityTiles.value = priorityTiles()
133 recentTiles.value = recentTiles()
134 }
135
136 fun onWidgetIdChanged(widgetId: Int) {
137 appWidgetId.value = widgetId
138 }
139
140 fun clearResult() {
141 result.value = null
142 }
143
144 fun onTileClicked(tile: PeopleTileViewModel) {
145 val widgetId = appWidgetId.value
146 if (PeopleSpaceUtils.DEBUG) {
147 Log.d(
148 TAG,
149 "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId"
150 )
151 }
152 widgetRepository.setWidgetTile(widgetId, tile.key)
153 result.value =
154 PeopleViewModel.Result.Success(
155 Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) }
156 )
157 }
158
159 fun onUserJourneyCancelled() {
160 result.value = PeopleViewModel.Result.Cancelled
161 }
162
163 return PeopleViewModel(
164 priorityTiles = priorityTiles.asStateFlow(),
165 recentTiles = recentTiles.asStateFlow(),
166 appWidgetId = appWidgetId.asStateFlow(),
167 result = result.asStateFlow(),
168 onTileRefreshRequested = ::onTileRefreshRequested,
169 onWidgetIdChanged = ::onWidgetIdChanged,
170 clearResult = ::clearResult,
171 onTileClicked = ::onTileClicked,
172 onUserJourneyCancelled = ::onUserJourneyCancelled,
173 )
174 }
175
toViewModelnull176 fun PeopleTileModel.toViewModel(@Application context: Context): PeopleTileViewModel {
177 val icon =
178 PeopleTileViewHelper.getPersonIconBitmap(
179 context,
180 this,
181 PeopleTileViewHelper.getSizeInDp(
182 context,
183 R.dimen.avatar_size_for_medium,
184 context.resources.displayMetrics.density,
185 )
186 )
187 return PeopleTileViewModel(key, icon, username)
188 }
189