diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index bbfe19682..733283a26 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -38,6 +38,7 @@ import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo import mihon.domain.extensionrepo.repository.ExtensionRepoRepository import mihon.domain.extensionrepo.service.ExtensionRepoService +import mihon.domain.migration.usecases.MigrateMangaUseCase import mihon.domain.upcoming.interactor.GetUpcomingManga import tachiyomi.data.category.CategoryRepositoryImpl import tachiyomi.data.chapter.ChapterRepositoryImpl @@ -133,6 +134,11 @@ class DomainModule : InjektModule { addFactory { SetMangaCategories(get()) } addFactory { GetExcludedScanlators(get()) } addFactory { SetExcludedScanlators(get()) } + addFactory { + MigrateMangaUseCase( + get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), + ) + } addSingletonFactory { ReleaseServiceImpl(get(), get()) } addFactory { GetApplicationRelease(get(), get()) } diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index cd0f2149b..c05b0a42b 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -2,6 +2,7 @@ package eu.kanade.domain.source.service import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.tachiyomi.util.system.LocaleHelper +import mihon.domain.migration.models.MigrationFlag import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.getEnum @@ -12,7 +13,7 @@ class SourcePreferences( private val preferenceStore: PreferenceStore, ) { - fun sourceDisplayMode() = preferenceStore.getObject( + fun sourceDisplayMode() = preferenceStore.getObjectFromString( "pref_display_mode_catalogue", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, @@ -119,4 +120,11 @@ class SourcePreferences( fun recommendationSearchFlags() = preferenceStore.getInt("rec_search_flags", Int.MAX_VALUE) // SY <-- + + fun migrationFlags() = preferenceStore.getObjectFromInt( + key = "migrate_flags", + defaultValue = MigrationFlag.entries.toSet(), + serializer = { MigrationFlag.toBit(it) }, + deserializer = { value: Int -> MigrationFlag.fromBit(value) }, + ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt deleted file mode 100644 index a4e822c5a..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt +++ /dev/null @@ -1,40 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration - -object MigrationFlags { - - const val CHAPTERS = 0b00001 - const val CATEGORIES = 0b00010 - const val TRACK = 0b00100 - const val CUSTOM_COVER = 0b01000 - const val EXTRA = 0b10000 - const val DELETE_CHAPTERS = 0b100000 - const val NOTES = 0b1000000 - - fun hasChapters(value: Int): Boolean { - return value and CHAPTERS != 0 - } - - fun hasCategories(value: Int): Boolean { - return value and CATEGORIES != 0 - } - - fun hasTracks(value: Int): Boolean { - return value and TRACK != 0 - } - - fun hasCustomCover(value: Int): Boolean { - return value and CUSTOM_COVER != 0 - } - - fun hasExtra(value: Int): Boolean { - return value and EXTRA != 0 - } - - fun hasDeleteChapters(value: Int): Boolean { - return value and DELETE_CHAPTERS != 0 - } - - fun hasNotes(value: Int): Boolean { - return value and NOTES != 0 - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt index 67c8f05b8..2115574e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt @@ -8,45 +8,53 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.MigrateSearchScreen import eu.kanade.presentation.util.Screen -import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel import eu.kanade.tachiyomi.ui.manga.MangaScreen +import mihon.feature.migration.dialog.MigrateMangaDialog -class MigrateSearchScreen(private val mangaId: Long, private val validSources: List) : Screen() { +class MigrateSearchScreen(private val mangaId: Long) : Screen() { @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow - val screenModel = - rememberScreenModel { MigrateSearchScreenModel(mangaId = mangaId, validSources = validSources) } - val state by screenModel.state.collectAsState() - val dialogScreenModel = rememberScreenModel { MigrateSearchScreenDialogScreenModel(mangaId = mangaId) } - val dialogState by dialogScreenModel.state.collectAsState() + val screenModel = rememberScreenModel { MigrateSearchScreenModel(mangaId = mangaId) } + val state by screenModel.state.collectAsState() MigrateSearchScreen( state = state, - fromSourceId = state.fromSourceId, + fromSourceId = state.from?.source, navigateUp = navigator::pop, onChangeSearchQuery = screenModel::updateSearchQuery, onSearch = { screenModel.search() }, getManga = { screenModel.getManga(it) }, onChangeSearchFilter = screenModel::setSourceFilter, onToggleResults = screenModel::toggleFilterResults, - onClickSource = { - // SY --> - navigator.push(SourceSearchScreen(dialogState.manga!!, it.id, state.searchQuery)) - // SY <-- - }, - onClickItem = { - // SY --> - navigator.items - .filterIsInstance() - .last() - .newSelectedItem = mangaId to it.id - navigator.popUntil { it is MigrationListScreen } - // SY <-- - }, + onClickSource = { navigator.push(MigrateSourceSearchScreen(state.from!!, it.id, state.searchQuery)) }, + onClickItem = { screenModel.setMigrateDialog(mangaId, it) }, onLongClickItem = { navigator.push(MangaScreen(it.id, true)) }, ) + + when (val dialog = state.dialog) { + is SearchScreenModel.Dialog.Migrate -> { + MigrateMangaDialog( + current = dialog.current, + target = dialog.target, + // Initiated from the context of [dialog.current] so we show [dialog.target]. + onClickTitle = { navigator.push(MangaScreen(dialog.target.id, true)) }, + onDismissRequest = { screenModel.clearDialog() }, + onComplete = { + if (navigator.lastItem is MangaScreen) { + val lastItem = navigator.lastItem + navigator.popUntil { navigator.items.contains(lastItem) } + navigator.push(MangaScreen(dialog.target.id)) + } else { + navigator.replace(MangaScreen(dialog.target.id)) + } + }, + ) + } + else -> {} + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenDialogScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenDialogScreenModel.kt deleted file mode 100644 index 9ef99f520..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenDialogScreenModel.kt +++ /dev/null @@ -1,32 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.search - -import androidx.compose.runtime.Immutable -import cafe.adriel.voyager.core.model.StateScreenModel -import cafe.adriel.voyager.core.model.screenModelScope -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import tachiyomi.domain.manga.interactor.GetManga -import tachiyomi.domain.manga.model.Manga -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class MigrateSearchScreenDialogScreenModel( - val mangaId: Long, - getManga: GetManga = Injekt.get(), -) : StateScreenModel(State()) { - - init { - screenModelScope.launch { - val manga = getManga.await(mangaId)!! - - mutableState.update { - it.copy(manga = manga) - } - } - } - - @Immutable - data class State( - val manga: Manga? = null, - ) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt index d70abd054..1faf00187 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt @@ -36,7 +36,7 @@ class MigrateSearchScreenModel( val manga = getManga.await(mangaId)!! mutableState.update { it.copy( - fromSourceId = manga.source, + from = manga, searchQuery = manga.title, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSourceSearchScreen.kt similarity index 70% rename from app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSourceSearchScreen.kt index 814b1d003..151753469 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSourceSearchScreen.kt @@ -1,37 +1,47 @@ package eu.kanade.tachiyomi.ui.browse.migration.search +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.FilterList +import androidx.compose.material3.Icon import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalUriHandler import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.BrowseSourceContent -import eu.kanade.presentation.browse.components.BrowseSourceFloatingActionButton import eu.kanade.presentation.components.SearchToolbar import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterDialog +import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.webview.WebViewScreen import exh.ui.ifSourcesLoaded import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.launch +import mihon.feature.migration.dialog.MigrateMangaDialog import mihon.presentation.core.util.collectAsLazyPagingItems import tachiyomi.core.common.Constants import tachiyomi.domain.manga.model.Manga +import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.components.material.Scaffold +import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.source.local.LocalSource -data class SourceSearchScreen( - private val oldManga: Manga, +data class MigrateSourceSearchScreen( + private val currentManga: Manga, private val sourceId: Long, private val query: String?, ) : Screen() { @@ -45,6 +55,7 @@ data class SourceSearchScreen( val uriHandler = LocalUriHandler.current val navigator = LocalNavigator.currentOrThrow + val scope = rememberCoroutineScope() val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId, query) } val state by screenModel.state.collectAsState() @@ -62,23 +73,18 @@ data class SourceSearchScreen( ) }, floatingActionButton = { - // SY --> - BrowseSourceFloatingActionButton( - isVisible = state.filters.isNotEmpty(), - onFabClick = screenModel::openFilterSheet, - ) - // SY <-- + AnimatedVisibility(visible = state.filters.isNotEmpty()) { + ExtendedFloatingActionButton( + text = { Text(text = stringResource(MR.strings.action_filter)) }, + icon = { Icon(Icons.Outlined.FilterList, contentDescription = null) }, + onClick = screenModel::openFilterSheet, + ) + } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { paddingValues -> val openMigrateDialog: (Manga) -> Unit = { - // SY --> - navigator.items - .filterIsInstance() - .last() - .newSelectedItem = oldManga.id to it.id - navigator.popUntil { it is MigrationListScreen } - // SY <-- + screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(target = it, current = currentManga)) } BrowseSourceContent( source = screenModel.source, @@ -108,7 +114,7 @@ data class SourceSearchScreen( } val onDismissRequest = { screenModel.setDialog(null) } - when (state.dialog) { + when (val dialog = state.dialog) { is BrowseSourceScreenModel.Dialog.Filter -> { SourceFilterDialog( onDismissRequest = onDismissRequest, @@ -127,6 +133,22 @@ data class SourceSearchScreen( // SY <-- ) } + is BrowseSourceScreenModel.Dialog.Migrate -> { + MigrateMangaDialog( + current = currentManga, + target = dialog.target, + // Initiated from the context of [currentManga] so we show [dialog.target]. + onClickTitle = { navigator.push(MangaScreen(dialog.target.id)) }, + onDismissRequest = onDismissRequest, + onComplete = { + scope.launch { + navigator.popUntilRoot() + HomeScreen.openTab(HomeScreen.Tab.Browse()) + navigator.push(MangaScreen(dialog.target.id)) + } + }, + ) + } else -> {} } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt index 421e8393f..5080e7ed1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.platform.LocalUriHandler import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.browse.BrowseSourceContent import eu.kanade.presentation.browse.MissingSourceScreen import eu.kanade.presentation.browse.components.BrowseSourceToolbar @@ -50,7 +49,6 @@ import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing import eu.kanade.tachiyomi.ui.category.CategoryScreen @@ -62,6 +60,7 @@ import exh.ui.ifSourcesLoaded import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow +import mihon.feature.migration.dialog.MigrateMangaDialog import mihon.presentation.core.util.collectAsLazyPagingItems import tachiyomi.core.common.Constants import tachiyomi.core.common.util.lang.launchIO @@ -72,8 +71,6 @@ import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.source.local.LocalSource -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get data class BrowseSourceScreen( val sourceId: Long, @@ -324,16 +321,17 @@ data class BrowseSourceScreen( onDismissRequest = onDismissRequest, onConfirm = { screenModel.addFavorite(dialog.manga) }, onOpenManga = { navigator.push(MangaScreen(it.id)) }, - onMigrate = { - // SY --> - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - it.id, - dialog.manga.id, - ) - // SY <-- - }, + onMigrate = { screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(dialog.manga, it)) }, + ) + } + + is BrowseSourceScreenModel.Dialog.Migrate -> { + MigrateMangaDialog( + current = dialog.current, + target = dialog.target, + // Initiated from the context of [dialog.target] so we show [dialog.current]. + onClickTitle = { navigator.push(MangaScreen(dialog.current.id)) }, + onDismissRequest = onDismissRequest, ) } is BrowseSourceScreenModel.Dialog.RemoveManga -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt index 13b335333..787c929a6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt @@ -450,7 +450,7 @@ open class BrowseSourceScreenModel( val manga: Manga, val initialSelection: ImmutableList>, ) : Dialog - data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog + data class Migrate(val target: Manga, val current: Manga) : Dialog // SY --> data class DeleteSavedSearch(val idToDelete: Long, val name: String) : Dialog diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt index 6548583b6..1a68e40bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mihon.domain.manga.model.toDomainManga import tachiyomi.core.common.preference.toggle +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.interactor.NetworkToLocalManga import tachiyomi.domain.manga.model.Manga @@ -205,19 +206,35 @@ abstract class SearchScreenModel( updateItems(newItems) } + fun setMigrateDialog(currentId: Long, target: Manga) { + screenModelScope.launchIO { + val current = getManga.await(currentId) ?: return@launchIO + mutableState.update { it.copy(dialog = Dialog.Migrate(target, current)) } + } + } + + fun clearDialog() { + mutableState.update { it.copy(dialog = null) } + } + @Immutable data class State( - val fromSourceId: Long? = null, + val from: Manga? = null, val searchQuery: String? = null, val sourceFilter: SourceFilter = SourceFilter.PinnedOnly, val onlyShowHasResults: Boolean = false, val items: PersistentMap = persistentMapOf(), + val dialog: Dialog? = null, ) { val progress: Int = items.count { it.value !is SearchItemResult.Loading } val total: Int = items.size val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) } .toImmutableMap() } + + sealed interface Dialog { + data class Migrate(val target: Manga, val current: Manga) : Dialog + } } enum class SourceFilter { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt index bdbbbe8b2..a95fae575 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt @@ -216,9 +216,9 @@ class HistoryScreenModel( } } - /*SY -->fun showMigrateDialog(currentManga: Manga, duplicate: Manga) { + /*SY -->fun showMigrateDialog(target: Manga, current: Manga) { mutableState.update { currentState -> - currentState.copy(dialog = Dialog.Migrate(newManga = currentManga, oldManga = duplicate)) + currentState.copy(dialog = Dialog.Migrate(target = target, current = current)) } } SY <--*/ @@ -252,7 +252,7 @@ class HistoryScreenModel( val manga: Manga, val initialSelection: ImmutableList>, ) : Dialog - /* SY --> data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog SY <-- */ + /* SY --> data class Migrate(val target: Manga, val current: Manga) : Dialog SY <-- */ } sealed interface Event { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt index 06d376d9c..204c09dcd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt @@ -19,7 +19,6 @@ import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions import eu.kanade.core.preference.asState -import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.category.components.ChangeCategoryDialog import eu.kanade.presentation.history.HistoryScreen @@ -28,7 +27,6 @@ import eu.kanade.presentation.history.components.HistoryDeleteDialog import eu.kanade.presentation.manga.DuplicateMangaDialog import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaScreen @@ -116,20 +114,9 @@ data object HistoryTab : Tab { DuplicateMangaDialog( duplicates = dialog.duplicates, onDismissRequest = onDismissRequest, - onConfirm = { - screenModel.addFavorite(dialog.manga) - }, + onConfirm = { screenModel.addFavorite(dialog.manga) }, onOpenManga = { navigator.push(MangaScreen(it.id)) }, - onMigrate = { - // SY --> - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - it.id, - dialog.manga.id, - ) - // SY <-- - }, + onMigrate = { screenModel.showMigrateDialog(dialog.manga, it) }, ) } is HistoryScreenModel.Dialog.ChangeCategory -> { @@ -143,13 +130,12 @@ data object HistoryTab : Tab { ) } /*SY -->is HistoryScreenModel.Dialog.Migrate -> { - MigrateDialog( - oldManga = dialog.oldManga, - newManga = dialog.newManga, - screenModel = MigrateDialogScreenModel(), + MigrateMangaDialog( + current = dialog.current, + target = dialog.target, + // Initiated from the context of [dialog.target] so we show [dialog.current]. + onClickTitle = { navigator.push(MangaScreen(dialog.current.id)) }, onDismissRequest = onDismissRequest, - onClickTitle = { navigator.push(MangaScreen(dialog.oldManga.id)) }, - onPopScreen = onDismissRequest, ) } SY <--*/ null -> {} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index 4d71b7eec..3dd57e72a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -43,8 +43,6 @@ import eu.kanade.presentation.util.isTabletUi import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.isLocalOrStub import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialog -import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialogScreenModel import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedScreen @@ -74,6 +72,7 @@ import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import logcat.LogPriority import mihon.feature.migration.config.MigrationConfigScreen +import mihon.feature.migration.dialog.MigrateMangaDialog import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.util.lang.launchUI import tachiyomi.core.common.util.lang.withIOContext @@ -273,13 +272,12 @@ class MangaScreen( } is MangaScreenModel.Dialog.Migrate -> { - MigrateDialog( - oldManga = dialog.oldManga, - newManga = dialog.newManga, - screenModel = MigrateDialogScreenModel(), + MigrateMangaDialog( + current = dialog.current, + target = dialog.target, + // Initiated from the context of [dialog.target] so we show [dialog.current]. + onClickTitle = { navigator.push(MangaScreen(dialog.current.id)) }, onDismissRequest = onDismissRequest, - onClickTitle = { navigator.push(MangaScreen(dialog.oldManga.id)) }, - onPopScreen = onDismissRequest, ) } MangaScreenModel.Dialog.SettingsSheet -> ChapterSettingsDialog( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index 396e73c0a..4f800a2c7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -141,8 +141,6 @@ import tachiyomi.source.local.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import kotlin.collections.filter -import kotlin.collections.forEach import kotlin.math.floor class MangaScreenModel( @@ -1657,7 +1655,7 @@ class MangaScreenModel( data class DuplicateManga(val manga: Manga, val duplicates: List) : Dialog /* SY --> - data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog + data class Migrate(val target: Manga, val current: Manga) : Dialog SY <-- */ data class SetFetchInterval(val manga: Manga) : Dialog @@ -1694,7 +1692,7 @@ class MangaScreenModel( /* SY --> fun showMigrateDialog(duplicate: Manga) { val manga = successState?.manga ?: return - updateSuccessState { it.copy(dialog = Dialog.Migrate(newManga = manga, oldManga = duplicate)) } + updateSuccessState { it.copy(dialog = Dialog.Migrate(target = manga, current = duplicate)) } } SY <-- */ fun setExcludedScanlators(excludedScanlators: Set) { diff --git a/app/src/main/java/mihon/domain/migration/models/MigrationFlag.kt b/app/src/main/java/mihon/domain/migration/models/MigrationFlag.kt new file mode 100644 index 000000000..101fb281f --- /dev/null +++ b/app/src/main/java/mihon/domain/migration/models/MigrationFlag.kt @@ -0,0 +1,28 @@ +package mihon.domain.migration.models + +enum class MigrationFlag(val flag: Int) { + CHAPTER(0b00001), + CATEGORY(0b00010), + + // 0b00100 was used for manga trackers + CUSTOM_COVER(0b01000), + NOTES(0b100000), + REMOVE_DOWNLOAD(0b10000), + ; + + companion object { + fun fromBit(bit: Int): Set { + return buildSet { + entries.forEach { entry -> + if (bit and entry.flag != 0) add(entry) + } + } + } + + fun toBit(flags: Set): Int { + return flags.map { it.flag } + .reduceOrNull { acc, mask -> acc or mask } + ?: 0 + } + } +} diff --git a/app/src/main/java/mihon/domain/migration/usecases/MigrateMangaUseCase.kt b/app/src/main/java/mihon/domain/migration/usecases/MigrateMangaUseCase.kt new file mode 100644 index 000000000..15612864b --- /dev/null +++ b/app/src/main/java/mihon/domain/migration/usecases/MigrateMangaUseCase.kt @@ -0,0 +1,145 @@ +package mihon.domain.migration.usecases + +import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource +import eu.kanade.domain.manga.interactor.UpdateManga +import eu.kanade.domain.manga.model.hasCustomCover +import eu.kanade.domain.manga.model.toSManga +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.data.cache.CoverCache +import eu.kanade.tachiyomi.data.download.DownloadManager +import eu.kanade.tachiyomi.data.track.EnhancedTracker +import eu.kanade.tachiyomi.data.track.TrackerManager +import kotlinx.coroutines.CancellationException +import mihon.domain.migration.models.MigrationFlag +import tachiyomi.domain.category.interactor.GetCategories +import tachiyomi.domain.category.interactor.SetMangaCategories +import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId +import tachiyomi.domain.chapter.interactor.UpdateChapter +import tachiyomi.domain.chapter.model.toChapterUpdate +import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.manga.model.MangaUpdate +import tachiyomi.domain.source.service.SourceManager +import tachiyomi.domain.track.interactor.GetTracks +import tachiyomi.domain.track.interactor.InsertTrack +import java.time.Instant + +class MigrateMangaUseCase( + private val sourcePreferences: SourcePreferences, + private val trackerManager: TrackerManager, + private val sourceManager: SourceManager, + private val downloadManager: DownloadManager, + private val updateManga: UpdateManga, + private val getChaptersByMangaId: GetChaptersByMangaId, + private val syncChaptersWithSource: SyncChaptersWithSource, + private val updateChapter: UpdateChapter, + private val getCategories: GetCategories, + private val setMangaCategories: SetMangaCategories, + private val getTracks: GetTracks, + private val insertTrack: InsertTrack, + private val coverCache: CoverCache, +) { + private val enhancedServices by lazy { trackerManager.trackers.filterIsInstance() } + + suspend operator fun invoke(current: Manga, target: Manga, replace: Boolean) { + val targetSource = sourceManager.get(target.source) ?: return + val currentSource = sourceManager.get(current.source) + val flags = sourcePreferences.migrationFlags().get() + + try { + val chapters = targetSource.getChapterList(target.toSManga()) + + try { + syncChaptersWithSource.await(chapters, target, targetSource) + } catch (_: Exception) { + // Worst case, chapters won't be synced + } + + // Update chapters read, bookmark and dateFetch + if (MigrationFlag.CHAPTER in flags) { + val prevMangaChapters = getChaptersByMangaId.await(current.id) + val mangaChapters = getChaptersByMangaId.await(target.id) + + val maxChapterRead = prevMangaChapters + .filter { it.read } + .maxOfOrNull { it.chapterNumber } + + val updatedMangaChapters = mangaChapters.map { mangaChapter -> + var updatedChapter = mangaChapter + if (updatedChapter.isRecognizedNumber) { + val prevChapter = prevMangaChapters + .find { it.isRecognizedNumber && it.chapterNumber == updatedChapter.chapterNumber } + + if (prevChapter != null) { + updatedChapter = updatedChapter.copy( + dateFetch = prevChapter.dateFetch, + bookmark = prevChapter.bookmark, + ) + } + + if (maxChapterRead != null && updatedChapter.chapterNumber <= maxChapterRead) { + updatedChapter = updatedChapter.copy(read = true) + } + } + + updatedChapter + } + + val chapterUpdates = updatedMangaChapters.map { it.toChapterUpdate() } + updateChapter.awaitAll(chapterUpdates) + } + + // Update categories + if (MigrationFlag.CHAPTER in flags) { + val categoryIds = getCategories.await(current.id).map { it.id } + setMangaCategories.await(target.id, categoryIds) + } + + // Update track + getTracks.await(current.id).mapNotNull { track -> + val updatedTrack = track.copy(mangaId = target.id) + + val service = enhancedServices + .firstOrNull { it.isTrackFrom(updatedTrack, current, currentSource) } + + if (service != null) { + service.migrateTrack(updatedTrack, target, targetSource) + } else { + updatedTrack + } + } + .takeIf { it.isNotEmpty() } + ?.let { insertTrack.awaitAll(it) } + + // Delete downloaded + if (MigrationFlag.REMOVE_DOWNLOAD in flags && currentSource != null) { + downloadManager.deleteManga(current, currentSource) + } + + // Update custom cover (recheck if custom cover exists) + if (MigrationFlag.CUSTOM_COVER in flags && current.hasCustomCover()) { + coverCache.setCustomCoverToCache(target, coverCache.getCustomCoverFile(current.id).inputStream()) + } + + val currentMangaUpdate = MangaUpdate( + id = current.id, + favorite = false, + dateAdded = 0, + ) + .takeIf { replace } + val targetMangaUpdate = MangaUpdate( + id = target.id, + favorite = true, + chapterFlags = current.chapterFlags, + viewerFlags = current.viewerFlags, + dateAdded = if (replace) current.dateAdded else Instant.now().toEpochMilli(), + notes = if (MigrationFlag.NOTES in flags) current.notes else null, + ) + + updateManga.awaitAll(listOfNotNull(currentMangaUpdate, targetMangaUpdate)) + } catch (e: Throwable) { + if (e is CancellationException) { + throw e + } + } + } +} diff --git a/app/src/main/java/mihon/feature/common/utils/MigrationFlag.kt b/app/src/main/java/mihon/feature/common/utils/MigrationFlag.kt new file mode 100644 index 000000000..77154ca2f --- /dev/null +++ b/app/src/main/java/mihon/feature/common/utils/MigrationFlag.kt @@ -0,0 +1,15 @@ +package mihon.feature.common.utils + +import dev.icerock.moko.resources.StringResource +import mihon.domain.migration.models.MigrationFlag +import tachiyomi.i18n.MR + +fun MigrationFlag.getLabel(): StringResource { + return when (this) { + MigrationFlag.CHAPTER -> MR.strings.chapters + MigrationFlag.CATEGORY -> MR.strings.categories + MigrationFlag.CUSTOM_COVER -> MR.strings.custom_cover + MigrationFlag.NOTES -> MR.strings.action_notes + MigrationFlag.REMOVE_DOWNLOAD -> MR.strings.delete_downloaded + } +} diff --git a/app/src/main/java/mihon/feature/migration/dialog/MigrateMangaDialog.kt b/app/src/main/java/mihon/feature/migration/dialog/MigrateMangaDialog.kt new file mode 100644 index 000000000..df5eaa446 --- /dev/null +++ b/app/src/main/java/mihon/feature/migration/dialog/MigrateMangaDialog.kt @@ -0,0 +1,167 @@ +package mihon.feature.migration.dialog + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.util.fastForEach +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import eu.kanade.domain.manga.model.hasCustomCover +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.data.cache.CoverCache +import eu.kanade.tachiyomi.data.download.DownloadManager +import kotlinx.coroutines.flow.update +import mihon.domain.migration.models.MigrationFlag +import mihon.domain.migration.usecases.MigrateMangaUseCase +import mihon.feature.common.utils.getLabel +import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.core.common.util.lang.withUIContext +import tachiyomi.domain.manga.model.Manga +import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.LabeledCheckbox +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.i18n.stringResource +import tachiyomi.presentation.core.screens.LoadingScreen +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +@Composable +internal fun Screen.MigrateMangaDialog( + current: Manga, + target: Manga, + onClickTitle: () -> Unit, + onDismissRequest: () -> Unit, + onComplete: () -> Unit = onDismissRequest, +) { + val scope = rememberCoroutineScope() + + val screenModel = rememberScreenModel { MigrateDialogScreenModel(current, target) } + val state by screenModel.state.collectAsState() + + if (state.isMigrating) { + LoadingScreen( + modifier = Modifier.background(MaterialTheme.colorScheme.background.copy(alpha = 0.7f)), + ) + return + } + + AlertDialog( + onDismissRequest = onDismissRequest, + title = { + Text(text = stringResource(MR.strings.migration_dialog_what_to_include)) + }, + text = { + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), + ) { + state.applicableFlags.fastForEach { flag -> + LabeledCheckbox( + label = stringResource(flag.getLabel()), + checked = flag in state.selectedFlags, + onCheckedChange = { screenModel.toggleSelection(flag) }, + ) + } + } + }, + confirmButton = { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), + ) { + TextButton( + onClick = { + onDismissRequest() + onClickTitle() + }, + ) { + Text(text = stringResource(MR.strings.action_show_manga)) + } + + Spacer(modifier = Modifier.weight(1f)) + + TextButton( + onClick = { + scope.launchIO { + screenModel.migrateManga(replace = false) + withUIContext { onComplete() } + } + }, + ) { + Text(text = stringResource(MR.strings.copy)) + } + TextButton( + onClick = { + scope.launchIO { + screenModel.migrateManga(replace = true) + withUIContext { onComplete() } + } + }, + ) { + Text(text = stringResource(MR.strings.migrate)) + } + } + }, + ) +} + +private class MigrateDialogScreenModel( + private val current: Manga, + private val target: Manga, + private val sourcePreference: SourcePreferences = Injekt.get(), + private val coverCache: CoverCache = Injekt.get(), + private val downloadManager: DownloadManager = Injekt.get(), + private val migrateManga: MigrateMangaUseCase = Injekt.get(), +) : StateScreenModel(State()) { + + init { + val applicableFlags = buildList { + MigrationFlag.entries.forEach { + val applicable = when (it) { + MigrationFlag.CHAPTER -> true + MigrationFlag.CATEGORY -> true + MigrationFlag.CUSTOM_COVER -> current.hasCustomCover(coverCache) + MigrationFlag.NOTES -> current.notes.isNotBlank() + MigrationFlag.REMOVE_DOWNLOAD -> downloadManager.getDownloadCount(current) > 0 + } + if (applicable) add(it) + } + } + val selectedFlags = sourcePreference.migrationFlags().get() + mutableState.update { it.copy(applicableFlags = applicableFlags, selectedFlags = selectedFlags) } + } + + fun toggleSelection(flag: MigrationFlag) { + mutableState.update { + val selectedFlags = it.selectedFlags.toMutableSet() + .apply { if (contains(flag)) remove(flag) else add(flag) } + .toSet() + it.copy(selectedFlags = selectedFlags) + } + } + + suspend fun migrateManga(replace: Boolean) { + sourcePreference.migrationFlags().set(state.value.selectedFlags) + mutableState.update { it.copy(isMigrating = true) } + migrateManga(current, target, replace) + mutableState.update { it.copy(isMigrating = false) } + } + + data class State( + val applicableFlags: List = emptyList(), + val selectedFlags: Set = emptySet(), + val isMigrating: Boolean = false, + ) +} 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 577d83687..026245d82 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 @@ -171,13 +171,13 @@ sealed class AndroidPreference( } } - class Object( + class ObjectAsString( preferences: SharedPreferences, keyFlow: Flow, key: String, defaultValue: T, - val serializer: (T) -> String, - val deserializer: (String) -> T, + private val serializer: (T) -> String, + private val deserializer: (String) -> T, ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T { return try { @@ -191,4 +191,25 @@ sealed class AndroidPreference( putString(key, serializer(value)) } } + + class ObjectAsInt( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: T, + private val serializer: (T) -> Int, + private val deserializer: (Int) -> T, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T { + return try { + if (preferences.contains(key)) preferences.getInt(key, 0).let(deserializer) else defaultValue + } catch (e: Exception) { + defaultValue + } + } + + override fun write(key: String, value: T): Editor.() -> Unit = { + putInt(key, serializer(value)) + } + } } 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 6bdb120cd..78f98f204 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 @@ -9,7 +9,8 @@ import tachiyomi.core.common.preference.AndroidPreference.BooleanPrimitive import tachiyomi.core.common.preference.AndroidPreference.FloatPrimitive import tachiyomi.core.common.preference.AndroidPreference.IntPrimitive import tachiyomi.core.common.preference.AndroidPreference.LongPrimitive -import tachiyomi.core.common.preference.AndroidPreference.Object +import tachiyomi.core.common.preference.AndroidPreference.ObjectAsInt +import tachiyomi.core.common.preference.AndroidPreference.ObjectAsString import tachiyomi.core.common.preference.AndroidPreference.StringPrimitive import tachiyomi.core.common.preference.AndroidPreference.StringSetPrimitive @@ -44,13 +45,29 @@ class AndroidPreferenceStore( return StringSetPrimitive(sharedPreferences, keyFlow, key, defaultValue) } - override fun getObject( + override fun getObjectFromString( key: String, defaultValue: T, serializer: (T) -> String, deserializer: (String) -> T, ): Preference { - return Object( + return ObjectAsString( + preferences = sharedPreferences, + keyFlow = keyFlow, + key = key, + defaultValue = defaultValue, + serializer = serializer, + deserializer = deserializer, + ) + } + + override fun getObjectFromInt( + key: String, + defaultValue: T, + serializer: (T) -> Int, + deserializer: (Int) -> T, + ): Preference { + return ObjectAsInt( preferences = sharedPreferences, keyFlow = keyFlow, key = key, 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 96e8644ad..1dae7fff9 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 @@ -52,7 +52,7 @@ class InMemoryPreferenceStore( } @Suppress("UNCHECKED_CAST") - override fun getObject( + override fun getObjectFromString( key: String, defaultValue: T, serializer: (T) -> String, @@ -63,6 +63,18 @@ class InMemoryPreferenceStore( return if (data == null) default else InMemoryPreference(key, data, defaultValue) } + @Suppress("UNCHECKED_CAST") + override fun getObjectFromInt( + key: String, + defaultValue: T, + serializer: (T) -> Int, + deserializer: (Int) -> T, + ): Preference { + val default = InMemoryPreference(key, null, defaultValue) + val data: T? = preferences[key]?.get() as? T + 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 6407224a6..2016f3d44 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 @@ -14,13 +14,20 @@ interface PreferenceStore { fun getStringSet(key: String, defaultValue: Set = emptySet()): Preference> - fun getObject( + fun getObjectFromString( key: String, defaultValue: T, serializer: (T) -> String, deserializer: (String) -> T, ): Preference + fun getObjectFromInt( + key: String, + defaultValue: T, + serializer: (T) -> Int, + deserializer: (Int) -> T, + ): Preference + fun getAll(): Map } @@ -28,7 +35,7 @@ fun PreferenceStore.getLongArray( key: String, defaultValue: List, ): Preference> { - return getObject( + return getObjectFromString( key = key, defaultValue = defaultValue, serializer = { it.joinToString(",") }, @@ -40,7 +47,7 @@ inline fun > PreferenceStore.getEnum( key: String, defaultValue: T, ): Preference { - return getObject( + return getObjectFromString( key = key, defaultValue = defaultValue, serializer = { it.name }, diff --git a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt index ea5c90b0a..4ed62a1e2 100644 --- a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt @@ -14,14 +14,14 @@ class LibraryPreferences( private val preferenceStore: PreferenceStore, ) { - fun displayMode() = preferenceStore.getObject( + fun displayMode() = preferenceStore.getObjectFromString( "pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize, ) - fun sortingMode() = preferenceStore.getObject( + fun sortingMode() = preferenceStore.getObjectFromString( "library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize,