Add vertical chapter navigator for paged reader with height adjustment option (#3531)

This commit is contained in:
AntsyLich
2026-07-09 14:11:56 +06:00
committed by GitHub
parent 8407439b8a
commit 44780e74b5
22 changed files with 261 additions and 64 deletions
+2
View File
@@ -19,6 +19,8 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Support resumable image downloads if supported by source ([@xMohnad](https://github.com/xMohnad)) ([#3167](https://github.com/mihonapp/mihon/pull/3167))
- Display authors and description in Shikimori search results ([@MajorTanya](https://github.com/MajorTanya)) ([#3499](https://github.com/mihonapp/mihon/pull/3499))
- Invalidate download cache after backup restore ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3096](https://github.com/mihonapp/mihon/pull/3096))
- Add setting to control vertical chapter navigator height ([@AntsyLich](https://github.com/AntsyLich)) ([#3528](https://github.com/mihonapp/mihon/pull/3528))
- Add option to set which readers use vertical chapter navigator ([@AntsyLich](https://github.com/AntsyLich)) ([#3528](https://github.com/mihonapp/mihon/pull/3528))
### Changed
- Don't use GMS to detect if GMS is available ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3525](https://github.com/mihonapp/mihon/pull/3525))
+1 -1
View File
@@ -32,7 +32,7 @@ android {
defaultConfig {
applicationId = "app.mihon"
versionCode = 24
versionCode = 25
versionName = "0.20.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getLatestCommitCount()}\"")
@@ -103,12 +103,13 @@ sealed class Preference {
* A [PreferenceItem] that displays a list of entries as a dialog.
* Multiple entries can be selected at the same time.
*/
data class MultiSelectListPreference(
val preference: PreferenceData<Set<String>>,
val entries: Map<String, String>,
@Suppress("UNCHECKED_CAST")
data class MultiSelectListPreference<T>(
val preference: PreferenceData<Set<T>>,
val entries: Map<T, String>,
override val title: String,
override val subtitle: String? = "%s",
val subtitleProvider: @Composable (value: Set<String>, entries: Map<String, String>) -> String? =
val subtitleProvider: @Composable (value: Set<T>, entries: Map<T, String>) -> String? =
{ v, e ->
val combined = remember(v, e) {
v.mapNotNull { e[it] }
@@ -120,8 +121,15 @@ sealed class Preference {
},
override val icon: ImageVector? = null,
override val enabled: Boolean = true,
override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true },
) : PreferenceItem<Set<String>, Boolean>()
override val onValueChanged: suspend (value: Set<T>) -> Boolean = { true },
) : PreferenceItem<Set<T>, Boolean>() {
internal fun internalSet(value: Set<Any?>) = preference.set(value as Set<T>)
internal suspend fun internalOnValueChanged(value: Set<Any?>) = onValueChanged(value as Set<T>)
@Composable
internal fun internalSubtitleProvider(value: Set<Any?>, entries: Map<out Any?, String>) =
subtitleProvider(value as Set<T>, entries as Map<T, String>)
}
/**
* A [PreferenceItem] that shows a EditText in the dialog.
@@ -128,15 +128,18 @@ internal fun PreferenceItem(
onValueChange = { scope.launch { item.onValueChanged(it) } },
)
}
is Preference.PreferenceItem.MultiSelectListPreference -> {
is Preference.PreferenceItem.MultiSelectListPreference<*> -> {
val values by item.preference.collectAsState()
MultiSelectListPreferenceWidget(
preference = item,
values = values,
title = item.title,
subtitle = item.internalSubtitleProvider(values, item.entries),
icon = item.icon,
entries = item.entries,
onValuesChange = { newValues ->
scope.launch {
if (item.onValueChanged(newValues)) {
item.preference.set(newValues.toMutableSet())
if (item.internalOnValueChanged(newValues)) {
item.internalSet(newValues)
}
}
},
@@ -115,7 +115,7 @@ object SettingsDownloadScreen : SearchableSettings {
private fun getExcludedCategoriesPreference(
downloadPreferences: DownloadPreferences,
categories: () -> List<Category>,
): Preference.PreferenceItem.MultiSelectListPreference {
): Preference.PreferenceItem.MultiSelectListPreference<String> {
return Preference.PreferenceItem.MultiSelectListPreference(
preference = downloadPreferences.removeExcludeCategories,
entries = categories()
@@ -71,7 +71,6 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val fullscreen by readerPreferences.fullscreen.collectAsState()
val verticalNavigatorForLongStrip by readerPreferences.verticalNavigatorForLongStrip.collectAsState()
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_display),
preferenceItems = listOf(
@@ -108,15 +107,6 @@ object SettingsReaderScreen : SearchableSettings {
preference = readerPreferences.showPageNumber,
title = stringResource(MR.strings.pref_show_page_number),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.verticalNavigatorForLongStrip,
title = stringResource(MR.strings.pref_webtoon_vertical_navigator),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.verticalNavigatorOnLeft,
title = stringResource(MR.strings.pref_webtoon_vertical_navigator_on_left),
enabled = verticalNavigatorForLongStrip,
),
),
)
}
@@ -392,6 +382,11 @@ object SettingsReaderScreen : SearchableSettings {
private fun getNavigationGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val readWithVolumeKeysPref = readerPreferences.readWithVolumeKeys
val readWithVolumeKeys by readWithVolumeKeysPref.collectAsState()
val verticalNavigator by readerPreferences.verticalNavigator.collectAsState()
val verticalNavigatorHeightPref = readerPreferences.verticalNavigatorHeight
val verticalNavigatorHeight by verticalNavigatorHeightPref.collectAsState()
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_reader_navigation),
preferenceItems = listOf(
@@ -404,6 +399,25 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_read_with_volume_keys_inverted),
enabled = readWithVolumeKeys,
),
Preference.PreferenceItem.MultiSelectListPreference(
preference = readerPreferences.verticalNavigator,
entries = ReadingMode.entries.filter { it != ReadingMode.DEFAULT }
.associate { it to stringResource(it.stringRes) },
title = stringResource(MR.strings.pref_vertical_navigator),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.verticalNavigatorOnLeft,
title = stringResource(MR.strings.pref_webtoon_vertical_navigator_on_left),
enabled = verticalNavigator.isNotEmpty(),
),
Preference.PreferenceItem.SliderPreference(
value = verticalNavigatorHeight,
valueRange = 65..100,
steps = 6,
title = stringResource(MR.strings.pref_vertical_navigator_height),
onValueChanged = { verticalNavigatorHeightPref.set(it) },
enabled = verticalNavigator.isNotEmpty(),
),
),
)
}
@@ -10,39 +10,42 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.window.DialogProperties
import eu.kanade.presentation.more.settings.Preference
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.i18n.stringResource
@Composable
fun MultiSelectListPreferenceWidget(
preference: Preference.PreferenceItem.MultiSelectListPreference,
values: Set<String>,
onValuesChange: (Set<String>) -> Unit,
fun <T> MultiSelectListPreferenceWidget(
values: Set<T>,
title: String,
subtitle: String?,
icon: ImageVector?,
entries: Map<out T, String>,
onValuesChange: (Set<T>) -> Unit,
) {
var isDialogShown by remember { mutableStateOf(false) }
TextPreferenceWidget(
title = preference.title,
subtitle = preference.subtitleProvider(values, preference.entries),
icon = preference.icon,
title = title,
subtitle = subtitle,
icon = icon,
onPreferenceClick = { isDialogShown = true },
)
if (isDialogShown) {
val selected = remember {
preference.entries.keys
entries.keys
.filter { values.contains(it) }
.toMutableStateList()
}
AlertDialog(
onDismissRequest = { isDialogShown = false },
title = { Text(text = preference.title) },
title = { Text(text = title) },
text = {
LazyColumn {
preference.entries.forEach { current ->
entries.forEach { current ->
item {
val isSelected = selected.contains(current.key)
LabeledCheckbox(
@@ -12,6 +12,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -26,6 +27,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.IntOffset
@@ -55,6 +57,7 @@ fun ReaderAppBars(
onShare: (() -> Unit)?,
chapterNavigatorType: ChapterNavigatorType,
verticalNavigatorHeight: Float,
onNextChapter: () -> Unit,
enabledNext: Boolean,
onPreviousChapter: () -> Unit,
@@ -111,7 +114,12 @@ fun ReaderAppBars(
) {
Row {
Spacer(modifier = Modifier.width(MaterialTheme.padding.small))
Box(
modifier = Modifier.fillMaxHeight(),
contentAlignment = Alignment.BottomCenter,
) {
ChapterNavigator(
modifier = Modifier.fillMaxHeight(verticalNavigatorHeight),
type = chapterNavigatorType,
onNextChapter = onNextChapter,
enabledNext = enabledNext,
@@ -123,6 +131,7 @@ fun ReaderAppBars(
)
}
}
}
Spacer(modifier = Modifier.weight(1f))
}
}
@@ -70,6 +70,7 @@ fun ChapterNavigator(
currentPage: Int,
totalPages: Int,
onPageIndexChange: (Int) -> Unit,
modifier: Modifier = Modifier,
) {
val haptic = LocalHapticFeedback.current
@@ -104,7 +105,7 @@ fun ChapterNavigator(
)
if (type.isHorizontal()) {
ChapterNavigator(
HorizontalChapterNavigator(
isRtl = type == ChapterNavigatorType.HORIZONTAL_RTL,
state = state,
onNextChapter = onNextChapter,
@@ -117,6 +118,7 @@ fun ChapterNavigator(
mainAxisPadding = mainAxisPadding,
backgroundColor = backgroundColor,
buttonColor = buttonColor,
modifier = modifier,
)
} else {
VerticalChapterNavigator(
@@ -131,12 +133,13 @@ fun ChapterNavigator(
mainAxisPadding = mainAxisPadding,
backgroundColor = backgroundColor,
buttonColor = buttonColor,
modifier = modifier,
)
}
}
@Composable
fun ChapterNavigator(
fun HorizontalChapterNavigator(
isRtl: Boolean,
state: SliderState,
onNextChapter: () -> Unit,
@@ -149,13 +152,14 @@ fun ChapterNavigator(
mainAxisPadding: Dp,
backgroundColor: Color,
buttonColor: IconButtonColors,
modifier: Modifier = Modifier,
) {
val layoutDirection = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
// We explicitly handle direction based on the reader viewer rather than the system direction
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
Row(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.padding(horizontal = mainAxisPadding),
verticalAlignment = Alignment.CenterVertically,
@@ -233,9 +237,10 @@ fun VerticalChapterNavigator(
mainAxisPadding: Dp,
backgroundColor: Color,
buttonColor: IconButtonColors,
modifier: Modifier = Modifier,
) {
Column(
modifier = Modifier
modifier = modifier
.fillMaxHeight()
.padding(vertical = mainAxisPadding),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.CheckboxItem
@@ -61,17 +62,41 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
pref = screenModel.preferences.showPageNumber,
)
val verticalNavigatorForLongStrip by screenModel.preferences.verticalNavigatorForLongStrip.collectAsState()
CheckboxItem(
label = stringResource(MR.strings.pref_webtoon_vertical_navigator),
pref = screenModel.preferences.verticalNavigatorForLongStrip,
)
val verticalNavigatorModes by screenModel.preferences.verticalNavigator.collectAsState()
SettingsChipRow(MR.strings.pref_vertical_navigator) {
ReadingMode.entries.filter { it != ReadingMode.DEFAULT }.forEach { mode ->
FilterChip(
selected = verticalNavigatorModes.contains(mode),
onClick = {
val newModes = if (verticalNavigatorModes.contains(mode)) {
verticalNavigatorModes - mode
} else {
verticalNavigatorModes + mode
}
screenModel.preferences.verticalNavigator.set(newModes)
},
label = { Text(stringResource(mode.stringRes)) },
)
}
}
if (verticalNavigatorModes.isNotEmpty()) {
val verticalNavigatorHeightPref = screenModel.preferences.verticalNavigatorHeight
val verticalNavigatorHeight by verticalNavigatorHeightPref.collectAsState()
if (verticalNavigatorForLongStrip) {
CheckboxItem(
label = stringResource(MR.strings.pref_webtoon_vertical_navigator_on_left),
pref = screenModel.preferences.verticalNavigatorOnLeft,
)
SliderItem(
label = stringResource(MR.strings.pref_vertical_navigator_height),
value = verticalNavigatorHeight,
valueRange = 65..100,
steps = 6,
onChange = { verticalNavigatorHeightPref.set(it) },
)
}
CheckboxItem(
@@ -461,8 +461,12 @@ class ReaderActivity : BaseActivity() {
val isPagerType = ReadingMode.isPagerType(viewModel.getMangaReadingMode())
val cropEnabled = if (isPagerType) cropBorderPaged else cropBorderWebtoon
val verticalNavigatorForLongStrip by readerPreferences.verticalNavigatorForLongStrip.collectAsState()
val verticalNavigatorModes by readerPreferences.verticalNavigator.collectAsState()
val verticalNavigator = verticalNavigatorModes.contains(
ReadingMode.fromPreference(viewModel.getMangaReadingMode()),
)
val verticalNavigatorOnLeft by readerPreferences.verticalNavigatorOnLeft.collectAsState()
val verticalNavigatorHeight by readerPreferences.verticalNavigatorHeight.collectAsState()
ReaderAppBars(
visible = state.menuVisible,
@@ -477,7 +481,7 @@ class ReaderActivity : BaseActivity() {
onOpenInBrowser = ::openChapterInBrowser.takeIf { isHttpSource },
onShare = ::shareChapter.takeIf { isHttpSource },
chapterNavigatorType = if (isPagerType || !verticalNavigatorForLongStrip) {
chapterNavigatorType = if (!verticalNavigator) {
if (state.viewer is R2LPagerViewer) {
ChapterNavigatorType.HORIZONTAL_RTL
} else {
@@ -490,6 +494,7 @@ class ReaderActivity : BaseActivity() {
ChapterNavigatorType.VERTICAL_RIGHT
}
},
verticalNavigatorHeight = verticalNavigatorHeight / 100f,
onNextChapter = ::loadNextChapter,
enabledNext = state.viewerChapters?.nextChapter != null,
onPreviousChapter = ::loadPreviousChapter,
@@ -6,6 +6,7 @@ import dev.icerock.moko.resources.StringResource
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum
import tachiyomi.core.common.preference.getEnumSet
import tachiyomi.i18n.MR
class ReaderPreferences(
@@ -28,16 +29,21 @@ class ReaderPreferences(
val showPageNumber: Preference<Boolean> = preferenceStore.getBoolean("pref_show_page_number_key", true)
val verticalNavigatorForLongStrip: Preference<Boolean> = preferenceStore.getBoolean(
"pref_webtoon_vertical_navigator",
true,
val verticalNavigator: Preference<Set<ReadingMode>> = preferenceStore.getEnumSet(
"pref_vertical_navigator",
emptySet(),
)
val verticalNavigatorOnLeft: Preference<Boolean> = preferenceStore.getBoolean(
"pref_webtoon_vertical_navigator_on_left",
"pref_vertical_navigator_on_left",
false,
)
val verticalNavigatorHeight: Preference<Int> = preferenceStore.getInt(
"pref_vertical_navigator_height",
65,
)
val showReadingMode: Preference<Boolean> = preferenceStore.getBoolean("pref_show_reading_mode", true)
val fullscreen: Preference<Boolean> = preferenceStore.getBoolean("fullscreen", true)
@@ -2,7 +2,10 @@ package mihon.core.migration
import uy.kohesive.injekt.Injekt
class MigrationContext(val dryrun: Boolean) {
class MigrationContext(
val dryrun: Boolean,
val previousVersion: Int,
) {
inline fun <reified T> get(): T? {
return Injekt.getInstanceOrNull(T::class.java)
@@ -19,7 +19,7 @@ object Migrator {
dryrun: Boolean = false,
onMigrationComplete: () -> Unit,
) {
val migrationContext = MigrationContext(dryrun)
val migrationContext = MigrationContext(dryrun, old)
val migrationJobFactory = MigrationJobFactory(migrationContext, scope)
val migrationStrategyFactory = MigrationStrategyFactory(migrationJobFactory, onMigrationComplete)
val strategy = migrationStrategyFactory.create(old, new)
@@ -9,4 +9,5 @@ val migrations: List<Migration>
TrustExtensionRepositoryMigration(),
CategoryPreferencesCleanupMigration(),
InstallationIdMigration(),
VerticalNavigatorMigration(),
)
@@ -0,0 +1,33 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.util.lang.withIOContext
class VerticalNavigatorMigration : Migration {
override val version: Float = 25f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val preferenceStore = migrationContext.get<PreferenceStore>() ?: return@withIOContext false
val readerPreferences = migrationContext.get<ReaderPreferences>() ?: return@withIOContext false
if (migrationContext.previousVersion == 24) {
val oldVerticalNavigator = preferenceStore.getBoolean("pref_webtoon_vertical_navigator", true)
if (oldVerticalNavigator.get()) {
readerPreferences.verticalNavigator.set(setOf(ReadingMode.WEBTOON, ReadingMode.CONTINUOUS_VERTICAL))
}
if (oldVerticalNavigator.isSet()) oldVerticalNavigator.delete()
}
val oldVerticalNavigatorOnLeft = preferenceStore.getBoolean("pref_webtoon_vertical_navigator_on_left", false)
if (oldVerticalNavigatorOnLeft.isSet()) {
readerPreferences.verticalNavigatorOnLeft.set(oldVerticalNavigatorOnLeft.get())
oldVerticalNavigatorOnLeft.delete()
}
return@withIOContext true
}
}
@@ -30,7 +30,7 @@ class MigratorTest {
@BeforeEach
fun initialize() {
migrationContext = MigrationContext(false)
migrationContext = MigrationContext(false, 0)
migrationJobFactory = spyk(MigrationJobFactory(migrationContext, CoroutineScope(Dispatchers.Main + Job())))
migrationCompletedListener = spyk<MigrationCompletedListener>(block = {})
migrationStrategyFactory = spyk(MigrationStrategyFactory(migrationJobFactory, migrationCompletedListener))
@@ -212,4 +212,25 @@ sealed class AndroidPreference<T>(
putInt(key, serializer(value))
}
}
class ObjectSetAsStringSet<T>(
preferences: SharedPreferences,
keyFlow: Flow<String?>,
key: String,
defaultValue: Set<T>,
private val serializer: (T) -> String,
private val deserializer: (String) -> T?,
) : AndroidPreference<Set<T>>(preferences, keyFlow, key, defaultValue) {
override fun read(preferences: SharedPreferences, key: String, defaultValue: Set<T>): Set<T> {
return try {
preferences.getStringSet(key, null)?.mapNotNull(deserializer)?.toSet() ?: defaultValue
} catch (_: Exception) {
defaultValue
}
}
override fun write(key: String, value: Set<T>): Editor.() -> Unit = {
putStringSet(key, value.map(serializer).toSet())
}
}
}
@@ -11,6 +11,7 @@ import tachiyomi.core.common.preference.AndroidPreference.IntPrimitive
import tachiyomi.core.common.preference.AndroidPreference.LongPrimitive
import tachiyomi.core.common.preference.AndroidPreference.ObjectAsInt
import tachiyomi.core.common.preference.AndroidPreference.ObjectAsString
import tachiyomi.core.common.preference.AndroidPreference.ObjectSetAsStringSet
import tachiyomi.core.common.preference.AndroidPreference.StringPrimitive
import tachiyomi.core.common.preference.AndroidPreference.StringSetPrimitive
@@ -77,6 +78,22 @@ class AndroidPreferenceStore(
)
}
override fun <T> getObjectSetFromStringSet(
key: String,
defaultValue: Set<T>,
serializer: (T) -> String,
deserializer: (String) -> T?,
): Preference<Set<T>> {
return ObjectSetAsStringSet(
preferences = sharedPreferences,
keyFlow = keyFlow,
key = key,
defaultValue = defaultValue,
serializer = serializer,
deserializer = deserializer,
)
}
override fun getAll(): Map<String, *> {
return sharedPreferences.all ?: emptyMap<String, Any>()
}
@@ -48,7 +48,11 @@ class InMemoryPreferenceStore(
}
override fun getStringSet(key: String, defaultValue: Set<String>): Preference<Set<String>> {
TODO("Not yet implemented")
val default = InMemoryPreference(key, null, defaultValue)
@Suppress("UNCHECKED_CAST")
val data: Set<String>? = preferences[key]?.get() as? Set<String>
return if (data == null) default else InMemoryPreference(key, data, defaultValue)
}
@Suppress("UNCHECKED_CAST")
@@ -75,6 +79,18 @@ class InMemoryPreferenceStore(
return if (data == null) default else InMemoryPreference(key, data, defaultValue)
}
@Suppress("UNCHECKED_CAST")
override fun <T> getObjectSetFromStringSet(
key: String,
defaultValue: Set<T>,
serializer: (T) -> String,
deserializer: (String) -> T?,
): Preference<Set<T>> {
val default = InMemoryPreference(key, null, defaultValue)
val data: Set<T>? = preferences[key]?.get() as? Set<T>
return if (data == null) default else InMemoryPreference(key, data, defaultValue)
}
override fun getAll(): Map<String, *> {
return preferences
}
@@ -28,6 +28,13 @@ interface PreferenceStore {
deserializer: (Int) -> T,
): Preference<T>
fun <T> getObjectSetFromStringSet(
key: String,
defaultValue: Set<T>,
serializer: (T) -> String,
deserializer: (String) -> T?,
): Preference<Set<T>>
fun getAll(): Map<String, *>
}
@@ -60,3 +67,21 @@ inline fun <reified T : Enum<T>> PreferenceStore.getEnum(
},
)
}
inline fun <reified T : Enum<T>> PreferenceStore.getEnumSet(
key: String,
defaultValue: Set<T>,
): Preference<Set<T>> {
return getObjectSetFromStringSet(
key = key,
defaultValue = defaultValue,
serializer = { it.name },
deserializer = {
try {
enumValueOf<T>(it)
} catch (_: IllegalArgumentException) {
null
}
},
)
}
@@ -400,7 +400,8 @@
<string name="pref_flash_style_white_black">White and Black</string>
<string name="pref_double_tap_anim_speed">Double tap animation speed</string>
<string name="pref_show_page_number">Show page number</string>
<string name="pref_webtoon_vertical_navigator">Use vertical navigator for long strip reading</string>
<string name="pref_vertical_navigator">Use vertical chapter navigator in</string>
<string name="pref_vertical_navigator_height">Vertical navigator height</string>
<string name="pref_webtoon_vertical_navigator_on_left">Place vertical navigator on the left side</string>
<string name="pref_show_reading_mode">Show reading mode</string>
<string name="pref_show_reading_mode_summary">Briefly show current mode when reader is opened</string>