From 48e25c2bb62e8aa5fd8515d0c7891010c36c1085 Mon Sep 17 00:00:00 2001 From: Arvin Date: Sun, 5 Apr 2026 15:16:53 +0200 Subject: [PATCH] fix: tablet player controls are too small, off-center, and hard to see on bright content (#97) Reported: "On Android tablet the player controls are so small and on the far left of the screen. It would be better to have them bigger and centralized. Also if the video is bright white, it's very difficult to see the controls, it would be nice to darken the gradient." Three separate sub-bugs in PlayerScreen.kt around the bottom controls row: 1. Button sizes used `if (isTouchDevice) ... else ...` which collapsed phone and tablet into one branch. That branch used SMALLER buttons than TV (24 dp vs 28 dp) \u2014 backwards for tablet where viewing distance is short and finger targets must hit Material's 48 dp minimum. Tablets inherited the phone sizing and looked cramped. 2. `horizontalArrangement = Arrangement.Start` left-aligned the row on every device. TV remotes tolerate left alignment but on a 10"+ tablet the icons cluster in the bottom-left corner with empty space to the right \u2014 exactly the "far left of the screen" complaint. 3. The bottom gradient peaked at `Color.Black.copy(alpha = 0.7f)` at the very bottom and faded out to 30% up. On bright white content the icons at the top of the control column sat on roughly 20% alpha, making them nearly invisible. Changes (all scoped to the single bottom-controls Column, #97 only): - Compute `isTablet` / `isPhone` from `LocalDeviceType.current` alongside the existing `isTouchDevice`, so the three device categories can be distinguished. - Replace each button-size / icon-size / gap `if (isTouchDevice)` expression with a three-way `when` that gives tablets the LARGEST sizes (36-48 dp button, 22-30 dp icon), phones the compact sizes (24-34 dp button, 17-26 dp icon), and TV the original medium sizes unchanged. This makes tablet controls meet Material's 48 dp touch target minimum. - Center the icon row on tablet (`Arrangement.Center`) instead of left-aligning. Phone keeps left-alignment because vertical orientation has limited horizontal space. TV keeps left-alignment because D-pad focus traversal assumes it. - Strengthen the bottom gradient for touch devices only: starts at Color.Black 50% alpha at 20% and peaks at 85% alpha at the bottom. The old 70% peak wasn't enough on bright content; the new value makes icons readable on white backgrounds without being opaque enough to obscure the video entirely. TV keeps the lighter original gradient because TV content is usually not as bright as mobile photo/app backgrounds. No behavioral or focus-traversal changes \u2014 buttons still respond to the same D-pad / touch events, same focus requesters, same actions. Closes #97 --- .../tv/ui/screens/player/PlayerScreen.kt | 73 +++++++++++++++---- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/player/PlayerScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/player/PlayerScreen.kt index fdfe35b07..3442fd7e9 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/player/PlayerScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/player/PlayerScreen.kt @@ -1338,7 +1338,10 @@ fun PlayerScreen( } } - val isTouchDevice = LocalDeviceType.current.isTouchDevice() + val playerDeviceType = LocalDeviceType.current + val isTouchDevice = playerDeviceType.isTouchDevice() + val isTablet = playerDeviceType == com.arflix.tv.util.DeviceType.TABLET + val isPhone = playerDeviceType == com.arflix.tv.util.DeviceType.PHONE // Read subtitle appearance prefs val subtitleSizePref = uiState.subtitleSize val subtitleColorPref = uiState.subtitleColor @@ -1845,14 +1848,20 @@ fun PlayerScreen( } } - // Bottom controls - positioned at very bottom + // Bottom controls - positioned at very bottom. + // Gradient made stronger on touch devices so the icon row stays readable + // against bright content. Issue #97. Column( modifier = Modifier .fillMaxWidth() .align(Alignment.BottomCenter) .background( Brush.verticalGradient( - colorStops = arrayOf( + colorStops = if (isTouchDevice) arrayOf( + 0.0f to Color.Transparent, + 0.2f to Color.Black.copy(alpha = 0.5f), + 1.0f to Color.Black.copy(alpha = 0.85f) + ) else arrayOf( 0.0f to Color.Transparent, 0.3f to Color.Black.copy(alpha = 0.2f), 1.0f to Color.Black.copy(alpha = 0.7f) @@ -1862,20 +1871,58 @@ fun PlayerScreen( .padding(horizontal = if (isTouchDevice) 24.dp else 48.dp) .padding(top = if (isTouchDevice) 16.dp else 24.dp, bottom = if (isTouchDevice) 32.dp else 24.dp) ) { - // Icon buttons row - left-aligned, tight above trackbar (avoids subtitle overlap) + // Icon buttons row. On tablet we center the row and use slightly + // larger buttons than TV to match the shorter viewing distance and + // the Material minimum touch-target of 48dp. Phone keeps the compact + // left-aligned layout to fit vertical orientation. Issue #97. Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Start, + horizontalArrangement = if (isTablet) Arrangement.Center else Arrangement.Start, verticalAlignment = Alignment.CenterVertically ) { - val smallBtn = if (isTouchDevice) 24.dp else 28.dp - val smallIcon = if (isTouchDevice) 17.dp else 19.dp - val midBtn = if (isTouchDevice) 28.dp else 30.dp - val midIcon = if (isTouchDevice) 20.dp else 22.dp - val bigBtn = if (isTouchDevice) 34.dp else 38.dp - val bigIcon = if (isTouchDevice) 26.dp else 28.dp - val gap = if (isTouchDevice) 10.dp else 14.dp - val wideGap = if (isTouchDevice) 14.dp else 18.dp + // Three-way sizing: phone (compact) < TV (medium) < tablet (largest). + // The old logic made touch devices SMALLER than TV which was + // backwards for tablet finger targets. + val smallBtn = when { + isTablet -> 36.dp + isPhone -> 24.dp + else -> 28.dp + } + val smallIcon = when { + isTablet -> 22.dp + isPhone -> 17.dp + else -> 19.dp + } + val midBtn = when { + isTablet -> 40.dp + isPhone -> 28.dp + else -> 30.dp + } + val midIcon = when { + isTablet -> 24.dp + isPhone -> 20.dp + else -> 22.dp + } + val bigBtn = when { + isTablet -> 48.dp + isPhone -> 34.dp + else -> 38.dp + } + val bigIcon = when { + isTablet -> 30.dp + isPhone -> 26.dp + else -> 28.dp + } + val gap = when { + isTablet -> 16.dp + isPhone -> 10.dp + else -> 14.dp + } + val wideGap = when { + isTablet -> 20.dp + isPhone -> 14.dp + else -> 18.dp + } // Subtitles PlayerIconButton(icon = Icons.Default.ClosedCaption, contentDescription = "Subtitles & Audio",