feat: Show Budget on Home toggle in Settings (#72) - #131
Merged
Conversation
The movie budget line on the home hero banner makes the metadata row noisy, particularly on small screens where it pushes release date, runtime, and rating onto a second line. This adds a new Settings toggle (General > Show Budget on Home, default ON) so users who don't care about movie budgets can hide the field without losing the rest of the hero metadata. Scope of this feature was one of the multi-part requests in #72. Changes: - HomeUiState: new `showBudget: Boolean = true` field. - HomeViewModel.init: loads the new `_show_budget_on_home` key from DataStore alongside the existing trailer_auto_play load, with a default of true so existing users see no change until they explicitly disable it. - HomeScreen: wraps the existing "Budget $budgetText" Text (and its preceding `|` separator) in `if (uiState.showBudget && ...)`. No other visual changes. - SettingsUiState: new `showBudget: Boolean = true` field. - SettingsViewModel: new `showBudgetKey()` helper, loaded into UI state, and `setShowBudget(enabled)` mutator that persists + triggers cloud sync (matches the trailerAutoPlay pattern exactly). - SettingsScreen: new `SettingsToggleRow` for "Show Budget on Home" inserted at focusedIndex == 13 (immediately after "Skip Profile Selection" and before the Network section). DNS Provider shifted from focusedIndex == 13 to focusedIndex == 14, and the max-index clamp in BOTH the auto-scroll LaunchedEffect and the D-pad-down handler bumped from 13 to 14 so DNS Provider remains reachable via remote navigation. The Enter-handler switch now maps 13 -> setShowBudget and 14 -> openDnsProviderPicker. This is the recurring "settings row focus index" footgun that burned PRs #110 and #112 previously \u2014 I've updated both the dynamic max-index AND the scroll auto-scroll max AND the per-index action switch in the same commit. - CloudSyncRepository: adds `showBudget` to the `CloudProfileSettings` data class, `showBudgetKeyFor(profileId)` helper, and push/pull wiring so the setting syncs across devices via the existing account_sync_state snapshot path (the same plumbing that handles trailer_auto_play). Closes #72 (Show Budget part). The other two parts of #72 (auto-hide top bar, native debrid manager) are scoped separately as larger features and are not in this PR.
ProdigyV21
pushed a commit
that referenced
this pull request
Apr 5, 2026
Adds a new Settings row "Volume Boost" under the Audio subsection that cycles through 0 / 3 / 6 / 9 / 12 / 15 dB. 0 dB is the default and attaches no effect. Above 0 dB, the player creates an Android `android.media.audiofx.LoudnessEnhancer` bound to the ExoPlayer audio session and applies the target gain. Useful for content whose source audio is very quiet relative to the user's TV/speaker setup \u2014 repeatedly requested in #88 and comparable to the volume boost feature in Debrify TV that the OP referenced. Changes: - `SettingsUiState` / `SettingsViewModel`: new `volumeBoostDb: Int` field. Stored as a string in DataStore via `profileManager.profileStringKey("volume_boost_db")` because ProfileManager has no int helper. Parsed back to Int on read with a 0-15 clamp. `cycleVolumeBoost()` mutator advances through the steps and triggers cloud sync. - `SettingsScreen`: new `Audio` subsection at the bottom of General (below `Network`) containing a single `SettingsRow` for "Volume Boost". The row is placed at `focusedIndex == 14` so no existing indices shift. Both the auto-scroll max-index and the D-pad-down max-index clamps are bumped from 13 to 14. The Enter handler maps `14 -> viewModel.cycleVolumeBoost()`. DNS Provider stays at index 13. - `PlayerUiState` / `PlayerViewModel`: new `volumeBoostDb: Int` field, loaded from the same DataStore key during `loadDetails` initialization. - `PlayerScreen`: new `DisposableEffect(uiState.volumeBoostDb, exoPlayer.audioSessionId)` that creates a `LoudnessEnhancer` when targetDb > 0 and the session id is valid, sets target gain in millibels (dB * 100), enables the effect, and releases it on dispose. Wrapped in try/catch because some Android TV devices reject audio-session effects when HDMI passthrough is enabled for DTS/AC3 \u2014 we fail silently and the user gets unboosted audio but playback still works. - `CloudSyncRepository`: `volumeBoostDb` added to `CloudProfileSettings`, `volumeBoostDbKeyFor(profileId)` helper, push/pull wiring so the setting syncs across devices. Default is 0 so existing users see no change. Stored as string via profileStringKeyFor for the same reason as the SettingsViewModel side. Cap: +15 dB (1500 millibels). Higher values tend to introduce audible distortion on already-compressed streaming audio. The LoudnessEnhancer class supports more but we intentionally don't expose it. ## Merge-conflict note This PR touches `SettingsScreen.kt`, `SettingsViewModel.kt`, and `CloudSyncRepository.kt` which PR #131 (Hide Budget, #72) also modifies. Both PRs add a new settings row with different indices and different fields in `CloudProfileSettings`. Whoever merges second will need a small rebase to resolve: - General section max-index should become 15 items (max 15) with both rows present. - `CloudProfileSettings` needs both `showBudget` and `volumeBoostDb` fields. - The Enter handler needs both `13 -> cycleVolumeBoost` and `14 -> openDnsProviderPicker` if #131 lands first, or an additional `15 -> setShowBudget` entry if this PR lands first. The conflict is purely additive and both PRs can coexist on main. Closes #88
ProdigyV21
pushed a commit
that referenced
this pull request
Apr 5, 2026
Adds a new Settings row "Volume Boost" under the Audio subsection that cycles through 0 / 3 / 6 / 9 / 12 / 15 dB. 0 dB is the default and attaches no effect. Above 0 dB, the player creates an Android `android.media.audiofx.LoudnessEnhancer` bound to the ExoPlayer audio session and applies the target gain. Useful for content whose source audio is very quiet relative to the user's TV/speaker setup \u2014 repeatedly requested in #88 and comparable to the volume boost feature in Debrify TV that the OP referenced. Changes: - `SettingsUiState` / `SettingsViewModel`: new `volumeBoostDb: Int` field. Stored as a string in DataStore via `profileManager.profileStringKey("volume_boost_db")` because ProfileManager has no int helper. Parsed back to Int on read with a 0-15 clamp. `cycleVolumeBoost()` mutator advances through the steps and triggers cloud sync. - `SettingsScreen`: new `Audio` subsection at the bottom of General (below `Network`) containing a single `SettingsRow` for "Volume Boost". The row is placed at `focusedIndex == 14` so no existing indices shift. Both the auto-scroll max-index and the D-pad-down max-index clamps are bumped from 13 to 14. The Enter handler maps `14 -> viewModel.cycleVolumeBoost()`. DNS Provider stays at index 13. - `PlayerUiState` / `PlayerViewModel`: new `volumeBoostDb: Int` field, loaded from the same DataStore key during `loadDetails` initialization. - `PlayerScreen`: new `DisposableEffect(uiState.volumeBoostDb, exoPlayer.audioSessionId)` that creates a `LoudnessEnhancer` when targetDb > 0 and the session id is valid, sets target gain in millibels (dB * 100), enables the effect, and releases it on dispose. Wrapped in try/catch because some Android TV devices reject audio-session effects when HDMI passthrough is enabled for DTS/AC3 \u2014 we fail silently and the user gets unboosted audio but playback still works. - `CloudSyncRepository`: `volumeBoostDb` added to `CloudProfileSettings`, `volumeBoostDbKeyFor(profileId)` helper, push/pull wiring so the setting syncs across devices. Default is 0 so existing users see no change. Stored as string via profileStringKeyFor for the same reason as the SettingsViewModel side. Cap: +15 dB (1500 millibels). Higher values tend to introduce audible distortion on already-compressed streaming audio. The LoudnessEnhancer class supports more but we intentionally don't expose it. This PR touches `SettingsScreen.kt`, `SettingsViewModel.kt`, and `CloudSyncRepository.kt` which PR #131 (Hide Budget, #72) also modifies. Both PRs add a new settings row with different indices and different fields in `CloudProfileSettings`. Whoever merges second will need a small rebase to resolve: - General section max-index should become 15 items (max 15) with both rows present. - `CloudProfileSettings` needs both `showBudget` and `volumeBoostDb` fields. - The Enter handler needs both `13 -> cycleVolumeBoost` and `14 -> openDnsProviderPicker` if #131 lands first, or an additional `15 -> setShowBudget` entry if this PR lands first. The conflict is purely additive and both PRs can coexist on main. Closes #88
ProdigyV21
added a commit
that referenced
this pull request
Apr 5, 2026
Adds a new Settings row "Volume Boost" under the Audio subsection that cycles through 0 / 3 / 6 / 9 / 12 / 15 dB. 0 dB is the default and attaches no effect. Above 0 dB, the player creates an Android `android.media.audiofx.LoudnessEnhancer` bound to the ExoPlayer audio session and applies the target gain. Useful for content whose source audio is very quiet relative to the user's TV/speaker setup \u2014 repeatedly requested in #88 and comparable to the volume boost feature in Debrify TV that the OP referenced. Changes: - `SettingsUiState` / `SettingsViewModel`: new `volumeBoostDb: Int` field. Stored as a string in DataStore via `profileManager.profileStringKey("volume_boost_db")` because ProfileManager has no int helper. Parsed back to Int on read with a 0-15 clamp. `cycleVolumeBoost()` mutator advances through the steps and triggers cloud sync. - `SettingsScreen`: new `Audio` subsection at the bottom of General (below `Network`) containing a single `SettingsRow` for "Volume Boost". The row is placed at `focusedIndex == 14` so no existing indices shift. Both the auto-scroll max-index and the D-pad-down max-index clamps are bumped from 13 to 14. The Enter handler maps `14 -> viewModel.cycleVolumeBoost()`. DNS Provider stays at index 13. - `PlayerUiState` / `PlayerViewModel`: new `volumeBoostDb: Int` field, loaded from the same DataStore key during `loadDetails` initialization. - `PlayerScreen`: new `DisposableEffect(uiState.volumeBoostDb, exoPlayer.audioSessionId)` that creates a `LoudnessEnhancer` when targetDb > 0 and the session id is valid, sets target gain in millibels (dB * 100), enables the effect, and releases it on dispose. Wrapped in try/catch because some Android TV devices reject audio-session effects when HDMI passthrough is enabled for DTS/AC3 \u2014 we fail silently and the user gets unboosted audio but playback still works. - `CloudSyncRepository`: `volumeBoostDb` added to `CloudProfileSettings`, `volumeBoostDbKeyFor(profileId)` helper, push/pull wiring so the setting syncs across devices. Default is 0 so existing users see no change. Stored as string via profileStringKeyFor for the same reason as the SettingsViewModel side. Cap: +15 dB (1500 millibels). Higher values tend to introduce audible distortion on already-compressed streaming audio. The LoudnessEnhancer class supports more but we intentionally don't expose it. This PR touches `SettingsScreen.kt`, `SettingsViewModel.kt`, and `CloudSyncRepository.kt` which PR #131 (Hide Budget, #72) also modifies. Both PRs add a new settings row with different indices and different fields in `CloudProfileSettings`. Whoever merges second will need a small rebase to resolve: - General section max-index should become 15 items (max 15) with both rows present. - `CloudProfileSettings` needs both `showBudget` and `volumeBoostDb` fields. - The Enter handler needs both `13 -> cycleVolumeBoost` and `14 -> openDnsProviderPicker` if #131 lands first, or an additional `15 -> setShowBudget` entry if this PR lands first. The conflict is purely additive and both PRs can coexist on main. Closes #88 Co-authored-by: Arvin <arvin@arflix.local>
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #72 (Show Budget part only).
Adds a new Settings toggle: General → Show Budget on Home (default ON). When disabled, the movie budget line is hidden from the home hero banner. Particularly useful on small screens where the budget pushes release date, runtime, and rating onto a second line.
The other two parts of #72 (auto-hide top bar, native debrid manager) are larger features scoped separately — this PR only covers the trivial "hide budget" toggle.
Changes
UI
HomeUiStategainsshowBudget: Boolean = true.HomeScreenwraps the existing "Budget $budgetText" Text (and its preceding|separator) inif (uiState.showBudget && ...). No layout changes, no visual regressions when the setting is ON (the default).SettingsScreenadds a newSettingsToggleRow"Show Budget on Home" atfocusedIndex == 13, immediately after "Skip Profile Selection" and before the Network subsection.Persistence
SettingsUiStategainsshowBudget: Boolean = true.SettingsViewModeladdsshowBudgetKey()(new DataStore key_show_budget_on_homeper profile) andsetShowBudget(enabled)that persists + triggers cloud sync. Follows thetrailerAutoPlaypattern exactly.HomeViewModel.initloads the new key alongside the existingtrailer_auto_playload. Defaults totrueso existing users see no change until they explicitly disable it.Cloud sync
CloudSyncRepository: addsshowBudgettoCloudProfileSettings, a newshowBudgetKeyFor(profileId)helper, and push/pull wiring so the setting syncs across devices via the existingaccount_sync_statesnapshot path. This was a recurring review comment on other settings PRs (feat: add toggle for trailer banner audio #110, feat: implement episode spoiler protection #112) — handled upfront in this PR.The recurring focus-index footgun — handled correctly this time
PRs #110 and #112 were blocked on review because they inserted new settings rows and forgot to update the D-pad max-index clamp, making
DNS Providerunreachable via remote navigation. I've updated all three places that need to change when a row is inserted:val maxIndex = when (sectionIndex) { 0 -> ... }) — bumped from 13 to 14.LaunchedEffect(contentFocusIndex, ...)— bumped from 13 to 14 with comment updated to "General: 15 items".13 -> viewModel.setShowBudget(!uiState.showBudget)and shifted13 -> openDnsProviderPicker()to14 -> openDnsProviderPicker().DNS Providerremains reachable via D-pad navigation after this change.Test plan
Risk
Low. Single new Boolean toggle, 56 additions, 7 deletions across 5 files. No existing code paths change when the setting is ON (the default). The focus-index footgun is the biggest risk surface and is specifically audited in the commit message.