From 022737cf13773a85b8bba523c774bba15c1d8088 Mon Sep 17 00:00:00 2001 From: Arvin Date: Sun, 5 Apr 2026 15:30:47 +0200 Subject: [PATCH 1/2] feat: persistent visible back button for phones (#43) On Android phones running gesture navigation, the system nav bar auto-hides after a short idle period. On deep screens (details, settings, search, watchlist) users were left with no visible way to leave the screen until they swiped up to re-reveal the system bar \u2014 which is non-obvious and disruptive. On TV the hardware Back key is always available so the problem doesn't exist there. Tablets typically have enough screen real estate that the system bar isn't an issue. Added `MobileBackButton` composable that renders a 40 dp translucent circular IconButton-shaped back button in the top-start corner, inset from the status bar. It gates itself on `LocalDeviceType.current == DeviceType.PHONE` and no-ops on tablet/TV, so callers can drop it unconditionally into their UI without per-device branching. Dropped into 4 screens, in each case as a sibling child of the existing root Box aligned `TopStart` so it floats above the scrolling content: - DetailsScreen (mobile layout branch only, not the TV layout) - SettingsScreen - SearchScreen - WatchlistScreen HomeScreen is excluded because it's the app root with nothing to go back to. PlayerScreen is excluded because it has its own dedicated close UI in the top controls. TvScreen (live TV) is excluded because the Back key handling there is already well-established and adding a floating button on top of the video would be visually noisy. Tablet and TV layouts are completely unchanged \u2014 the component early-returns for those device types so no layout recomposition or extra node is added. Closes #43 --- .../tv/ui/components/MobileBackButton.kt | 73 +++++++++++++++++++ .../tv/ui/screens/details/DetailsScreen.kt | 8 ++ .../tv/ui/screens/search/SearchScreen.kt | 6 ++ .../tv/ui/screens/settings/SettingsScreen.kt | 8 ++ .../ui/screens/watchlist/WatchlistScreen.kt | 6 ++ 5 files changed, 101 insertions(+) create mode 100644 app/src/main/kotlin/com/arflix/tv/ui/components/MobileBackButton.kt diff --git a/app/src/main/kotlin/com/arflix/tv/ui/components/MobileBackButton.kt b/app/src/main/kotlin/com/arflix/tv/ui/components/MobileBackButton.kt new file mode 100644 index 000000000..6b70f7300 --- /dev/null +++ b/app/src/main/kotlin/com/arflix/tv/ui/components/MobileBackButton.kt @@ -0,0 +1,73 @@ +package com.arflix.tv.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.arflix.tv.util.DeviceType +import com.arflix.tv.util.LocalDeviceType + +/** + * A persistent, visible back button for phone layouts. + * + * On Android phones and tablets running gesture navigation, the system nav bar + * auto-hides after a short idle, leaving users with no visible way to go back + * until they swipe up to re-reveal it. On TV this is a non-issue (Back key on + * the remote is always available) but on touch devices it makes deep screens + * feel trapped \u2014 the exact complaint in issue #43. + * + * This composable renders an IconButton-shaped circle in the top-start corner + * of the screen, inset from the status bar, but only when the device type is + * [DeviceType.PHONE]. On TV and tablet it returns an empty Box so callers can + * drop it unconditionally into their UI without per-device branching. + * + * Callers should place this inside a `Box` root (or similar) that fills the + * screen, so the absolute `.align(Alignment.TopStart)` positioning lands above + * their main content. + * + * Usage: + * ``` + * Box(modifier = Modifier.fillMaxSize()) { + * MyScreenContent(...) + * MobileBackButton(onBack = onBack) + * } + * ``` + */ +@Composable +fun MobileBackButton( + onBack: () -> Unit, + modifier: Modifier = Modifier +) { + val deviceType = LocalDeviceType.current + if (deviceType != DeviceType.PHONE) return + + Box( + modifier = modifier + .statusBarsPadding() + .padding(start = 12.dp, top = 12.dp) + .size(40.dp) + .clip(CircleShape) + .background(Color.Black.copy(alpha = 0.55f)) + .clickable(onClick = onBack), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = Color.White, + modifier = Modifier.size(22.dp) + ) + } +} diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt index bed3da458..f1c1667bd 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt @@ -1272,6 +1272,14 @@ private fun DetailsContent( // Bottom spacing Spacer(modifier = Modifier.height(32.dp)) } + + // Persistent back button for phone users (hidden on tablet/TV). + // Sits on top of the scrolling Column so it's always reachable even + // when the system nav bar auto-hides. Issue #43. + com.arflix.tv.ui.components.MobileBackButton( + onBack = onBack, + modifier = Modifier.align(Alignment.TopStart) + ) } return } diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/search/SearchScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/search/SearchScreen.kt index 159eba793..cc028fe00 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/search/SearchScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/search/SearchScreen.kt @@ -345,6 +345,12 @@ fun SearchScreen( } } } + + // Persistent back button for phone users (hidden on tablet/TV). Issue #43. + com.arflix.tv.ui.components.MobileBackButton( + onBack = onBack, + modifier = Modifier.align(Alignment.TopStart) + ) } } diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsScreen.kt index d6f96b516..3156f040d 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsScreen.kt @@ -1130,6 +1130,14 @@ fun SettingsScreen( onDismiss = { viewModel.dismissToast() } ) } + + // Persistent back button for phone users (hidden on tablet/TV). + // Always visible in the top-start corner even when the system nav bar + // auto-hides. Issue #43. + com.arflix.tv.ui.components.MobileBackButton( + onBack = onBack, + modifier = Modifier.align(Alignment.TopStart) + ) } } diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/watchlist/WatchlistScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/watchlist/WatchlistScreen.kt index ec9db3764..693405cfa 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/watchlist/WatchlistScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/watchlist/WatchlistScreen.kt @@ -388,5 +388,11 @@ fun WatchlistScreen( onDismiss = { viewModel.dismissToast() } ) } + + // Persistent back button for phone users (hidden on tablet/TV). Issue #43. + com.arflix.tv.ui.components.MobileBackButton( + onBack = onBack, + modifier = Modifier.align(Alignment.TopStart) + ) } } From fafc226a706acf748ff01383a918a30b608c0d28 Mon Sep 17 00:00:00 2001 From: Arvin Date: Sun, 5 Apr 2026 15:40:22 +0200 Subject: [PATCH 2/2] fix: thread onBack param through DetailsContent for mobile back button (scope fix) --- .../kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt index f1c1667bd..09690f7ef 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt @@ -597,6 +597,7 @@ fun DetailsScreen( contentHasFocus = !isSidebarFocused, usePosterCards = usePosterCards, isMobile = isMobile, + onBack = onBack, onButtonClick = { idx -> when (idx) { 0 -> { // Play @@ -935,6 +936,9 @@ private fun DetailsContent( contentHasFocus: Boolean = true, usePosterCards: Boolean = false, isMobile: Boolean = false, + // Persistent back callback used by the phone-layout back button overlay + // (issue #43). No-op by default so tablet/TV callers don't need to pass it. + onBack: () -> Unit = {}, onButtonClick: (Int) -> Unit = {}, onSeasonClick: (Int) -> Unit = {}, onEpisodeClick: (Int) -> Unit = {},