Loading packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +45 −12 Original line number Diff line number Diff line Loading @@ -25,13 +25,19 @@ import androidx.compose.animation.togetherWith 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.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.systemGestureExclusion import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue Loading @@ -44,6 +50,7 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSliderDefaults Loading Loading @@ -71,6 +78,7 @@ import com.android.systemui.qs.ui.composable.QuickSettingsShade.systemGestureExc import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.composable.Overlay import com.android.systemui.shade.ui.composable.OverlayShade Loading Loading @@ -291,19 +299,44 @@ fun ContentScope.QuickSettingsLayout( enabled = { layoutState.transitionState is TransitionState.Idle } ) ) { VolumeSlider( Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { VolumeSlider( modifier = Modifier.weight(1f), showLabel = false, state = volumeSliderState, onValueChange = { newValue: Float -> volumeSliderViewModel.onValueChanged(volumeSliderState, newValue) }, onValueChangeFinished = { volumeSliderViewModel.onValueChangeFinished() }, onValueChangeFinished = { volumeSliderViewModel.onValueChangeFinished() }, onIconTapped = { volumeSliderViewModel.toggleMuted(volumeSliderState) }, sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(), hapticsViewModelFactory = volumeSliderViewModel.getSliderHapticsViewModelFactory(), ) IconButton( colors = IconButtonDefaults.iconButtonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary, ), onClick = { viewModel.detailsViewModel.onVolumeSettingsButtonClicked( viewModel.audioDetailsViewModelFactory.create() ) }, ) { Icon( painterResource(R.drawable.horizontal_ellipsis), // TODO(b/378513663): Update the placeholder content description contentDescription = "Volume settings", ) } } } } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt +6 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.dialog.audioDetailsViewModelFactory import com.android.systemui.shade.domain.interactor.disableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.testKosmos Loading Loading @@ -86,6 +87,11 @@ class DetailsViewModelTest : SysuiTestCase() { assertThat(underTest.onTileClicked(specNoDetails)).isFalse() assertThat(underTest.activeTileDetails).isNull() // Click on the volume settings button. underTest.onVolumeSettingsButtonClicked(audioDetailsViewModelFactory.create()) assertThat(underTest.activeTileDetails).isNotNull() assertThat(underTest.activeTileDetails?.title).isEqualTo("Volume") underTest.closeDetailedView() assertThat(underTest.activeTileDetails).isNull() Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt +13 −3 Original line number Diff line number Diff line Loading @@ -23,14 +23,17 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.dialog.AudioDetailsViewModel import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import javax.inject.Inject @SysUISingleton @Stable class DetailsViewModel @Inject constructor( class DetailsViewModel @Inject constructor( val currentTilesInteractor: CurrentTilesInteractor, val shadeModeInteractor: ShadeModeInteractor val shadeModeInteractor: ShadeModeInteractor, ) { /** Loading Loading @@ -72,4 +75,11 @@ class DetailsViewModel @Inject constructor( _activeTileDetails.value = detailsViewModel } ?: false } /** Update the active [TileDetailsViewModel] to [AudioDetailsViewModel]. */ fun onVolumeSettingsButtonClicked(audioDetailsViewModel: AudioDetailsViewModel?) { if (shadeModeInteractor.isDualShade) { _activeTileDetails.value = audioDetailsViewModel } } } packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/AudioDetailsViewModel.kt 0 → 100644 +56 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.qs.tiles.dialog import android.content.Intent import android.provider.Settings import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.tiles.base.domain.actions.QSTileIntentUserInputHandler import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation class AudioDetailsViewModel @AssistedInject constructor(private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler) : TileDetailsViewModel, ExclusiveActivatable() { // TODO(b/378513663): Implement this AudioDetailsViewModel override val title: String get() = "Volume" override val subTitle: String get() = "" override fun clickOnSettingsButton() { qsTileIntentUserActionHandler.handle( /* expandable= */ null, Intent(Settings.ACTION_SOUND_SETTINGS), ) } override suspend fun onActivated(): Nothing { // TODO(b/378513663): Create the VolumePanelViewModel here awaitCancellation() } @AssistedFactory fun interface Factory { fun create(): AudioDetailsViewModel } } packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt +2 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel import com.android.systemui.qs.tiles.dialog.AudioDetailsViewModel import com.android.systemui.res.R import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor Loading @@ -60,6 +61,7 @@ constructor( @ShadeDisplayAware shadeContext: Context, brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory, audioStreamSliderViewModelFactory: AudioStreamSliderViewModel.Factory, val audioDetailsViewModelFactory: AudioDetailsViewModel.Factory, shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory, tileGridViewModelFactory: TileGridViewModel.Factory, @Assisted private val supportsBrightnessMirroring: Boolean, Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +45 −12 Original line number Diff line number Diff line Loading @@ -25,13 +25,19 @@ import androidx.compose.animation.togetherWith 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.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.systemGestureExclusion import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue Loading @@ -44,6 +50,7 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSliderDefaults Loading Loading @@ -71,6 +78,7 @@ import com.android.systemui.qs.ui.composable.QuickSettingsShade.systemGestureExc import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.composable.Overlay import com.android.systemui.shade.ui.composable.OverlayShade Loading Loading @@ -291,19 +299,44 @@ fun ContentScope.QuickSettingsLayout( enabled = { layoutState.transitionState is TransitionState.Idle } ) ) { VolumeSlider( Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { VolumeSlider( modifier = Modifier.weight(1f), showLabel = false, state = volumeSliderState, onValueChange = { newValue: Float -> volumeSliderViewModel.onValueChanged(volumeSliderState, newValue) }, onValueChangeFinished = { volumeSliderViewModel.onValueChangeFinished() }, onValueChangeFinished = { volumeSliderViewModel.onValueChangeFinished() }, onIconTapped = { volumeSliderViewModel.toggleMuted(volumeSliderState) }, sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(), hapticsViewModelFactory = volumeSliderViewModel.getSliderHapticsViewModelFactory(), ) IconButton( colors = IconButtonDefaults.iconButtonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary, ), onClick = { viewModel.detailsViewModel.onVolumeSettingsButtonClicked( viewModel.audioDetailsViewModelFactory.create() ) }, ) { Icon( painterResource(R.drawable.horizontal_ellipsis), // TODO(b/378513663): Update the placeholder content description contentDescription = "Volume settings", ) } } } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt +6 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.dialog.audioDetailsViewModelFactory import com.android.systemui.shade.domain.interactor.disableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.testKosmos Loading Loading @@ -86,6 +87,11 @@ class DetailsViewModelTest : SysuiTestCase() { assertThat(underTest.onTileClicked(specNoDetails)).isFalse() assertThat(underTest.activeTileDetails).isNull() // Click on the volume settings button. underTest.onVolumeSettingsButtonClicked(audioDetailsViewModelFactory.create()) assertThat(underTest.activeTileDetails).isNotNull() assertThat(underTest.activeTileDetails?.title).isEqualTo("Volume") underTest.closeDetailedView() assertThat(underTest.activeTileDetails).isNull() Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt +13 −3 Original line number Diff line number Diff line Loading @@ -23,14 +23,17 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.dialog.AudioDetailsViewModel import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import javax.inject.Inject @SysUISingleton @Stable class DetailsViewModel @Inject constructor( class DetailsViewModel @Inject constructor( val currentTilesInteractor: CurrentTilesInteractor, val shadeModeInteractor: ShadeModeInteractor val shadeModeInteractor: ShadeModeInteractor, ) { /** Loading Loading @@ -72,4 +75,11 @@ class DetailsViewModel @Inject constructor( _activeTileDetails.value = detailsViewModel } ?: false } /** Update the active [TileDetailsViewModel] to [AudioDetailsViewModel]. */ fun onVolumeSettingsButtonClicked(audioDetailsViewModel: AudioDetailsViewModel?) { if (shadeModeInteractor.isDualShade) { _activeTileDetails.value = audioDetailsViewModel } } }
packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/AudioDetailsViewModel.kt 0 → 100644 +56 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.qs.tiles.dialog import android.content.Intent import android.provider.Settings import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.tiles.base.domain.actions.QSTileIntentUserInputHandler import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation class AudioDetailsViewModel @AssistedInject constructor(private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler) : TileDetailsViewModel, ExclusiveActivatable() { // TODO(b/378513663): Implement this AudioDetailsViewModel override val title: String get() = "Volume" override val subTitle: String get() = "" override fun clickOnSettingsButton() { qsTileIntentUserActionHandler.handle( /* expandable= */ null, Intent(Settings.ACTION_SOUND_SETTINGS), ) } override suspend fun onActivated(): Nothing { // TODO(b/378513663): Create the VolumePanelViewModel here awaitCancellation() } @AssistedFactory fun interface Factory { fun create(): AudioDetailsViewModel } }
packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt +2 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel import com.android.systemui.qs.tiles.dialog.AudioDetailsViewModel import com.android.systemui.res.R import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor Loading @@ -60,6 +61,7 @@ constructor( @ShadeDisplayAware shadeContext: Context, brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory, audioStreamSliderViewModelFactory: AudioStreamSliderViewModel.Factory, val audioDetailsViewModelFactory: AudioDetailsViewModel.Factory, shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory, tileGridViewModelFactory: TileGridViewModel.Factory, @Assisted private val supportsBrightnessMirroring: Boolean, Loading