From 9bd38224b173c8e97129e9c0d646c2e287e819d8 Mon Sep 17 00:00:00 2001 From: cottongin Date: Thu, 12 Mar 2026 05:49:28 -0400 Subject: [PATCH] feat: replace bounce marquee with constant-speed ticker Replaces BounceMarqueeText with TickerText that scrolls at a fixed 33 dp/s regardless of text length. Text scrolls left off-screen with a container-width gap before looping. Initial 1.5s delay lets the user read the beginning. Made-with: Cursor --- .../ui/screens/nowplaying/NowPlayingScreen.kt | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt b/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt index d9b53de..25613f0 100644 --- a/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt +++ b/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt @@ -5,7 +5,6 @@ import android.content.res.Configuration import android.graphics.BitmapFactory import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat @@ -447,12 +446,13 @@ private fun ArtworkImage( } @Composable -private fun BounceMarqueeText( +private fun TickerText( text: String, style: TextStyle, color: Color, strokeColor: Color? = null, strokeWidth: Float = 2f, + velocityDpPerSecond: Float = 33f, modifier: Modifier = Modifier ) { val density = LocalDensity.current @@ -475,25 +475,26 @@ private fun BounceMarqueeText( if (overflowPx > 0f) { key(text) { - val scrollMs = ((overflowPx / density.density) * 18f).toInt().coerceIn(2000, 8000) - val pauseMs = 1500 - val totalMs = pauseMs + scrollMs + pauseMs + scrollMs + val velocityPxPerMs = with(density) { velocityDpPerSecond.dp.toPx() } / 1000f + val totalScrollPx = textWidthPx + containerWidthPx + val scrollMs = (totalScrollPx / velocityPxPerMs).toInt() + val initialDelayMs = 1500 + val durationMs = initialDelayMs + scrollMs - val transition = rememberInfiniteTransition(label = "marquee") + val transition = rememberInfiniteTransition(label = "ticker") val offset by transition.animateFloat( initialValue = 0f, targetValue = 0f, animationSpec = infiniteRepeatable( animation = keyframes { - durationMillis = totalMs + durationMillis = durationMs 0f at 0 using LinearEasing - 0f at pauseMs using FastOutSlowInEasing - -overflowPx at (pauseMs + scrollMs) using LinearEasing - -overflowPx at (pauseMs + scrollMs + pauseMs) using FastOutSlowInEasing + 0f at initialDelayMs using LinearEasing + -totalScrollPx at durationMs using LinearEasing }, repeatMode = RepeatMode.Restart ), - label = "bounce" + label = "ticker" ) Box( @@ -575,7 +576,7 @@ private fun TrackInfoSection( overflow = TextOverflow.Ellipsis ) Spacer(modifier = Modifier.height(6.dp)) - BounceMarqueeText( + TickerText( text = trackText, style = trackStyle, color = trackColor,