1 /* <lambda>null2 * Copyright (C) 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.systemui.volume.panel.component.mediaoutput.ui.viewmodel 18 19 import android.content.Context 20 import com.android.internal.logging.UiEventLogger 21 import com.android.systemui.animation.Expandable 22 import com.android.systemui.common.shared.model.Color 23 import com.android.systemui.common.shared.model.Icon 24 import com.android.systemui.res.R 25 import com.android.systemui.volume.domain.model.AudioOutputDevice 26 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor 27 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputComponentInteractor 28 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel 29 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope 30 import com.android.systemui.volume.panel.shared.model.Result 31 import com.android.systemui.volume.panel.shared.model.filterData 32 import com.android.systemui.volume.panel.ui.VolumePanelUiEvent 33 import javax.inject.Inject 34 import kotlinx.coroutines.CoroutineScope 35 import kotlinx.coroutines.ExperimentalCoroutinesApi 36 import kotlinx.coroutines.flow.SharingStarted 37 import kotlinx.coroutines.flow.StateFlow 38 import kotlinx.coroutines.flow.map 39 import kotlinx.coroutines.flow.stateIn 40 41 /** Models the UI of the Media Output Volume Panel component. */ 42 @OptIn(ExperimentalCoroutinesApi::class) 43 @VolumePanelScope 44 class MediaOutputViewModel 45 @Inject 46 constructor( 47 private val context: Context, 48 @VolumePanelScope private val coroutineScope: CoroutineScope, 49 private val actionsInteractor: MediaOutputActionsInteractor, 50 private val mediaOutputComponentInteractor: MediaOutputComponentInteractor, 51 private val uiEventLogger: UiEventLogger, 52 ) { 53 54 val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> = 55 mediaOutputComponentInteractor.mediaOutputModel 56 .filterData() 57 .map { mediaOutputModel -> 58 val label = 59 when (mediaOutputModel) { 60 is MediaOutputComponentModel.Idle -> { 61 context.getString(R.string.media_output_title_without_playing) 62 } 63 is MediaOutputComponentModel.MediaSession -> { 64 if (mediaOutputModel.isPlaybackActive) { 65 context.getString( 66 R.string.media_output_label_title, 67 mediaOutputModel.session.appLabel, 68 ) 69 } else { 70 context.getString(R.string.media_output_title_without_playing) 71 } 72 } 73 is MediaOutputComponentModel.Calling -> { 74 context.getString(R.string.media_output_title_ongoing_call) 75 } 76 } 77 ConnectedDeviceViewModel( 78 label, 79 if (mediaOutputModel.isInAudioSharing) { 80 context.getString(R.string.audio_sharing_description) 81 } else { 82 mediaOutputModel.device.name 83 }, 84 ) 85 } 86 .stateIn( 87 coroutineScope, 88 SharingStarted.Eagerly, 89 null, 90 ) 91 92 val deviceIconViewModel: StateFlow<DeviceIconViewModel?> = 93 mediaOutputComponentInteractor.mediaOutputModel 94 .filterData() 95 .map { mediaOutputModel -> 96 val icon: Icon = 97 mediaOutputModel.device 98 .takeIf { it !is AudioOutputDevice.Unknown } 99 ?.icon 100 ?.let { Icon.Loaded(it, null) } 101 ?: Icon.Resource(R.drawable.ic_media_home_devices, null) 102 val isPlaybackActive = 103 (mediaOutputModel as? MediaOutputComponentModel.MediaSession) 104 ?.isPlaybackActive == true 105 val isCalling = mediaOutputModel is MediaOutputComponentModel.Calling 106 if (isPlaybackActive || isCalling) { 107 DeviceIconViewModel.IsPlaying( 108 icon = icon, 109 iconColor = 110 Color.Attribute(com.android.internal.R.attr.materialColorSurface), 111 backgroundColor = 112 Color.Attribute(com.android.internal.R.attr.materialColorSecondary), 113 ) 114 } else { 115 DeviceIconViewModel.IsNotPlaying( 116 icon = icon, 117 iconColor = 118 Color.Attribute( 119 com.android.internal.R.attr.materialColorOnSurfaceVariant 120 ), 121 backgroundColor = 122 Color.Attribute(com.android.internal.R.attr.materialColorSurface), 123 ) 124 } 125 } 126 .stateIn( 127 coroutineScope, 128 SharingStarted.Eagerly, 129 null, 130 ) 131 132 val enabled: StateFlow<Boolean> = 133 mediaOutputComponentInteractor.mediaOutputModel 134 .filterData() 135 .map { !it.isInAudioSharing } 136 .stateIn( 137 coroutineScope, 138 SharingStarted.Eagerly, 139 true, 140 ) 141 142 fun onBarClick(expandable: Expandable?) { 143 uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED) 144 val result: Result<MediaOutputComponentModel> = 145 mediaOutputComponentInteractor.mediaOutputModel.value 146 actionsInteractor.onBarClick( 147 (result as? Result.Data<MediaOutputComponentModel>)?.data, 148 expandable 149 ) 150 } 151 } 152