blob: 398341d256d211f7b4265a2ae74261ad56aaca1e [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.user.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.systemui.R
import com.android.systemui.common.ui.drawable.CircularDrawable
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
/** Models UI state for the user switcher feature. */
class UserSwitcherViewModel
private constructor(
private val userInteractor: UserInteractor,
private val powerInteractor: PowerInteractor,
) : ViewModel() {
/** On-device users. */
val users: Flow<List<UserViewModel>> =
userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
/** The maximum number of columns that the user selection grid should use. */
val maximumUserColumns: Flow<Int> =
users.map { LegacyUserUiHelper.getMaxUserSwitcherItemColumns(it.size) }
/** Whether the button to open the user action menu is visible. */
val isOpenMenuButtonVisible: Flow<Boolean> = userInteractor.actions.map { it.isNotEmpty() }
private val _isMenuVisible = MutableStateFlow(false)
/**
* Whether the user action menu should be shown. Once the action menu is dismissed/closed, the
* consumer must invoke [onMenuClosed].
*/
val isMenuVisible: Flow<Boolean> = _isMenuVisible
/** The user action menu. */
val menu: Flow<List<UserActionViewModel>> =
userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
private val hasCancelButtonBeenClicked = MutableStateFlow(false)
/**
* Whether the observer should finish the experience. Once consumed, [onFinished] must be called
* by the consumer.
*/
val isFinishRequested: Flow<Boolean> = createFinishRequestedFlow()
/** Notifies that the user has clicked the cancel button. */
fun onCancelButtonClicked() {
hasCancelButtonBeenClicked.value = true
}
/**
* Notifies that the user experience is finished.
*
* Call this after consuming [isFinishRequested] with a `true` value in order to mark it as
* consumed such that the next consumer doesn't immediately finish itself.
*/
fun onFinished() {
hasCancelButtonBeenClicked.value = false
}
/** Notifies that the user has clicked the "open menu" button. */
fun onOpenMenuButtonClicked() {
_isMenuVisible.value = true
}
/**
* Notifies that the user has dismissed or closed the user action menu.
*
* Call this after consuming [isMenuVisible] with a `true` value in order to reset it to `false`
* such that the next consumer doesn't immediately show the menu again.
*/
fun onMenuClosed() {
_isMenuVisible.value = false
}
private fun createFinishRequestedFlow(): Flow<Boolean> {
var mostRecentSelectedUserId: Int? = null
var mostRecentIsInteractive: Boolean? = null
return combine(
// When the user is switched, we should finish.
userInteractor.selectedUser
.map { it.id }
.map {
val selectedUserChanged =
mostRecentSelectedUserId != null && mostRecentSelectedUserId != it
mostRecentSelectedUserId = it
selectedUserChanged
},
// When the screen turns off, we should finish.
powerInteractor.isInteractive.map {
val screenTurnedOff = mostRecentIsInteractive == true && !it
mostRecentIsInteractive = it
screenTurnedOff
},
// When the cancel button is clicked, we should finish.
hasCancelButtonBeenClicked,
) { selectedUserChanged, screenTurnedOff, cancelButtonClicked ->
selectedUserChanged || screenTurnedOff || cancelButtonClicked
}
}
private fun toViewModel(
model: UserModel,
): UserViewModel {
return UserViewModel(
viewKey = model.id,
name = model.name,
image = CircularDrawable(model.image),
isSelectionMarkerVisible = model.isSelected,
alpha =
if (model.isSelectable) {
LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA
} else {
LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA
},
onClicked = createOnSelectedCallback(model),
)
}
private fun toViewModel(
model: UserActionModel,
): UserActionViewModel {
return UserActionViewModel(
viewKey = model.ordinal.toLong(),
iconResourceId =
if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) {
R.drawable.ic_manage_users
} else {
LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
isAddUser = model == UserActionModel.ADD_USER,
isGuest = model == UserActionModel.ENTER_GUEST_MODE,
)
},
textResourceId =
if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) {
R.string.manage_users
} else {
LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
isGuest = model == UserActionModel.ENTER_GUEST_MODE,
isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
isGuestUserResetting = userInteractor.isGuestUserResetting,
isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
isAddUser = model == UserActionModel.ADD_USER,
)
},
onClicked = { userInteractor.executeAction(action = model) },
)
}
private fun createOnSelectedCallback(model: UserModel): (() -> Unit)? {
return if (!model.isSelectable) {
null
} else {
{ userInteractor.selectUser(model.id) }
}
}
class Factory
@Inject
constructor(
private val userInteractor: UserInteractor,
private val powerInteractor: PowerInteractor,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return UserSwitcherViewModel(
userInteractor = userInteractor,
powerInteractor = powerInteractor,
)
as T
}
}
}