diff --git a/CHANGELOG.md b/CHANGELOG.md index 310022f9f..c8a9e399c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a0e5776ba..44f9d951a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,7 +32,7 @@ android { defaultConfig { applicationId = "app.mihon" - versionCode = 24 + versionCode = 25 versionName = "0.20.0" buildConfigField("String", "COMMIT_COUNT", "\"${getLatestCommitCount()}\"") diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt b/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt index 467ad5672..05d0116c4 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt @@ -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>, - val entries: Map, + @Suppress("UNCHECKED_CAST") + data class MultiSelectListPreference( + val preference: PreferenceData>, + val entries: Map, override val title: String, override val subtitle: String? = "%s", - val subtitleProvider: @Composable (value: Set, entries: Map) -> String? = + val subtitleProvider: @Composable (value: Set, entries: Map) -> 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) -> Boolean = { true }, - ) : PreferenceItem, Boolean>() + override val onValueChanged: suspend (value: Set) -> Boolean = { true }, + ) : PreferenceItem, Boolean>() { + internal fun internalSet(value: Set) = preference.set(value as Set) + internal suspend fun internalOnValueChanged(value: Set) = onValueChanged(value as Set) + + @Composable + internal fun internalSubtitleProvider(value: Set, entries: Map) = + subtitleProvider(value as Set, entries as Map) + } /** * A [PreferenceItem] that shows a EditText in the dialog. diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt index fb6508d55..179e8553c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt @@ -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) } } }, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt index fbcd0dbc0..3a5328fde 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt @@ -115,7 +115,7 @@ object SettingsDownloadScreen : SearchableSettings { private fun getExcludedCategoriesPreference( downloadPreferences: DownloadPreferences, categories: () -> List, - ): Preference.PreferenceItem.MultiSelectListPreference { + ): Preference.PreferenceItem.MultiSelectListPreference { return Preference.PreferenceItem.MultiSelectListPreference( preference = downloadPreferences.removeExcludeCategories, entries = categories() diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt index 4a259a903..bae7edce5 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt @@ -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(), + ), ), ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt index c9ac35184..31d1ca45e 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt @@ -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, - onValuesChange: (Set) -> Unit, +fun MultiSelectListPreferenceWidget( + values: Set, + title: String, + subtitle: String?, + icon: ImageVector?, + entries: Map, + onValuesChange: (Set) -> 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( diff --git a/app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt b/app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt index 09e14bede..916d00ff8 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt @@ -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,16 +114,22 @@ fun ReaderAppBars( ) { Row { Spacer(modifier = Modifier.width(MaterialTheme.padding.small)) - ChapterNavigator( - type = chapterNavigatorType, - onNextChapter = onNextChapter, - enabledNext = enabledNext, - onPreviousChapter = onPreviousChapter, - enabledPrevious = enabledPrevious, - currentPage = currentPage, - totalPages = totalPages, - onPageIndexChange = onPageIndexChange, - ) + Box( + modifier = Modifier.fillMaxHeight(), + contentAlignment = Alignment.BottomCenter, + ) { + ChapterNavigator( + modifier = Modifier.fillMaxHeight(verticalNavigatorHeight), + type = chapterNavigatorType, + onNextChapter = onNextChapter, + enabledNext = enabledNext, + onPreviousChapter = onPreviousChapter, + enabledPrevious = enabledPrevious, + currentPage = currentPage, + totalPages = totalPages, + onPageIndexChange = onPageIndexChange, + ) + } } } Spacer(modifier = Modifier.weight(1f)) diff --git a/app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt b/app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt index 79d96e35e..34b4f34ab 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt index 1e1ad2b25..8f90e189a 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt @@ -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( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index a91928267..ab0a9e7ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt index 6254d62d3..5c91991b6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt @@ -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 = preferenceStore.getBoolean("pref_show_page_number_key", true) - val verticalNavigatorForLongStrip: Preference = preferenceStore.getBoolean( - "pref_webtoon_vertical_navigator", - true, + val verticalNavigator: Preference> = preferenceStore.getEnumSet( + "pref_vertical_navigator", + emptySet(), ) val verticalNavigatorOnLeft: Preference = preferenceStore.getBoolean( - "pref_webtoon_vertical_navigator_on_left", + "pref_vertical_navigator_on_left", false, ) + val verticalNavigatorHeight: Preference = preferenceStore.getInt( + "pref_vertical_navigator_height", + 65, + ) + val showReadingMode: Preference = preferenceStore.getBoolean("pref_show_reading_mode", true) val fullscreen: Preference = preferenceStore.getBoolean("fullscreen", true) diff --git a/app/src/main/java/mihon/core/migration/MigrationContext.kt b/app/src/main/java/mihon/core/migration/MigrationContext.kt index 3d0473f27..f37785f05 100644 --- a/app/src/main/java/mihon/core/migration/MigrationContext.kt +++ b/app/src/main/java/mihon/core/migration/MigrationContext.kt @@ -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 get(): T? { return Injekt.getInstanceOrNull(T::class.java) diff --git a/app/src/main/java/mihon/core/migration/Migrator.kt b/app/src/main/java/mihon/core/migration/Migrator.kt index c01a3873e..a7c1f4429 100644 --- a/app/src/main/java/mihon/core/migration/Migrator.kt +++ b/app/src/main/java/mihon/core/migration/Migrator.kt @@ -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) diff --git a/app/src/main/java/mihon/core/migration/migrations/Migrations.kt b/app/src/main/java/mihon/core/migration/migrations/Migrations.kt index 83c6f6224..523b8e846 100644 --- a/app/src/main/java/mihon/core/migration/migrations/Migrations.kt +++ b/app/src/main/java/mihon/core/migration/migrations/Migrations.kt @@ -9,4 +9,5 @@ val migrations: List TrustExtensionRepositoryMigration(), CategoryPreferencesCleanupMigration(), InstallationIdMigration(), + VerticalNavigatorMigration(), ) diff --git a/app/src/main/java/mihon/core/migration/migrations/VerticalNavigatorMigration.kt b/app/src/main/java/mihon/core/migration/migrations/VerticalNavigatorMigration.kt new file mode 100644 index 000000000..b85c48670 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/VerticalNavigatorMigration.kt @@ -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() ?: return@withIOContext false + val readerPreferences = migrationContext.get() ?: 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 + } +} diff --git a/app/src/test/java/mihon/core/migration/MigratorTest.kt b/app/src/test/java/mihon/core/migration/MigratorTest.kt index 2f8070676..9aabcca45 100644 --- a/app/src/test/java/mihon/core/migration/MigratorTest.kt +++ b/app/src/test/java/mihon/core/migration/MigratorTest.kt @@ -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(block = {}) migrationStrategyFactory = spyk(MigrationStrategyFactory(migrationJobFactory, migrationCompletedListener)) diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/preference/AndroidPreference.kt b/core/common/src/main/kotlin/tachiyomi/core/common/preference/AndroidPreference.kt index 026245d82..32ead117b 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/preference/AndroidPreference.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/preference/AndroidPreference.kt @@ -212,4 +212,25 @@ sealed class AndroidPreference( putInt(key, serializer(value)) } } + + class ObjectSetAsStringSet( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Set, + private val serializer: (T) -> String, + private val deserializer: (String) -> T?, + ) : AndroidPreference>(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: Set): Set { + return try { + preferences.getStringSet(key, null)?.mapNotNull(deserializer)?.toSet() ?: defaultValue + } catch (_: Exception) { + defaultValue + } + } + + override fun write(key: String, value: Set): Editor.() -> Unit = { + putStringSet(key, value.map(serializer).toSet()) + } + } } diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/preference/AndroidPreferenceStore.kt b/core/common/src/main/kotlin/tachiyomi/core/common/preference/AndroidPreferenceStore.kt index 78f98f204..cc3d2d067 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/preference/AndroidPreferenceStore.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/preference/AndroidPreferenceStore.kt @@ -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 getObjectSetFromStringSet( + key: String, + defaultValue: Set, + serializer: (T) -> String, + deserializer: (String) -> T?, + ): Preference> { + return ObjectSetAsStringSet( + preferences = sharedPreferences, + keyFlow = keyFlow, + key = key, + defaultValue = defaultValue, + serializer = serializer, + deserializer = deserializer, + ) + } + override fun getAll(): Map { return sharedPreferences.all ?: emptyMap() } diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/preference/InMemoryPreferenceStore.kt b/core/common/src/main/kotlin/tachiyomi/core/common/preference/InMemoryPreferenceStore.kt index 1dae7fff9..8363fc58f 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/preference/InMemoryPreferenceStore.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/preference/InMemoryPreferenceStore.kt @@ -48,7 +48,11 @@ class InMemoryPreferenceStore( } override fun getStringSet(key: String, defaultValue: Set): Preference> { - TODO("Not yet implemented") + val default = InMemoryPreference(key, null, defaultValue) + + @Suppress("UNCHECKED_CAST") + val data: Set? = preferences[key]?.get() as? Set + 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 getObjectSetFromStringSet( + key: String, + defaultValue: Set, + serializer: (T) -> String, + deserializer: (String) -> T?, + ): Preference> { + val default = InMemoryPreference(key, null, defaultValue) + val data: Set? = preferences[key]?.get() as? Set + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + override fun getAll(): Map { return preferences } diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/preference/PreferenceStore.kt b/core/common/src/main/kotlin/tachiyomi/core/common/preference/PreferenceStore.kt index 2016f3d44..40ee0dbc1 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/preference/PreferenceStore.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/preference/PreferenceStore.kt @@ -28,6 +28,13 @@ interface PreferenceStore { deserializer: (Int) -> T, ): Preference + fun getObjectSetFromStringSet( + key: String, + defaultValue: Set, + serializer: (T) -> String, + deserializer: (String) -> T?, + ): Preference> + fun getAll(): Map } @@ -60,3 +67,21 @@ inline fun > PreferenceStore.getEnum( }, ) } + +inline fun > PreferenceStore.getEnumSet( + key: String, + defaultValue: Set, +): Preference> { + return getObjectSetFromStringSet( + key = key, + defaultValue = defaultValue, + serializer = { it.name }, + deserializer = { + try { + enumValueOf(it) + } catch (_: IllegalArgumentException) { + null + } + }, + ) +} diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 122781492..9ef56fbd5 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -400,7 +400,8 @@ White and Black Double tap animation speed Show page number - Use vertical navigator for long strip reading + Use vertical chapter navigator in + Vertical navigator height Place vertical navigator on the left side Show reading mode Briefly show current mode when reader is opened