feat(maestro): match activity rail blink/flash to web timings
Replaces the tween/Reverse pulse with keyframe animations mirroring web/src/components/sidebar/sidebar.css exactly: a hard 1200ms on/off blink for AwaitingInput (steps(1), no crossfade) and a 1400ms ease-out double-flash for Error.
This commit is contained in:
@@ -2,12 +2,12 @@ package pt.naps.maestro.ui.sidebar
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.InfiniteTransition
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.keyframes
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
@@ -52,18 +52,51 @@ fun activityStateWord(activity: ActivityState): String =
|
||||
ActivityState.Error -> "Error"
|
||||
}
|
||||
|
||||
/** A held bar that pulses its alpha between 1 and [minAlpha] every
|
||||
* [periodMs] — the blink/flash motion `AwaitingInput`/`Error` earn. */
|
||||
/** A hard on/off blink, one cycle per 1200ms — mirrors the web rail's
|
||||
* `maestro-rail-blink` keyframes (docs/SIDEBAR.md, web/src/components/sidebar/sidebar.css):
|
||||
* held at full alpha for the first half of the cycle, then snapped to 0 for
|
||||
* the second half, with no crossfade. */
|
||||
@Composable
|
||||
private fun InfiniteTransition.pulsingAlpha(minAlpha: Float, periodMs: Int): Float {
|
||||
private fun InfiniteTransition.blinkAlpha(): Float {
|
||||
val alpha by animateFloat(
|
||||
initialValue = 1f,
|
||||
targetValue = minAlpha,
|
||||
targetValue = 0f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(durationMillis = periodMs, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Reverse,
|
||||
animation = keyframes {
|
||||
durationMillis = 1200
|
||||
1f at 0
|
||||
1f at 599
|
||||
0f at 600
|
||||
0f at 1199
|
||||
},
|
||||
repeatMode = RepeatMode.Restart,
|
||||
),
|
||||
label = "activity-rail-alpha",
|
||||
label = "activity-rail-blink",
|
||||
)
|
||||
return alpha
|
||||
}
|
||||
|
||||
/** A fast double-flash every 1400ms — mirrors the web rail's
|
||||
* `maestro-rail-flash` keyframes: two dips to quarter alpha early in the
|
||||
* cycle, then held at full alpha for the remainder. */
|
||||
@Composable
|
||||
private fun InfiniteTransition.flashAlpha(): Float {
|
||||
val alpha by animateFloat(
|
||||
initialValue = 1f,
|
||||
targetValue = 1f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = 1400
|
||||
1f at 0 using LinearOutSlowInEasing
|
||||
0.25f at 210 using LinearOutSlowInEasing
|
||||
1f at 420 using LinearOutSlowInEasing
|
||||
0.25f at 630 using LinearOutSlowInEasing
|
||||
1f at 840 using LinearOutSlowInEasing
|
||||
1f at 1400
|
||||
},
|
||||
repeatMode = RepeatMode.Restart,
|
||||
),
|
||||
label = "activity-rail-flash",
|
||||
)
|
||||
return alpha
|
||||
}
|
||||
@@ -77,8 +110,8 @@ fun ActivityRail(activity: ActivityState, modifier: Modifier = Modifier) {
|
||||
val color by animateColorAsState(targetValue = activityColor(activity), label = "activity-rail-color")
|
||||
val infinite = rememberInfiniteTransition(label = "activity-rail-motion")
|
||||
val alpha = when (activity) {
|
||||
ActivityState.AwaitingInput -> infinite.pulsingAlpha(minAlpha = 0.35f, periodMs = 600)
|
||||
ActivityState.Error -> infinite.pulsingAlpha(minAlpha = 0.25f, periodMs = 220)
|
||||
ActivityState.AwaitingInput -> infinite.blinkAlpha()
|
||||
ActivityState.Error -> infinite.flashAlpha()
|
||||
else -> 1f
|
||||
}
|
||||
Box(
|
||||
|
||||
Reference in New Issue
Block a user