From c022d4b40fa39a3386ec39f668b56a1aa77fbf3b Mon Sep 17 00:00:00 2001 From: Himanth Reddy Date: Sun, 5 Apr 2026 07:48:41 +0530 Subject: [PATCH 1/3] feat(phase-1): Add UiModeWarningDialog Composable --- .../tv/ui/screens/settings/SettingsScreen.kt | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) 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..676515435 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 @@ -4177,6 +4177,142 @@ private fun SubtitlePickerModal( } /** All TMDB-supported languages as (code, displayName) pairs. */ +@Composable +private fun UiModeWarningDialog( + nextMode: String, + onConfirm: () -> Unit, + onDismiss: () -> Unit +) { + var focusedIndex by remember { mutableIntStateOf(0) } // 0 = Confirm, 1 = Cancel + val focusRequester = remember { FocusRequester() } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + + androidx.compose.ui.window.Dialog( + onDismissRequest = onDismiss, + properties = androidx.compose.ui.window.DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = true, + usePlatformDefaultWidth = false + ) + ) { + ModalScrim(onDismiss = onDismiss) { + Column( + modifier = Modifier + .then( + if (LocalDeviceType.current.isTouchDevice()) Modifier.fillMaxWidth(0.92f).widthIn(max = 400.dp) + else Modifier.width(400.dp) + ) + .background(BackgroundElevated, RoundedCornerShape(16.dp)) + .padding(if (LocalDeviceType.current.isTouchDevice()) 20.dp else 28.dp) + .focusRequester(focusRequester) + .focusable() + .onPreviewKeyEvent { event -> + if (event.type == KeyEventType.KeyDown) { + when (event.key) { + Key.Back, Key.Escape -> { + onDismiss() + true + } + Key.DirectionLeft -> { + if (focusedIndex > 0) focusedIndex-- + true + } + Key.DirectionRight -> { + if (focusedIndex < 1) focusedIndex++ + true + } + Key.Enter, Key.DirectionCenter -> { + if (focusedIndex == 0) onConfirm() else onDismiss() + true + } + else -> false + } + } else false + } + ) { + Text( + text = "Change UI Mode", + style = ArflixTypography.sectionTitle, + color = TextPrimary + ) + + Spacer(modifier = Modifier.height(16.dp)) + + val modeString = when (nextMode) { + "tv" -> "TV" + "tablet" -> "Tablet" + "phone" -> "Phone" + else -> "Auto" + } + + Text( + text = "Are you sure you want to change the UI mode to $modeString?", + style = ArflixTypography.body, + color = TextSecondary + ) + + Spacer(modifier = Modifier.height(32.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + val isConfirmFocused = focusedIndex == 0 + Box( + modifier = Modifier + .weight(1f) + .clip(RoundedCornerShape(8.dp)) + .background( + color = if (isConfirmFocused) SuccessGreen else SuccessGreen.copy(alpha = 0.6f) + ) + .clickable { onConfirm() } + .border( + width = if (isConfirmFocused) 2.dp else 0.dp, + color = if (isConfirmFocused) Color.White else Color.Transparent, + shape = RoundedCornerShape(8.dp) + ) + .padding(vertical = 14.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "Confirm", + style = ArflixTypography.button, + color = Color.White + ) + } + + val isCancelFocused = focusedIndex == 1 + Box( + modifier = Modifier + .weight(1f) + .clip(RoundedCornerShape(8.dp)) + .background( + color = if (isCancelFocused) Color.White.copy(alpha = 0.2f) else Color.White.copy(alpha = 0.1f) + ) + .clickable { onDismiss() } + .border( + width = if (isCancelFocused) 2.dp else 0.dp, + color = if (isCancelFocused) Pink else Color.Transparent, + shape = RoundedCornerShape(8.dp) + ) + .padding(vertical = 14.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "Cancel", + style = ArflixTypography.button, + color = if (isCancelFocused) TextPrimary else TextSecondary + ) + } + } + } + } + } +} + val TMDB_LANGUAGES = listOf( "en-US" to "English", "nl-NL" to "Dutch (Nederlands)", From 9c7180bf3704b1675f587e95fd6e251d61046a36 Mon Sep 17 00:00:00 2001 From: Himanth Reddy Date: Sun, 5 Apr 2026 07:51:19 +0530 Subject: [PATCH 2/3] feat(phase-2): intercept device mode click and add dialog state --- .../tv/ui/screens/settings/SettingsScreen.kt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) 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 676515435..19546fbe3 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 @@ -178,6 +178,8 @@ fun SettingsScreen( var dnsProviderPickerIndex by remember { mutableIntStateOf(0) } var showContentLanguagePicker by remember { mutableStateOf(false) } var contentLanguagePickerIndex by remember { mutableIntStateOf(0) } + var showUiModeWarningDialog by remember { mutableStateOf(false) } + var nextUiMode by remember { mutableStateOf("") } val sections = remember { listOf("general", "iptv", "catalogs", "addons", "accounts") } @@ -305,6 +307,7 @@ fun SettingsScreen( showAudioLanguagePicker || showDnsProviderPicker || showContentLanguagePicker || + showUiModeWarningDialog || uiState.showCloudPairDialog || uiState.showCloudEmailPasswordDialog || uiState.showAppUpdateDialog || @@ -628,7 +631,8 @@ fun SettingsScreen( onTrailerAutoPlayToggle = { viewModel.setTrailerAutoPlay(it) }, onDeviceModeClick = { val next = when (uiState.deviceModeOverride) { "auto" -> "tv"; "tv" -> "tablet"; "tablet" -> "phone"; else -> "auto" } - viewModel.setDeviceModeOverride(next) + nextUiMode = next + showUiModeWarningDialog = true }, onContentLanguageClick = openContentLanguagePicker, onSubtitleSizeClick = { viewModel.cycleSubtitleSize() }, @@ -800,7 +804,8 @@ fun SettingsScreen( onTrailerAutoPlayToggle = { viewModel.setTrailerAutoPlay(it) }, onDeviceModeClick = { val next = when (uiState.deviceModeOverride) { "auto" -> "tv"; "tv" -> "tablet"; "tablet" -> "phone"; else -> "auto" } - viewModel.setDeviceModeOverride(next) + nextUiMode = next + showUiModeWarningDialog = true }, onContentLanguageClick = openContentLanguagePicker, onSkipProfileSelectionToggle = { viewModel.setSkipProfileSelection(it) }, @@ -1117,6 +1122,19 @@ fun SettingsScreen( ) } + if (showUiModeWarningDialog) { + UiModeWarningDialog( + nextMode = nextUiMode, + onConfirm = { + viewModel.setDeviceModeOverride(nextUiMode) + showUiModeWarningDialog = false + }, + onDismiss = { + showUiModeWarningDialog = false + } + ) + } + // Toast notification uiState.toastMessage?.let { message -> Toast( From b4c5e9e71eb0dfbd920e9d59896df5749fb94406 Mon Sep 17 00:00:00 2001 From: Himanth Reddy Date: Sun, 5 Apr 2026 09:02:08 +0530 Subject: [PATCH 3/3] fix(settings): route UI mode changes through dialog --- .../tv/ui/screens/settings/SettingsScreen.kt | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) 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 19546fbe3..331b91d1f 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 @@ -209,6 +209,15 @@ fun SettingsScreen( contentLanguagePickerIndex = TMDB_LANGUAGES.indexOfFirst { it.first == uiState.contentLanguage }.coerceAtLeast(0) showContentLanguagePicker = true } + val openUiModeWarningDialog = { + nextUiMode = when (uiState.deviceModeOverride) { + "auto" -> "tv" + "tv" -> "tablet" + "tablet" -> "phone" + else -> "auto" + } + showUiModeWarningDialog = true + } LaunchedEffect(Unit) { focusRequester.requestFocus() @@ -487,7 +496,7 @@ fun SettingsScreen( 8 -> viewModel.setTrailerAutoPlay(!uiState.trailerAutoPlay) 9 -> viewModel.cycleFrameRateMatchingMode() 10 -> viewModel.toggleCardLayoutMode() - 11 -> { val next = when (uiState.deviceModeOverride) { "auto" -> "tv"; "tv" -> "tablet"; "tablet" -> "phone"; else -> "auto" }; viewModel.setDeviceModeOverride(next) } + 11 -> openUiModeWarningDialog() 12 -> viewModel.setSkipProfileSelection(!uiState.skipProfileSelection) 13 -> openDnsProviderPicker() } @@ -629,11 +638,7 @@ fun SettingsScreen( onAutoPlayMinQualityClick = { viewModel.cycleAutoPlayMinQuality() }, trailerAutoPlay = uiState.trailerAutoPlay, onTrailerAutoPlayToggle = { viewModel.setTrailerAutoPlay(it) }, - onDeviceModeClick = { - val next = when (uiState.deviceModeOverride) { "auto" -> "tv"; "tv" -> "tablet"; "tablet" -> "phone"; else -> "auto" } - nextUiMode = next - showUiModeWarningDialog = true - }, + onDeviceModeClick = openUiModeWarningDialog, onContentLanguageClick = openContentLanguagePicker, onSubtitleSizeClick = { viewModel.cycleSubtitleSize() }, onSkipProfileSelectionToggle = { viewModel.setSkipProfileSelection(it) }, @@ -802,11 +807,7 @@ fun SettingsScreen( onAutoPlayMinQualityClick = { viewModel.cycleAutoPlayMinQuality() }, trailerAutoPlay = uiState.trailerAutoPlay, onTrailerAutoPlayToggle = { viewModel.setTrailerAutoPlay(it) }, - onDeviceModeClick = { - val next = when (uiState.deviceModeOverride) { "auto" -> "tv"; "tv" -> "tablet"; "tablet" -> "phone"; else -> "auto" } - nextUiMode = next - showUiModeWarningDialog = true - }, + onDeviceModeClick = openUiModeWarningDialog, onContentLanguageClick = openContentLanguagePicker, onSkipProfileSelectionToggle = { viewModel.setSkipProfileSelection(it) }, onSubtitleSizeClick = { viewModel.cycleSubtitleSize() },