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