From 0b4e1d18bc395cdef0c258647118f061016b5a9c Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Thu, 15 Oct 2026 15:25:35 +0000 Subject: [PATCH] settings: snap the preamp to its grid and drop a duplicate header Review follow-ups. - Remove the second "Playback" section header the rows were added under; the section already had one, so the screen rendered it twice. - Snap the preamp to the 0.5 dB grid in the dialog. Material's slider snaps in pixel space and converts back, so the value it reports is near the configured grid but not on it: -1.0 dB arrives as -0.99999905, was displayed verbatim and was stored that way until the next drag. The step count is now derived from the range rather than hardcoded. - Format the decibel value with the resource locale so translated layouts get their own decimal separator. - The controls move to `ReplayGainControls.kt`, since the file now holds the grid as well as the labels, and the snap is covered by a test that reproduces the off-grid value without it. --- .../ui/settings/ReplayGainControls.kt | 37 +++++++++++++++++++ .../subtracks/ui/settings/ReplayGainLabels.kt | 23 ------------ .../subtracks/ui/settings/SettingsScreen.kt | 7 +--- ...abelsTest.kt => ReplayGainControlsTest.kt} | 20 +++++++++- 4 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainControls.kt delete mode 100644 app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainLabels.kt rename app/src/test/kotlin/com/subtracks/ui/settings/{ReplayGainLabelsTest.kt => ReplayGainControlsTest.kt} (54%) diff --git a/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainControls.kt b/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainControls.kt new file mode 100644 index 00000000..833fef72 --- /dev/null +++ b/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainControls.kt @@ -0,0 +1,37 @@ +package com.subtracks.ui.settings + +import android.content.res.Resources +import com.subtracks.R +import com.subtracks.data.prefs.MAX_REPLAY_GAIN_PREAMP_DB +import com.subtracks.data.prefs.MIN_REPLAY_GAIN_PREAMP_DB +import com.subtracks.data.prefs.ReplayGainMode +import kotlin.math.roundToInt + +internal const val REPLAY_GAIN_PREAMP_STEP_DB = 0.5f + +internal val REPLAY_GAIN_PREAMP_STEPS = + ((MAX_REPLAY_GAIN_PREAMP_DB - MIN_REPLAY_GAIN_PREAMP_DB) / REPLAY_GAIN_PREAMP_STEP_DB).toInt() - 1 + +// Material's slider snaps in pixel space and converts back, so the value it reports is near, but not +// on, the grid it was configured with (for example -0.99999905 for -1.0). Snap before showing or +// storing it. +internal fun snapReplayGainPreampDb(value: Float): Float = (value / REPLAY_GAIN_PREAMP_STEP_DB).roundToInt() * REPLAY_GAIN_PREAMP_STEP_DB + +internal fun replayGainModeLabel( + resources: Resources, + mode: ReplayGainMode, +): String = + when (mode) { + ReplayGainMode.Off -> resources.getString(R.string.settings_replay_gain_mode_off) + ReplayGainMode.Track -> resources.getString(R.string.settings_replay_gain_mode_track) + ReplayGainMode.Album -> resources.getString(R.string.settings_replay_gain_mode_album) + } + +internal fun replayGainPreampLabel( + resources: Resources, + decibels: Float, +): String { + val sign = if (decibels > 0f) "+" else "" + val magnitude = String.format(resources.configuration.locales[0], "%.1f", decibels) + return resources.getString(R.string.settings_replay_gain_preamp_value, "$sign$magnitude") +} diff --git a/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainLabels.kt b/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainLabels.kt deleted file mode 100644 index 4bfc533e..00000000 --- a/app/src/main/kotlin/com/subtracks/ui/settings/ReplayGainLabels.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.subtracks.ui.settings - -import android.content.res.Resources -import com.subtracks.R -import com.subtracks.data.prefs.ReplayGainMode - -internal fun replayGainModeLabel( - resources: Resources, - mode: ReplayGainMode, -): String = - when (mode) { - ReplayGainMode.Off -> resources.getString(R.string.settings_replay_gain_mode_off) - ReplayGainMode.Track -> resources.getString(R.string.settings_replay_gain_mode_track) - ReplayGainMode.Album -> resources.getString(R.string.settings_replay_gain_mode_album) - } - -internal fun replayGainPreampLabel( - resources: Resources, - decibels: Float, -): String { - val sign = if (decibels > 0f) "+" else "" - return resources.getString(R.string.settings_replay_gain_preamp_value, "$sign$decibels") -} diff --git a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt index 3709857a..7df3d06b 100644 --- a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt @@ -361,7 +361,6 @@ fun SettingsScreen( colors = ListItemDefaults.colors(containerColor = Color.Transparent), ) } - item { SectionHeader(stringResource(R.string.settings_music_name)) } item { ListItem( headlineContent = { Text(stringResource(R.string.settings_replay_gain_title)) }, @@ -729,8 +728,6 @@ private fun SyncModeDialog( ) } -private const val REPLAY_GAIN_PREAMP_STEPS = 59 - @Composable private fun ReplayGainModeDialog( selected: ReplayGainMode, @@ -771,7 +768,7 @@ private fun ReplayGainPreampDialog( onDismiss: () -> Unit, ) { val resources = LocalResources.current - var draft by remember(selected) { mutableFloatStateOf(selected) } + var draft by remember(selected) { mutableFloatStateOf(snapReplayGainPreampDb(selected)) } AlertDialog( onDismissRequest = onDismiss, title = { Text(stringResource(R.string.settings_replay_gain_preamp_title)) }, @@ -784,7 +781,7 @@ private fun ReplayGainPreampDialog( ) Slider( value = draft, - onValueChange = { draft = it }, + onValueChange = { draft = snapReplayGainPreampDb(it) }, valueRange = MIN_REPLAY_GAIN_PREAMP_DB..MAX_REPLAY_GAIN_PREAMP_DB, steps = REPLAY_GAIN_PREAMP_STEPS, ) diff --git a/app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainLabelsTest.kt b/app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainControlsTest.kt similarity index 54% rename from app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainLabelsTest.kt rename to app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainControlsTest.kt index 3d5bddbe..b6a5032d 100644 --- a/app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainLabelsTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/settings/ReplayGainControlsTest.kt @@ -3,13 +3,15 @@ package com.subtracks.ui.settings import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.subtracks.data.prefs.MAX_REPLAY_GAIN_PREAMP_DB +import com.subtracks.data.prefs.MIN_REPLAY_GAIN_PREAMP_DB import com.subtracks.data.prefs.ReplayGainMode import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class ReplayGainLabelsTest { +class ReplayGainControlsTest { private val resources = ApplicationProvider.getApplicationContext().resources @Test @@ -25,4 +27,20 @@ class ReplayGainLabelsTest { assertEquals("-2.5 dB", replayGainPreampLabel(resources, -2.5f)) assertEquals("0.0 dB", replayGainPreampLabel(resources, 0f)) } + + @Test + fun theSliderHasOneStepPerHalfDecibel() { + assertEquals(60, ((MAX_REPLAY_GAIN_PREAMP_DB - MIN_REPLAY_GAIN_PREAMP_DB) / REPLAY_GAIN_PREAMP_STEP_DB).toInt()) + assertEquals(59, REPLAY_GAIN_PREAMP_STEPS) + } + + @Test + fun thePreampSnapsBackOntoTheHalfDecibelGrid() { + assertEquals(0f, snapReplayGainPreampDb(-0f), 0f) + assertEquals(-1f, snapReplayGainPreampDb(-0.99999905f), 0f) + assertEquals(-3f, snapReplayGainPreampDb(-3.000001f), 0f) + assertEquals(14.5f, snapReplayGainPreampDb(14.6f), 0f) + assertEquals(MAX_REPLAY_GAIN_PREAMP_DB, snapReplayGainPreampDb(14.9f), 0f) + assertEquals(MIN_REPLAY_GAIN_PREAMP_DB, snapReplayGainPreampDb(-14.9f), 0f) + } }