From e60748a0d0ed483ecb1ae25fdce12f15b537025e Mon Sep 17 00:00:00 2001 From: Justin Caveda Date: Fri, 19 Dec 2025 09:42:34 -0600 Subject: [PATCH] [UI] Add playback overlay dark gradient background (#505) ## Description This PR adds a background dimming effect behind the playback controls to fix readability issues against bright video content. It implements a full-screen vertical gradient that ensures white text and buttons remain legible. I added a vertical gradient fading from transparent at the top to ~95% black at the bottom which adds contrast for UI elements without obscuring the center of the video. ### Related issues Solves [#485](https://github.com/damontecres/Wholphin/issues/485) ### Screenshots playback no_gradient ### AI/LLM usage This change was developed with the assistance of AI, but was not wholly generated by AI. --------- Co-authored-by: Claude Opus 4.5 Co-authored-by: Damontecres Co-authored-by: Ray <154766448+damontecres@users.noreply.github.com> --- .../wholphin/ui/components/TimeDisplay.kt | 1 + .../wholphin/ui/playback/PlaybackOverlay.kt | 30 +++++++++ .../wholphin/ui/playback/PlaybackPage.kt | 62 +++++++++---------- .../wholphin/ui/playback/SkipIndicator.kt | 1 + 4 files changed, 63 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/TimeDisplay.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/TimeDisplay.kt index 79bd2f73..8fa6f60f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/TimeDisplay.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/TimeDisplay.kt @@ -17,6 +17,7 @@ fun BoxScope.TimeDisplay(modifier: Modifier = Modifier) { text = LocalClock.current.timeString, fontSize = 18.sp, color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, modifier = modifier .align(Alignment.TopEnd) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt index 2200b954..74821924 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -34,6 +35,8 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type @@ -127,10 +130,37 @@ fun PlaybackOverlay( var controllerHeight by remember { mutableStateOf(0.dp) } var state by remember { mutableStateOf(OverlayViewState.CONTROLLER) } + // Background scrim for OSD readability + val scrimBrush = + remember { + Brush.verticalGradient( + colors = + listOf( + Color.Transparent, + Color.Black.copy(alpha = 0.5f), + Color.Black.copy(alpha = 0.80f), + ), + ) + } + Box( modifier = modifier, contentAlignment = Alignment.BottomCenter, ) { + AnimatedVisibility( + visible = controllerViewState.controlsVisible, + enter = fadeIn(), + exit = fadeOut(), + modifier = Modifier.matchParentSize(), + ) { + Box( + modifier = + Modifier + .fillMaxSize() + .background(scrimBrush), + ) + } + AnimatedVisibility( state == OverlayViewState.CONTROLLER, enter = slideInVertically() + fadeIn(), diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt index c905e3f0..e27ea11f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt @@ -332,37 +332,6 @@ fun PlaybackPage( } } - // Subtitles - if (skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled) { - val maxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f) - AndroidView( - factory = { context -> - SubtitleView(context).apply { - preferences.appPreferences.interfacePreferences.subtitlesPreferences.let { - setStyle(it.toSubtitleStyle()) - setFixedTextSize(Dimension.SP, it.fontSize.toFloat()) - setBottomPaddingFraction(it.margin.toFloat() / 100f) - } - } - }, - update = { - it.setCues(cues) - Media3SubtitleOverride( - preferences.appPreferences.interfacePreferences.subtitlesPreferences - .calculateEdgeSize(density), - ).apply(it) - }, - onReset = { - it.setCues(null) - }, - modifier = - Modifier - .fillMaxSize(maxSize) - .align(Alignment.TopCenter) - .background(Color.Transparent), - ) - } - // The playback controls AnimatedVisibility( controllerViewState.controlsVisible, @@ -402,6 +371,37 @@ fun PlaybackPage( showClock = preferences.appPreferences.interfacePreferences.showClock, ) } + + // Subtitles + if (skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled) { + val maxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f) + AndroidView( + factory = { context -> + SubtitleView(context).apply { + preferences.appPreferences.interfacePreferences.subtitlesPreferences.let { + setStyle(it.toSubtitleStyle()) + setFixedTextSize(Dimension.SP, it.fontSize.toFloat()) + setBottomPaddingFraction(it.margin.toFloat() / 100f) + } + } + }, + update = { + it.setCues(cues) + Media3SubtitleOverride( + preferences.appPreferences.interfacePreferences.subtitlesPreferences + .calculateEdgeSize(density), + ).apply(it) + }, + onReset = { + it.setCues(null) + }, + modifier = + Modifier + .fillMaxSize(maxSize) + .align(Alignment.TopCenter) + .background(Color.Transparent), + ) + } } // Ask to skip intros, etc button diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SkipIndicator.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SkipIndicator.kt index 0a3e343d..3e417a91 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SkipIndicator.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SkipIndicator.kt @@ -61,6 +61,7 @@ fun SkipIndicator( modifier = Modifier.align(Alignment.Center), color = MaterialTheme.colorScheme.onBackground, fontSize = 13.sp, + style = MaterialTheme.typography.bodySmall, text = abs(durationMs / 1000).toString(), ) }