From 5f4711dabe9d1bc40288c5d06bd6c594a9fd45f4 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 29 Jun 2026 00:01:07 +0200 Subject: [PATCH] ui: move the action press flourish onto the shape, spin the gear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../agendula/ui/common/ShapedActionButton.kt | 27 ++++++++++++++----- .../agendula/ui/lists/ListsScreen.kt | 1 + 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/ShapedActionButton.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/ShapedActionButton.kt index a434e43..f7ddf99 100644 --- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/ShapedActionButton.kt +++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/ShapedActionButton.kt @@ -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), ) } } diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsScreen.kt index 778c149..90bdff0 100644 --- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsScreen.kt @@ -285,6 +285,7 @@ private fun HomeTopBar( modifier = Modifier.padding(start = 8.dp), size = 48.dp, iconSize = 26.dp, + spinIcon = true, ) } }