Loading packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +116 −38 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.animation.expandVertically Loading @@ -28,10 +30,8 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size Loading @@ -39,6 +39,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment Loading @@ -49,15 +50,21 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.PlatformSliderColors import com.android.compose.modifiers.padding import com.android.systemui.res.R import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel private const val EXPAND_DURATION_MILLIS = 500 private const val COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS = 350 private const val COLLAPSE_DURATION_MILLIS = 300 private const val EXPAND_BUTTON_ANIMATION_DURATION_MILLIS = 350 private const val TOP_SLIDER_ANIMATION_DURATION_MILLIS = 400 private const val SHRINK_FRACTION = 0.55f private const val SCALE_FRACTION = 0.9f private const val EXPAND_BUTTON_SCALE = 0.8f /** Volume sliders laid out in a collapsable column */ @OptIn(ExperimentalAnimationApi::class) Loading @@ -73,14 +80,15 @@ fun ColumnVolumeSliders( require(viewModels.isNotEmpty()) val transition = updateTransition(isExpanded, label = "CollapsableSliders") Column(modifier = modifier) { Row( Box( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), ) { val sliderViewModel: SliderViewModel = viewModels.first() val sliderState by viewModels.first().slider.collectAsState() val sliderPadding by topSliderPadding(isExpandable) VolumeSlider( modifier = Modifier.weight(1f), modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(), state = sliderState, onValueChange = { newValue: Float -> sliderViewModel.onValueChanged(sliderState, newValue) Loading @@ -90,22 +98,14 @@ fun ColumnVolumeSliders( sliderColors = sliderColors, ) val expandButtonStateDescription = if (isExpanded) stringResource(R.string.volume_panel_expanded_sliders) else stringResource(R.string.volume_panel_collapsed_sliders) if (isExpandable) { ExpandButton( modifier = Modifier.semantics { role = Role.Switch stateDescription = expandButtonStateDescription }, modifier = Modifier.align(Alignment.CenterEnd), isExpanded = isExpanded, isExpandable = isExpandable, onExpandedChanged = onExpandedChanged, sliderColors = sliderColors, ) } } transition.AnimatedVisibility( visible = { it || !isExpandable }, enter = Loading Loading @@ -147,12 +147,29 @@ fun ColumnVolumeSliders( @Composable private fun ExpandButton( isExpanded: Boolean, isExpandable: Boolean, onExpandedChanged: (Boolean) -> Unit, sliderColors: PlatformSliderColors, modifier: Modifier = Modifier, ) { val expandButtonStateDescription = if (isExpanded) { stringResource(R.string.volume_panel_expanded_sliders) } else { stringResource(R.string.volume_panel_collapsed_sliders) } AnimatedVisibility( modifier = modifier, visible = isExpandable, enter = expandButtonEnterTransition(), exit = expandButtonExitTransition(), ) { IconButton( modifier = modifier.size(64.dp), modifier = Modifier.size(64.dp).semantics { role = Role.Switch stateDescription = expandButtonStateDescription }, onClick = { onExpandedChanged(!isExpanded) }, colors = IconButtonDefaults.filledIconButtonColors( Loading @@ -173,6 +190,7 @@ private fun ExpandButton( ) } } } private fun enterTransition(index: Int, totalCount: Int): EnterTransition { val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0) Loading Loading @@ -204,3 +222,63 @@ private fun exitTransition(index: Int, totalCount: Int): ExitTransition { ) + fadeOut(animationSpec = tween(durationMillis = exitDuration)) } private fun expandButtonEnterTransition(): EnterTransition { return fadeIn( tween( delayMillis = COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS, durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, ) ) + scaleIn( animationSpec = tween( delayMillis = COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS, durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, ), initialScale = EXPAND_BUTTON_SCALE, ) } private fun expandButtonExitTransition(): ExitTransition { return fadeOut( tween( delayMillis = EXPAND_DURATION_MILLIS, durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, ) ) + scaleOut( animationSpec = tween( delayMillis = EXPAND_DURATION_MILLIS, durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, ), targetScale = EXPAND_BUTTON_SCALE, ) } @Composable private fun topSliderPadding(isExpandable: Boolean): State<Dp> { val animationSpec: AnimationSpec<Dp> = if (isExpandable) { tween( delayMillis = COLLAPSE_DURATION_MILLIS, durationMillis = TOP_SLIDER_ANIMATION_DURATION_MILLIS, ) } else { tween( delayMillis = EXPAND_DURATION_MILLIS, durationMillis = TOP_SLIDER_ANIMATION_DURATION_MILLIS, ) } return animateDpAsState( targetValue = if (isExpandable) { 72.dp } else { 0.dp }, animationSpec = animationSpec, label = "TopVolumeSliderPadding" ) } packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt +11 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier import com.android.compose.PlatformSliderDefaults import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel import com.android.systemui.volume.panel.component.volume.ui.viewmodel.AudioVolumeComponentViewModel import com.android.systemui.volume.panel.component.volume.ui.viewmodel.SlidersExpandableViewModel import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope import com.android.systemui.volume.panel.ui.composable.isPortrait Loading @@ -48,13 +49,21 @@ constructor( modifier = modifier.fillMaxWidth(), ) } else { val isExpanded by viewModel.isExpanded.collectAsState() val expandableViewModel: SlidersExpandableViewModel by viewModel .isExpandable(isPortrait) .collectAsState(SlidersExpandableViewModel.Unavailable) if (expandableViewModel is SlidersExpandableViewModel.Unavailable) { return } val isExpanded = (expandableViewModel as? SlidersExpandableViewModel.Expandable)?.isExpanded ?: true ColumnVolumeSliders( viewModels = sliderViewModels, isExpanded = isExpanded, onExpandedChanged = viewModel::onExpandedChanged, sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(), isExpandable = isPortrait, isExpandable = expandableViewModel is SlidersExpandableViewModel.Expandable, modifier = modifier.fillMaxWidth(), ) } Loading packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt +40 −19 Original line number Diff line number Diff line Loading @@ -31,13 +31,15 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch Loading @@ -53,12 +55,39 @@ class AudioVolumeComponentViewModel constructor( @VolumePanelScope private val scope: CoroutineScope, mediaOutputInteractor: MediaOutputInteractor, private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory, private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory, streamsInteractor: AudioSlidersInteractor, ) { private val mutableIsExpanded = MutableStateFlow<Boolean?>(null) private val isPlaybackActive: Flow<Boolean?> = mediaOutputInteractor.defaultActiveMediaSession .filterData() .flatMapLatest { session -> if (session == null) { flowOf(false) } else { mediaDeviceSessionInteractor.playbackState(session).map { it?.isActive == true } } } .onEach { isPlaybackActive -> mutableIsExpanded.value = !isPlaybackActive } .stateIn(scope, SharingStarted.Eagerly, null) private val portraitExpandable: Flow<SlidersExpandableViewModel> = isPlaybackActive .filterNotNull() .flatMapLatest { isActive -> if (isActive) { mutableIsExpanded.filterNotNull().map { isExpanded -> SlidersExpandableViewModel.Expandable(isExpanded) } } else { flowOf(SlidersExpandableViewModel.Fixed) } } .stateIn(scope, SharingStarted.Eagerly, SlidersExpandableViewModel.Unavailable) val sliderViewModels: StateFlow<List<SliderViewModel>> = streamsInteractor.volumePanelSliders .transformLatest { sliderTypes -> Loading @@ -76,24 +105,16 @@ constructor( } .stateIn(scope, SharingStarted.Eagerly, emptyList()) private val mutableIsExpanded = MutableSharedFlow<Boolean>() val isExpanded: StateFlow<Boolean> = merge( mutableIsExpanded, mediaOutputInteractor.defaultActiveMediaSession.filterData().flatMapLatest { session -> if (session == null) flowOf(true) else mediaDeviceSessionInteractor.playbackState(session).map { it?.isActive != true fun isExpandable(isPortrait: Boolean): Flow<SlidersExpandableViewModel> { return if (isPortrait) { portraitExpandable } else { flowOf(SlidersExpandableViewModel.Fixed) } } }, ) .stateIn(scope, SharingStarted.Eagerly, false) fun onExpandedChanged(isExpanded: Boolean) { scope.launch { mutableIsExpanded.emit(isExpanded) } scope.launch { mutableIsExpanded.value = isExpanded } } private fun CoroutineScope.createSessionViewModel( Loading packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.volume.panel.component.volume.ui.viewmodel /** * Models expandability state of the * [com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent]. */ sealed interface SlidersExpandableViewModel { /** [SlidersExpandableViewModel] is not loaded. */ data object Unavailable : SlidersExpandableViewModel data class Expandable(val isExpanded: Boolean) : SlidersExpandableViewModel data object Fixed : SlidersExpandableViewModel } Loading
packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +116 −38 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.animation.expandVertically Loading @@ -28,10 +30,8 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size Loading @@ -39,6 +39,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment Loading @@ -49,15 +50,21 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.PlatformSliderColors import com.android.compose.modifiers.padding import com.android.systemui.res.R import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel private const val EXPAND_DURATION_MILLIS = 500 private const val COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS = 350 private const val COLLAPSE_DURATION_MILLIS = 300 private const val EXPAND_BUTTON_ANIMATION_DURATION_MILLIS = 350 private const val TOP_SLIDER_ANIMATION_DURATION_MILLIS = 400 private const val SHRINK_FRACTION = 0.55f private const val SCALE_FRACTION = 0.9f private const val EXPAND_BUTTON_SCALE = 0.8f /** Volume sliders laid out in a collapsable column */ @OptIn(ExperimentalAnimationApi::class) Loading @@ -73,14 +80,15 @@ fun ColumnVolumeSliders( require(viewModels.isNotEmpty()) val transition = updateTransition(isExpanded, label = "CollapsableSliders") Column(modifier = modifier) { Row( Box( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), ) { val sliderViewModel: SliderViewModel = viewModels.first() val sliderState by viewModels.first().slider.collectAsState() val sliderPadding by topSliderPadding(isExpandable) VolumeSlider( modifier = Modifier.weight(1f), modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(), state = sliderState, onValueChange = { newValue: Float -> sliderViewModel.onValueChanged(sliderState, newValue) Loading @@ -90,22 +98,14 @@ fun ColumnVolumeSliders( sliderColors = sliderColors, ) val expandButtonStateDescription = if (isExpanded) stringResource(R.string.volume_panel_expanded_sliders) else stringResource(R.string.volume_panel_collapsed_sliders) if (isExpandable) { ExpandButton( modifier = Modifier.semantics { role = Role.Switch stateDescription = expandButtonStateDescription }, modifier = Modifier.align(Alignment.CenterEnd), isExpanded = isExpanded, isExpandable = isExpandable, onExpandedChanged = onExpandedChanged, sliderColors = sliderColors, ) } } transition.AnimatedVisibility( visible = { it || !isExpandable }, enter = Loading Loading @@ -147,12 +147,29 @@ fun ColumnVolumeSliders( @Composable private fun ExpandButton( isExpanded: Boolean, isExpandable: Boolean, onExpandedChanged: (Boolean) -> Unit, sliderColors: PlatformSliderColors, modifier: Modifier = Modifier, ) { val expandButtonStateDescription = if (isExpanded) { stringResource(R.string.volume_panel_expanded_sliders) } else { stringResource(R.string.volume_panel_collapsed_sliders) } AnimatedVisibility( modifier = modifier, visible = isExpandable, enter = expandButtonEnterTransition(), exit = expandButtonExitTransition(), ) { IconButton( modifier = modifier.size(64.dp), modifier = Modifier.size(64.dp).semantics { role = Role.Switch stateDescription = expandButtonStateDescription }, onClick = { onExpandedChanged(!isExpanded) }, colors = IconButtonDefaults.filledIconButtonColors( Loading @@ -173,6 +190,7 @@ private fun ExpandButton( ) } } } private fun enterTransition(index: Int, totalCount: Int): EnterTransition { val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0) Loading Loading @@ -204,3 +222,63 @@ private fun exitTransition(index: Int, totalCount: Int): ExitTransition { ) + fadeOut(animationSpec = tween(durationMillis = exitDuration)) } private fun expandButtonEnterTransition(): EnterTransition { return fadeIn( tween( delayMillis = COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS, durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, ) ) + scaleIn( animationSpec = tween( delayMillis = COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS, durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, ), initialScale = EXPAND_BUTTON_SCALE, ) } private fun expandButtonExitTransition(): ExitTransition { return fadeOut( tween( delayMillis = EXPAND_DURATION_MILLIS, durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, ) ) + scaleOut( animationSpec = tween( delayMillis = EXPAND_DURATION_MILLIS, durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, ), targetScale = EXPAND_BUTTON_SCALE, ) } @Composable private fun topSliderPadding(isExpandable: Boolean): State<Dp> { val animationSpec: AnimationSpec<Dp> = if (isExpandable) { tween( delayMillis = COLLAPSE_DURATION_MILLIS, durationMillis = TOP_SLIDER_ANIMATION_DURATION_MILLIS, ) } else { tween( delayMillis = EXPAND_DURATION_MILLIS, durationMillis = TOP_SLIDER_ANIMATION_DURATION_MILLIS, ) } return animateDpAsState( targetValue = if (isExpandable) { 72.dp } else { 0.dp }, animationSpec = animationSpec, label = "TopVolumeSliderPadding" ) }
packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt +11 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier import com.android.compose.PlatformSliderDefaults import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel import com.android.systemui.volume.panel.component.volume.ui.viewmodel.AudioVolumeComponentViewModel import com.android.systemui.volume.panel.component.volume.ui.viewmodel.SlidersExpandableViewModel import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope import com.android.systemui.volume.panel.ui.composable.isPortrait Loading @@ -48,13 +49,21 @@ constructor( modifier = modifier.fillMaxWidth(), ) } else { val isExpanded by viewModel.isExpanded.collectAsState() val expandableViewModel: SlidersExpandableViewModel by viewModel .isExpandable(isPortrait) .collectAsState(SlidersExpandableViewModel.Unavailable) if (expandableViewModel is SlidersExpandableViewModel.Unavailable) { return } val isExpanded = (expandableViewModel as? SlidersExpandableViewModel.Expandable)?.isExpanded ?: true ColumnVolumeSliders( viewModels = sliderViewModels, isExpanded = isExpanded, onExpandedChanged = viewModel::onExpandedChanged, sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(), isExpandable = isPortrait, isExpandable = expandableViewModel is SlidersExpandableViewModel.Expandable, modifier = modifier.fillMaxWidth(), ) } Loading
packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt +40 −19 Original line number Diff line number Diff line Loading @@ -31,13 +31,15 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch Loading @@ -53,12 +55,39 @@ class AudioVolumeComponentViewModel constructor( @VolumePanelScope private val scope: CoroutineScope, mediaOutputInteractor: MediaOutputInteractor, private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory, private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory, streamsInteractor: AudioSlidersInteractor, ) { private val mutableIsExpanded = MutableStateFlow<Boolean?>(null) private val isPlaybackActive: Flow<Boolean?> = mediaOutputInteractor.defaultActiveMediaSession .filterData() .flatMapLatest { session -> if (session == null) { flowOf(false) } else { mediaDeviceSessionInteractor.playbackState(session).map { it?.isActive == true } } } .onEach { isPlaybackActive -> mutableIsExpanded.value = !isPlaybackActive } .stateIn(scope, SharingStarted.Eagerly, null) private val portraitExpandable: Flow<SlidersExpandableViewModel> = isPlaybackActive .filterNotNull() .flatMapLatest { isActive -> if (isActive) { mutableIsExpanded.filterNotNull().map { isExpanded -> SlidersExpandableViewModel.Expandable(isExpanded) } } else { flowOf(SlidersExpandableViewModel.Fixed) } } .stateIn(scope, SharingStarted.Eagerly, SlidersExpandableViewModel.Unavailable) val sliderViewModels: StateFlow<List<SliderViewModel>> = streamsInteractor.volumePanelSliders .transformLatest { sliderTypes -> Loading @@ -76,24 +105,16 @@ constructor( } .stateIn(scope, SharingStarted.Eagerly, emptyList()) private val mutableIsExpanded = MutableSharedFlow<Boolean>() val isExpanded: StateFlow<Boolean> = merge( mutableIsExpanded, mediaOutputInteractor.defaultActiveMediaSession.filterData().flatMapLatest { session -> if (session == null) flowOf(true) else mediaDeviceSessionInteractor.playbackState(session).map { it?.isActive != true fun isExpandable(isPortrait: Boolean): Flow<SlidersExpandableViewModel> { return if (isPortrait) { portraitExpandable } else { flowOf(SlidersExpandableViewModel.Fixed) } } }, ) .stateIn(scope, SharingStarted.Eagerly, false) fun onExpandedChanged(isExpanded: Boolean) { scope.launch { mutableIsExpanded.emit(isExpanded) } scope.launch { mutableIsExpanded.value = isExpanded } } private fun CoroutineScope.createSessionViewModel( Loading
packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.volume.panel.component.volume.ui.viewmodel /** * Models expandability state of the * [com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent]. */ sealed interface SlidersExpandableViewModel { /** [SlidersExpandableViewModel] is not loaded. */ data object Unavailable : SlidersExpandableViewModel data class Expandable(val isExpanded: Boolean) : SlidersExpandableViewModel data object Fixed : SlidersExpandableViewModel }