Loading packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt +53 −3 Original line number Diff line number Diff line Loading @@ -17,13 +17,20 @@ package com.android.compose.animation import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.layout import androidx.compose.ui.node.LayoutModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import kotlin.math.roundToInt /** A component that can bounce in one dimension, for instance when it is tapped. */ @Stable interface Bounceable { val bounce: Dp } Loading @@ -46,6 +53,7 @@ interface Bounceable { * RTL layouts) side. This can be used for grids for which the last item does not align perfectly * with the end of the grid. */ @Stable fun Modifier.bounceable( bounceable: Bounceable, previousBounceable: Bounceable?, Loading @@ -53,7 +61,47 @@ fun Modifier.bounceable( orientation: Orientation, bounceEnd: Boolean = nextBounceable != null, ): Modifier { return layout { measurable, constraints -> return this then BounceableElement(bounceable, previousBounceable, nextBounceable, orientation, bounceEnd) } private data class BounceableElement( private val bounceable: Bounceable, private val previousBounceable: Bounceable?, private val nextBounceable: Bounceable?, private val orientation: Orientation, private val bounceEnd: Boolean, ) : ModifierNodeElement<BounceableNode>() { override fun create(): BounceableNode { return BounceableNode( bounceable, previousBounceable, nextBounceable, orientation, bounceEnd, ) } override fun update(node: BounceableNode) { node.bounceable = bounceable node.previousBounceable = previousBounceable node.nextBounceable = nextBounceable node.orientation = orientation node.bounceEnd = bounceEnd } } private class BounceableNode( var bounceable: Bounceable, var previousBounceable: Bounceable?, var nextBounceable: Bounceable?, var orientation: Orientation, var bounceEnd: Boolean = nextBounceable != null, ) : Modifier.Node(), LayoutModifierNode { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints, ): MeasureResult { // The constraints in the orientation should be fixed, otherwise there is no way to know // what the size of our child node will be without this animation code. checkFixedSize(constraints, orientation) Loading @@ -61,10 +109,12 @@ fun Modifier.bounceable( var sizePrevious = 0f var sizeNext = 0f val previousBounceable = previousBounceable if (previousBounceable != null) { sizePrevious += bounceable.bounce.toPx() - previousBounceable.bounce.toPx() } val nextBounceable = nextBounceable if (nextBounceable != null) { sizeNext += bounceable.bounce.toPx() - nextBounceable.bounce.toPx() } else if (bounceEnd) { Loading @@ -84,7 +134,7 @@ fun Modifier.bounceable( // constraints, otherwise the parent will automatically center this node given the // size that it expects us to be. This allows us to then place the element where we // want it to be. layout(idleWidth, placeable.height) { return layout(idleWidth, placeable.height) { placeable.placeRelative(-sizePrevious.roundToInt(), 0) } } Loading @@ -95,7 +145,7 @@ fun Modifier.bounceable( constraints.copy(minHeight = animatedHeight, maxHeight = animatedHeight) val placeable = measurable.measure(animatedConstraints) layout(placeable.width, idleHeight) { return layout(placeable.width, idleHeight) { placeable.placeRelative(0, -sizePrevious.roundToInt()) } } Loading packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt +7 −3 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ package com.android.systemui.compose.modifiers import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.semantics Loading @@ -26,7 +26,11 @@ import androidx.compose.ui.semantics.testTagsAsResourceId * Set a test tag on this node so that it is associated with [resId]. This node will then be * accessible by integration tests using `sysuiResSelector(resId)`. */ @OptIn(ExperimentalComposeUiApi::class) @Stable fun Modifier.sysuiResTag(resId: String): Modifier { return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId") // TODO(b/372412931): Only compose the semantics modifier once, at the root of the SystemUI // window. return this.then(TestTagAsResourceIdModifier).testTag("com.android.systemui:id/$resId") } private val TestTagAsResourceIdModifier = Modifier.semantics { testTagsAsResourceId = true } packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +25 −16 Original line number Diff line number Diff line Loading @@ -141,7 +141,7 @@ fun FooterActions( mutableStateOf<FooterActionsForegroundServicesButtonViewModel?>(null) } var userSwitcher by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) } var power by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) } var power by remember { mutableStateOf(viewModel.initialPower()) } LaunchedEffect( context, Loading Loading @@ -218,23 +218,19 @@ fun FooterActions( } val useModifierBasedExpandable = remember { QSComposeFragment.isEnabled } security?.let { SecurityButton(it, useModifierBasedExpandable, Modifier.weight(1f)) } foregroundServices?.let { ForegroundServicesButton(it, useModifierBasedExpandable) } userSwitcher?.let { SecurityButton({ security }, useModifierBasedExpandable, Modifier.weight(1f)) ForegroundServicesButton({ foregroundServices }, useModifierBasedExpandable) IconButton( it, { userSwitcher }, useModifierBasedExpandable, Modifier.sysuiResTag("multi_user_switch"), ) } IconButton( viewModel.settings, { viewModel.settings }, useModifierBasedExpandable, Modifier.sysuiResTag("settings_button_container"), ) power?.let { IconButton(it, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite")) } IconButton({ power }, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite")) } } } Loading @@ -242,10 +238,11 @@ fun FooterActions( /** The security button. */ @Composable private fun SecurityButton( model: FooterActionsSecurityButtonViewModel, model: () -> FooterActionsSecurityButtonViewModel?, useModifierBasedExpandable: Boolean, modifier: Modifier = Modifier, ) { val model = model() ?: return val onClick: ((Expandable) -> Unit)? = model.onClick?.let { onClick -> val context = LocalContext.current Loading @@ -265,9 +262,10 @@ private fun SecurityButton( /** The foreground services button. */ @Composable private fun RowScope.ForegroundServicesButton( model: FooterActionsForegroundServicesButtonViewModel, model: () -> FooterActionsForegroundServicesButtonViewModel?, useModifierBasedExpandable: Boolean, ) { val model = model() ?: return if (model.displayText) { TextButton( Icon.Resource(R.drawable.ic_info_outline, contentDescription = null), Loading @@ -288,6 +286,17 @@ private fun RowScope.ForegroundServicesButton( } } /** A button with an icon. */ @Composable fun IconButton( model: () -> FooterActionsButtonViewModel?, useModifierBasedExpandable: Boolean, modifier: Modifier = Modifier, ) { val model = model() ?: return IconButton(model, useModifierBasedExpandable, modifier) } /** A button with an icon. */ @Composable fun IconButton( Loading packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt +6 −11 Original line number Diff line number Diff line Loading @@ -165,16 +165,11 @@ fun BrightnessSlider( val activeIconColor = colors.activeTickColor val inactiveIconColor = colors.inactiveTickColor val trackIcon: DrawScope.(Offset, Color, Float) -> Unit = remember(painter) { val trackIcon: DrawScope.(Offset, Color, Float) -> Unit = remember { { offset, color, alpha -> translate(offset.x + IconPadding.toPx(), offset.y) { with(painter) { draw( IconSize.toSize(), colorFilter = ColorFilter.tint(color), alpha = alpha, ) draw(IconSize.toSize(), colorFilter = ColorFilter.tint(color), alpha = alpha) } } } Loading packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt +2 −0 Original line number Diff line number Diff line Loading @@ -19,11 +19,13 @@ package com.android.systemui.common.shared.model import android.annotation.AttrRes import android.annotation.ColorInt import android.annotation.ColorRes import androidx.compose.runtime.Stable /** * Models a color that can be either a specific [Color.Loaded] value or a resolvable theme * [Color.Attribute] */ @Stable sealed interface Color { data class Loaded(@ColorInt val color: Int) : Color Loading Loading
packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt +53 −3 Original line number Diff line number Diff line Loading @@ -17,13 +17,20 @@ package com.android.compose.animation import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.layout import androidx.compose.ui.node.LayoutModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import kotlin.math.roundToInt /** A component that can bounce in one dimension, for instance when it is tapped. */ @Stable interface Bounceable { val bounce: Dp } Loading @@ -46,6 +53,7 @@ interface Bounceable { * RTL layouts) side. This can be used for grids for which the last item does not align perfectly * with the end of the grid. */ @Stable fun Modifier.bounceable( bounceable: Bounceable, previousBounceable: Bounceable?, Loading @@ -53,7 +61,47 @@ fun Modifier.bounceable( orientation: Orientation, bounceEnd: Boolean = nextBounceable != null, ): Modifier { return layout { measurable, constraints -> return this then BounceableElement(bounceable, previousBounceable, nextBounceable, orientation, bounceEnd) } private data class BounceableElement( private val bounceable: Bounceable, private val previousBounceable: Bounceable?, private val nextBounceable: Bounceable?, private val orientation: Orientation, private val bounceEnd: Boolean, ) : ModifierNodeElement<BounceableNode>() { override fun create(): BounceableNode { return BounceableNode( bounceable, previousBounceable, nextBounceable, orientation, bounceEnd, ) } override fun update(node: BounceableNode) { node.bounceable = bounceable node.previousBounceable = previousBounceable node.nextBounceable = nextBounceable node.orientation = orientation node.bounceEnd = bounceEnd } } private class BounceableNode( var bounceable: Bounceable, var previousBounceable: Bounceable?, var nextBounceable: Bounceable?, var orientation: Orientation, var bounceEnd: Boolean = nextBounceable != null, ) : Modifier.Node(), LayoutModifierNode { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints, ): MeasureResult { // The constraints in the orientation should be fixed, otherwise there is no way to know // what the size of our child node will be without this animation code. checkFixedSize(constraints, orientation) Loading @@ -61,10 +109,12 @@ fun Modifier.bounceable( var sizePrevious = 0f var sizeNext = 0f val previousBounceable = previousBounceable if (previousBounceable != null) { sizePrevious += bounceable.bounce.toPx() - previousBounceable.bounce.toPx() } val nextBounceable = nextBounceable if (nextBounceable != null) { sizeNext += bounceable.bounce.toPx() - nextBounceable.bounce.toPx() } else if (bounceEnd) { Loading @@ -84,7 +134,7 @@ fun Modifier.bounceable( // constraints, otherwise the parent will automatically center this node given the // size that it expects us to be. This allows us to then place the element where we // want it to be. layout(idleWidth, placeable.height) { return layout(idleWidth, placeable.height) { placeable.placeRelative(-sizePrevious.roundToInt(), 0) } } Loading @@ -95,7 +145,7 @@ fun Modifier.bounceable( constraints.copy(minHeight = animatedHeight, maxHeight = animatedHeight) val placeable = measurable.measure(animatedConstraints) layout(placeable.width, idleHeight) { return layout(placeable.width, idleHeight) { placeable.placeRelative(0, -sizePrevious.roundToInt()) } } Loading
packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt +7 −3 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ package com.android.systemui.compose.modifiers import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.semantics Loading @@ -26,7 +26,11 @@ import androidx.compose.ui.semantics.testTagsAsResourceId * Set a test tag on this node so that it is associated with [resId]. This node will then be * accessible by integration tests using `sysuiResSelector(resId)`. */ @OptIn(ExperimentalComposeUiApi::class) @Stable fun Modifier.sysuiResTag(resId: String): Modifier { return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId") // TODO(b/372412931): Only compose the semantics modifier once, at the root of the SystemUI // window. return this.then(TestTagAsResourceIdModifier).testTag("com.android.systemui:id/$resId") } private val TestTagAsResourceIdModifier = Modifier.semantics { testTagsAsResourceId = true }
packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +25 −16 Original line number Diff line number Diff line Loading @@ -141,7 +141,7 @@ fun FooterActions( mutableStateOf<FooterActionsForegroundServicesButtonViewModel?>(null) } var userSwitcher by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) } var power by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) } var power by remember { mutableStateOf(viewModel.initialPower()) } LaunchedEffect( context, Loading Loading @@ -218,23 +218,19 @@ fun FooterActions( } val useModifierBasedExpandable = remember { QSComposeFragment.isEnabled } security?.let { SecurityButton(it, useModifierBasedExpandable, Modifier.weight(1f)) } foregroundServices?.let { ForegroundServicesButton(it, useModifierBasedExpandable) } userSwitcher?.let { SecurityButton({ security }, useModifierBasedExpandable, Modifier.weight(1f)) ForegroundServicesButton({ foregroundServices }, useModifierBasedExpandable) IconButton( it, { userSwitcher }, useModifierBasedExpandable, Modifier.sysuiResTag("multi_user_switch"), ) } IconButton( viewModel.settings, { viewModel.settings }, useModifierBasedExpandable, Modifier.sysuiResTag("settings_button_container"), ) power?.let { IconButton(it, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite")) } IconButton({ power }, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite")) } } } Loading @@ -242,10 +238,11 @@ fun FooterActions( /** The security button. */ @Composable private fun SecurityButton( model: FooterActionsSecurityButtonViewModel, model: () -> FooterActionsSecurityButtonViewModel?, useModifierBasedExpandable: Boolean, modifier: Modifier = Modifier, ) { val model = model() ?: return val onClick: ((Expandable) -> Unit)? = model.onClick?.let { onClick -> val context = LocalContext.current Loading @@ -265,9 +262,10 @@ private fun SecurityButton( /** The foreground services button. */ @Composable private fun RowScope.ForegroundServicesButton( model: FooterActionsForegroundServicesButtonViewModel, model: () -> FooterActionsForegroundServicesButtonViewModel?, useModifierBasedExpandable: Boolean, ) { val model = model() ?: return if (model.displayText) { TextButton( Icon.Resource(R.drawable.ic_info_outline, contentDescription = null), Loading @@ -288,6 +286,17 @@ private fun RowScope.ForegroundServicesButton( } } /** A button with an icon. */ @Composable fun IconButton( model: () -> FooterActionsButtonViewModel?, useModifierBasedExpandable: Boolean, modifier: Modifier = Modifier, ) { val model = model() ?: return IconButton(model, useModifierBasedExpandable, modifier) } /** A button with an icon. */ @Composable fun IconButton( Loading
packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt +6 −11 Original line number Diff line number Diff line Loading @@ -165,16 +165,11 @@ fun BrightnessSlider( val activeIconColor = colors.activeTickColor val inactiveIconColor = colors.inactiveTickColor val trackIcon: DrawScope.(Offset, Color, Float) -> Unit = remember(painter) { val trackIcon: DrawScope.(Offset, Color, Float) -> Unit = remember { { offset, color, alpha -> translate(offset.x + IconPadding.toPx(), offset.y) { with(painter) { draw( IconSize.toSize(), colorFilter = ColorFilter.tint(color), alpha = alpha, ) draw(IconSize.toSize(), colorFilter = ColorFilter.tint(color), alpha = alpha) } } } Loading
packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt +2 −0 Original line number Diff line number Diff line Loading @@ -19,11 +19,13 @@ package com.android.systemui.common.shared.model import android.annotation.AttrRes import android.annotation.ColorInt import android.annotation.ColorRes import androidx.compose.runtime.Stable /** * Models a color that can be either a specific [Color.Loaded] value or a resolvable theme * [Color.Attribute] */ @Stable sealed interface Color { data class Loaded(@ColorInt val color: Int) : Color Loading