@@ -9,6 +9,9 @@ import androidx.compose.foundation.layout.padding
99import androidx.compose.foundation.layout.size
1010import androidx.compose.foundation.layout.wrapContentHeight
1111import androidx.compose.foundation.shape.RoundedCornerShape
12+ import androidx.compose.material.icons.Icons
13+ import androidx.compose.material.icons.rounded.Add
14+ import androidx.compose.material.icons.rounded.Remove
1215import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
1316import androidx.compose.material3.Icon
1417import androidx.compose.material3.IconButton
@@ -67,16 +70,24 @@ fun SliderPreferenceItem(
6770 } else {
6871 0.0001f
6972 }
70- val isAtDefault = abs(sliderValue - defaultValue) <= defaultEpsilon
73+
74+ fun labelOf (value : Float ) = type.valueLabel?.invoke(value)
75+ ? : value.roundToInt().toString()
76+
77+ // Treat a value as default when it's within epsilon OR renders the same label as
78+ // the default (a continuous slider snaps many raw floats to one rounded "Ndp" label).
79+ fun isDefault (value : Float ) =
80+ abs(value - defaultValue) <= defaultEpsilon || labelOf(value) == labelOf(defaultValue)
81+
82+ val isAtDefault = isDefault(sliderValue)
7183
7284 LaunchedEffect (persistedValue) {
7385 if (sliderValue != persistedValue) {
7486 sliderValue = persistedValue
7587 }
7688 }
7789
78- val originalValueLabel = type.valueLabel?.invoke(sliderValue)
79- ? : sliderValue.roundToInt().toString()
90+ val originalValueLabel = labelOf(sliderValue)
8091 val valueLabel = if (type.showDefaultIndicator && isAtDefault) {
8192 if (type.hideDefaultValue) {
8293 stringResource(R .string.opt_default).replaceAll(" (" to " " , " )" to " " )
@@ -104,8 +115,7 @@ fun SliderPreferenceItem(
104115 fun updateUiValue (newValue : Float ) {
105116 if (! isEnabled || sliderValue == newValue) return
106117
107- val newLabel = type.valueLabel?.invoke(newValue)
108- ? : newValue.roundToInt().toString()
118+ val newLabel = labelOf(newValue)
109119
110120 if (newLabel != previousLabel) {
111121 onValueChangeWithHaptic()
@@ -116,14 +126,25 @@ fun SliderPreferenceItem(
116126 }
117127
118128 fun persistValue (value : Float ) {
119- val snapped = if (abs(value - defaultValue) <= defaultEpsilon) {
120- defaultValue
121- } else {
122- value
123- }
129+ // Snap to exact default when it reads as default so the stored value stays clean
130+ // and matches what the "Default" label and reset button report.
131+ val snapped = if (isDefault(value)) defaultValue else value
124132 prefController.setFloat(prefDefinition.key, snapped)
125133 }
126134
135+ // One discrete step; matches the slider's tick spacing, or 1 for a continuous slider.
136+ val stepIncrement = if (type.steps > 0 ) {
137+ (type.max - type.min) / (type.steps + 1 )
138+ } else {
139+ 1f
140+ }
141+
142+ fun stepBy (delta : Float ) {
143+ val newValue = (sliderValue + delta).coerceIn(type.min, type.max)
144+ updateUiValue(newValue)
145+ persistValue(newValue)
146+ }
147+
127148 PreferenceContainer (
128149 shape = shape,
129150 isEnabled = isEnabled,
@@ -148,8 +169,23 @@ fun SliderPreferenceItem(
148169 .fillMaxWidth()
149170 .padding(top = 6 .dp),
150171 verticalAlignment = Alignment .CenterVertically ,
151- horizontalArrangement = Arrangement .spacedBy(16 .dp)
172+ horizontalArrangement = Arrangement .spacedBy(8 .dp)
152173 ) {
174+ if (type.showStepButtons) {
175+ IconButton (
176+ enabled = isEnabled && sliderValue > type.min,
177+ shapes = IconButtonDefaults .shapes(),
178+ colors = IconButtonDefaults .filledTonalIconButtonColors(),
179+ onClick = { stepBy(- stepIncrement) },
180+ modifier = Modifier .size(28 .dp)
181+ ) {
182+ Icon (
183+ imageVector = Icons .Rounded .Remove ,
184+ contentDescription = stringResource(R .string.btn_decrease),
185+ modifier = Modifier .size(16 .dp)
186+ )
187+ }
188+ }
153189 Slider (
154190 value = sliderValue,
155191 onValueChange = { newValue ->
@@ -170,8 +206,22 @@ fun SliderPreferenceItem(
170206 modifier = Modifier
171207 .wrapContentHeight()
172208 .weight(1f )
173- .padding(top = 4 .dp)
174209 )
210+ if (type.showStepButtons) {
211+ IconButton (
212+ enabled = isEnabled && sliderValue < type.max,
213+ shapes = IconButtonDefaults .shapes(),
214+ colors = IconButtonDefaults .filledTonalIconButtonColors(),
215+ onClick = { stepBy(stepIncrement) },
216+ modifier = Modifier .size(28 .dp)
217+ ) {
218+ Icon (
219+ imageVector = Icons .Rounded .Add ,
220+ contentDescription = stringResource(R .string.btn_increase),
221+ modifier = Modifier .size(16 .dp)
222+ )
223+ }
224+ }
175225 if (type.showResetButton) {
176226 IconButton (
177227 enabled = isEnabled && ! isAtDefault,
0 commit comments