Make more sliders discrete and ensure they don't look out of place (#1840)

Also cleanup the underlying code
This commit is contained in:
AntsyLich 2025-03-09 12:28:24 +06:00 committed by GitHub
parent 7913679f9d
commit 4f06c1cc09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 89 additions and 65 deletions

View File

@ -13,7 +13,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
## [Unreleased] ## [Unreleased]
### Added ### Added
- Add option to always decode long strip images with SSIV - Add option to always decode long strip images with SSIV
- Change option label ([@AntsyLich](https://github.com/AntsyLich)) ([#1835](https://github.com/mihonapp/mihon/pull/1835)) - Change option label ([@AntsyLich](https://github.com/AntsyLich)) ([#1835](https://github.com/mihonapp/mihon/pull/1835))
- Added option to enable incognito per extension ([@sdaqo](https://github.com/sdaqo), [@AntsyLich](https://github.com/AntsyLich)) ([#157](https://github.com/mihonapp/mihon/pull/157)) - Added option to enable incognito per extension ([@sdaqo](https://github.com/sdaqo), [@AntsyLich](https://github.com/AntsyLich)) ([#157](https://github.com/mihonapp/mihon/pull/157))
- Add button to favorite manga from history screen ([@Animeboynz](https://github.com/Animeboynz)) ([#1733](https://github.com/mihonapp/mihon/pull/1733)) - Add button to favorite manga from history screen ([@Animeboynz](https://github.com/Animeboynz)) ([#1733](https://github.com/mihonapp/mihon/pull/1733))
- Add Monochrome theme (made with e-ink displays in mind) ([@MajorTanya](https://github.com/MajorTanya)) ([#1752](https://github.com/mihonapp/mihon/pull/1752)) - Add Monochrome theme (made with e-ink displays in mind) ([@MajorTanya](https://github.com/MajorTanya)) ([#1752](https://github.com/mihonapp/mihon/pull/1752))
@ -25,6 +25,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Display staff information on Anilist tracker search results ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#1810](https://github.com/mihonapp/mihon/pull/1810)) - Display staff information on Anilist tracker search results ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#1810](https://github.com/mihonapp/mihon/pull/1810))
### Changed ### Changed
- Sliders UI
- Apply "Downloaded only" filter to all entries regardless of favourite status ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#1603](https://github.com/mihonapp/mihon/pull/1603)) - Apply "Downloaded only" filter to all entries regardless of favourite status ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#1603](https://github.com/mihonapp/mihon/pull/1603))
- Ignore hidden files/folders for Local Source chapter list ([@BrutuZ](https://github.com/BrutuZ)) ([#1763](https://github.com/mihonapp/mihon/pull/1763)) - Ignore hidden files/folders for Local Source chapter list ([@BrutuZ](https://github.com/BrutuZ)) ([#1763](https://github.com/mihonapp/mihon/pull/1763))
- Migrate to newer Bangumi API ([@MajorTanya](https://github.com/MajorTanya)) ([#1748](https://github.com/mihonapp/mihon/pull/1748)) - Migrate to newer Bangumi API ([@MajorTanya](https://github.com/MajorTanya)) ([#1748](https://github.com/mihonapp/mihon/pull/1748))

View File

@ -252,9 +252,9 @@ private fun ColumnScope.DisplayPage(
val columns by columnPreference.collectAsState() val columns by columnPreference.collectAsState()
SliderItem( SliderItem(
label = stringResource(MR.strings.pref_library_columns),
max = 10,
value = columns, value = columns,
valueRange = 0..10,
label = stringResource(MR.strings.pref_library_columns),
valueText = if (columns > 0) { valueText = if (columns > 0) {
columns.toString() columns.toString()
} else { } else {

View File

@ -1,5 +1,6 @@
package eu.kanade.presentation.more.settings package eu.kanade.presentation.more.settings
import androidx.annotation.IntRange
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@ -50,10 +51,9 @@ sealed class Preference {
*/ */
data class SliderPreference( data class SliderPreference(
val value: Int, val value: Int,
val max: Int,
val min: Int = 0,
val steps: Int = 0,
override val title: String, override val title: String,
val valueRange: IntProgression = 0..1,
@IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 },
override val subtitle: String? = null, override val subtitle: String? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: Int) -> Boolean = { true }, override val onValueChanged: suspend (value: Int) -> Boolean = { true },

View File

@ -5,6 +5,7 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
@ -13,16 +14,20 @@ import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.InfoWidget import eu.kanade.presentation.more.settings.widget.InfoWidget
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
import eu.kanade.presentation.more.settings.widget.MultiSelectListPreferenceWidget import eu.kanade.presentation.more.settings.widget.MultiSelectListPreferenceWidget
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
import eu.kanade.presentation.more.settings.widget.PrefsVerticalPadding
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TitleFontSize
import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.SliderItem import tachiyomi.presentation.core.components.BaseSliderItem
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false } val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
@ -77,19 +82,22 @@ internal fun PreferenceItem(
) )
} }
is Preference.PreferenceItem.SliderPreference -> { is Preference.PreferenceItem.SliderPreference -> {
SliderItem( BaseSliderItem(
label = item.title, label = item.title,
min = item.min,
max = item.max,
steps = item.steps,
value = item.value, value = item.value,
valueRange = item.valueRange,
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(), valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
steps = item.steps,
labelStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize),
onChange = { onChange = {
scope.launch { scope.launch {
item.onValueChanged(it) item.onValueChanged(it)
} }
}, },
labelStyle = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(
horizontal = PrefsHorizontalPadding,
vertical = PrefsVerticalPadding,
),
) )
} }
is Preference.PreferenceItem.ListPreference<*> -> { is Preference.PreferenceItem.ListPreference<*> -> {

View File

@ -141,9 +141,7 @@ object SettingsReaderScreen : SearchableSettings {
), ),
Preference.PreferenceItem.SliderPreference( Preference.PreferenceItem.SliderPreference(
value = flashMillis / ReaderPreferences.MILLI_CONVERSION, value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
max = 15, valueRange = 1..15,
min = 1,
steps = 13,
title = stringResource(MR.strings.pref_flash_duration), title = stringResource(MR.strings.pref_flash_duration),
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis), subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
enabled = flashPageState, enabled = flashPageState,
@ -154,9 +152,7 @@ object SettingsReaderScreen : SearchableSettings {
), ),
Preference.PreferenceItem.SliderPreference( Preference.PreferenceItem.SliderPreference(
value = flashInterval, value = flashInterval,
max = 10, valueRange = 1..10,
min = 1,
steps = 8,
title = stringResource(MR.strings.pref_flash_page_interval), title = stringResource(MR.strings.pref_flash_page_interval),
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval), subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
enabled = flashPageState, enabled = flashPageState,
@ -342,8 +338,9 @@ object SettingsReaderScreen : SearchableSettings {
), ),
Preference.PreferenceItem.SliderPreference( Preference.PreferenceItem.SliderPreference(
value = webtoonSidePadding, value = webtoonSidePadding,
max = ReaderPreferences.WEBTOON_PADDING_MAX, valueRange = ReaderPreferences.let {
min = ReaderPreferences.WEBTOON_PADDING_MIN, it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX
},
title = stringResource(MR.strings.pref_webtoon_side_padding), title = stringResource(MR.strings.pref_webtoon_side_padding),
subtitle = numberFormat.format(webtoonSidePadding / 100f), subtitle = numberFormat.format(webtoonSidePadding / 100f),
onValueChanged = { onValueChanged = {

View File

@ -37,11 +37,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
if (customBrightness) { if (customBrightness) {
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState() val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
SliderItem( SliderItem(
label = stringResource(MR.strings.pref_custom_brightness),
min = -75,
max = 100,
value = customBrightnessValue, value = customBrightnessValue,
valueText = customBrightnessValue.toString(), valueRange = -75..100,
steps = 0,
label = stringResource(MR.strings.pref_custom_brightness),
onChange = { screenModel.preferences.customBrightnessValue().set(it) }, onChange = { screenModel.preferences.customBrightnessValue().set(it) },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
@ -55,10 +54,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
if (colorFilter) { if (colorFilter) {
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState() val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
SliderItem( SliderItem(
label = stringResource(MR.strings.color_filter_r_value),
max = 255,
value = colorFilterValue.red, value = colorFilterValue.red,
valueText = colorFilterValue.red.toString(), valueRange = 0..255,
steps = 0,
label = stringResource(MR.strings.color_filter_r_value),
onChange = { newRValue -> onChange = { newRValue ->
screenModel.preferences.colorFilterValue().getAndSet { screenModel.preferences.colorFilterValue().getAndSet {
getColorValue(it, newRValue, RED_MASK, 16) getColorValue(it, newRValue, RED_MASK, 16)
@ -67,10 +66,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SliderItem( SliderItem(
label = stringResource(MR.strings.color_filter_g_value),
max = 255,
value = colorFilterValue.green, value = colorFilterValue.green,
valueText = colorFilterValue.green.toString(), valueRange = 0..255,
steps = 0,
label = stringResource(MR.strings.color_filter_g_value),
onChange = { newGValue -> onChange = { newGValue ->
screenModel.preferences.colorFilterValue().getAndSet { screenModel.preferences.colorFilterValue().getAndSet {
getColorValue(it, newGValue, GREEN_MASK, 8) getColorValue(it, newGValue, GREEN_MASK, 8)
@ -79,10 +78,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SliderItem( SliderItem(
label = stringResource(MR.strings.color_filter_b_value),
max = 255,
value = colorFilterValue.blue, value = colorFilterValue.blue,
valueText = colorFilterValue.blue.toString(), valueRange = 0..255,
steps = 0,
label = stringResource(MR.strings.color_filter_b_value),
onChange = { newBValue -> onChange = { newBValue ->
screenModel.preferences.colorFilterValue().getAndSet { screenModel.preferences.colorFilterValue().getAndSet {
getColorValue(it, newBValue, BLUE_MASK, 0) getColorValue(it, newBValue, BLUE_MASK, 0)
@ -91,10 +90,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SliderItem( SliderItem(
label = stringResource(MR.strings.color_filter_a_value),
max = 255,
value = colorFilterValue.alpha, value = colorFilterValue.alpha,
valueText = colorFilterValue.alpha.toString(), valueRange = 0..255,
steps = 0,
label = stringResource(MR.strings.color_filter_a_value),
onChange = { newAValue -> onChange = { newAValue ->
screenModel.preferences.colorFilterValue().getAndSet { screenModel.preferences.colorFilterValue().getAndSet {
getColorValue(it, newAValue, ALPHA_MASK, 24) getColorValue(it, newAValue, ALPHA_MASK, 24)

View File

@ -98,24 +98,20 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
if (flashPageState) { if (flashPageState) {
SliderItem( SliderItem(
value = flashMillis / ReaderPreferences.MILLI_CONVERSION, value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
valueRange = 1..15,
label = stringResource(MR.strings.pref_flash_duration), label = stringResource(MR.strings.pref_flash_duration),
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis), valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) }, onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
min = 1,
max = 15,
steps = 13,
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SliderItem( SliderItem(
value = flashInterval, value = flashInterval,
valueRange = 1..10,
label = stringResource(MR.strings.pref_flash_page_interval), label = stringResource(MR.strings.pref_flash_page_interval),
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval), valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
onChange = { onChange = {
flashIntervalPref.set(it) flashIntervalPref.set(it)
}, },
min = 1,
max = 10,
steps = 8,
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SettingsChipRow(MR.strings.pref_flash_with) { SettingsChipRow(MR.strings.pref_flash_with) {

View File

@ -153,10 +153,9 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState() val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
SliderItem( SliderItem(
label = stringResource(MR.strings.pref_webtoon_side_padding),
min = ReaderPreferences.WEBTOON_PADDING_MIN,
max = ReaderPreferences.WEBTOON_PADDING_MAX,
value = webtoonSidePadding, value = webtoonSidePadding,
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
label = stringResource(MR.strings.pref_webtoon_side_padding),
valueText = numberFormat.format(webtoonSidePadding / 100f), valueText = numberFormat.format(webtoonSidePadding / 100f),
onChange = { onChange = {
screenModel.preferences.webtoonSidePadding().set(it) screenModel.preferences.webtoonSidePadding().set(it)

View File

@ -36,6 +36,7 @@ import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -172,25 +173,49 @@ fun RadioItem(label: String, selected: Boolean, onClick: () -> Unit) {
@Composable @Composable
fun SliderItem( fun SliderItem(
label: String,
value: Int, value: Int,
valueText: String, valueRange: IntProgression,
label: String,
onChange: (Int) -> Unit, onChange: (Int) -> Unit,
max: Int, steps: Int = with(valueRange) { (last - first) - 1 },
min: Int = 0, valueText: String = value.toString(),
steps: Int = 0, labelStyle: TextStyle = MaterialTheme.typography.bodyMedium,
pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
) {
BaseSliderItem(
value = value,
valueRange = valueRange,
steps = steps,
label = label,
valueText = valueText,
onChange = onChange,
labelStyle = labelStyle,
pillColor = pillColor,
modifier = Modifier.padding(
horizontal = SettingsItemsPaddings.Horizontal,
vertical = SettingsItemsPaddings.Vertical,
),
)
}
@Composable
fun BaseSliderItem(
value: Int,
valueRange: IntProgression,
label: String,
onChange: (Int) -> Unit,
modifier: Modifier = Modifier,
steps: Int = with(valueRange) { (last - first) - 1 },
valueText: String = value.toString(),
labelStyle: TextStyle = MaterialTheme.typography.bodyMedium, labelStyle: TextStyle = MaterialTheme.typography.bodyMedium,
pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding( .then(modifier),
horizontal = SettingsItemsPaddings.Horizontal, verticalArrangement = Arrangement.spacedBy(2.dp),
vertical = SettingsItemsPaddings.Vertical,
),
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@ -214,7 +239,7 @@ fun SliderItem(
onChange(it) onChange(it)
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
}, },
valueRange = min..max, valueRange = valueRange,
steps = steps, steps = steps,
) )
} }
@ -224,15 +249,14 @@ fun SliderItem(
@PreviewLightDark @PreviewLightDark
fun SliderItemPreview() { fun SliderItemPreview() {
MaterialTheme(if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()) { MaterialTheme(if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()) {
var value by remember { mutableIntStateOf(0) }
Surface { Surface {
SliderItem( SliderItem(
value = value,
valueRange = 0..10,
label = "Item per row", label = "Item per row",
valueText = "Auto", valueText = if (value == 0) "Auto" else value.toString(),
value = 0, onChange = { value = it },
onChange = {},
min = 0,
max = 10,
steps = 8,
) )
} }
} }

View File

@ -17,8 +17,8 @@ fun Slider(
onValueChange: (Int) -> Unit, onValueChange: (Int) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
valueRange: ClosedRange<Int> = 0..1, valueRange: IntProgression = 0..1,
@IntRange(from = 0) steps: Int = 0, @IntRange(from = 0) steps: Int = with(valueRange) { (last - first) - 1 },
onValueChangeFinished: (() -> Unit)? = null, onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(), colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
@ -38,7 +38,7 @@ fun Slider(
onValueChange = { onValueChange(it.roundToInt()) }, onValueChange = { onValueChange(it.roundToInt()) },
modifier = modifier, modifier = modifier,
enabled = enabled, enabled = enabled,
valueRange = with(valueRange) { start.toFloat()..endInclusive.toFloat() }, valueRange = with(valueRange) { first.toFloat()..last.toFloat() },
steps = steps, steps = steps,
onValueChangeFinished = onValueChangeFinished, onValueChangeFinished = onValueChangeFinished,
colors = colors, colors = colors,