Loading packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt +165 −86 Original line number Original line Diff line number Diff line Loading @@ -16,38 +16,38 @@ package com.android.systemui.volume.panel.component.selector.ui.composable package com.android.systemui.volume.panel.component.selector.ui.composable import androidx.compose.animation.core.animateOffsetAsState import androidx.compose.animation.core.Animatable import androidx.compose.foundation.Canvas import androidx.compose.animation.core.VectorConverter import androidx.compose.foundation.background import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasurePolicy import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastFirst import kotlinx.coroutines.launch /** /** * Radio button group for the Volume Panel. It allows selecting a single item * Radio button group for the Volume Panel. It allows selecting a single item Loading @@ -65,8 +65,8 @@ fun VolumePanelRadioButtonBar( spacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultSpacing, spacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultSpacing, labelIndicatorBackgroundSpacing: Dp = labelIndicatorBackgroundSpacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultLabelIndicatorBackgroundSpacing, VolumePanelRadioButtonBarDefaults.DefaultLabelIndicatorBackgroundSpacing, indicatorCornerRadius: CornerRadius = indicatorCornerSize: CornerSize = VolumePanelRadioButtonBarDefaults.defaultIndicatorCornerRadius(), CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorCornerRadius), indicatorBackgroundCornerSize: CornerSize = indicatorBackgroundCornerSize: CornerSize = CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundCornerRadius), CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundCornerRadius), colors: VolumePanelRadioButtonBarColors = VolumePanelRadioButtonBarDefaults.defaultColors(), colors: VolumePanelRadioButtonBarColors = VolumePanelRadioButtonBarDefaults.defaultColors(), Loading @@ -76,60 +76,41 @@ fun VolumePanelRadioButtonBar( VolumePanelRadioButtonBarScopeImpl().apply(content).apply { VolumePanelRadioButtonBarScopeImpl().apply(content).apply { require(hasSelectedItem) { "At least one item should be selected" } require(hasSelectedItem) { "At least one item should be selected" } } } val items = scope.items val items = scope.items var selectedIndex by remember { mutableIntStateOf(items.indexOfFirst { it.isSelected }) } val coroutineScope = rememberCoroutineScope() val offsetAnimatable = remember { Animatable(UNSET_OFFSET, Int.VectorConverter) } var size by remember { mutableStateOf(IntSize(0, 0)) } Layout( val spacingPx = with(LocalDensity.current) { spacing.toPx() } modifier = modifier, val indicatorWidth = size.width / items.size - (spacingPx * (items.size - 1) / items.size) content = { val offset by Spacer( animateOffsetAsState( targetValue = Offset( selectedIndex * indicatorWidth + (spacingPx * selectedIndex), 0f, ), label = "VolumePanelRadioButtonOffsetAnimation", finishedListener = { for (itemIndex in items.indices) { val item = items[itemIndex] if (itemIndex == selectedIndex) { item.onItemSelected() break } } } ) Column(modifier = modifier) { Box(modifier = Modifier.height(IntrinsicSize.Max)) { Canvas( modifier = modifier = Modifier.fillMaxSize() Modifier.layoutId(RadioButtonBarComponent.ButtonsBackground) .background( .background( colors.indicatorBackgroundColor, colors.indicatorBackgroundColor, RoundedCornerShape(indicatorBackgroundCornerSize), RoundedCornerShape(indicatorBackgroundCornerSize), ) ) ) Spacer( modifier = Modifier.layoutId(RadioButtonBarComponent.Indicator) .offset { IntOffset(offsetAnimatable.value, 0) } .padding(indicatorBackgroundPadding) .padding(indicatorBackgroundPadding) .onGloballyPositioned { size = it.size } .background( ) { colors.indicatorColor, drawRoundRect( RoundedCornerShape(indicatorCornerSize), color = colors.indicatorColor, ) topLeft = offset, size = Size(indicatorWidth, size.height.toFloat()), cornerRadius = indicatorCornerRadius, ) ) } Row( Row( modifier = Modifier.padding(indicatorBackgroundPadding), modifier = Modifier.layoutId(RadioButtonBarComponent.Buttons) .padding(indicatorBackgroundPadding), horizontalArrangement = Arrangement.spacedBy(spacing) horizontalArrangement = Arrangement.spacedBy(spacing) ) { ) { for (itemIndex in items.indices) { for (itemIndex in items.indices) { TextButton( TextButton( modifier = Modifier.weight(1f), modifier = Modifier.weight(1f), onClick = { selectedIndex = itemIndex }, onClick = { items[itemIndex].onItemSelected() }, ) { ) { val item = items[itemIndex] val item = items[itemIndex] if (item.icon !== Empty) { if (item.icon !== Empty) { Loading @@ -138,11 +119,10 @@ fun VolumePanelRadioButtonBar( } } } } } } } Row( Row( modifier = modifier = Modifier.padding( Modifier.layoutId(RadioButtonBarComponent.Labels) .padding( start = indicatorBackgroundPadding, start = indicatorBackgroundPadding, top = labelIndicatorBackgroundSpacing, top = labelIndicatorBackgroundSpacing, end = indicatorBackgroundPadding end = indicatorBackgroundPadding Loading @@ -152,7 +132,7 @@ fun VolumePanelRadioButtonBar( for (itemIndex in items.indices) { for (itemIndex in items.indices) { TextButton( TextButton( modifier = Modifier.weight(1f), modifier = Modifier.weight(1f), onClick = { selectedIndex = itemIndex }, onClick = { items[itemIndex].onItemSelected() }, ) { ) { val item = items[itemIndex] val item = items[itemIndex] if (item.icon !== Empty) { if (item.icon !== Empty) { Loading @@ -161,6 +141,95 @@ fun VolumePanelRadioButtonBar( } } } } } } }, measurePolicy = with(LocalDensity.current) { val spacingPx = (spacing - indicatorBackgroundPadding * 2).roundToPx().coerceAtLeast(0) BarMeasurePolicy( buttonsCount = items.size, selectedIndex = scope.selectedIndex, spacingPx = spacingPx, ) { coroutineScope.launch { if (offsetAnimatable.value == UNSET_OFFSET) { offsetAnimatable.snapTo(it) } else { offsetAnimatable.animateTo(it) } } } }, ) } private class BarMeasurePolicy( private val buttonsCount: Int, private val selectedIndex: Int, private val spacingPx: Int, private val onTargetIndicatorOffsetMeasured: (Int) -> Unit, ) : MeasurePolicy { override fun MeasureScope.measure( measurables: List<Measurable>, constraints: Constraints ): MeasureResult { val fillWidthConstraints = constraints.copy(minWidth = constraints.maxWidth) val buttonsPlaceable: Placeable = measurables .fastFirst { it.layoutId == RadioButtonBarComponent.Buttons } .measure(fillWidthConstraints) val labelsPlaceable: Placeable = measurables .fastFirst { it.layoutId == RadioButtonBarComponent.Labels } .measure(fillWidthConstraints) val buttonsBackgroundPlaceable: Placeable = measurables .fastFirst { it.layoutId == RadioButtonBarComponent.ButtonsBackground } .measure( Constraints( minWidth = buttonsPlaceable.width, maxWidth = buttonsPlaceable.width, minHeight = buttonsPlaceable.height, maxHeight = buttonsPlaceable.height, ) ) val totalSpacing = spacingPx * (buttonsCount - 1) val indicatorWidth = (buttonsBackgroundPlaceable.width - totalSpacing) / buttonsCount val indicatorPlaceable: Placeable = measurables .fastFirst { it.layoutId == RadioButtonBarComponent.Indicator } .measure( Constraints( minWidth = indicatorWidth, maxWidth = indicatorWidth, minHeight = buttonsBackgroundPlaceable.height, maxHeight = buttonsBackgroundPlaceable.height, ) ) onTargetIndicatorOffsetMeasured( selectedIndex * indicatorWidth + (spacingPx * selectedIndex) ) return layout(constraints.maxWidth, buttonsPlaceable.height + labelsPlaceable.height) { buttonsBackgroundPlaceable.placeRelative( 0, 0, RadioButtonBarComponent.ButtonsBackground.zIndex, ) indicatorPlaceable.placeRelative(0, 0, RadioButtonBarComponent.Indicator.zIndex) buttonsPlaceable.placeRelative(0, 0, RadioButtonBarComponent.Buttons.zIndex) labelsPlaceable.placeRelative( 0, buttonsBackgroundPlaceable.height, RadioButtonBarComponent.Labels.zIndex, ) } } } } } Loading @@ -179,12 +248,6 @@ object VolumePanelRadioButtonBarDefaults { val DefaultIndicatorCornerRadius = 20.dp val DefaultIndicatorCornerRadius = 20.dp val DefaultIndicatorBackgroundCornerRadius = 20.dp val DefaultIndicatorBackgroundCornerRadius = 20.dp @Composable fun defaultIndicatorCornerRadius( x: Dp = DefaultIndicatorCornerRadius, y: Dp = DefaultIndicatorCornerRadius, ): CornerRadius = with(LocalDensity.current) { CornerRadius(x.toPx(), y.toPx()) } /** /** * Returns the default VolumePanelRadioButtonBar colors. * Returns the default VolumePanelRadioButtonBar colors. * * Loading Loading @@ -225,9 +288,12 @@ private val Empty: @Composable RowScope.() -> Unit = {} private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScope { private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScope { var hasSelectedItem: Boolean = false var selectedIndex: Int = UNSET_INDEX private set private set val hasSelectedItem: Boolean get() = selectedIndex != UNSET_INDEX private val mutableItems: MutableList<Item> = mutableListOf() private val mutableItems: MutableList<Item> = mutableListOf() val items: List<Item> = mutableItems val items: List<Item> = mutableItems Loading @@ -238,21 +304,34 @@ private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScop label: @Composable RowScope.() -> Unit, label: @Composable RowScope.() -> Unit, ) { ) { require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" } require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" } hasSelectedItem = hasSelectedItem || isSelected if (isSelected) { selectedIndex = mutableItems.size } mutableItems.add( mutableItems.add( Item( Item( isSelected = isSelected, onItemSelected = onItemSelected, onItemSelected = onItemSelected, icon = icon, icon = icon, label = label, label = label, ) ) ) ) } } private companion object { const val UNSET_INDEX = -1 } } } private class Item( private class Item( val isSelected: Boolean, val onItemSelected: () -> Unit, val onItemSelected: () -> Unit, val icon: @Composable RowScope.() -> Unit, val icon: @Composable RowScope.() -> Unit, val label: @Composable RowScope.() -> Unit, val label: @Composable RowScope.() -> Unit, ) ) private const val UNSET_OFFSET = -1 private enum class RadioButtonBarComponent(val zIndex: Float) { ButtonsBackground(0f), Indicator(1f), Buttons(2f), Labels(2f), } packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -65,7 +65,7 @@ constructor( return return } } val enabledModelStates by viewModel.spatialAudioButtonByEnabled.collectAsState() val enabledModelStates by viewModel.spatialAudioButtons.collectAsState() if (enabledModelStates.isEmpty()) { if (enabledModelStates.isEmpty()) { return return } } Loading packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -56,7 +56,7 @@ constructor( val isAvailable: StateFlow<Boolean> = val isAvailable: StateFlow<Boolean> = availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true) availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true) val spatialAudioButtonByEnabled: StateFlow<List<SpatialAudioButtonViewModel>> = val spatialAudioButtons: StateFlow<List<SpatialAudioButtonViewModel>> = combine(interactor.isEnabled, interactor.isAvailable) { currentIsEnabled, isAvailable -> combine(interactor.isEnabled, interactor.isAvailable) { currentIsEnabled, isAvailable -> SpatialAudioEnabledModel.values SpatialAudioEnabledModel.values .filter { .filter { Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt +165 −86 Original line number Original line Diff line number Diff line Loading @@ -16,38 +16,38 @@ package com.android.systemui.volume.panel.component.selector.ui.composable package com.android.systemui.volume.panel.component.selector.ui.composable import androidx.compose.animation.core.animateOffsetAsState import androidx.compose.animation.core.Animatable import androidx.compose.foundation.Canvas import androidx.compose.animation.core.VectorConverter import androidx.compose.foundation.background import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasurePolicy import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastFirst import kotlinx.coroutines.launch /** /** * Radio button group for the Volume Panel. It allows selecting a single item * Radio button group for the Volume Panel. It allows selecting a single item Loading @@ -65,8 +65,8 @@ fun VolumePanelRadioButtonBar( spacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultSpacing, spacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultSpacing, labelIndicatorBackgroundSpacing: Dp = labelIndicatorBackgroundSpacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultLabelIndicatorBackgroundSpacing, VolumePanelRadioButtonBarDefaults.DefaultLabelIndicatorBackgroundSpacing, indicatorCornerRadius: CornerRadius = indicatorCornerSize: CornerSize = VolumePanelRadioButtonBarDefaults.defaultIndicatorCornerRadius(), CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorCornerRadius), indicatorBackgroundCornerSize: CornerSize = indicatorBackgroundCornerSize: CornerSize = CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundCornerRadius), CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundCornerRadius), colors: VolumePanelRadioButtonBarColors = VolumePanelRadioButtonBarDefaults.defaultColors(), colors: VolumePanelRadioButtonBarColors = VolumePanelRadioButtonBarDefaults.defaultColors(), Loading @@ -76,60 +76,41 @@ fun VolumePanelRadioButtonBar( VolumePanelRadioButtonBarScopeImpl().apply(content).apply { VolumePanelRadioButtonBarScopeImpl().apply(content).apply { require(hasSelectedItem) { "At least one item should be selected" } require(hasSelectedItem) { "At least one item should be selected" } } } val items = scope.items val items = scope.items var selectedIndex by remember { mutableIntStateOf(items.indexOfFirst { it.isSelected }) } val coroutineScope = rememberCoroutineScope() val offsetAnimatable = remember { Animatable(UNSET_OFFSET, Int.VectorConverter) } var size by remember { mutableStateOf(IntSize(0, 0)) } Layout( val spacingPx = with(LocalDensity.current) { spacing.toPx() } modifier = modifier, val indicatorWidth = size.width / items.size - (spacingPx * (items.size - 1) / items.size) content = { val offset by Spacer( animateOffsetAsState( targetValue = Offset( selectedIndex * indicatorWidth + (spacingPx * selectedIndex), 0f, ), label = "VolumePanelRadioButtonOffsetAnimation", finishedListener = { for (itemIndex in items.indices) { val item = items[itemIndex] if (itemIndex == selectedIndex) { item.onItemSelected() break } } } ) Column(modifier = modifier) { Box(modifier = Modifier.height(IntrinsicSize.Max)) { Canvas( modifier = modifier = Modifier.fillMaxSize() Modifier.layoutId(RadioButtonBarComponent.ButtonsBackground) .background( .background( colors.indicatorBackgroundColor, colors.indicatorBackgroundColor, RoundedCornerShape(indicatorBackgroundCornerSize), RoundedCornerShape(indicatorBackgroundCornerSize), ) ) ) Spacer( modifier = Modifier.layoutId(RadioButtonBarComponent.Indicator) .offset { IntOffset(offsetAnimatable.value, 0) } .padding(indicatorBackgroundPadding) .padding(indicatorBackgroundPadding) .onGloballyPositioned { size = it.size } .background( ) { colors.indicatorColor, drawRoundRect( RoundedCornerShape(indicatorCornerSize), color = colors.indicatorColor, ) topLeft = offset, size = Size(indicatorWidth, size.height.toFloat()), cornerRadius = indicatorCornerRadius, ) ) } Row( Row( modifier = Modifier.padding(indicatorBackgroundPadding), modifier = Modifier.layoutId(RadioButtonBarComponent.Buttons) .padding(indicatorBackgroundPadding), horizontalArrangement = Arrangement.spacedBy(spacing) horizontalArrangement = Arrangement.spacedBy(spacing) ) { ) { for (itemIndex in items.indices) { for (itemIndex in items.indices) { TextButton( TextButton( modifier = Modifier.weight(1f), modifier = Modifier.weight(1f), onClick = { selectedIndex = itemIndex }, onClick = { items[itemIndex].onItemSelected() }, ) { ) { val item = items[itemIndex] val item = items[itemIndex] if (item.icon !== Empty) { if (item.icon !== Empty) { Loading @@ -138,11 +119,10 @@ fun VolumePanelRadioButtonBar( } } } } } } } Row( Row( modifier = modifier = Modifier.padding( Modifier.layoutId(RadioButtonBarComponent.Labels) .padding( start = indicatorBackgroundPadding, start = indicatorBackgroundPadding, top = labelIndicatorBackgroundSpacing, top = labelIndicatorBackgroundSpacing, end = indicatorBackgroundPadding end = indicatorBackgroundPadding Loading @@ -152,7 +132,7 @@ fun VolumePanelRadioButtonBar( for (itemIndex in items.indices) { for (itemIndex in items.indices) { TextButton( TextButton( modifier = Modifier.weight(1f), modifier = Modifier.weight(1f), onClick = { selectedIndex = itemIndex }, onClick = { items[itemIndex].onItemSelected() }, ) { ) { val item = items[itemIndex] val item = items[itemIndex] if (item.icon !== Empty) { if (item.icon !== Empty) { Loading @@ -161,6 +141,95 @@ fun VolumePanelRadioButtonBar( } } } } } } }, measurePolicy = with(LocalDensity.current) { val spacingPx = (spacing - indicatorBackgroundPadding * 2).roundToPx().coerceAtLeast(0) BarMeasurePolicy( buttonsCount = items.size, selectedIndex = scope.selectedIndex, spacingPx = spacingPx, ) { coroutineScope.launch { if (offsetAnimatable.value == UNSET_OFFSET) { offsetAnimatable.snapTo(it) } else { offsetAnimatable.animateTo(it) } } } }, ) } private class BarMeasurePolicy( private val buttonsCount: Int, private val selectedIndex: Int, private val spacingPx: Int, private val onTargetIndicatorOffsetMeasured: (Int) -> Unit, ) : MeasurePolicy { override fun MeasureScope.measure( measurables: List<Measurable>, constraints: Constraints ): MeasureResult { val fillWidthConstraints = constraints.copy(minWidth = constraints.maxWidth) val buttonsPlaceable: Placeable = measurables .fastFirst { it.layoutId == RadioButtonBarComponent.Buttons } .measure(fillWidthConstraints) val labelsPlaceable: Placeable = measurables .fastFirst { it.layoutId == RadioButtonBarComponent.Labels } .measure(fillWidthConstraints) val buttonsBackgroundPlaceable: Placeable = measurables .fastFirst { it.layoutId == RadioButtonBarComponent.ButtonsBackground } .measure( Constraints( minWidth = buttonsPlaceable.width, maxWidth = buttonsPlaceable.width, minHeight = buttonsPlaceable.height, maxHeight = buttonsPlaceable.height, ) ) val totalSpacing = spacingPx * (buttonsCount - 1) val indicatorWidth = (buttonsBackgroundPlaceable.width - totalSpacing) / buttonsCount val indicatorPlaceable: Placeable = measurables .fastFirst { it.layoutId == RadioButtonBarComponent.Indicator } .measure( Constraints( minWidth = indicatorWidth, maxWidth = indicatorWidth, minHeight = buttonsBackgroundPlaceable.height, maxHeight = buttonsBackgroundPlaceable.height, ) ) onTargetIndicatorOffsetMeasured( selectedIndex * indicatorWidth + (spacingPx * selectedIndex) ) return layout(constraints.maxWidth, buttonsPlaceable.height + labelsPlaceable.height) { buttonsBackgroundPlaceable.placeRelative( 0, 0, RadioButtonBarComponent.ButtonsBackground.zIndex, ) indicatorPlaceable.placeRelative(0, 0, RadioButtonBarComponent.Indicator.zIndex) buttonsPlaceable.placeRelative(0, 0, RadioButtonBarComponent.Buttons.zIndex) labelsPlaceable.placeRelative( 0, buttonsBackgroundPlaceable.height, RadioButtonBarComponent.Labels.zIndex, ) } } } } } Loading @@ -179,12 +248,6 @@ object VolumePanelRadioButtonBarDefaults { val DefaultIndicatorCornerRadius = 20.dp val DefaultIndicatorCornerRadius = 20.dp val DefaultIndicatorBackgroundCornerRadius = 20.dp val DefaultIndicatorBackgroundCornerRadius = 20.dp @Composable fun defaultIndicatorCornerRadius( x: Dp = DefaultIndicatorCornerRadius, y: Dp = DefaultIndicatorCornerRadius, ): CornerRadius = with(LocalDensity.current) { CornerRadius(x.toPx(), y.toPx()) } /** /** * Returns the default VolumePanelRadioButtonBar colors. * Returns the default VolumePanelRadioButtonBar colors. * * Loading Loading @@ -225,9 +288,12 @@ private val Empty: @Composable RowScope.() -> Unit = {} private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScope { private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScope { var hasSelectedItem: Boolean = false var selectedIndex: Int = UNSET_INDEX private set private set val hasSelectedItem: Boolean get() = selectedIndex != UNSET_INDEX private val mutableItems: MutableList<Item> = mutableListOf() private val mutableItems: MutableList<Item> = mutableListOf() val items: List<Item> = mutableItems val items: List<Item> = mutableItems Loading @@ -238,21 +304,34 @@ private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScop label: @Composable RowScope.() -> Unit, label: @Composable RowScope.() -> Unit, ) { ) { require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" } require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" } hasSelectedItem = hasSelectedItem || isSelected if (isSelected) { selectedIndex = mutableItems.size } mutableItems.add( mutableItems.add( Item( Item( isSelected = isSelected, onItemSelected = onItemSelected, onItemSelected = onItemSelected, icon = icon, icon = icon, label = label, label = label, ) ) ) ) } } private companion object { const val UNSET_INDEX = -1 } } } private class Item( private class Item( val isSelected: Boolean, val onItemSelected: () -> Unit, val onItemSelected: () -> Unit, val icon: @Composable RowScope.() -> Unit, val icon: @Composable RowScope.() -> Unit, val label: @Composable RowScope.() -> Unit, val label: @Composable RowScope.() -> Unit, ) ) private const val UNSET_OFFSET = -1 private enum class RadioButtonBarComponent(val zIndex: Float) { ButtonsBackground(0f), Indicator(1f), Buttons(2f), Labels(2f), }
packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -65,7 +65,7 @@ constructor( return return } } val enabledModelStates by viewModel.spatialAudioButtonByEnabled.collectAsState() val enabledModelStates by viewModel.spatialAudioButtons.collectAsState() if (enabledModelStates.isEmpty()) { if (enabledModelStates.isEmpty()) { return return } } Loading
packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -56,7 +56,7 @@ constructor( val isAvailable: StateFlow<Boolean> = val isAvailable: StateFlow<Boolean> = availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true) availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true) val spatialAudioButtonByEnabled: StateFlow<List<SpatialAudioButtonViewModel>> = val spatialAudioButtons: StateFlow<List<SpatialAudioButtonViewModel>> = combine(interactor.isEnabled, interactor.isAvailable) { currentIsEnabled, isAvailable -> combine(interactor.isEnabled, interactor.isAvailable) { currentIsEnabled, isAvailable -> SpatialAudioEnabledModel.values SpatialAudioEnabledModel.values .filter { .filter { Loading