From e2ed74a47dbf08620a38410e69f3c245b42c71f6 Mon Sep 17 00:00:00 2001 From: avi Date: Tue, 8 Sep 2026 10:13:06 -0500 Subject: [PATCH] feat(ui): mini-player buffering pose + one-tap retry on error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Equalizer chip dims while BUFFERING/INITIALIZING (no false 'playing' signal during reconnect) - On ERROR the bar's subtitle becomes 'Couldn't reach · Retry' — tapping replays the current station without leaving the tab - NowPlayingViewModel.retry() re-plays current URL --- .../java/com/rada/ui/player/NowPlayingBar.kt | 38 ++++++++++++++++++- .../com/rada/ui/player/NowPlayingViewModel.kt | 6 +++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/rada/ui/player/NowPlayingBar.kt b/app/src/main/java/com/rada/ui/player/NowPlayingBar.kt index b5982b0..b9cf1b4 100644 --- a/app/src/main/java/com/rada/ui/player/NowPlayingBar.kt +++ b/app/src/main/java/com/rada/ui/player/NowPlayingBar.kt @@ -65,6 +65,10 @@ fun NowPlayingBar( val isLocal by viewModel.isLocal.collectAsState() val streamMeta by viewModel.streamMeta.collectAsState() val localMedia by viewModel.localMedia.collectAsState() + val isError = bufferState == PlaybackManager.BufferState.ERROR + val isBuffering = + bufferState == PlaybackManager.BufferState.BUFFERING || + bufferState == PlaybackManager.BufferState.INITIALIZING // Swipe-down (or quick drag-down) dismisses the bar until a *new* // source starts playing — a floating thing should answer to gestures. var dismissedFor by remember { mutableStateOf(null) } @@ -145,13 +149,22 @@ fun NowPlayingBar( .background(LocalTokens.current.accentFill), contentAlignment = Alignment.Center, ) { - if (isPlaying) { + if (isPlaying && !isBuffering) { EqualizerBars( active = true, color = LocalTokens.current.accentOnFill, barWidth = 3.dp, modifier = Modifier.size(22.dp), ) + } else if (isBuffering && !isError) { + EqualizerBars( + active = false, + color = LocalTokens.current.accentOnFill.copy(alpha = 0.7f), + barWidth = 3.dp, + modifier = Modifier + .size(22.dp) + .padding(2.dp), + ) } else { Icon( if (isLocal) Icons.Default.MusicNote else Icons.Default.Radio, @@ -186,7 +199,28 @@ fun NowPlayingBar( color = LocalTokens.current.text, ) } - if (subtitle.isNotEmpty()) { + if (isError) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { viewModel.retry() }, + ), + ) { + Text( + "Couldn't reach · ", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error, + ) + Text( + "Retry", + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Bold, + color = tokens.accentText, + ) + } + } else if (subtitle.isNotEmpty()) { AnimatedContent( targetState = subtitle, transitionSpec = { metadataEnter(reduce) togetherWith metadataExit(reduce) }, diff --git a/app/src/main/java/com/rada/ui/player/NowPlayingViewModel.kt b/app/src/main/java/com/rada/ui/player/NowPlayingViewModel.kt index 9105fc3..698b501 100644 --- a/app/src/main/java/com/rada/ui/player/NowPlayingViewModel.kt +++ b/app/src/main/java/com/rada/ui/player/NowPlayingViewModel.kt @@ -23,4 +23,10 @@ class NowPlayingViewModel @Inject constructor( fun togglePlayPause() { playbackManager.togglePlayPause() } + + /** Reconnect the current station (mini-player error state). */ + fun retry() { + val url = playbackManager.currentStationUrl ?: return + playbackManager.playStation(url, playbackManager.currentStationName) + } }