diff options
8 files changed, 313 insertions, 58 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt index 6d03118645f3..0e35e1d83d6d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable +import com.android.systemui.Flags import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel @@ -56,7 +57,7 @@ import kotlinx.coroutines.flow.StateFlow /** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */ class ButtonComponent( private val viewModelFlow: StateFlow<ButtonViewModel?>, - private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit + private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit, ) : ComposeVolumePanelUiComponent { @Composable @@ -84,14 +85,26 @@ class ButtonComponent( }, color = if (viewModel.isActive) { - MaterialTheme.colorScheme.tertiaryContainer + if (Flags.volumeRedesign()) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.tertiaryContainer + } } else { - MaterialTheme.colorScheme.surface + if (Flags.volumeRedesign()) { + MaterialTheme.colorScheme.surfaceContainerHigh + } else { + MaterialTheme.colorScheme.surface + } }, shape = RoundedCornerShape(20.dp), contentColor = if (viewModel.isActive) { - MaterialTheme.colorScheme.onTertiaryContainer + if (Flags.volumeRedesign()) { + MaterialTheme.colorScheme.onPrimary + } else { + MaterialTheme.colorScheme.onTertiaryContainer + } } else { MaterialTheme.colorScheme.onSurface }, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt index bb2daecd3a25..2cd73040fa3f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.Flags import com.android.systemui.common.ui.compose.Icon import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent @@ -51,7 +52,7 @@ import kotlinx.coroutines.flow.StateFlow /** [ComposeVolumePanelUiComponent] implementing a toggleable button from a bottom row. */ class ToggleButtonComponent( private val viewModelFlow: StateFlow<ButtonViewModel?>, - private val onCheckedChange: (isChecked: Boolean) -> Unit + private val onCheckedChange: (isChecked: Boolean) -> Unit, ) : ComposeVolumePanelUiComponent { @Composable @@ -68,15 +69,29 @@ class ToggleButtonComponent( BottomComponentButtonSurface { val colors = if (viewModel.isActive) { - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer, - ) + if (Flags.volumeRedesign()) { + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ) + } else { + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer, + ) + } } else { - ButtonDefaults.buttonColors( - containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.onSurfaceVariant, - ) + if (Flags.volumeRedesign()) { + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor = MaterialTheme.colorScheme.onSurface, + ) + } else { + ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } } Button( modifier = @@ -93,7 +108,7 @@ class ToggleButtonComponent( onClick = { onCheckedChange(!viewModel.isActive) }, shape = RoundedCornerShape(20.dp), colors = colors, - contentPadding = PaddingValues(0.dp) + contentPadding = PaddingValues(0.dp), ) { Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt index 581fb9d77c59..25892c5a75cc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt @@ -37,11 +37,13 @@ import androidx.compose.foundation.layout.size 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.State import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role @@ -51,8 +53,11 @@ import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.PlatformIconButton import com.android.compose.PlatformSliderColors import com.android.compose.modifiers.padding +import com.android.compose.modifiers.thenIf +import com.android.systemui.Flags import com.android.systemui.res.R import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel @@ -84,7 +89,11 @@ fun ColumnVolumeSliders( val sliderPadding by topSliderPadding(isExpandable) VolumeSlider( - modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(), + modifier = + Modifier.thenIf(!Flags.volumeRedesign()) { + Modifier.padding(end = { sliderPadding.roundToPx() }) + } + .fillMaxWidth(), state = sliderState, onValueChange = { newValue: Float -> sliderViewModel.onValueChanged(sliderState, newValue) @@ -93,15 +102,29 @@ fun ColumnVolumeSliders( onIconTapped = { sliderViewModel.toggleMuted(sliderState) }, sliderColors = sliderColors, hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(), + button = + if (Flags.volumeRedesign()) { + { + ExpandButton( + isExpanded = isExpanded, + isExpandable = isExpandable, + onExpandedChanged = onExpandedChanged, + ) + } + } else { + null + }, ) - ExpandButton( - modifier = Modifier.align(Alignment.CenterEnd), - isExpanded = isExpanded, - isExpandable = isExpandable, - onExpandedChanged = onExpandedChanged, - sliderColors = sliderColors, - ) + if (!Flags.volumeRedesign()) { + ExpandButtonLegacy( + modifier = Modifier.align(Alignment.CenterEnd), + isExpanded = isExpanded, + isExpandable = isExpandable, + onExpandedChanged = onExpandedChanged, + sliderColors = sliderColors, + ) + } } AnimatedVisibility( visible = isExpanded || !isExpandable, @@ -153,7 +176,7 @@ fun ColumnVolumeSliders( } @Composable -private fun ExpandButton( +private fun ExpandButtonLegacy( isExpanded: Boolean, isExpandable: Boolean, onExpandedChanged: (Boolean) -> Unit, @@ -200,6 +223,48 @@ private fun ExpandButton( } } +@Composable +private fun ExpandButton( + isExpanded: Boolean, + isExpandable: Boolean, + onExpandedChanged: (Boolean) -> Unit, + 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(), + ) { + PlatformIconButton( + modifier = + Modifier.size(width = 48.dp, height = 40.dp).semantics { + role = Role.Switch + stateDescription = expandButtonStateDescription + }, + onClick = { onExpandedChanged(!isExpanded) }, + colors = + IconButtonDefaults.iconButtonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant, + ), + iconResource = + if (isExpanded) { + R.drawable.ic_arrow_down_24dp + } else { + R.drawable.ic_arrow_up_24dp + }, + contentDescription = null, + ) + } +} + private fun enterTransition(index: Int, totalCount: Int): EnterTransition { val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0) val enterDuration = (EXPAND_DURATION_MILLIS - enterDelay).coerceAtLeast(100) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 97ce429cf938..fa5f72bc0997 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -24,9 +24,18 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.interaction.MutableInteractionSource +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.getValue @@ -48,6 +57,7 @@ import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp import com.android.compose.PlatformSlider import com.android.compose.PlatformSliderColors +import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.compose.modifiers.sysuiResTag @@ -61,11 +71,104 @@ import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.Sl fun VolumeSlider( state: SliderState, onValueChange: (newValue: Float) -> Unit, - onValueChangeFinished: (() -> Unit)? = null, onIconTapped: () -> Unit, + sliderColors: PlatformSliderColors, modifier: Modifier = Modifier, + hapticsViewModelFactory: SliderHapticsViewModel.Factory?, + onValueChangeFinished: (() -> Unit)? = null, + button: (@Composable () -> Unit)? = null, +) { + if (!Flags.volumeRedesign()) { + LegacyVolumeSlider( + state = state, + onValueChange = onValueChange, + onIconTapped = onIconTapped, + sliderColors = sliderColors, + onValueChangeFinished = onValueChangeFinished, + modifier = modifier, + hapticsViewModelFactory = hapticsViewModelFactory, + ) + return + } + + val value by valueState(state) + Column(modifier) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.fillMaxWidth(), + ) { + state.icon?.let { + Icon( + icon = it, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(40.dp).padding(8.dp), + ) + } + Text( + text = state.label, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.weight(1f).align(Alignment.CenterVertically), + ) + button?.invoke() + } + Slider( + value = value, + valueRange = state.valueRange, + onValueChange = onValueChange, + onValueChangeFinished = onValueChangeFinished, + enabled = state.isEnabled, + modifier = + Modifier.height(40.dp).sysuiResTag(state.label).clearAndSetSemantics { + if (state.isEnabled) { + contentDescription = state.label + state.a11yClickDescription?.let { + customActions = + listOf( + CustomAccessibilityAction(it) { + onIconTapped() + true + } + ) + } + + state.a11yStateDescription?.let { stateDescription = it } + progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) + } else { + disabled() + contentDescription = + state.disabledMessage?.let { "${state.label}, $it" } ?: state.label + } + setProgress { targetValue -> + val targetDirection = + when { + targetValue > value -> 1 + targetValue < value -> -1 + else -> 0 + } + + val newValue = + (value + targetDirection * state.a11yStep).coerceIn( + state.valueRange.start, + state.valueRange.endInclusive, + ) + onValueChange(newValue) + true + } + }, + ) + } +} + +@Composable +private fun LegacyVolumeSlider( + state: SliderState, + onValueChange: (newValue: Float) -> Unit, + onIconTapped: () -> Unit, sliderColors: PlatformSliderColors, hapticsViewModelFactory: SliderHapticsViewModel.Factory?, + modifier: Modifier = Modifier, + onValueChangeFinished: (() -> Unit)? = null, ) { val value by valueState(state) val interactionSource = remember { MutableInteractionSource() } @@ -178,7 +281,7 @@ private fun valueState(state: SliderState): State<Float> { val shouldSkipAnimation = prevState is SliderState.Empty || prevState.isEnabled != state.isEnabled val value = - if (shouldSkipAnimation) mutableFloatStateOf(state.value) + if (shouldSkipAnimation) remember { mutableFloatStateOf(state.value) } else animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation") prevState = state return value diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt index 4ae4eb875953..28226ff05ee9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt @@ -53,7 +53,7 @@ private enum class VolumeSliderContentComponent { DisabledMessage, } -/** Shows label of the [VolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */ +/** Shows label of the [LegacyVolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */ @Composable fun VolumeSliderContent( label: String, @@ -89,7 +89,7 @@ fun VolumeSliderContent( } } }, - measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled) + measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled), ) } @@ -102,7 +102,7 @@ private class VolumeSliderContentMeasurePolicy(private val isEnabled: Boolean) : override fun MeasureScope.measure( measurables: List<Measurable>, - constraints: Constraints + constraints: Constraints, ): MeasureResult { val labelPlaceable = measurables diff --git a/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml new file mode 100644 index 000000000000..0640116a4e97 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960"> + <path + android:fillColor="#000000" + android:pathData="M480,616 L240,376l56,-56 184,184 184,-184 56,56 -240,240Z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml new file mode 100644 index 000000000000..65a3eef4bdf1 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960"> + <path + android:fillColor="#000000" + android:pathData="M480,432 L296,616l-56,-56 240,-240 240,240 -56,56 -184,-184Z" /> +</vector> diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt index e565de5c55ea..02747d7e6996 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt @@ -17,7 +17,9 @@ package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel import android.content.Context +import android.graphics.Color as GraphicsColor import com.android.internal.logging.UiEventLogger +import com.android.systemui.Flags import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Color import com.android.systemui.common.shared.model.Icon @@ -77,7 +79,13 @@ constructor( ConnectedDeviceViewModel( label = label, labelColor = - Color.Attribute(com.android.internal.R.attr.materialColorOnSurfaceVariant), + if (Flags.volumeRedesign()) { + Color.Attribute(com.android.internal.R.attr.materialColorOnSurface) + } else { + Color.Attribute( + com.android.internal.R.attr.materialColorOnSurfaceVariant + ) + }, deviceName = if (mediaOutputModel.isInAudioSharing) { context.getString(R.string.audio_sharing_description) @@ -96,11 +104,7 @@ constructor( }, ) } - .stateIn( - coroutineScope, - SharingStarted.Eagerly, - null, - ) + .stateIn(coroutineScope, SharingStarted.Eagerly, null) val deviceIconViewModel: StateFlow<DeviceIconViewModel?> = mediaOutputComponentInteractor.mediaOutputModel @@ -121,7 +125,15 @@ constructor( icon = icon, iconColor = if (mediaOutputModel.canOpenAudioSwitcher) { - Color.Attribute(com.android.internal.R.attr.materialColorSurface) + if (Flags.volumeRedesign()) { + Color.Attribute( + com.android.internal.R.attr.materialColorOnPrimary + ) + } else { + Color.Attribute( + com.android.internal.R.attr.materialColorSurface + ) + } } else { Color.Attribute( com.android.internal.R.attr.materialColorSurfaceContainerHighest @@ -129,7 +141,15 @@ constructor( }, backgroundColor = if (mediaOutputModel.canOpenAudioSwitcher) { - Color.Attribute(com.android.internal.R.attr.materialColorSecondary) + if (Flags.volumeRedesign()) { + Color.Attribute( + com.android.internal.R.attr.materialColorPrimary + ) + } else { + Color.Attribute( + com.android.internal.R.attr.materialColorSecondary + ) + } } else { Color.Attribute(com.android.internal.R.attr.materialColorOutline) }, @@ -139,38 +159,29 @@ constructor( icon = icon, iconColor = if (mediaOutputModel.canOpenAudioSwitcher) { - Color.Attribute( - com.android.internal.R.attr.materialColorOnSurfaceVariant - ) + if (Flags.volumeRedesign()) { + Color.Attribute( + com.android.internal.R.attr.materialColorPrimary + ) + } else { + Color.Attribute( + com.android.internal.R.attr.materialColorOnSurfaceVariant + ) + } } else { Color.Attribute(com.android.internal.R.attr.materialColorOutline) }, - backgroundColor = - if (mediaOutputModel.canOpenAudioSwitcher) { - Color.Attribute(com.android.internal.R.attr.materialColorSurface) - } else { - Color.Attribute( - com.android.internal.R.attr.materialColorSurfaceContainerHighest - ) - }, + backgroundColor = Color.Loaded(GraphicsColor.TRANSPARENT), ) } } - .stateIn( - coroutineScope, - SharingStarted.Eagerly, - null, - ) + .stateIn(coroutineScope, SharingStarted.Eagerly, null) val enabled: StateFlow<Boolean> = mediaOutputComponentInteractor.mediaOutputModel .filterData() .map { it.canOpenAudioSwitcher } - .stateIn( - coroutineScope, - SharingStarted.Eagerly, - true, - ) + .stateIn(coroutineScope, SharingStarted.Eagerly, true) fun onBarClick(expandable: Expandable?) { uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED) @@ -178,7 +189,7 @@ constructor( mediaOutputComponentInteractor.mediaOutputModel.value actionsInteractor.onBarClick( (result as? Result.Data<MediaOutputComponentModel>)?.data, - expandable + expandable, ) } } |