Add subtitle support to slider preference and general cleanup (#2635)

This commit is contained in:
AntsyLich
2025-11-01 18:47:06 +06:00
committed by GitHub
parent aef3beb15f
commit f36c259c1f
10 changed files with 71 additions and 63 deletions

View File

@@ -255,7 +255,7 @@ private fun ColumnScope.DisplayPage(
value = columns, value = columns,
valueRange = 0..10, valueRange = 0..10,
label = stringResource(MR.strings.pref_library_columns), label = stringResource(MR.strings.pref_library_columns),
valueText = if (columns > 0) { valueString = if (columns > 0) {
columns.toString() columns.toString()
} else { } else {
stringResource(MR.strings.label_auto) stringResource(MR.strings.label_auto)

View File

@@ -15,10 +15,10 @@ sealed class Preference {
abstract val title: String abstract val title: String
abstract val enabled: Boolean abstract val enabled: Boolean
sealed class PreferenceItem<T> : Preference() { sealed class PreferenceItem<T, R> : Preference() {
abstract val subtitle: String? abstract val subtitle: String?
abstract val icon: ImageVector? abstract val icon: ImageVector?
abstract val onValueChanged: suspend (value: T) -> Boolean abstract val onValueChanged: suspend (value: T) -> R
/** /**
* A basic [PreferenceItem] that only displays texts. * A basic [PreferenceItem] that only displays texts.
@@ -28,9 +28,9 @@ sealed class Preference {
override val subtitle: String? = null, override val subtitle: String? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
val onClick: (() -> Unit)? = null, val onClick: (() -> Unit)? = null,
) : PreferenceItem<String>() { ) : PreferenceItem<String, Unit>() {
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (value: String) -> Boolean = { true } override val onValueChanged: suspend (value: String) -> Unit = {}
} }
/** /**
@@ -42,7 +42,7 @@ sealed class Preference {
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: Boolean) -> Boolean = { true }, override val onValueChanged: suspend (value: Boolean) -> Boolean = { true },
) : PreferenceItem<Boolean>() { ) : PreferenceItem<Boolean, Boolean>() {
override val icon: ImageVector? = null override val icon: ImageVector? = null
} }
@@ -52,12 +52,13 @@ sealed class Preference {
data class SliderPreference( data class SliderPreference(
val value: Int, val value: Int,
override val title: String, override val title: String,
override val subtitle: String? = null,
val valueString: String? = null,
val valueRange: IntProgression = 0..1, val valueRange: IntProgression = 0..1,
@IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 }, @IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 },
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) -> Unit = {},
) : PreferenceItem<Int>() { ) : PreferenceItem<Int, Unit>() {
override val icon: ImageVector? = null override val icon: ImageVector? = null
} }
@@ -75,7 +76,7 @@ sealed class Preference {
override val icon: ImageVector? = null, override val icon: ImageVector? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: T) -> Boolean = { true }, override val onValueChanged: suspend (value: T) -> Boolean = { true },
) : PreferenceItem<T>() { ) : PreferenceItem<T, Boolean>() {
internal fun internalSet(value: Any) = preference.set(value as T) internal fun internalSet(value: Any) = preference.set(value as T)
internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T) internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T)
@@ -96,8 +97,8 @@ sealed class Preference {
{ v, e -> subtitle?.format(e[v]) }, { v, e -> subtitle?.format(e[v]) },
override val icon: ImageVector? = null, override val icon: ImageVector? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: String) -> Boolean = { true }, override val onValueChanged: suspend (value: String) -> Unit = {},
) : PreferenceItem<String>() ) : PreferenceItem<String, Unit>()
/** /**
* A [PreferenceItem] that displays a list of entries as a dialog. * A [PreferenceItem] that displays a list of entries as a dialog.
@@ -121,7 +122,7 @@ sealed class Preference {
override val icon: ImageVector? = null, override val icon: ImageVector? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true }, override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true },
) : PreferenceItem<Set<String>>() ) : PreferenceItem<Set<String>, Boolean>()
/** /**
* A [PreferenceItem] that shows a EditText in the dialog. * A [PreferenceItem] that shows a EditText in the dialog.
@@ -132,7 +133,7 @@ sealed class Preference {
override val subtitle: String? = "%s", override val subtitle: String? = "%s",
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: String) -> Boolean = { true }, override val onValueChanged: suspend (value: String) -> Boolean = { true },
) : PreferenceItem<String>() { ) : PreferenceItem<String, Boolean>() {
override val icon: ImageVector? = null override val icon: ImageVector? = null
} }
@@ -143,31 +144,31 @@ sealed class Preference {
val tracker: Tracker, val tracker: Tracker,
val login: () -> Unit, val login: () -> Unit,
val logout: () -> Unit, val logout: () -> Unit,
) : PreferenceItem<String>() { ) : PreferenceItem<String, Unit>() {
override val title: String = "" override val title: String = ""
override val enabled: Boolean = true override val enabled: Boolean = true
override val subtitle: String? = null override val subtitle: String? = null
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (value: String) -> Boolean = { true } override val onValueChanged: suspend (value: String) -> Unit = {}
} }
data class InfoPreference( data class InfoPreference(
override val title: String, override val title: String,
) : PreferenceItem<String>() { ) : PreferenceItem<String, Unit>() {
override val enabled: Boolean = true override val enabled: Boolean = true
override val subtitle: String? = null override val subtitle: String? = null
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (value: String) -> Boolean = { true } override val onValueChanged: suspend (value: String) -> Unit = {}
} }
data class CustomPreference( data class CustomPreference(
override val title: String, override val title: String,
val content: @Composable () -> Unit, val content: @Composable () -> Unit,
) : PreferenceItem<Unit>() { ) : PreferenceItem<Unit, Unit>() {
override val enabled: Boolean = true override val enabled: Boolean = true
override val subtitle: String? = null override val subtitle: String? = null
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (value: Unit) -> Boolean = { true } override val onValueChanged: suspend (value: Unit) -> Unit = {}
} }
} }
@@ -175,6 +176,6 @@ sealed class Preference {
override val title: String, override val title: String,
override val enabled: Boolean = true, override val enabled: Boolean = true,
val preferenceItems: ImmutableList<PreferenceItem<out Any>>, val preferenceItems: ImmutableList<PreferenceItem<out Any, out Any>>,
) : Preference() ) : Preference()
} }

View File

@@ -35,7 +35,7 @@ val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) {
@Composable @Composable
fun StatusWrapper( fun StatusWrapper(
item: Preference.PreferenceItem<*>, item: Preference.PreferenceItem<*, *>,
highlightKey: String?, highlightKey: String?,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
@@ -56,7 +56,7 @@ fun StatusWrapper(
@Composable @Composable
internal fun PreferenceItem( internal fun PreferenceItem(
item: Preference.PreferenceItem<*>, item: Preference.PreferenceItem<*, *>,
highlightKey: String?, highlightKey: String?,
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -83,17 +83,18 @@ internal fun PreferenceItem(
} }
is Preference.PreferenceItem.SliderPreference -> { is Preference.PreferenceItem.SliderPreference -> {
BaseSliderItem( BaseSliderItem(
label = item.title,
value = item.value, value = item.value,
valueRange = item.valueRange, valueRange = item.valueRange,
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
steps = item.steps, steps = item.steps,
labelStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize), title = item.title,
subtitle = item.subtitle,
valueString = item.valueString.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
onChange = { onChange = {
scope.launch { scope.launch {
item.onValueChanged(it) item.onValueChanged(it)
} }
}, },
titleStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize),
modifier = Modifier.padding( modifier = Modifier.padding(
horizontal = PrefsHorizontalPadding, horizontal = PrefsHorizontalPadding,
vertical = PrefsVerticalPadding, vertical = PrefsVerticalPadding,

View File

@@ -71,7 +71,7 @@ fun PreferenceScreen(
} }
// Create Preference Item // Create Preference Item
is Preference.PreferenceItem<*> -> item { is Preference.PreferenceItem<*, *> -> item {
PreferenceItem( PreferenceItem(
item = preference, item = preference,
highlightKey = highlightKey, highlightKey = highlightKey,

View File

@@ -143,23 +143,17 @@ object SettingsReaderScreen : SearchableSettings {
value = flashMillis / ReaderPreferences.MILLI_CONVERSION, value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
valueRange = 1..15, valueRange = 1..15,
title = stringResource(MR.strings.pref_flash_duration), title = stringResource(MR.strings.pref_flash_duration),
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis), valueString = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
enabled = flashPageState, enabled = flashPageState,
onValueChanged = { onValueChanged = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
true
},
), ),
Preference.PreferenceItem.SliderPreference( Preference.PreferenceItem.SliderPreference(
value = flashInterval, value = flashInterval,
valueRange = 1..10, valueRange = 1..10,
title = stringResource(MR.strings.pref_flash_page_interval), title = stringResource(MR.strings.pref_flash_page_interval),
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval), valueString = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
enabled = flashPageState, enabled = flashPageState,
onValueChanged = { onValueChanged = { flashIntervalPref.set(it) },
flashIntervalPref.set(it)
true
},
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = flashColorPref, preference = flashColorPref,
@@ -342,11 +336,8 @@ object SettingsReaderScreen : SearchableSettings {
it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX 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), valueString = numberFormat.format(webtoonSidePadding / 100f),
onValueChanged = { onValueChanged = { webtoonSidePaddingPref.set(it) },
webtoonSidePaddingPref.set(it)
true
},
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerHideThreshold(), preference = readerPreferences.readerHideThreshold(),

View File

@@ -183,7 +183,7 @@ private fun SearchResult(
emptySequence() emptySequence()
} }
} }
is Preference.PreferenceItem<*> -> sequenceOf(null to p) is Preference.PreferenceItem<*, *> -> sequenceOf(null to p)
} }
} }
// Don't show info preference // Don't show info preference

View File

@@ -99,7 +99,7 @@ class DebugInfoScreen : Screen() {
} }
private fun getDeviceInfoGroup(): Preference.PreferenceGroup { private fun getDeviceInfoGroup(): Preference.PreferenceGroup {
val items = persistentListOf<Preference.PreferenceItem<out Any>>().mutate { val items = persistentListOf<Preference.PreferenceItem<out Any, out Any>>().mutate {
it.add( it.add(
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = "Model", title = "Model",

View File

@@ -100,7 +100,7 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
value = flashMillis / ReaderPreferences.MILLI_CONVERSION, value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
valueRange = 1..15, 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), valueString = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) }, onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
@@ -108,7 +108,7 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
value = flashInterval, value = flashInterval,
valueRange = 1..10, 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), valueString = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
onChange = { onChange = {
flashIntervalPref.set(it) flashIntervalPref.set(it)
}, },

View File

@@ -156,7 +156,7 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
value = webtoonSidePadding, value = webtoonSidePadding,
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX }, valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
label = stringResource(MR.strings.pref_webtoon_side_padding), label = stringResource(MR.strings.pref_webtoon_side_padding),
valueText = numberFormat.format(webtoonSidePadding / 100f), valueString = numberFormat.format(webtoonSidePadding / 100f),
onChange = { onChange = {
screenModel.preferences.webtoonSidePadding().set(it) screenModel.preferences.webtoonSidePadding().set(it)
}, },

View File

@@ -59,6 +59,7 @@ import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.theme.header import tachiyomi.presentation.core.theme.header
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import tachiyomi.presentation.core.util.secondaryItemAlpha
object SettingsItemsPaddings { object SettingsItemsPaddings {
val Horizontal = 24.dp val Horizontal = 24.dp
@@ -178,7 +179,7 @@ fun SliderItem(
label: String, label: String,
onChange: (Int) -> Unit, onChange: (Int) -> Unit,
steps: Int = with(valueRange) { (last - first) - 1 }, steps: Int = with(valueRange) { (last - first) - 1 },
valueText: String = value.toString(), valueString: String = value.toString(),
labelStyle: TextStyle = MaterialTheme.typography.bodyMedium, labelStyle: TextStyle = MaterialTheme.typography.bodyMedium,
pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
@@ -186,10 +187,10 @@ fun SliderItem(
value = value, value = value,
valueRange = valueRange, valueRange = valueRange,
steps = steps, steps = steps,
label = label, title = label,
valueText = valueText, valueString = valueString,
onChange = onChange, onChange = onChange,
labelStyle = labelStyle, titleStyle = labelStyle,
pillColor = pillColor, pillColor = pillColor,
modifier = Modifier.padding( modifier = Modifier.padding(
horizontal = SettingsItemsPaddings.Horizontal, horizontal = SettingsItemsPaddings.Horizontal,
@@ -202,12 +203,14 @@ fun SliderItem(
fun BaseSliderItem( fun BaseSliderItem(
value: Int, value: Int,
valueRange: IntProgression, valueRange: IntProgression,
label: String, title: String,
onChange: (Int) -> Unit, onChange: (Int) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
subtitle: String? = null,
steps: Int = with(valueRange) { (last - first) - 1 }, steps: Int = with(valueRange) { (last - first) - 1 },
valueText: String = value.toString(), valueString: String = value.toString(),
labelStyle: TextStyle = MaterialTheme.typography.bodyMedium, titleStyle: TextStyle = MaterialTheme.typography.titleLarge,
subtitleStyle: TextStyle = MaterialTheme.typography.bodySmall,
pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
@@ -221,13 +224,21 @@ fun BaseSliderItem(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) { ) {
Text( Column(modifier = Modifier.weight(1f)) {
text = label, Text(
style = labelStyle, text = title,
modifier = Modifier.weight(1f), style = titleStyle,
) )
if (subtitle != null) {
Text(
text = subtitle,
style = subtitleStyle,
modifier = Modifier.secondaryItemAlpha(),
)
}
}
Pill( Pill(
text = valueText, text = valueString,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = pillColor, color = pillColor,
) )
@@ -251,12 +262,16 @@ fun SliderItemPreview() {
MaterialTheme(if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()) { MaterialTheme(if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()) {
var value by remember { mutableIntStateOf(0) } var value by remember { mutableIntStateOf(0) }
Surface { Surface {
SliderItem( BaseSliderItem(
value = value, value = value,
valueRange = 0..10, valueRange = 0..10,
label = "Item per row", title = "Item per row",
valueText = if (value == 0) "Auto" else value.toString(), valueString = if (value == 0) "Auto" else value.toString(),
onChange = { value = it }, onChange = { value = it },
modifier = Modifier.padding(
horizontal = SettingsItemsPaddings.Horizontal,
vertical = SettingsItemsPaddings.Vertical,
),
) )
} }
} }