blob: aeed5fc5928129185adcd822f504051c11cdf2b2 [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 com.android.systemui.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.ui.drawable.CircularDrawable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.user.domain.interactor.GuestUserInteractor
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 kotlin.math.ceil
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. */
@SysUISingleton
class UserSwitcherViewModel
@Inject
constructor(
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
) {
/** 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 { getMaxUserSwitcherItemColumns(it.size) }
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) } }
/** Whether the button to open the user action menu is visible. */
val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
private val hasCancelButtonBeenClicked = MutableStateFlow(false)
private val isFinishRequiredDueToExecutedAction = 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
isFinishRequiredDueToExecutedAction.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
}
/** Returns the maximum number of columns for user items in the user switcher. */
private fun getMaxUserSwitcherItemColumns(userCount: Int): Int {
return if (userCount < 5) {
4
} else {
ceil(userCount / 2.0).toInt()
}
}
private fun createFinishRequestedFlow(): Flow<Boolean> =
combine(
// When the cancel button is clicked, we should finish.
hasCancelButtonBeenClicked,
// If an executed action told us to finish, we should finish,
isFinishRequiredDueToExecutedAction,
) { cancelButtonClicked, executedActionFinish ->
cancelButtonClicked || executedActionFinish
}
private fun toViewModel(
model: UserModel,
): UserViewModel {
return UserViewModel(
viewKey = model.id,
name =
if (model.isGuest && model.isSelected) {
Text.Resource(com.android.settingslib.R.string.guest_exit_quick_settings_button)
} else {
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 =
LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
isAddUser = model == UserActionModel.ADD_USER,
isGuest = model == UserActionModel.ENTER_GUEST_MODE,
isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
isTablet = true,
),
textResourceId =
LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
isGuest = model == UserActionModel.ENTER_GUEST_MODE,
isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
isAddUser = model == UserActionModel.ADD_USER,
isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
isTablet = true,
),
onClicked = {
userInteractor.executeAction(action = model)
// We don't finish because we want to show a dialog over the full-screen UI and
// that dialog can be dismissed in case the user changes their mind and decides not
// to add a user.
//
// We finish for all other actions because they navigate us away from the
// full-screen experience or are destructive (like changing to the guest user).
val shouldFinish = model != UserActionModel.ADD_USER
if (shouldFinish) {
isFinishRequiredDueToExecutedAction.value = true
}
},
)
}
private fun createOnSelectedCallback(model: UserModel): (() -> Unit)? {
return if (!model.isSelectable) {
null
} else {
{ userInteractor.selectUser(model.id) }
}
}
}