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
This commit is contained in:
cottongin
2026-03-12 05:49:28 -04:00
parent 5d551c9380
commit 9bd38224b1

View File

@@ -5,7 +5,6 @@ import android.content.res.Configuration
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloat
@@ -447,12 +446,13 @@ private fun ArtworkImage(
} }
@Composable @Composable
private fun BounceMarqueeText( private fun TickerText(
text: String, text: String,
style: TextStyle, style: TextStyle,
color: Color, color: Color,
strokeColor: Color? = null, strokeColor: Color? = null,
strokeWidth: Float = 2f, strokeWidth: Float = 2f,
velocityDpPerSecond: Float = 33f,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val density = LocalDensity.current val density = LocalDensity.current
@@ -475,25 +475,26 @@ private fun BounceMarqueeText(
if (overflowPx > 0f) { if (overflowPx > 0f) {
key(text) { key(text) {
val scrollMs = ((overflowPx / density.density) * 18f).toInt().coerceIn(2000, 8000) val velocityPxPerMs = with(density) { velocityDpPerSecond.dp.toPx() } / 1000f
val pauseMs = 1500 val totalScrollPx = textWidthPx + containerWidthPx
val totalMs = pauseMs + scrollMs + pauseMs + scrollMs 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( val offset by transition.animateFloat(
initialValue = 0f, initialValue = 0f,
targetValue = 0f, targetValue = 0f,
animationSpec = infiniteRepeatable( animationSpec = infiniteRepeatable(
animation = keyframes { animation = keyframes {
durationMillis = totalMs durationMillis = durationMs
0f at 0 using LinearEasing 0f at 0 using LinearEasing
0f at pauseMs using FastOutSlowInEasing 0f at initialDelayMs using LinearEasing
-overflowPx at (pauseMs + scrollMs) using LinearEasing -totalScrollPx at durationMs using LinearEasing
-overflowPx at (pauseMs + scrollMs + pauseMs) using FastOutSlowInEasing
}, },
repeatMode = RepeatMode.Restart repeatMode = RepeatMode.Restart
), ),
label = "bounce" label = "ticker"
) )
Box( Box(
@@ -575,7 +576,7 @@ private fun TrackInfoSection(
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(6.dp)) Spacer(modifier = Modifier.height(6.dp))
BounceMarqueeText( TickerText(
text = trackText, text = trackText,
style = trackStyle, style = trackStyle,
color = trackColor, color = trackColor,