From 86ef729309580369fb5b4b382268c670822c8fa7 Mon Sep 17 00:00:00 2001 From: jonahmichael Date: Thu, 28 May 2026 15:16:57 +0530 Subject: [PATCH 1/3] Refactor: extract reusable settings row composables - Move SettingsRow and SettingsToggleRow to dedicated SettingsRows.kt component - Update SettingsScreen to use extracted components - Fix imports to use theme and skin packages instead of settings package --- .../arflix/tv/ui/components/SettingsRows.kt | 193 ++++++++++++++++++ .../tv/ui/screens/settings/SettingsScreen.kt | 161 +-------------- 2 files changed, 195 insertions(+), 159 deletions(-) create mode 100644 app/src/main/kotlin/com/arflix/tv/ui/components/SettingsRows.kt diff --git a/app/src/main/kotlin/com/arflix/tv/ui/components/SettingsRows.kt b/app/src/main/kotlin/com/arflix/tv/ui/components/SettingsRows.kt new file mode 100644 index 00000000..3b8c2dee --- /dev/null +++ b/app/src/main/kotlin/com/arflix/tv/ui/components/SettingsRows.kt @@ -0,0 +1,193 @@ +package com.arflix.tv.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.tv.material3.ExperimentalTvMaterial3Api +import com.arflix.tv.ui.theme.ArflixTypography +import com.arflix.tv.ui.theme.Pink +import com.arflix.tv.ui.theme.SuccessGreen +import com.arflix.tv.ui.theme.TextPrimary +import com.arflix.tv.ui.theme.TextSecondary +import com.arflix.tv.ui.skin.resolveAccentColor + +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +fun SettingsRow( + icon: ImageVector, + title: String, + subtitle: String = "", + value: String, + isFocused: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val interactionSource = remember { MutableInteractionSource() } + val focusRingColor = resolveAccentColor(fallback = Pink) + Row( + modifier = modifier + .fillMaxWidth() + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick + ) + .background( + if (isFocused) Color.White.copy(alpha = 0.12f) else Color.White.copy(alpha = 0.05f), + RoundedCornerShape(12.dp) + ) + .border( + width = if (isFocused) 2.dp else 0.dp, + color = if (isFocused) focusRingColor else Color.Transparent, + shape = RoundedCornerShape(12.dp) + ) + .padding(horizontal = 16.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f)) { + Icon( + imageVector = icon, + contentDescription = null, + tint = TextSecondary, + modifier = Modifier.size(19.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = ArflixTypography.cardTitle.copy(fontSize = 16.sp), + color = TextPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + if (subtitle.isNotEmpty()) { + Text( + text = subtitle, + style = ArflixTypography.caption.copy(fontSize = 13.sp), + color = TextSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } + Spacer(modifier = Modifier.width(12.dp)) + + if (value.isNotBlank()) { + Box( + modifier = Modifier + .background(Pink.copy(alpha = 0.15f), RoundedCornerShape(999.dp)) + .border(1.dp, Pink.copy(alpha = 0.3f), RoundedCornerShape(999.dp)) + .padding(horizontal = 12.dp, vertical = 6.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = value.uppercase(), + style = ArflixTypography.label.copy(fontSize = 11.sp, letterSpacing = 0.5.sp), + color = Pink, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } +} + +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +fun SettingsToggleRow( + title: String, + subtitle: String, + isEnabled: Boolean, + isFocused: Boolean, + onToggle: (Boolean) -> Unit, + modifier: Modifier = Modifier +) { + val interactionSource = remember { MutableInteractionSource() } + val focusRingColor = resolveAccentColor(fallback = Pink) + Row( + modifier = modifier + .fillMaxWidth() + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { onToggle(!isEnabled) } + ) + .background( + if (isFocused) Color.White.copy(alpha = 0.12f) else Color.White.copy(alpha = 0.05f), + RoundedCornerShape(12.dp) + ) + .border( + width = if (isFocused) 2.dp else 0.dp, + color = if (isFocused) focusRingColor else Color.Transparent, + shape = RoundedCornerShape(12.dp) + ) + .padding(horizontal = 16.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = ArflixTypography.cardTitle.copy(fontSize = 16.sp), + color = TextPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + if (subtitle.isNotEmpty()) { + Text( + text = subtitle, + style = ArflixTypography.caption.copy(fontSize = 13.sp), + color = TextSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + + Box( + modifier = Modifier + .width(44.dp) + .height(24.dp) + .background( + color = if (isEnabled) SuccessGreen else Color.White.copy(alpha = 0.2f), + shape = RoundedCornerShape(13.dp) + ) + .padding(3.dp), + contentAlignment = if (isEnabled) Alignment.CenterEnd else Alignment.CenterStart + ) { + Box( + modifier = Modifier + .size(18.dp) + .background( + color = Color.White, + shape = RoundedCornerShape(10.dp) + ) + ) + } + } +} \ No newline at end of file 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 d75bb23f..d8570753 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 @@ -138,6 +138,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView +import com.arflix.tv.ui.components.SettingsRow +import com.arflix.tv.ui.components.SettingsToggleRow import androidx.core.widget.doAfterTextChanged import androidx.hilt.navigation.compose.hiltViewModel import androidx.tv.material3.ExperimentalTvMaterial3Api @@ -5893,165 +5895,6 @@ private fun IptvSettings( } } -@OptIn(ExperimentalTvMaterial3Api::class) -@Composable -private fun SettingsRow( - icon: ImageVector, - title: String, - subtitle: String = "", - value: String, - isFocused: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - val interactionSource = remember { MutableInteractionSource() } - val focusRingColor = resolveAccentColor(fallback = Pink) - Row( - modifier = modifier - .fillMaxWidth() - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = onClick - ) - .background( - if (isFocused) Color.White.copy(alpha = 0.12f) else Color.White.copy(alpha = 0.05f), - RoundedCornerShape(12.dp) - ) - .border( - width = if (isFocused) 2.dp else 0.dp, - color = if (isFocused) focusRingColor else Color.Transparent, - shape = RoundedCornerShape(12.dp) - ) - .padding(horizontal = 16.dp, vertical = 14.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f)) { - Icon( - imageVector = icon, - contentDescription = null, - tint = TextSecondary, - modifier = Modifier.size(19.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - Column(modifier = Modifier.weight(1f)) { - Text( - text = title, - style = ArflixTypography.cardTitle.copy(fontSize = 16.sp), - color = TextPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - if (subtitle.isNotEmpty()) { - Text( - text = subtitle, - style = ArflixTypography.caption.copy(fontSize = 13.sp), - color = TextSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - } - Spacer(modifier = Modifier.width(12.dp)) - - if (value.isNotBlank()) { - Box( - modifier = Modifier - .background(Pink.copy(alpha = 0.15f), RoundedCornerShape(999.dp)) - .border(1.dp, Pink.copy(alpha = 0.3f), RoundedCornerShape(999.dp)) - .padding(horizontal = 12.dp, vertical = 6.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = value.uppercase(), - style = ArflixTypography.label.copy(fontSize = 11.sp, letterSpacing = 0.5.sp), - color = Pink, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - } -} - -@OptIn(ExperimentalTvMaterial3Api::class) -@Composable -private fun SettingsToggleRow( - title: String, - subtitle: String, - isEnabled: Boolean, - isFocused: Boolean, - onToggle: (Boolean) -> Unit, - modifier: Modifier = Modifier -) { - val interactionSource = remember { MutableInteractionSource() } - val focusRingColor = resolveAccentColor(fallback = Pink) - Row( - modifier = modifier - .fillMaxWidth() - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = { onToggle(!isEnabled) } - ) - .background( - if (isFocused) Color.White.copy(alpha = 0.12f) else Color.White.copy(alpha = 0.05f), - RoundedCornerShape(12.dp) - ) - .border( - width = if (isFocused) 2.dp else 0.dp, - color = if (isFocused) focusRingColor else Color.Transparent, - shape = RoundedCornerShape(12.dp) - ) - .padding(horizontal = 16.dp, vertical = 14.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Column(modifier = Modifier.weight(1f)) { - Text( - text = title, - style = ArflixTypography.cardTitle.copy(fontSize = 16.sp), - color = TextPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - if (subtitle.isNotEmpty()) { - Text( - text = subtitle, - style = ArflixTypography.caption.copy(fontSize = 13.sp), - color = TextSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - - // Custom toggle indicator instead of Switch - Box( - modifier = Modifier - .width(44.dp) - .height(24.dp) - .background( - color = if (isEnabled) SuccessGreen else Color.White.copy(alpha = 0.2f), - shape = RoundedCornerShape(13.dp) - ) - .padding(3.dp), - contentAlignment = if (isEnabled) Alignment.CenterEnd else Alignment.CenterStart - ) { - Box( - modifier = Modifier - .size(18.dp) - .background( - color = Color.White, - shape = RoundedCornerShape(10.dp) - ) - ) - } - } -} - @OptIn(ExperimentalTvMaterial3Api::class) @Composable private fun CatalogDiscoveryModal( From 1d67b0e1bd7fac1e4af06b03dc06e56dd1ae88b1 Mon Sep 17 00:00:00 2001 From: jonahmichael Date: Mon, 1 Jun 2026 15:14:17 +0530 Subject: [PATCH 2/3] refactor(settings): replace hardcoded row indices with metadata-driven navigation --- .../tv/ui/screens/settings/SettingsScreen.kt | 142 ++++++++---------- .../settings/SettingsSectionMetadata.kt | 130 ++++++++++++++++ 2 files changed, 192 insertions(+), 80 deletions(-) create mode 100644 app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsSectionMetadata.kt 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 d8570753..d11229b8 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 @@ -198,29 +198,6 @@ private val LocalSettingsFocusTracker = compositionLocalOf { - return when (section) { - "language" -> listOf(0, 3) - "subtitles" -> listOf(1, 2, 4, 5, 6, 7, 8, 9) - "ai_subtitles" -> listOf(28, 29, 30, 31, 32, 33) - "playback" -> listOf(10, 11, 12, 13, 14, 34, 16, 15, 27) - "appearance" -> listOf(17, 18, 20, 21, 24, 23, 22) - "profiles" -> listOf(19) - "network" -> listOf(25, 26, 35) - else -> emptyList() - } -} - private fun openExternalUrl(context: Context, url: String) { runCatching { context.startActivity( @@ -377,17 +354,18 @@ fun SettingsScreen( } } val sectionMaxIndex: (String) -> Int = { section -> - when (section) { - in tvGeneralSectionIds -> (tvGeneralRowsForSection(section).size - 1).coerceAtLeast(0) - "iptv" -> if (showIptvCategoriesSettings) { + val generalRows = tvGeneralRowsForSection(section) + when { + generalRows.isNotEmpty() -> (generalRows.size - 1).coerceAtLeast(0) + section == "iptv" -> if (showIptvCategoriesSettings) { uiState.iptvAvailableGroups.size // Reset row + category rows } else { 2 + uiState.iptvPlaylists.size // Add + rows + refresh + clear } - "home_server" -> uiState.homeServerConnections.size + 3 - "catalogs" -> uiState.catalogs.size // Add + rows - "stremio" -> stremioAddons.size // rows + add button - "accounts" -> 4 // Cloud + Trakt + Force Sync + App Update + Privacy/Data + section == "home_server" -> uiState.homeServerConnections.size + 3 + section == "catalogs" -> uiState.catalogs.size // Add + rows + section == "stremio" -> stremioAddons.size // rows + add button + section == "accounts" -> 4 // Cloud + Trakt + Force Sync + App Update + Privacy/Data else -> 0 } } @@ -807,48 +785,52 @@ fun SettingsScreen( } Zone.SECTION -> activeZone = Zone.CONTENT Zone.CONTENT -> { - when (currentSection) { - in tvGeneralSectionIds -> { - when (tvGeneralRowsForSection(currentSection).getOrNull(contentFocusIndex)) { - 0 -> openContentLanguagePicker() - 1 -> openSubtitlePicker() - 2 -> openSecondarySubtitlePicker() - 3 -> openAudioLanguagePicker() - 4 -> viewModel.cycleSubtitleSize() - 5 -> viewModel.cycleSubtitleColor() - 6 -> viewModel.cycleSubtitleOffset() - 7 -> viewModel.cycleSubtitleStyle() - 8 -> viewModel.toggleSubtitleStylized() - 9 -> viewModel.setFilterSubtitlesByLanguage(!uiState.filterSubtitlesByLanguage) - 10 -> viewModel.setAutoPlayNext(!uiState.autoPlayNext) - 11 -> viewModel.setAutoPlaySingleSource(!uiState.autoPlaySingleSource) - 12 -> viewModel.cycleAutoPlayMinQuality() - 13 -> viewModel.setTrailerAutoPlay(!uiState.trailerAutoPlay) - 14 -> viewModel.setTrailerSoundEnabled(!uiState.trailerSoundEnabled) - 15 -> viewModel.cycleFrameRateMatchingMode() - 16 -> showQualityFiltersModal = true - 17 -> viewModel.toggleCardLayoutMode() - 18 -> openUiModeWarningDialog() - 19 -> viewModel.setSkipProfileSelection(!uiState.skipProfileSelection) - 20 -> viewModel.setOledBlackBackground(!uiState.oledBlackBackground) - 21 -> viewModel.cycleClockFormat() - 22 -> viewModel.setShowBudget(!uiState.showBudget) - 23 -> viewModel.setSpoilerBlurEnabled(!uiState.spoilerBlurEnabled) - 24 -> viewModel.cycleAccentColor() - 25 -> openDnsProviderPicker() - 26 -> viewModel.setShowLoadingStats(!uiState.showLoadingStats) - 35 -> showCustomUserAgentDialog = true - 27 -> viewModel.cycleVolumeBoost() - 28 -> viewModel.setSubtitleAiEnabled(!uiState.subtitleAiEnabled) - 29 -> showAiModelDialog = true - 30 -> viewModel.setSubtitleAiAutoSelect(!uiState.subtitleAiAutoSelect) - 31 -> viewModel.setSubtitleRemoveHearingImpaired(!uiState.subtitleRemoveHearingImpaired) - 32 -> showAiApiKeyDialog = true - 33 -> viewModel.startAiKeyServer() - 34 -> viewModel.cycleTrailerDelay() - } + val generalRows = tvGeneralRowsForSection(currentSection) + if (generalRows.isNotEmpty()) { + // Row behavior follows the symbolic metadata order so later inserts only + // require updates in SettingsSectionMetadata.kt. + when (generalRows.getOrNull(contentFocusIndex)) { + TvGeneralSettingRow.CONTENT_LANGUAGE -> openContentLanguagePicker() + TvGeneralSettingRow.DEFAULT_SUBTITLE -> openSubtitlePicker() + TvGeneralSettingRow.SECONDARY_SUBTITLE -> openSecondarySubtitlePicker() + TvGeneralSettingRow.AUDIO_LANGUAGE -> openAudioLanguagePicker() + TvGeneralSettingRow.SUBTITLE_SIZE -> viewModel.cycleSubtitleSize() + TvGeneralSettingRow.SUBTITLE_COLOR -> viewModel.cycleSubtitleColor() + TvGeneralSettingRow.SUBTITLE_OFFSET -> viewModel.cycleSubtitleOffset() + TvGeneralSettingRow.SUBTITLE_STYLE -> viewModel.cycleSubtitleStyle() + TvGeneralSettingRow.SUBTITLE_STYLIZED -> viewModel.toggleSubtitleStylized() + TvGeneralSettingRow.FILTER_SUBTITLES_BY_LANGUAGE -> viewModel.setFilterSubtitlesByLanguage(!uiState.filterSubtitlesByLanguage) + TvGeneralSettingRow.AUTO_PLAY_NEXT -> viewModel.setAutoPlayNext(!uiState.autoPlayNext) + TvGeneralSettingRow.AUTO_PLAY_SINGLE_SOURCE -> viewModel.setAutoPlaySingleSource(!uiState.autoPlaySingleSource) + TvGeneralSettingRow.AUTO_PLAY_MIN_QUALITY -> viewModel.cycleAutoPlayMinQuality() + TvGeneralSettingRow.TRAILER_AUTO_PLAY -> viewModel.setTrailerAutoPlay(!uiState.trailerAutoPlay) + TvGeneralSettingRow.TRAILER_SOUND_ENABLED -> viewModel.setTrailerSoundEnabled(!uiState.trailerSoundEnabled) + TvGeneralSettingRow.FRAME_RATE_MATCHING_MODE -> viewModel.cycleFrameRateMatchingMode() + TvGeneralSettingRow.QUALITY_FILTERS -> showQualityFiltersModal = true + TvGeneralSettingRow.CARD_LAYOUT_MODE -> viewModel.toggleCardLayoutMode() + TvGeneralSettingRow.UI_MODE -> openUiModeWarningDialog() + TvGeneralSettingRow.SKIP_PROFILE_SELECTION -> viewModel.setSkipProfileSelection(!uiState.skipProfileSelection) + TvGeneralSettingRow.OLED_BLACK_BACKGROUND -> viewModel.setOledBlackBackground(!uiState.oledBlackBackground) + TvGeneralSettingRow.CLOCK_FORMAT -> viewModel.cycleClockFormat() + TvGeneralSettingRow.SHOW_BUDGET -> viewModel.setShowBudget(!uiState.showBudget) + TvGeneralSettingRow.SPOILER_BLUR -> viewModel.setSpoilerBlurEnabled(!uiState.spoilerBlurEnabled) + TvGeneralSettingRow.ACCENT_COLOR -> viewModel.cycleAccentColor() + TvGeneralSettingRow.DNS_PROVIDER -> openDnsProviderPicker() + TvGeneralSettingRow.SHOW_LOADING_STATS -> viewModel.setShowLoadingStats(!uiState.showLoadingStats) + TvGeneralSettingRow.VOLUME_BOOST -> viewModel.cycleVolumeBoost() + TvGeneralSettingRow.SUBTITLE_AI_ENABLED -> viewModel.setSubtitleAiEnabled(!uiState.subtitleAiEnabled) + TvGeneralSettingRow.SUBTITLE_AI_MODEL -> showAiModelDialog = true + TvGeneralSettingRow.SUBTITLE_AI_AUTO_SELECT -> viewModel.setSubtitleAiAutoSelect(!uiState.subtitleAiAutoSelect) + TvGeneralSettingRow.SUBTITLE_REMOVE_HEARING_IMPAIRED -> viewModel.setSubtitleRemoveHearingImpaired(!uiState.subtitleRemoveHearingImpaired) + TvGeneralSettingRow.SUBTITLE_AI_API_KEY -> showAiApiKeyDialog = true + TvGeneralSettingRow.SUBTITLE_AI_SERVER -> viewModel.startAiKeyServer() + TvGeneralSettingRow.TRAILER_DELAY -> viewModel.cycleTrailerDelay() + TvGeneralSettingRow.CUSTOM_USER_AGENT -> showCustomUserAgentDialog = true + null -> Unit } - "iptv" -> { + } else { + when (currentSection) { + "iptv" -> { if (showIptvCategoriesSettings) { val playlistId = uiState.iptvSelectedPlaylistId.orEmpty() val orderedGroups = ( @@ -922,8 +904,8 @@ fun SettingsScreen( viewModel.clearIptvConfig() } } - } - "home_server" -> { + } + "home_server" -> { when (contentFocusIndex) { 0 -> { homeServerUrl = "" @@ -948,8 +930,8 @@ fun SettingsScreen( uiState.homeServerConnections.size + 2 -> viewModel.testHomeServerConnection() uiState.homeServerConnections.size + 3 -> viewModel.disconnectHomeServer() } - } - "catalogs" -> { + } + "catalogs" -> { if (contentFocusIndex == 0) { showCatalogInput = true } else { @@ -972,8 +954,8 @@ fun SettingsScreen( } } } - } - "stremio" -> { + } + "stremio" -> { when { contentFocusIndex in 0 until stremioAddons.size -> { val addon = stremioAddons[contentFocusIndex] @@ -992,8 +974,8 @@ fun SettingsScreen( showCustomAddonInput = true } } - } - "accounts" -> { + } + "accounts" -> { when (contentFocusIndex) { 0 -> { if (uiState.isLoggedIn) { @@ -1022,8 +1004,8 @@ fun SettingsScreen( } } } + else -> Unit } - else -> Unit } } else -> {} diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsSectionMetadata.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsSectionMetadata.kt new file mode 100644 index 00000000..3a125a45 --- /dev/null +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsSectionMetadata.kt @@ -0,0 +1,130 @@ +package com.arflix.tv.ui.screens.settings + +/** + * Symbolic row metadata for the TV/general settings sections. + * + * The settings screen uses these row keys instead of hard-coded numeric + * positions, so reordering or inserting rows only requires updating this + * single definition table. + */ +internal enum class TvGeneralSettingRow { + CONTENT_LANGUAGE, + DEFAULT_SUBTITLE, + SECONDARY_SUBTITLE, + AUDIO_LANGUAGE, + SUBTITLE_SIZE, + SUBTITLE_COLOR, + SUBTITLE_OFFSET, + SUBTITLE_STYLE, + SUBTITLE_STYLIZED, + FILTER_SUBTITLES_BY_LANGUAGE, + AUTO_PLAY_NEXT, + AUTO_PLAY_SINGLE_SOURCE, + AUTO_PLAY_MIN_QUALITY, + TRAILER_AUTO_PLAY, + TRAILER_SOUND_ENABLED, + FRAME_RATE_MATCHING_MODE, + QUALITY_FILTERS, + CARD_LAYOUT_MODE, + UI_MODE, + SKIP_PROFILE_SELECTION, + OLED_BLACK_BACKGROUND, + CLOCK_FORMAT, + SHOW_BUDGET, + SPOILER_BLUR, + ACCENT_COLOR, + DNS_PROVIDER, + SHOW_LOADING_STATS, + VOLUME_BOOST, + SUBTITLE_AI_ENABLED, + SUBTITLE_AI_MODEL, + SUBTITLE_AI_AUTO_SELECT, + SUBTITLE_REMOVE_HEARING_IMPAIRED, + SUBTITLE_AI_API_KEY, + SUBTITLE_AI_SERVER, + TRAILER_DELAY, + CUSTOM_USER_AGENT, +} + +internal data class SettingsSectionDefinition( + val id: String, + val rows: List, +) + +private val tvGeneralSectionDefinitions = listOf( + SettingsSectionDefinition( + id = "language", + rows = listOf( + TvGeneralSettingRow.CONTENT_LANGUAGE, + TvGeneralSettingRow.AUDIO_LANGUAGE, + ), + ), + SettingsSectionDefinition( + id = "subtitles", + rows = listOf( + TvGeneralSettingRow.DEFAULT_SUBTITLE, + TvGeneralSettingRow.SECONDARY_SUBTITLE, + TvGeneralSettingRow.SUBTITLE_SIZE, + TvGeneralSettingRow.SUBTITLE_COLOR, + TvGeneralSettingRow.SUBTITLE_OFFSET, + TvGeneralSettingRow.SUBTITLE_STYLE, + TvGeneralSettingRow.SUBTITLE_STYLIZED, + TvGeneralSettingRow.FILTER_SUBTITLES_BY_LANGUAGE, + ), + ), + SettingsSectionDefinition( + id = "ai_subtitles", + rows = listOf( + TvGeneralSettingRow.SUBTITLE_AI_ENABLED, + TvGeneralSettingRow.SUBTITLE_AI_MODEL, + TvGeneralSettingRow.SUBTITLE_AI_AUTO_SELECT, + TvGeneralSettingRow.SUBTITLE_REMOVE_HEARING_IMPAIRED, + TvGeneralSettingRow.SUBTITLE_AI_API_KEY, + TvGeneralSettingRow.SUBTITLE_AI_SERVER, + ), + ), + SettingsSectionDefinition( + id = "playback", + rows = listOf( + TvGeneralSettingRow.AUTO_PLAY_NEXT, + TvGeneralSettingRow.AUTO_PLAY_SINGLE_SOURCE, + TvGeneralSettingRow.AUTO_PLAY_MIN_QUALITY, + TvGeneralSettingRow.TRAILER_AUTO_PLAY, + TvGeneralSettingRow.TRAILER_SOUND_ENABLED, + TvGeneralSettingRow.TRAILER_DELAY, + TvGeneralSettingRow.QUALITY_FILTERS, + TvGeneralSettingRow.FRAME_RATE_MATCHING_MODE, + TvGeneralSettingRow.VOLUME_BOOST, + ), + ), + SettingsSectionDefinition( + id = "appearance", + rows = listOf( + TvGeneralSettingRow.CARD_LAYOUT_MODE, + TvGeneralSettingRow.UI_MODE, + TvGeneralSettingRow.OLED_BLACK_BACKGROUND, + TvGeneralSettingRow.CLOCK_FORMAT, + TvGeneralSettingRow.ACCENT_COLOR, + TvGeneralSettingRow.SPOILER_BLUR, + TvGeneralSettingRow.SHOW_BUDGET, + ), + ), + SettingsSectionDefinition( + id = "profiles", + rows = listOf( + TvGeneralSettingRow.SKIP_PROFILE_SELECTION, + ), + ), + SettingsSectionDefinition( + id = "network", + rows = listOf( + TvGeneralSettingRow.DNS_PROVIDER, + TvGeneralSettingRow.SHOW_LOADING_STATS, + TvGeneralSettingRow.CUSTOM_USER_AGENT, + ), + ), +) + +internal fun tvGeneralRowsForSection(section: String): List { + return tvGeneralSectionDefinitions.firstOrNull { it.id == section }?.rows.orEmpty() +} \ No newline at end of file From 5174560381ebd9269e7022b1dba84d32a5acd8c1 Mon Sep 17 00:00:00 2001 From: jonahmichael Date: Mon, 1 Jun 2026 15:34:46 +0530 Subject: [PATCH 3/3] refactor(settings): centralize settings row metadata --- .../settings/SettingsSectionMetadata.kt | 123 ++++++------------ 1 file changed, 39 insertions(+), 84 deletions(-) diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsSectionMetadata.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsSectionMetadata.kt index 3a125a45..1d928aaf 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsSectionMetadata.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsSectionMetadata.kt @@ -1,130 +1,85 @@ package com.arflix.tv.ui.screens.settings -/** - * Symbolic row metadata for the TV/general settings sections. - * - * The settings screen uses these row keys instead of hard-coded numeric - * positions, so reordering or inserting rows only requires updating this - * single definition table. - */ -internal enum class TvGeneralSettingRow { - CONTENT_LANGUAGE, - DEFAULT_SUBTITLE, - SECONDARY_SUBTITLE, - AUDIO_LANGUAGE, - SUBTITLE_SIZE, - SUBTITLE_COLOR, - SUBTITLE_OFFSET, - SUBTITLE_STYLE, - SUBTITLE_STYLIZED, - FILTER_SUBTITLES_BY_LANGUAGE, - AUTO_PLAY_NEXT, - AUTO_PLAY_SINGLE_SOURCE, - AUTO_PLAY_MIN_QUALITY, - TRAILER_AUTO_PLAY, - TRAILER_SOUND_ENABLED, - FRAME_RATE_MATCHING_MODE, - QUALITY_FILTERS, - CARD_LAYOUT_MODE, - UI_MODE, - SKIP_PROFILE_SELECTION, - OLED_BLACK_BACKGROUND, - CLOCK_FORMAT, - SHOW_BUDGET, - SPOILER_BLUR, - ACCENT_COLOR, - DNS_PROVIDER, - SHOW_LOADING_STATS, - VOLUME_BOOST, - SUBTITLE_AI_ENABLED, - SUBTITLE_AI_MODEL, - SUBTITLE_AI_AUTO_SELECT, - SUBTITLE_REMOVE_HEARING_IMPAIRED, - SUBTITLE_AI_API_KEY, - SUBTITLE_AI_SERVER, - TRAILER_DELAY, - CUSTOM_USER_AGENT, -} - internal data class SettingsSectionDefinition( val id: String, - val rows: List, + val rows: List, ) private val tvGeneralSectionDefinitions = listOf( SettingsSectionDefinition( id = "language", rows = listOf( - TvGeneralSettingRow.CONTENT_LANGUAGE, - TvGeneralSettingRow.AUDIO_LANGUAGE, + 0, + 3, ), ), SettingsSectionDefinition( id = "subtitles", rows = listOf( - TvGeneralSettingRow.DEFAULT_SUBTITLE, - TvGeneralSettingRow.SECONDARY_SUBTITLE, - TvGeneralSettingRow.SUBTITLE_SIZE, - TvGeneralSettingRow.SUBTITLE_COLOR, - TvGeneralSettingRow.SUBTITLE_OFFSET, - TvGeneralSettingRow.SUBTITLE_STYLE, - TvGeneralSettingRow.SUBTITLE_STYLIZED, - TvGeneralSettingRow.FILTER_SUBTITLES_BY_LANGUAGE, + 1, + 2, + 4, + 5, + 6, + 7, + 8, + 9, ), ), SettingsSectionDefinition( id = "ai_subtitles", rows = listOf( - TvGeneralSettingRow.SUBTITLE_AI_ENABLED, - TvGeneralSettingRow.SUBTITLE_AI_MODEL, - TvGeneralSettingRow.SUBTITLE_AI_AUTO_SELECT, - TvGeneralSettingRow.SUBTITLE_REMOVE_HEARING_IMPAIRED, - TvGeneralSettingRow.SUBTITLE_AI_API_KEY, - TvGeneralSettingRow.SUBTITLE_AI_SERVER, + 28, + 29, + 30, + 31, + 32, + 33, ), ), SettingsSectionDefinition( id = "playback", rows = listOf( - TvGeneralSettingRow.AUTO_PLAY_NEXT, - TvGeneralSettingRow.AUTO_PLAY_SINGLE_SOURCE, - TvGeneralSettingRow.AUTO_PLAY_MIN_QUALITY, - TvGeneralSettingRow.TRAILER_AUTO_PLAY, - TvGeneralSettingRow.TRAILER_SOUND_ENABLED, - TvGeneralSettingRow.TRAILER_DELAY, - TvGeneralSettingRow.QUALITY_FILTERS, - TvGeneralSettingRow.FRAME_RATE_MATCHING_MODE, - TvGeneralSettingRow.VOLUME_BOOST, + 10, + 11, + 12, + 13, + 14, + 34, + 16, + 15, + 27, ), ), SettingsSectionDefinition( id = "appearance", rows = listOf( - TvGeneralSettingRow.CARD_LAYOUT_MODE, - TvGeneralSettingRow.UI_MODE, - TvGeneralSettingRow.OLED_BLACK_BACKGROUND, - TvGeneralSettingRow.CLOCK_FORMAT, - TvGeneralSettingRow.ACCENT_COLOR, - TvGeneralSettingRow.SPOILER_BLUR, - TvGeneralSettingRow.SHOW_BUDGET, + 17, + 18, + 20, + 21, + 24, + 23, + 22, + 36, ), ), SettingsSectionDefinition( id = "profiles", rows = listOf( - TvGeneralSettingRow.SKIP_PROFILE_SELECTION, + 19, ), ), SettingsSectionDefinition( id = "network", rows = listOf( - TvGeneralSettingRow.DNS_PROVIDER, - TvGeneralSettingRow.SHOW_LOADING_STATS, - TvGeneralSettingRow.CUSTOM_USER_AGENT, + 25, + 26, + 35, ), ), ) -internal fun tvGeneralRowsForSection(section: String): List { +internal fun tvGeneralRowsForSection(section: String): List { return tvGeneralSectionDefinitions.firstOrNull { it.id == section }?.rows.orEmpty() } \ No newline at end of file