Rework slider UI

Fixes #1474
This commit is contained in:
AntsyLich 2025-02-05 23:37:30 +06:00
parent d592ab2e87
commit e8c9cb2c2e
No known key found for this signature in database
11 changed files with 95 additions and 22 deletions

View File

@ -9,6 +9,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@ -259,11 +260,12 @@ private fun ColumnScope.DisplayPage(
max = 10, max = 10,
value = columns, value = columns,
valueText = if (columns > 0) { valueText = if (columns > 0) {
stringResource(MR.strings.pref_library_columns_per_row, columns) columns.toString()
} else { } else {
stringResource(MR.strings.label_default) stringResource(MR.strings.label_auto)
}, },
onChange = columnPreference::set, onChange = columnPreference::set,
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
} }

View File

@ -49,8 +49,9 @@ sealed class Preference {
*/ */
data class SliderPreference( data class SliderPreference(
val value: Int, val value: Int,
val min: Int = 0,
val max: Int, val max: Int,
val min: Int = 0,
val steps: Int = 0,
override val title: String = "", override val title: String = "",
override val subtitle: String? = null, override val subtitle: String? = null,
override val icon: ImageVector? = null, override val icon: ImageVector? = null,

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.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@ -76,11 +77,11 @@ internal fun PreferenceItem(
) )
} }
is Preference.PreferenceItem.SliderPreference -> { is Preference.PreferenceItem.SliderPreference -> {
// TODO: use different composable?
SliderItem( SliderItem(
label = item.title, label = item.title,
min = item.min, min = item.min,
max = item.max, max = item.max,
steps = item.steps,
value = item.value, value = item.value,
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(), valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
onChange = { onChange = {
@ -88,6 +89,7 @@ internal fun PreferenceItem(
item.onValueChanged(it) item.onValueChanged(it)
} }
}, },
labelStyle = MaterialTheme.typography.titleLarge,
) )
} }
is Preference.PreferenceItem.ListPreference<*> -> { is Preference.PreferenceItem.ListPreference<*> -> {

View File

@ -143,6 +143,7 @@ object SettingsReaderScreen : SearchableSettings {
value = flashMillis / ReaderPreferences.MILLI_CONVERSION, value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
min = 1, min = 1,
max = 15, max = 15,
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),
onValueChanged = { onValueChanged = {
@ -155,6 +156,7 @@ object SettingsReaderScreen : SearchableSettings {
value = flashInterval, value = flashInterval,
min = 1, min = 1,
max = 10, max = 10,
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),
onValueChanged = { onValueChanged = {

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -42,6 +43,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
value = customBrightnessValue, value = customBrightnessValue,
valueText = customBrightnessValue.toString(), valueText = customBrightnessValue.toString(),
onChange = { screenModel.preferences.customBrightnessValue().set(it) }, onChange = { screenModel.preferences.customBrightnessValue().set(it) },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
} }
@ -62,6 +64,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
getColorValue(it, newRValue, RED_MASK, 16) getColorValue(it, newRValue, RED_MASK, 16)
} }
}, },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SliderItem( SliderItem(
label = stringResource(MR.strings.color_filter_g_value), label = stringResource(MR.strings.color_filter_g_value),
@ -73,6 +76,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
getColorValue(it, newGValue, GREEN_MASK, 8) getColorValue(it, newGValue, GREEN_MASK, 8)
} }
}, },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SliderItem( SliderItem(
label = stringResource(MR.strings.color_filter_b_value), label = stringResource(MR.strings.color_filter_b_value),
@ -84,6 +88,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
getColorValue(it, newBValue, BLUE_MASK, 0) getColorValue(it, newBValue, BLUE_MASK, 0)
} }
}, },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SliderItem( SliderItem(
label = stringResource(MR.strings.color_filter_a_value), label = stringResource(MR.strings.color_filter_a_value),
@ -95,6 +100,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
getColorValue(it, newAValue, ALPHA_MASK, 24) getColorValue(it, newAValue, ALPHA_MASK, 24)
} }
}, },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState() val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -102,6 +103,8 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) }, onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
min = 1, min = 1,
max = 15, max = 15,
steps = 13,
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SliderItem( SliderItem(
value = flashInterval, value = flashInterval,
@ -112,6 +115,8 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
}, },
min = 1, min = 1,
max = 10, max = 10,
steps = 8,
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
SettingsChipRow(MR.strings.pref_flash_with) { SettingsChipRow(MR.strings.pref_flash_with) {
flashColors.map { (labelRes, value) -> flashColors.map { (labelRes, value) ->

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@ -160,6 +161,7 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
onChange = { onChange = {
screenModel.preferences.webtoonSidePadding().set(it) screenModel.preferences.webtoonSidePadding().set(it)
}, },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
CheckboxItem( CheckboxItem(

View File

@ -38,6 +38,7 @@
<string name="label_help">Help</string> <string name="label_help">Help</string>
<string name="label_default">Default</string> <string name="label_default">Default</string>
<string name="label_warning">Warning</string> <string name="label_warning">Warning</string>
<string name="label_auto">Auto</string>
<!-- Shared labels --> <!-- Shared labels -->
<string name="label_started">Started</string> <string name="label_started">Started</string>
@ -267,8 +268,7 @@
<!-- Library section --> <!-- Library section -->
<string name="pref_category_display">Display</string> <string name="pref_category_display">Display</string>
<string name="pref_library_columns">Grid size</string> <string name="pref_library_columns">Items per row</string>
<string name="pref_library_columns_per_row">%d per row</string>
<string name="portrait">Portrait</string> <string name="portrait">Portrait</string>
<string name="landscape">Landscape</string> <string name="landscape">Landscape</string>
@ -471,10 +471,10 @@
<string name="rotation_landscape">Landscape</string> <string name="rotation_landscape">Landscape</string>
<string name="rotation_force_portrait">Locked portrait</string> <string name="rotation_force_portrait">Locked portrait</string>
<string name="rotation_force_landscape">Locked landscape</string> <string name="rotation_force_landscape">Locked landscape</string>
<string name="color_filter_r_value">R</string> <string name="color_filter_r_value">Red</string>
<string name="color_filter_g_value">G</string> <string name="color_filter_g_value">Green</string>
<string name="color_filter_b_value">B</string> <string name="color_filter_b_value">Blue</string>
<string name="color_filter_a_value">A</string> <string name="color_filter_a_value">Alpha</string>
<string name="pref_always_show_chapter_transition">Always show chapter transition</string> <string name="pref_always_show_chapter_transition">Always show chapter transition</string>
<string name="pref_category_reading_mode">Reading mode</string> <string name="pref_category_reading_mode">Reading mode</string>
<string name="pref_category_reading">Reading</string> <string name="pref_category_reading">Reading</string>

View File

@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -19,7 +20,7 @@ fun Pill(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.surfaceContainerHigh, color: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
contentColor: Color = MaterialTheme.colorScheme.onSurface, contentColor: Color = MaterialTheme.colorScheme.onSurface,
fontSize: TextUnit = LocalTextStyle.current.fontSize, style: TextStyle = LocalTextStyle.current,
) { ) {
Surface( Surface(
modifier = modifier modifier = modifier
@ -35,9 +36,27 @@ fun Pill(
) { ) {
Text( Text(
text = text, text = text,
fontSize = fontSize,
maxLines = 1, maxLines = 1,
style = style,
) )
} }
} }
} }
@Composable
fun Pill(
text: String,
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
fontSize: TextUnit = LocalTextStyle.current.fontSize,
) {
val style = LocalTextStyle.current
Pill(
text = text,
modifier = modifier,
color = color,
contentColor = contentColor,
style = MaterialTheme.typography.bodyMedium.copy(fontSize = fontSize),
)
}

View File

@ -1,6 +1,7 @@
package tachiyomi.presentation.core.components package tachiyomi.presentation.core.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
@ -29,7 +30,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.darkColorScheme
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.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -37,9 +41,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.Preference
@ -171,29 +178,36 @@ fun SliderItem(
onChange: (Int) -> Unit, onChange: (Int) -> Unit,
max: Int, max: Int,
min: Int = 0, min: Int = 0,
steps: Int = 0,
labelStyle: TextStyle = MaterialTheme.typography.bodyMedium,
pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
Row( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding( .padding(
horizontal = SettingsItemsPaddings.Horizontal, horizontal = SettingsItemsPaddings.Horizontal,
vertical = SettingsItemsPaddings.Vertical, vertical = SettingsItemsPaddings.Vertical,
), ),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(24.dp),
) { ) {
Column(modifier = Modifier.weight(0.5f)) { Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
Text( Text(
text = label, text = label,
style = MaterialTheme.typography.bodyMedium, style = labelStyle,
modifier = Modifier.weight(1f),
)
Pill(
text = valueText,
style = MaterialTheme.typography.bodyMedium,
color = pillColor,
) )
Text(valueText)
} }
Slider( Slider(
modifier = Modifier.weight(1.5f),
value = value, value = value,
onValueChange = f@{ onValueChange = f@{
if (it == value) return@f if (it == value) return@f
@ -201,10 +215,29 @@ fun SliderItem(
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
}, },
valueRange = min..max, valueRange = min..max,
steps = steps,
) )
} }
} }
@Composable
@PreviewLightDark
fun SliderItemPreview() {
MaterialTheme(if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()) {
Surface {
SliderItem(
label = "Item per row",
valueText = "Auto",
value = 0,
onChange = {},
min = 0,
max = 10,
steps = 8,
)
}
}
}
@Composable @Composable
fun SelectItem( fun SelectItem(
label: String, label: String,

View File

@ -9,6 +9,7 @@ import androidx.compose.material3.SliderState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import kotlin.math.roundToInt
@Composable @Composable
fun Slider( fun Slider(
@ -17,7 +18,7 @@ fun Slider(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
valueRange: ClosedRange<Int> = 0..1, valueRange: ClosedRange<Int> = 0..1,
@IntRange(from = 0) steps: Int = with(valueRange) { (endInclusive - start) - 1 }, @IntRange(from = 0) steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null, onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(), colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
@ -34,7 +35,7 @@ fun Slider(
) { ) {
Slider( Slider(
value = value.toFloat(), value = value.toFloat(),
onValueChange = { onValueChange(it.toInt()) }, onValueChange = { onValueChange(it.roundToInt()) },
modifier = modifier, modifier = modifier,
enabled = enabled, enabled = enabled,
valueRange = with(valueRange) { start.toFloat()..endInclusive.toFloat() }, valueRange = with(valueRange) { start.toFloat()..endInclusive.toFloat() },