ui: move the action press flourish onto the shape, spin the gear
Some checks failed
Release — F-Droid repo + Gitea/Codeberg release / detect (push) Successful in 7s
Release — F-Droid repo + Gitea/Codeberg release / release (push) Has been cancelled

The press animation turned the glyph; put it on the scalloped cookie
container instead and make it stronger (scale 1→0.82, rotate 0→40°). The
glyph holds upright via a counter-rotation, so the shape spins while a
magnifier or list icon stays readable. New spinIcon opts a glyph into its own
quarter turn — the settings gear uses it, so it reads as a gear cranking.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 00:01:07 +02:00
parent 6d3fbc05c1
commit 5f4711dabe
2 changed files with 21 additions and 7 deletions

View File

@@ -31,8 +31,10 @@ import androidx.graphics.shapes.RoundedPolygon
* its own [shape] and [containerColor], so a row of them reads as a set of
* distinct little tokens rather than identical grey glyphs.
*
* Pressing springs the icon down a touch and gives it a small turn — a light
* expressive flourish, no library motion APIs needed.
* Pressing springs the whole shape down and gives it a turn — the scalloped
* container is what dips and spins, while the glyph inside stays upright (a
* spinning magnifier or list icon would just read as wrong). [spinIcon] opts a
* glyph into turning too, for icons that read well mid-spin like the gear.
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -46,15 +48,25 @@ fun ShapedActionButton(
contentColor: Color = MaterialTheme.colorScheme.onTertiaryContainer,
size: Dp = 40.dp,
iconSize: Dp = 22.dp,
spinIcon: Boolean = false,
) {
val interaction = remember { MutableInteractionSource() }
val pressed by interaction.collectIsPressedAsState()
val scale by animateFloatAsState(if (pressed) 0.88f else 1f, label = "actionScale")
val rotation by animateFloatAsState(if (pressed) 24f else 0f, label = "actionRotation")
// The shape (the scalloped cookie) turns and dips on press.
val shapeRotation by animateFloatAsState(if (pressed) 40f else 0f, label = "shapeRotation")
val scale by animateFloatAsState(if (pressed) 0.82f else 1f, label = "shapeScale")
// The glyph's *net* turn: 0 keeps it upright, spinIcon gives it a quarter turn.
val iconRotation by animateFloatAsState(
if (pressed && spinIcon) 90f else 0f,
label = "iconRotation",
)
Surface(
onClick = onClick,
modifier = modifier.size(size),
modifier = modifier
.size(size)
.scale(scale)
.rotate(shapeRotation),
shape = shape.toShape(),
color = containerColor,
contentColor = contentColor,
@@ -64,10 +76,11 @@ fun ShapedActionButton(
Icon(
imageVector = icon,
contentDescription = contentDescription,
// Counter the container's turn so the glyph's net rotation is just
// [iconRotation] — upright by default, a quarter turn for the gear.
modifier = Modifier
.size(iconSize)
.scale(scale)
.rotate(rotation),
.rotate(iconRotation - shapeRotation),
)
}
}

View File

@@ -285,6 +285,7 @@ private fun HomeTopBar(
modifier = Modifier.padding(start = 8.dp),
size = 48.dp,
iconSize = 26.dp,
spinIcon = true,
)
}
}