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