diff --git a/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncRepository.kt b/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncRepository.kt index 224d97b8d..56bb65f4f 100644 --- a/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncRepository.kt +++ b/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncRepository.kt @@ -59,6 +59,7 @@ class CloudSyncRepository @Inject constructor( val autoPlaySingleSource: Boolean = true, val autoPlayMinQuality: String = "Any", val trailerAutoPlay: Boolean = false, + val showBudget: Boolean = true, val includeSpecials: Boolean = false, val iptvHiddenGroups: String = "", val iptvGroupOrder: String = "" @@ -70,6 +71,8 @@ class CloudSyncRepository @Inject constructor( profileManager.profileStringKeyFor(profileId, "content_language") private fun trailerAutoPlayKeyFor(profileId: String) = profileManager.profileBooleanKeyFor(profileId, "trailer_auto_play") + private fun showBudgetKeyFor(profileId: String) = + profileManager.profileBooleanKeyFor(profileId, "show_budget_on_home") private fun subtitleSizeKeyFor(profileId: String) = profileManager.profileStringKeyFor(profileId, "subtitle_size") @@ -151,6 +154,7 @@ class CloudSyncRepository @Inject constructor( contentLanguage = prefs[contentLanguageKeyFor(profile.id)] ?: "en-US", trailerAutoPlay = prefs[trailerAutoPlayKeyFor(profile.id)] ?: false, + showBudget = prefs[showBudgetKeyFor(profile.id)] ?: true, subtitleSize = prefs[subtitleSizeKeyFor(profile.id)] ?: "Medium", subtitleColor = prefs[subtitleColorKeyFor(profile.id)] ?: "White", iptvHiddenGroups = prefs[iptvHiddenGroupsKeyFor(profile.id)] ?: "", @@ -370,6 +374,7 @@ class CloudSyncRepository @Inject constructor( prefs[contentLanguageKeyFor(profileId)] = state.contentLanguage prefs[trailerAutoPlayKeyFor(profileId)] = state.trailerAutoPlay + prefs[showBudgetKeyFor(profileId)] = state.showBudget prefs[subtitleSizeKeyFor(profileId)] = state.subtitleSize prefs[subtitleColorKeyFor(profileId)] = state.subtitleColor if (state.iptvHiddenGroups.isNotBlank()) prefs[iptvHiddenGroupsKeyFor(profileId)] = state.iptvHiddenGroups diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeScreen.kt index d5f18b5c0..c3298f44a 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeScreen.kt @@ -625,6 +625,7 @@ fun HomeScreen( heroOverviewOverride = displayHeroOverview, contentStartPadding = contentStartPadding, isMobile = isMobile, + showBudget = uiState.showBudget, onNavigateToDetails = onNavigateToDetails, onNavigateToTv = { channelId, streamUrl -> onNavigateToTv(channelId, streamUrl) }, isIptvItem = { item -> viewModel.isIptvItem(item) }, @@ -733,6 +734,11 @@ private fun HeroSection( item: MediaItem, logoUrl: String?, overviewOverride: String? = null, + // Hide the Budget line on the hero metadata row when false. Plumbed from + // HomeUiState.showBudget, which is loaded from the per-profile + // `show_budget_on_home` DataStore key and defaults to true so existing + // users see no behavior change. Issue #72. + showBudget: Boolean = true, modifier: Modifier = Modifier ) { val context = LocalContext.current @@ -994,7 +1000,10 @@ private fun HeroSection( } } - if (!budgetText.isNullOrBlank()) { + // Budget line can be hidden via Settings -> General -> Show Budget on Home. + // Default is shown (showBudget = true) to preserve existing behavior. + // Issue #72. + if (showBudget && !budgetText.isNullOrBlank()) { if (displayDate.isNotEmpty() || hasGenre || hasDuration || ratingValue > 0f) { Text( text = "|", @@ -1070,6 +1079,7 @@ private fun HomeHeroLayer( heroOverviewOverride: String?, contentStartPadding: androidx.compose.ui.unit.Dp, isMobile: Boolean = false, + showBudget: Boolean = true, onNavigateToDetails: (MediaType, Int, Int?, Int?) -> Unit = { _, _, _, _ -> }, onNavigateToTv: (channelId: String?, streamUrl: String?) -> Unit = { _, _ -> }, isIptvItem: (MediaItem) -> Boolean = { false }, @@ -1098,6 +1108,7 @@ private fun HomeHeroLayer( item = item, logoUrl = heroLogoUrl, overviewOverride = heroOverviewOverride, + showBudget = showBudget, modifier = Modifier .align(Alignment.BottomStart) .padding(start = contentStartPadding, end = 400.dp) diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeViewModel.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeViewModel.kt index 5f85353d1..4414be9e6 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeViewModel.kt @@ -66,6 +66,8 @@ data class HomeUiState( val heroLogoUrl: String? = null, val heroTrailerKey: String? = null, val trailerAutoPlay: Boolean = false, + // Home hero metadata visibility toggles (issue #72) + val showBudget: Boolean = true, val heroOverviewOverride: String? = null, val cardLogoUrls: Map = emptyMap(), // Previous hero for crossfade (Phase 2.1) @@ -640,7 +642,7 @@ class HomeViewModel @Inject constructor( } init { - // Load trailer auto-play setting + // Load trailer auto-play and show-budget settings viewModelScope.launch { try { val prefs = context.settingsDataStore.data.first() @@ -648,7 +650,17 @@ class HomeViewModel @Inject constructor( val trailerEnabled = prefs.asMap().any { (key, value) -> key.name.endsWith("_trailer_auto_play") && value == true } - _uiState.value = _uiState.value.copy(trailerAutoPlay = trailerEnabled) + // show_budget_on_home defaults to TRUE so existing users see no change + // until they explicitly disable it. We check any active-profile key; if + // none exist yet the default of true is preserved. Issue #72. + val showBudgetExplicit = prefs.asMap().entries + .firstOrNull { (key, _) -> key.name.endsWith("_show_budget_on_home") } + ?.value as? Boolean + val showBudget = showBudgetExplicit ?: true + _uiState.value = _uiState.value.copy( + trailerAutoPlay = trailerEnabled, + showBudget = showBudget + ) } catch (_: Exception) {} } // Restore logo URL cache from disk for instant clearlogos on cold start 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..33877bc3d 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 @@ -253,7 +253,7 @@ fun SettingsScreen( if (scrollState.maxValue <= 0) return@LaunchedEffect val maxIndex = when (sectionIndex) { - 0 -> 13 // General: 14 items + 0 -> 14 // General: 15 items (added Show Budget toggle for #72) 1 -> 3 // IPTV: Configure + Refresh + Delete + Stalker 2 -> uiState.catalogs.size // Catalogs 3 -> uiState.addons.size // Addons @@ -433,7 +433,7 @@ fun SettingsScreen( Zone.CONTENT -> { // Dynamic max based on current section val maxIndex = when (sectionIndex) { - 0 -> 13 // General: 14 items + 0 -> 14 // General: 15 items (added Show Budget toggle for #72) 1 -> 3 // IPTV: Configure + Refresh + Delete + Stalker 2 -> uiState.catalogs.size // Catalogs: Add + N catalogs 3 -> uiState.addons.size // Addons: N addons + "Add Custom" button @@ -486,7 +486,8 @@ fun SettingsScreen( 10 -> viewModel.toggleCardLayoutMode() 11 -> { val next = when (uiState.deviceModeOverride) { "auto" -> "tv"; "tv" -> "tablet"; "tablet" -> "phone"; else -> "auto" }; viewModel.setDeviceModeOverride(next) } 12 -> viewModel.setSkipProfileSelection(!uiState.skipProfileSelection) - 13 -> openDnsProviderPicker() + 13 -> viewModel.setShowBudget(!uiState.showBudget) + 14 -> openDnsProviderPicker() } } 1 -> { // IPTV @@ -615,6 +616,7 @@ fun SettingsScreen( subtitleColor = uiState.subtitleColor, deviceModeOverride = uiState.deviceModeOverride, skipProfileSelection = uiState.skipProfileSelection, + showBudget = uiState.showBudget, focusedIndex = -1, onSubtitleClick = openSubtitlePicker, onAudioLanguageClick = openAudioLanguagePicker, @@ -633,6 +635,7 @@ fun SettingsScreen( onContentLanguageClick = openContentLanguagePicker, onSubtitleSizeClick = { viewModel.cycleSubtitleSize() }, onSkipProfileSelectionToggle = { viewModel.setSkipProfileSelection(it) }, + onShowBudgetToggle = { viewModel.setShowBudget(it) }, onSubtitleColorClick = { viewModel.cycleSubtitleColor() } ) "iptv" -> IptvSettings( @@ -787,6 +790,7 @@ fun SettingsScreen( subtitleColor = uiState.subtitleColor, deviceModeOverride = uiState.deviceModeOverride, skipProfileSelection = uiState.skipProfileSelection, + showBudget = uiState.showBudget, focusedIndex = if (activeZone == Zone.CONTENT) contentFocusIndex else -1, onSubtitleClick = openSubtitlePicker, onAudioLanguageClick = openAudioLanguagePicker, @@ -804,6 +808,7 @@ fun SettingsScreen( }, onContentLanguageClick = openContentLanguagePicker, onSkipProfileSelectionToggle = { viewModel.setSkipProfileSelection(it) }, + onShowBudgetToggle = { viewModel.setShowBudget(it) }, onSubtitleSizeClick = { viewModel.cycleSubtitleSize() }, onSubtitleColorClick = { viewModel.cycleSubtitleColor() } ) @@ -2115,6 +2120,7 @@ private fun GeneralSettings( subtitleColor: String = "White", deviceModeOverride: String = "auto", skipProfileSelection: Boolean = false, + showBudget: Boolean = true, focusedIndex: Int, onSubtitleClick: () -> Unit, onAudioLanguageClick: () -> Unit, @@ -2127,6 +2133,7 @@ private fun GeneralSettings( onDeviceModeClick: () -> Unit = {}, onContentLanguageClick: () -> Unit = {}, onSkipProfileSelectionToggle: (Boolean) -> Unit = {}, + onShowBudgetToggle: (Boolean) -> Unit = {}, trailerAutoPlay: Boolean = false, onSubtitleSizeClick: () -> Unit = {}, onSubtitleColorClick: () -> Unit = {}, @@ -2276,6 +2283,16 @@ private fun GeneralSettings( isFocused = focusedIndex == 12, onToggle = onSkipProfileSelectionToggle ) + Spacer(modifier = Modifier.height(10.dp)) + // Home hero controls — issue #72. The movie Budget line on the hero banner + // makes the metadata row noisy on small screens and some users want to hide it. + SettingsToggleRow( + title = "Show Budget on Home", + subtitle = "Display the movie budget on the home hero banner", + isEnabled = showBudget, + isFocused = focusedIndex == 13, + onToggle = onShowBudgetToggle + ) // ── Network ── Spacer(modifier = Modifier.height(24.dp)) @@ -2291,7 +2308,7 @@ private fun GeneralSettings( title = "DNS Provider", subtitle = "Resolve API and stream requests", value = dnsProvider, - isFocused = focusedIndex == 13, + isFocused = focusedIndex == 14, onClick = onDnsProviderClick ) } diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsViewModel.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsViewModel.kt index 1ce2c2c27..698e3e351 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/settings/SettingsViewModel.kt @@ -77,6 +77,7 @@ data class SettingsUiState( val subtitleSize: String = "Medium", val subtitleColor: String = "White", val trailerAutoPlay: Boolean = false, + val showBudget: Boolean = true, val includeSpecials: Boolean = false, val isLoggedIn: Boolean = false, val accountEmail: String? = null, @@ -178,6 +179,7 @@ class SettingsViewModel @Inject constructor( private fun autoPlayMinQualityKey() = profileManager.profileStringKey("auto_play_min_quality") private fun autoPlayMinQualityKeyFor(profileId: String) = profileManager.profileStringKeyFor(profileId, "auto_play_min_quality") private fun trailerAutoPlayKey() = profileManager.profileBooleanKey("trailer_auto_play") + private fun showBudgetKey() = profileManager.profileBooleanKey("show_budget_on_home") private fun subtitleSizeKey() = profileManager.profileStringKey("subtitle_size") private fun subtitleColorKey() = profileManager.profileStringKey("subtitle_color") @@ -265,6 +267,7 @@ class SettingsViewModel @Inject constructor( } val autoPlayMinQuality = normalizeAutoPlayMinQuality(prefs[autoPlayMinQualityKey()]) val trailerAutoPlay = prefs[trailerAutoPlayKey()] ?: false + val showBudget = prefs[showBudgetKey()] ?: true val subtitleSize = prefs[subtitleSizeKey()] ?: "Medium" val subtitleColor = prefs[subtitleColorKey()] ?: "White" @@ -304,6 +307,7 @@ class SettingsViewModel @Inject constructor( autoPlaySingleSource = autoPlaySingleSource, autoPlayMinQuality = autoPlayMinQuality, trailerAutoPlay = trailerAutoPlay, + showBudget = showBudget, subtitleSize = subtitleSize, subtitleColor = subtitleColor, @@ -784,6 +788,14 @@ class SettingsViewModel @Inject constructor( viewModelScope.launch { context.settingsDataStore.edit { it[trailerAutoPlayKey()] = enabled }; _uiState.value = _uiState.value.copy(trailerAutoPlay = enabled); syncLocalStateToCloud(silent = true) } } + fun setShowBudget(enabled: Boolean) { + viewModelScope.launch { + context.settingsDataStore.edit { it[showBudgetKey()] = enabled } + _uiState.value = _uiState.value.copy(showBudget = enabled) + syncLocalStateToCloud(silent = true) + } + } + fun cycleSubtitleSize() { val next = when (_uiState.value.subtitleSize) { "Small" -> "Medium"; "Medium" -> "Large"; "Large" -> "Extra Large"; else -> "Small" } viewModelScope.launch { context.settingsDataStore.edit { it[subtitleSizeKey()] = next }; _uiState.value = _uiState.value.copy(subtitleSize = next); syncLocalStateToCloud(silent = true) }