summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
blob: afd72e7ed1beae969f5ed2fdc1e8313bcef36cb8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/*
 * 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(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) }
        }
    }
}