Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 12f65cd1 authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Update Volume Panel visuals for new redesign." into main

parents 557d6c40 4e15a5a4
Loading
Loading
Loading
Loading
+17 −4
Original line number Original line Diff line number Diff line
@@ -45,6 +45,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
import com.android.compose.animation.Expandable
import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
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.component.button.ui.viewmodel.ButtonViewModel
@@ -56,7 +57,7 @@ import kotlinx.coroutines.flow.StateFlow
/** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
/** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
class ButtonComponent(
class ButtonComponent(
    private val viewModelFlow: StateFlow<ButtonViewModel?>,
    private val viewModelFlow: StateFlow<ButtonViewModel?>,
    private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit
    private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit,
) : ComposeVolumePanelUiComponent {
) : ComposeVolumePanelUiComponent {


    @Composable
    @Composable
@@ -84,14 +85,26 @@ class ButtonComponent(
                        },
                        },
                    color =
                    color =
                        if (viewModel.isActive) {
                        if (viewModel.isActive) {
                            if (Flags.volumeRedesign()) {
                                MaterialTheme.colorScheme.primary
                            } else {
                                MaterialTheme.colorScheme.tertiaryContainer
                                MaterialTheme.colorScheme.tertiaryContainer
                            }
                        } else {
                            if (Flags.volumeRedesign()) {
                                MaterialTheme.colorScheme.surfaceContainerHigh
                            } else {
                            } else {
                                MaterialTheme.colorScheme.surface
                                MaterialTheme.colorScheme.surface
                            }
                        },
                        },
                    shape = RoundedCornerShape(20.dp),
                    shape = RoundedCornerShape(20.dp),
                    contentColor =
                    contentColor =
                        if (viewModel.isActive) {
                        if (viewModel.isActive) {
                            if (Flags.volumeRedesign()) {
                                MaterialTheme.colorScheme.onPrimary
                            } else {
                                MaterialTheme.colorScheme.onTertiaryContainer
                                MaterialTheme.colorScheme.onTertiaryContainer
                            }
                        } else {
                        } else {
                            MaterialTheme.colorScheme.onSurface
                            MaterialTheme.colorScheme.onSurface
                        },
                        },
+25 −10
Original line number Original line Diff line number Diff line
@@ -42,6 +42,7 @@ import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.Flags
import com.android.systemui.common.ui.compose.Icon
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.component.button.ui.viewmodel.ButtonViewModel
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
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. */
/** [ComposeVolumePanelUiComponent] implementing a toggleable button from a bottom row. */
class ToggleButtonComponent(
class ToggleButtonComponent(
    private val viewModelFlow: StateFlow<ButtonViewModel?>,
    private val viewModelFlow: StateFlow<ButtonViewModel?>,
    private val onCheckedChange: (isChecked: Boolean) -> Unit
    private val onCheckedChange: (isChecked: Boolean) -> Unit,
) : ComposeVolumePanelUiComponent {
) : ComposeVolumePanelUiComponent {


    @Composable
    @Composable
@@ -68,16 +69,30 @@ class ToggleButtonComponent(
            BottomComponentButtonSurface {
            BottomComponentButtonSurface {
                val colors =
                val colors =
                    if (viewModel.isActive) {
                    if (viewModel.isActive) {
                        if (Flags.volumeRedesign()) {
                            ButtonDefaults.buttonColors(
                                containerColor = MaterialTheme.colorScheme.primary,
                                contentColor = MaterialTheme.colorScheme.onPrimary,
                            )
                        } else {
                            ButtonDefaults.buttonColors(
                            ButtonDefaults.buttonColors(
                                containerColor = MaterialTheme.colorScheme.tertiaryContainer,
                                containerColor = MaterialTheme.colorScheme.tertiaryContainer,
                                contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
                                contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
                            )
                            )
                        }
                    } else {
                        if (Flags.volumeRedesign()) {
                            ButtonDefaults.buttonColors(
                                containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
                                contentColor = MaterialTheme.colorScheme.onSurface,
                            )
                        } else {
                        } else {
                            ButtonDefaults.buttonColors(
                            ButtonDefaults.buttonColors(
                                containerColor = Color.Transparent,
                                containerColor = Color.Transparent,
                                contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
                                contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
                            )
                            )
                        }
                        }
                    }
                Button(
                Button(
                    modifier =
                    modifier =
                        Modifier.fillMaxSize().padding(8.dp).semantics {
                        Modifier.fillMaxSize().padding(8.dp).semantics {
@@ -93,7 +108,7 @@ class ToggleButtonComponent(
                    onClick = { onCheckedChange(!viewModel.isActive) },
                    onClick = { onCheckedChange(!viewModel.isActive) },
                    shape = RoundedCornerShape(20.dp),
                    shape = RoundedCornerShape(20.dp),
                    colors = colors,
                    colors = colors,
                    contentPadding = PaddingValues(0.dp)
                    contentPadding = PaddingValues(0.dp),
                ) {
                ) {
                    Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
                    Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
                }
                }
+74 −9
Original line number Original line Diff line number Diff line
@@ -37,11 +37,13 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
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.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.PlatformIconButton
import com.android.compose.PlatformSliderColors
import com.android.compose.PlatformSliderColors
import com.android.compose.modifiers.padding
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.res.R
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel


@@ -84,7 +89,11 @@ fun ColumnVolumeSliders(
            val sliderPadding by topSliderPadding(isExpandable)
            val sliderPadding by topSliderPadding(isExpandable)


            VolumeSlider(
            VolumeSlider(
                modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(),
                modifier =
                    Modifier.thenIf(!Flags.volumeRedesign()) {
                            Modifier.padding(end = { sliderPadding.roundToPx() })
                        }
                        .fillMaxWidth(),
                state = sliderState,
                state = sliderState,
                onValueChange = { newValue: Float ->
                onValueChange = { newValue: Float ->
                    sliderViewModel.onValueChanged(sliderState, newValue)
                    sliderViewModel.onValueChanged(sliderState, newValue)
@@ -93,9 +102,22 @@ fun ColumnVolumeSliders(
                onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                sliderColors = sliderColors,
                sliderColors = sliderColors,
                hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
                hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
                button =
                    if (Flags.volumeRedesign()) {
                        {
                            ExpandButton(
                                isExpanded = isExpanded,
                                isExpandable = isExpandable,
                                onExpandedChanged = onExpandedChanged,
                            )
                        }
                    } else {
                        null
                    },
            )
            )


            ExpandButton(
            if (!Flags.volumeRedesign()) {
                ExpandButtonLegacy(
                    modifier = Modifier.align(Alignment.CenterEnd),
                    modifier = Modifier.align(Alignment.CenterEnd),
                    isExpanded = isExpanded,
                    isExpanded = isExpanded,
                    isExpandable = isExpandable,
                    isExpandable = isExpandable,
@@ -103,6 +125,7 @@ fun ColumnVolumeSliders(
                    sliderColors = sliderColors,
                    sliderColors = sliderColors,
                )
                )
            }
            }
        }
        AnimatedVisibility(
        AnimatedVisibility(
            visible = isExpanded || !isExpandable,
            visible = isExpanded || !isExpandable,
            label = "CollapsableSliders",
            label = "CollapsableSliders",
@@ -153,7 +176,7 @@ fun ColumnVolumeSliders(
}
}


@Composable
@Composable
private fun ExpandButton(
private fun ExpandButtonLegacy(
    isExpanded: Boolean,
    isExpanded: Boolean,
    isExpandable: Boolean,
    isExpandable: Boolean,
    onExpandedChanged: (Boolean) -> Unit,
    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 {
private fun enterTransition(index: Int, totalCount: Int): EnterTransition {
    val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0)
    val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0)
    val enterDuration = (EXPAND_DURATION_MILLIS - enterDelay).coerceAtLeast(100)
    val enterDuration = (EXPAND_DURATION_MILLIS - enterDelay).coerceAtLeast(100)
+105 −2
Original line number Original line Diff line number Diff line
@@ -24,9 +24,18 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.clickable
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.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.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.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
@@ -48,6 +57,7 @@ import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
import com.android.compose.PlatformSliderColors
import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.compose.modifiers.sysuiResTag
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(
fun VolumeSlider(
    state: SliderState,
    state: SliderState,
    onValueChange: (newValue: Float) -> Unit,
    onValueChange: (newValue: Float) -> Unit,
    onValueChangeFinished: (() -> Unit)? = null,
    onIconTapped: () -> Unit,
    onIconTapped: () -> Unit,
    sliderColors: PlatformSliderColors,
    modifier: Modifier = Modifier,
    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,
    sliderColors: PlatformSliderColors,
    hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
    hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
    modifier: Modifier = Modifier,
    onValueChangeFinished: (() -> Unit)? = null,
) {
) {
    val value by valueState(state)
    val value by valueState(state)
    val interactionSource = remember { MutableInteractionSource() }
    val interactionSource = remember { MutableInteractionSource() }
@@ -178,7 +281,7 @@ private fun valueState(state: SliderState): State<Float> {
    val shouldSkipAnimation =
    val shouldSkipAnimation =
        prevState is SliderState.Empty || prevState.isEnabled != state.isEnabled
        prevState is SliderState.Empty || prevState.isEnabled != state.isEnabled
    val value =
    val value =
        if (shouldSkipAnimation) mutableFloatStateOf(state.value)
        if (shouldSkipAnimation) remember { mutableFloatStateOf(state.value) }
        else animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
        else animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
    prevState = state
    prevState = state
    return value
    return value
+3 −3
Original line number Original line Diff line number Diff line
@@ -53,7 +53,7 @@ private enum class VolumeSliderContentComponent {
    DisabledMessage,
    DisabledMessage,
}
}


/** Shows label of the [VolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
/** Shows label of the [LegacyVolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
@Composable
@Composable
fun VolumeSliderContent(
fun VolumeSliderContent(
    label: String,
    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(
    override fun MeasureScope.measure(
        measurables: List<Measurable>,
        measurables: List<Measurable>,
        constraints: Constraints
        constraints: Constraints,
    ): MeasureResult {
    ): MeasureResult {
        val labelPlaceable =
        val labelPlaceable =
            measurables
            measurables
Loading