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..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 = {}, @@ -1272,6 +1276,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) + ) } }