Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +25 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.graphics.drawable.Animatable import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable import android.text.TextUtils import androidx.annotation.VisibleForTesting import androidx.compose.animation.animateColorAsState import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter Loading Loading @@ -67,6 +68,7 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorProducer import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.painter.Painter Loading Loading @@ -107,6 +109,8 @@ import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState import com.android.systemui.qs.ui.compose.borderOnFocus import com.android.systemui.res.R import kotlin.math.abs import platform.test.motion.compose.values.MotionTestValueKey import platform.test.motion.compose.values.motionTestValues private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target" Loading @@ -122,6 +126,7 @@ fun LargeTileContent( isVisible: () -> Boolean = { true }, accessibilityUiState: AccessibilityUiState? = null, iconShape: RoundedCornerShape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius), textScale: () -> Float = { 1f }, toggleClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null, ) { Loading Loading @@ -178,7 +183,7 @@ fun LargeTileContent( colors = colors, accessibilityUiState = accessibilityUiState, isVisible = isVisible, modifier = Modifier.weight(1f), modifier = Modifier.weight(1f).bounceScale(TransformOrigin(0f, .5f), textScale), ) if (sideDrawable != null) { Loading Loading @@ -381,6 +386,25 @@ private fun DrawScope.drawFadedEdge(startX: Float, endX: Float, colors: List<Col ) } fun Modifier.bounceScale( transformOrigin: TransformOrigin = TransformOrigin.Center, scale: () -> Float, ): Modifier { return motionTestValues { scale() exportAs TileBounceMotionTestKeys.BounceScale } .graphicsLayer { scale().let { scaleY = it scaleX = it this.transformOrigin = transformOrigin } } } @VisibleForTesting object TileBounceMotionTestKeys { val BounceScale = MotionTestValueKey<Float>("bounceScale") } object CommonTileDefaults { val IconSize = 32.dp val LargeTileIconSize = 28.dp Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +17 −11 Original line number Diff line number Diff line Loading @@ -229,19 +229,21 @@ fun Tile( hapticsViewModel?.setTileInteractionState( TileHapticsViewModel.TileInteractionState.CLICKED ) if (uiState.accessibilityUiState.toggleableState != null) { // Bounce unless we're a large dual target tile. These don't toggle on // main click. if (iconOnly || !isDualTarget) { val bounceContainer = uiState.isToggleable && (iconOnly || !isDualTarget) coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() // Bounce the tile's container if it is toggleable and is not a large // dual target tile. These don't toggle on main click. Otherwise bounce // the content of the tile. if (bounceContainer) { currentBounceableInfo.bounceable.animateContainerBounce() } else { currentBounceableInfo.bounceable.animateContentBounce(iconOnly) } } if (uiState.isToggleable && iconOnly) { // And show footer text feedback for icons if (iconOnly) { requestToggleTextFeedback(tile.spec) } } }, onLongClick = longClick, accessibilityUiState = uiState.accessibilityUiState, Loading @@ -253,7 +255,10 @@ fun Tile( SmallTileContent( iconProvider = iconProvider, color = colors.icon, modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center).bounceScale { bounceableInfo.bounceable.iconBounceScale }, ) } else { val iconShape by TileDefaults.animateIconShapeAsState(uiState.state) Loading @@ -277,6 +282,7 @@ fun Tile( accessibilityUiState = uiState.accessibilityUiState, squishiness = squishiness, isVisible = isVisible, textScale = { bounceableInfo.bounceable.textBounceScale }, modifier = Modifier.largeTilePadding(isDualTarget = uiState.handlesLongClick), ) Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt +56 −5 Original line number Diff line number Diff line Loading @@ -17,22 +17,73 @@ package com.android.systemui.qs.panels.ui.viewmodel import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.tween import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.animation.Bounceable class BounceableTileViewModel : Bounceable { private val animatableBounce = Animatable(0.dp, Dp.VectorConverter) private val animatableContainerBounce = Animatable(0.dp, Dp.VectorConverter) private val animatableIconBounceScale = Animatable(1f) private val animatableTextBounceScale = Animatable(1f) override val bounce: Dp get() = animatableBounce.value get() = animatableContainerBounce.value val iconBounceScale: Float get() = animatableIconBounceScale.value val textBounceScale: Float get() = animatableTextBounceScale.value suspend fun animateContainerBounce() { animatableContainerBounce.animateToBounce(BounceSize) animatableContainerBounce.animateToRest(ContainerBounceAtRest) } suspend fun animateContentBounce(iconOnly: Boolean) { if (iconOnly) { animateIconBounce() } else { animateTextBounce() } } private suspend fun animateIconBounce() { animatableIconBounceScale.animateToBounce(ICON_BOUNCE_SCALE) animatableIconBounceScale.animateToRest(SCALE_BOUNCE_AT_REST) } private suspend fun animateTextBounce() { animatableTextBounceScale.animateToBounce(TEXT_BOUNCE_SCALE) animatableTextBounceScale.animateToRest(SCALE_BOUNCE_AT_REST) } private suspend fun <T, V : AnimationVector> Animatable<T, V>.animateToBounce(targetValue: T) { animateTo( targetValue, tween(durationMillis = BOUNCE_DURATION_MILLIS, easing = BounceStartEasing), ) } suspend fun animateBounce() { animatableBounce.animateTo(BounceSize) animatableBounce.animateTo(0.dp) private suspend fun <T, V : AnimationVector> Animatable<T, V>.animateToRest(targetValue: T) { animateTo( targetValue, tween(durationMillis = BOUNCE_DURATION_MILLIS, easing = BounceEndEasing), ) } private companion object { val BounceSize = 8.dp val BounceStartEasing = CubicBezierEasing(.05f, 0f, 0f, 1f) val BounceEndEasing = CubicBezierEasing(1f, 0f, .95f, 1f) val ContainerBounceAtRest = 0.dp const val ICON_BOUNCE_SCALE = 1.1f const val TEXT_BOUNCE_SCALE = 1.06f const val SCALE_BOUNCE_AT_REST = 1f const val BOUNCE_DURATION_MILLIS = 167 } } packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +4 −1 Original line number Diff line number Diff line Loading @@ -43,7 +43,10 @@ data class TileUiState( val handlesSecondaryClick: Boolean, val sideDrawable: Drawable?, val accessibilityUiState: AccessibilityUiState, ) ) { val isToggleable: Boolean get() = accessibilityUiState.toggleableState != null } data class AccessibilityUiState( val contentDescription: String, Loading packages/SystemUI/tests/goldens/containerBounce_bounceEndDisabled.json 0 → 100644 +252 −0 Original line number Diff line number Diff line { "frame_ids": [ "before", 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 272, 288, 304, 320, 336, 352, 368, "after" ], "features": [ { "name": "tile-small_previous", "type": "dpSize", "data_points": [ { "width": 96.85714, "height": 72 }, { "width": 96.85714, "height": 72 }, { "width": 96.85714, "height": 72 }, { "width": 93.71429, "height": 72 }, { "width": 92.28571, "height": 72 }, { "width": 91.14286, "height": 72 }, { "width": 90.28571, "height": 72 }, { "width": 89.71429, "height": 72 }, { "width": 89.42857, "height": 72 }, { "width": 89.14286, "height": 72 }, { "width": 89.14286, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 89.14286, "height": 72 }, { "width": 89.42857, "height": 72 }, { "width": 89.71429, "height": 72 }, { "width": 90, "height": 72 }, { "width": 90.85714, "height": 72 }, { "width": 91.71429, "height": 72 }, { "width": 92.85714, "height": 72 }, { "width": 95.42857, "height": 72 }, { "width": 96.85714, "height": 72 } ] }, { "name": "tile-small_clicked", "type": "dpSize", "data_points": [ { "width": 96.85714, "height": 72 }, { "width": 96.85714, "height": 72 }, { "width": 96.85714, "height": 72 }, { "width": 100, "height": 72 }, { "width": 101.42857, "height": 72 }, { "width": 102.57143, "height": 72 }, { "width": 103.42857, "height": 72 }, { "width": 104, "height": 72 }, { "width": 104.28571, "height": 72 }, { "width": 104.57143, "height": 72 }, { "width": 104.57143, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.57143, "height": 72 }, { "width": 104.28571, "height": 72 }, { "width": 104, "height": 72 }, { "width": 103.71429, "height": 72 }, { "width": 102.85714, "height": 72 }, { "width": 102, "height": 72 }, { "width": 100.85714, "height": 72 }, { "width": 98.28571, "height": 72 }, { "width": 96.85714, "height": 72 } ] } ] } Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +25 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.graphics.drawable.Animatable import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable import android.text.TextUtils import androidx.annotation.VisibleForTesting import androidx.compose.animation.animateColorAsState import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter Loading Loading @@ -67,6 +68,7 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorProducer import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.painter.Painter Loading Loading @@ -107,6 +109,8 @@ import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState import com.android.systemui.qs.ui.compose.borderOnFocus import com.android.systemui.res.R import kotlin.math.abs import platform.test.motion.compose.values.MotionTestValueKey import platform.test.motion.compose.values.motionTestValues private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target" Loading @@ -122,6 +126,7 @@ fun LargeTileContent( isVisible: () -> Boolean = { true }, accessibilityUiState: AccessibilityUiState? = null, iconShape: RoundedCornerShape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius), textScale: () -> Float = { 1f }, toggleClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null, ) { Loading Loading @@ -178,7 +183,7 @@ fun LargeTileContent( colors = colors, accessibilityUiState = accessibilityUiState, isVisible = isVisible, modifier = Modifier.weight(1f), modifier = Modifier.weight(1f).bounceScale(TransformOrigin(0f, .5f), textScale), ) if (sideDrawable != null) { Loading Loading @@ -381,6 +386,25 @@ private fun DrawScope.drawFadedEdge(startX: Float, endX: Float, colors: List<Col ) } fun Modifier.bounceScale( transformOrigin: TransformOrigin = TransformOrigin.Center, scale: () -> Float, ): Modifier { return motionTestValues { scale() exportAs TileBounceMotionTestKeys.BounceScale } .graphicsLayer { scale().let { scaleY = it scaleX = it this.transformOrigin = transformOrigin } } } @VisibleForTesting object TileBounceMotionTestKeys { val BounceScale = MotionTestValueKey<Float>("bounceScale") } object CommonTileDefaults { val IconSize = 32.dp val LargeTileIconSize = 28.dp Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +17 −11 Original line number Diff line number Diff line Loading @@ -229,19 +229,21 @@ fun Tile( hapticsViewModel?.setTileInteractionState( TileHapticsViewModel.TileInteractionState.CLICKED ) if (uiState.accessibilityUiState.toggleableState != null) { // Bounce unless we're a large dual target tile. These don't toggle on // main click. if (iconOnly || !isDualTarget) { val bounceContainer = uiState.isToggleable && (iconOnly || !isDualTarget) coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() // Bounce the tile's container if it is toggleable and is not a large // dual target tile. These don't toggle on main click. Otherwise bounce // the content of the tile. if (bounceContainer) { currentBounceableInfo.bounceable.animateContainerBounce() } else { currentBounceableInfo.bounceable.animateContentBounce(iconOnly) } } if (uiState.isToggleable && iconOnly) { // And show footer text feedback for icons if (iconOnly) { requestToggleTextFeedback(tile.spec) } } }, onLongClick = longClick, accessibilityUiState = uiState.accessibilityUiState, Loading @@ -253,7 +255,10 @@ fun Tile( SmallTileContent( iconProvider = iconProvider, color = colors.icon, modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center).bounceScale { bounceableInfo.bounceable.iconBounceScale }, ) } else { val iconShape by TileDefaults.animateIconShapeAsState(uiState.state) Loading @@ -277,6 +282,7 @@ fun Tile( accessibilityUiState = uiState.accessibilityUiState, squishiness = squishiness, isVisible = isVisible, textScale = { bounceableInfo.bounceable.textBounceScale }, modifier = Modifier.largeTilePadding(isDualTarget = uiState.handlesLongClick), ) Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt +56 −5 Original line number Diff line number Diff line Loading @@ -17,22 +17,73 @@ package com.android.systemui.qs.panels.ui.viewmodel import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.tween import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.animation.Bounceable class BounceableTileViewModel : Bounceable { private val animatableBounce = Animatable(0.dp, Dp.VectorConverter) private val animatableContainerBounce = Animatable(0.dp, Dp.VectorConverter) private val animatableIconBounceScale = Animatable(1f) private val animatableTextBounceScale = Animatable(1f) override val bounce: Dp get() = animatableBounce.value get() = animatableContainerBounce.value val iconBounceScale: Float get() = animatableIconBounceScale.value val textBounceScale: Float get() = animatableTextBounceScale.value suspend fun animateContainerBounce() { animatableContainerBounce.animateToBounce(BounceSize) animatableContainerBounce.animateToRest(ContainerBounceAtRest) } suspend fun animateContentBounce(iconOnly: Boolean) { if (iconOnly) { animateIconBounce() } else { animateTextBounce() } } private suspend fun animateIconBounce() { animatableIconBounceScale.animateToBounce(ICON_BOUNCE_SCALE) animatableIconBounceScale.animateToRest(SCALE_BOUNCE_AT_REST) } private suspend fun animateTextBounce() { animatableTextBounceScale.animateToBounce(TEXT_BOUNCE_SCALE) animatableTextBounceScale.animateToRest(SCALE_BOUNCE_AT_REST) } private suspend fun <T, V : AnimationVector> Animatable<T, V>.animateToBounce(targetValue: T) { animateTo( targetValue, tween(durationMillis = BOUNCE_DURATION_MILLIS, easing = BounceStartEasing), ) } suspend fun animateBounce() { animatableBounce.animateTo(BounceSize) animatableBounce.animateTo(0.dp) private suspend fun <T, V : AnimationVector> Animatable<T, V>.animateToRest(targetValue: T) { animateTo( targetValue, tween(durationMillis = BOUNCE_DURATION_MILLIS, easing = BounceEndEasing), ) } private companion object { val BounceSize = 8.dp val BounceStartEasing = CubicBezierEasing(.05f, 0f, 0f, 1f) val BounceEndEasing = CubicBezierEasing(1f, 0f, .95f, 1f) val ContainerBounceAtRest = 0.dp const val ICON_BOUNCE_SCALE = 1.1f const val TEXT_BOUNCE_SCALE = 1.06f const val SCALE_BOUNCE_AT_REST = 1f const val BOUNCE_DURATION_MILLIS = 167 } }
packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +4 −1 Original line number Diff line number Diff line Loading @@ -43,7 +43,10 @@ data class TileUiState( val handlesSecondaryClick: Boolean, val sideDrawable: Drawable?, val accessibilityUiState: AccessibilityUiState, ) ) { val isToggleable: Boolean get() = accessibilityUiState.toggleableState != null } data class AccessibilityUiState( val contentDescription: String, Loading
packages/SystemUI/tests/goldens/containerBounce_bounceEndDisabled.json 0 → 100644 +252 −0 Original line number Diff line number Diff line { "frame_ids": [ "before", 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 272, 288, 304, 320, 336, 352, 368, "after" ], "features": [ { "name": "tile-small_previous", "type": "dpSize", "data_points": [ { "width": 96.85714, "height": 72 }, { "width": 96.85714, "height": 72 }, { "width": 96.85714, "height": 72 }, { "width": 93.71429, "height": 72 }, { "width": 92.28571, "height": 72 }, { "width": 91.14286, "height": 72 }, { "width": 90.28571, "height": 72 }, { "width": 89.71429, "height": 72 }, { "width": 89.42857, "height": 72 }, { "width": 89.14286, "height": 72 }, { "width": 89.14286, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 88.85714, "height": 72 }, { "width": 89.14286, "height": 72 }, { "width": 89.42857, "height": 72 }, { "width": 89.71429, "height": 72 }, { "width": 90, "height": 72 }, { "width": 90.85714, "height": 72 }, { "width": 91.71429, "height": 72 }, { "width": 92.85714, "height": 72 }, { "width": 95.42857, "height": 72 }, { "width": 96.85714, "height": 72 } ] }, { "name": "tile-small_clicked", "type": "dpSize", "data_points": [ { "width": 96.85714, "height": 72 }, { "width": 96.85714, "height": 72 }, { "width": 96.85714, "height": 72 }, { "width": 100, "height": 72 }, { "width": 101.42857, "height": 72 }, { "width": 102.57143, "height": 72 }, { "width": 103.42857, "height": 72 }, { "width": 104, "height": 72 }, { "width": 104.28571, "height": 72 }, { "width": 104.57143, "height": 72 }, { "width": 104.57143, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.85714, "height": 72 }, { "width": 104.57143, "height": 72 }, { "width": 104.28571, "height": 72 }, { "width": 104, "height": 72 }, { "width": 103.71429, "height": 72 }, { "width": 102.85714, "height": 72 }, { "width": 102, "height": 72 }, { "width": 100.85714, "height": 72 }, { "width": 98.28571, "height": 72 }, { "width": 96.85714, "height": 72 } ] } ] }