# Now Playing UX Improvements **Date:** 2026-03-10 ## Task Description Five UX enhancements to the Now Playing screen in the Android 247 Radio app. ## Changes Made ### 1. Bouncing Marquee Text (`NowPlayingScreen.kt`) - Created `BounceMarqueeText` composable that measures text width vs container - If text fits: displays centered, stationary (single line) - If text overflows: animates horizontal translation with a bounce pattern (scroll to end, pause 1.5s, scroll back, pause 1.5s, repeat) - Scroll speed scales proportionally with overflow distance (18ms per dp) - Uses `rememberTextMeasurer` for unconstrained width measurement, `rememberInfiniteTransition` with `keyframes` for bounce animation - Both stroke and fill text layers share the same animated offset - Replaced multi-line wrapping in `TrackInfoSection` with single-line marquee ### 2. Timer/Latency Icons (`NowPlayingScreen.kt`) - Replaced the concatenated text string in `TimerSection` with a `Row` of icon+value pairs - Session elapsed: `Icons.Outlined.Schedule` icon - Connection elapsed: `Icons.Outlined.Wifi` icon - Latency: `Icons.Outlined.Speed` icon - Icons sized at 14dp, tinted with same dimmed color as text ### 3. Cross-Fade Metadata and Artwork (`NowPlayingViewModel.kt`, `NowPlayingScreen.kt`) - Added `displayMetadata` and `displayArtworkUrl` StateFlows to ViewModel - These are "committed" values that only update after artwork resolution completes - When metadata changes, artwork resolve runs in background while old metadata+art remains displayed - Once resolve completes, both metadata and artwork URL update atomically - Station changes trigger immediate display reset - Added `AnimatedContent` with 600ms fade transition around `ArtworkImage` - Screen now reads `displayMetadata` for track info instead of raw `playbackState.metadata` ### 4. Stream Quality Indicator (5 files) - `StreamConnection.kt`: Added `StreamInfo` data class (bitrate, ssl, contentType); parsed from ICY response headers (`icy-br`, `Content-Type`, URL scheme) - `AudioEngineEvent.kt`: Added `StreamInfoReceived` event - `AudioEngine.kt`: Emits `StreamInfoReceived` after connection opens - `PlaybackState.kt`: Added `streamInfo: StreamInfo?` to `Playing` and `Paused` states - `RadioPlaybackService.kt`: Handles `StreamInfoReceived` event, updates `Playing` state - `RadioController.kt`: Carries `streamInfo` to `Paused` state on pause - `NowPlayingScreen.kt`: Added `QualityBadge` composable showing bitrate, codec (MP3/AAC/OGG/FLAC), and SSL lock icon ### 5. Dominant Color White Handling (`NowPlayingScreen.kt`) - Added `.clearFilters()` to `Palette.Builder` chain before `.generate()` - Removes the default Palette filter that excludes near-white and near-black colors - White-dominant album art now correctly produces a white background with dark text ## Files Modified - `app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt` - `app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingViewModel.kt` - `app/src/main/java/xyz/cottongin/radio247/audio/StreamConnection.kt` - `app/src/main/java/xyz/cottongin/radio247/audio/AudioEngine.kt` - `app/src/main/java/xyz/cottongin/radio247/audio/AudioEngineEvent.kt` - `app/src/main/java/xyz/cottongin/radio247/service/PlaybackState.kt` - `app/src/main/java/xyz/cottongin/radio247/service/RadioPlaybackService.kt` - `app/src/main/java/xyz/cottongin/radio247/service/RadioController.kt` ## Verification - Build: `./gradlew compileDebugKotlin` -- exit 0 (only pre-existing deprecation warnings) - Tests: `./gradlew testDebugUnitTest` -- 56/56 pass, 0 failures ## Follow-up Items - Test on device: marquee animation with various track name lengths - Test on device: cross-fade timing with slow MusicBrainz lookups - Test on device: verify ICY headers (`icy-br`) are present for non-SomaFM stations