diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarFabColumn.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarFabColumn.kt index 7a68d40e..c561b6f5 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarFabColumn.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarFabColumn.kt @@ -1,7 +1,7 @@ package de.jeanlucmakiola.calendula.ui.common import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -9,6 +9,7 @@ import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.height @@ -16,7 +17,6 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.MoreHoriz import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExtendedFloatingActionButton @@ -25,10 +25,14 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R @@ -40,7 +44,6 @@ class FabSelection( val bar: @Composable () -> Unit, ) -private enum class FabIcon { Create, Open, Fold } /** The standard FAB's size, which the row it sits in is held to. */ private val FAB_SIZE = 56.dp @@ -101,29 +104,53 @@ fun CalendarFabColumn( ) { heldBar.value?.invoke() } - val icon = when { - selection == null -> FabIcon.Create - selection.expanded -> FabIcon.Fold - else -> FabIcon.Open - } - FloatingActionButton(onClick = selection?.onToggle ?: onCreate) { - Crossfade(targetState = icon, label = "fab-icon") { shown -> - when (shown) { - FabIcon.Create -> Icon( - imageVector = Icons.Default.Add, - contentDescription = stringResource(R.string.event_edit_new_title), - ) - FabIcon.Open -> Icon( - imageVector = Icons.Default.MoreHoriz, - contentDescription = stringResource(R.string.event_selection_actions), - ) - FabIcon.Fold -> Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = stringResource(R.string.event_selection_hide), - ) - } - } + val label = stringResource( + when { + selection == null -> R.string.event_edit_new_title + selection.expanded -> R.string.event_selection_hide + else -> R.string.event_selection_actions + }, + ) + FloatingActionButton( + onClick = selection?.onToggle ?: onCreate, + modifier = Modifier.semantics { contentDescription = label }, + ) { + FabGlyph(selecting = selection != null, expanded = selection?.expanded == true) } } } } + +/** + * The FAB's glyph: "+", or with an event selected a chevron pointing where the + * bar unfolds — turned half round once it has, to fold it back. The "+" and the + * chevron spin past each other rather than fading. + */ +@Composable +private fun FabGlyph(selecting: Boolean, expanded: Boolean) { + val spatial = MaterialTheme.motionScheme.fastSpatialSpec() + val swap by animateFloatAsState(if (selecting) 1f else 0f, spatial, label = "fab-swap") + val turn by animateFloatAsState(if (expanded) 0f else 180f, spatial, label = "fab-turn") + Box(contentAlignment = Alignment.Center) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.graphicsLayer { + rotationZ = 90f * swap + scaleX = 1f - 0.5f * swap + scaleY = 1f - 0.5f * swap + alpha = (1f - swap).coerceIn(0f, 1f) + }, + ) + Icon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = null, + modifier = Modifier.graphicsLayer { + rotationZ = turn - 90f * (1f - swap) + scaleX = 0.5f + 0.5f * swap + scaleY = 0.5f + 0.5f * swap + alpha = swap.coerceIn(0f, 1f) + }, + ) + } +}