1 /*
2  * Copyright 2024 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.photopicker.features.profileselector
18 
19 import android.content.Context
20 import androidx.compose.runtime.getValue
21 import androidx.compose.runtime.setValue
22 import androidx.lifecycle.ViewModel
23 import androidx.lifecycle.viewModelScope
24 import com.android.photopicker.core.selection.Selection
25 import com.android.photopicker.core.user.SwitchUserProfileResult
26 import com.android.photopicker.core.user.UserMonitor
27 import com.android.photopicker.core.user.UserProfile
28 import com.android.photopicker.data.model.Media
29 import dagger.hilt.android.lifecycle.HiltViewModel
30 import javax.inject.Inject
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.flow.SharingStarted
33 import kotlinx.coroutines.flow.StateFlow
34 import kotlinx.coroutines.flow.map
35 import kotlinx.coroutines.flow.stateIn
36 import kotlinx.coroutines.launch
37 
38 /**
39  * The view model for the Profile selector, it provides various [UserStatus] and [UserProfile] flows
40  * to the compose UI, and handles switching profiles when a UI element is selected.
41  */
42 @HiltViewModel
43 class ProfileSelectorViewModel
44 @Inject
45 constructor(
46     private val scopeOverride: CoroutineScope?,
47     private val selection: Selection<Media>,
48     private val userMonitor: UserMonitor,
49 ) : ViewModel() {
50 
51     companion object {
52         val TAG: String = ProfileSelectorFeature.TAG
53     }
54 
55     // Check if a scope override was injected before using the default [viewModelScope]
56     private val scope: CoroutineScope =
57         if (scopeOverride == null) {
58             this.viewModelScope
59         } else {
60             scopeOverride
61         }
62 
63     /** All of the profiles that are available to Photopicker */
64     val allProfiles: StateFlow<List<UserProfile>> =
65         userMonitor.userStatus
<lambda>null66             .map { it.allProfiles }
67             .stateIn(
68                 scope,
69                 SharingStarted.WhileSubscribed(),
70                 initialValue = userMonitor.userStatus.value.allProfiles
71             )
72 
73     /** The current active profile */
74     val selectedProfile: StateFlow<UserProfile> =
75         userMonitor.userStatus
<lambda>null76             .map { it.activeUserProfile }
77             .stateIn(
78                 scope,
79                 SharingStarted.WhileSubscribed(),
80                 initialValue = userMonitor.userStatus.value.activeUserProfile
81             )
82 
83     /**
84      * Request for the profile to be changed to the provided profile.
85      * This is not guaranteed to succeed (the profile could be disabled/unavailable etc)
86      *
87      * If it does succeed, this will also clear out any selected media since
88      * media cannot be selected from multiple profiles simultaneously.
89      */
requestSwitchUsernull90     fun requestSwitchUser(context: Context, requested: UserProfile) {
91         scope.launch {
92             val result = userMonitor.requestSwitchActiveUserProfile(requested, context)
93             if (result == SwitchUserProfileResult.SUCCESS) {
94                 // If the profile is actually changed, ensure the selection is cleared since
95                 // content cannot be chosen from multiple profiles simultaneously.
96                 selection.clear()
97             }
98         }
99     }
100 }
101