Cleanup migrate manga dialog and related code (#2156)
(cherry picked from commit 2b126f1ff56b63e470b48a04149e28c609f01148) # Conflicts: # app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenDialogScreenModel.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSourceSearchScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt # app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt # app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
This commit is contained in:
@@ -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<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||
addFactory { GetApplicationRelease(get(), get()) }
|
||||
|
||||
@@ -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) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+30
-22
@@ -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<Long>) : 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<MigrationListScreen>()
|
||||
.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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-32
@@ -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<MigrateSearchScreenDialogScreenModel.State>(State()) {
|
||||
|
||||
init {
|
||||
screenModelScope.launch {
|
||||
val manga = getManga.await(mangaId)!!
|
||||
|
||||
mutableState.update {
|
||||
it.copy(manga = manga)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val manga: Manga? = null,
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ class MigrateSearchScreenModel(
|
||||
val manga = getManga.await(mangaId)!!
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
fromSourceId = manga.source,
|
||||
from = manga,
|
||||
searchQuery = manga.title,
|
||||
)
|
||||
}
|
||||
|
||||
+40
-18
@@ -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<MigrationListScreen>()
|
||||
.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 -> {}
|
||||
}
|
||||
}
|
||||
+12
-14
@@ -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<SourcePreferences>().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 -> {
|
||||
|
||||
+1
-1
@@ -450,7 +450,7 @@ open class BrowseSourceScreenModel(
|
||||
val manga: Manga,
|
||||
val initialSelection: ImmutableList<CheckboxState.State<Category>>,
|
||||
) : 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
|
||||
|
||||
+18
-1
@@ -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<CatalogueSource, SearchItemResult> = 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 {
|
||||
|
||||
@@ -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<CheckboxState<Category>>,
|
||||
) : 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 {
|
||||
|
||||
@@ -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<SourcePreferences>().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 -> {}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<MangaWithChapterCount>) : 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<String>) {
|
||||
|
||||
@@ -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<MigrationFlag> {
|
||||
return buildSet {
|
||||
entries.forEach { entry ->
|
||||
if (bit and entry.flag != 0) add(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toBit(flags: Set<MigrationFlag>): Int {
|
||||
return flags.map { it.flag }
|
||||
.reduceOrNull { acc, mask -> acc or mask }
|
||||
?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<EnhancedTracker>() }
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<MigrateDialogScreenModel.State>(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<MigrationFlag> = emptyList(),
|
||||
val selectedFlags: Set<MigrationFlag> = emptySet(),
|
||||
val isMigrating: Boolean = false,
|
||||
)
|
||||
}
|
||||
@@ -171,13 +171,13 @@ sealed class AndroidPreference<T>(
|
||||
}
|
||||
}
|
||||
|
||||
class Object<T>(
|
||||
class ObjectAsString<T>(
|
||||
preferences: SharedPreferences,
|
||||
keyFlow: Flow<String?>,
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
val serializer: (T) -> String,
|
||||
val deserializer: (String) -> T,
|
||||
private val serializer: (T) -> String,
|
||||
private val deserializer: (String) -> T,
|
||||
) : AndroidPreference<T>(preferences, keyFlow, key, defaultValue) {
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T {
|
||||
return try {
|
||||
@@ -191,4 +191,25 @@ sealed class AndroidPreference<T>(
|
||||
putString(key, serializer(value))
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectAsInt<T>(
|
||||
preferences: SharedPreferences,
|
||||
keyFlow: Flow<String?>,
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
private val serializer: (T) -> Int,
|
||||
private val deserializer: (Int) -> T,
|
||||
) : AndroidPreference<T>(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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-3
@@ -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 <T> getObject(
|
||||
override fun <T> getObjectFromString(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
serializer: (T) -> String,
|
||||
deserializer: (String) -> T,
|
||||
): Preference<T> {
|
||||
return Object(
|
||||
return ObjectAsString(
|
||||
preferences = sharedPreferences,
|
||||
keyFlow = keyFlow,
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
serializer = serializer,
|
||||
deserializer = deserializer,
|
||||
)
|
||||
}
|
||||
|
||||
override fun <T> getObjectFromInt(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
serializer: (T) -> Int,
|
||||
deserializer: (Int) -> T,
|
||||
): Preference<T> {
|
||||
return ObjectAsInt(
|
||||
preferences = sharedPreferences,
|
||||
keyFlow = keyFlow,
|
||||
key = key,
|
||||
|
||||
+13
-1
@@ -52,7 +52,7 @@ class InMemoryPreferenceStore(
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T> getObject(
|
||||
override fun <T> 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 <T> getObjectFromInt(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
serializer: (T) -> Int,
|
||||
deserializer: (Int) -> T,
|
||||
): Preference<T> {
|
||||
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<String, *> {
|
||||
return preferences
|
||||
}
|
||||
|
||||
@@ -14,13 +14,20 @@ interface PreferenceStore {
|
||||
|
||||
fun getStringSet(key: String, defaultValue: Set<String> = emptySet()): Preference<Set<String>>
|
||||
|
||||
fun <T> getObject(
|
||||
fun <T> getObjectFromString(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
serializer: (T) -> String,
|
||||
deserializer: (String) -> T,
|
||||
): Preference<T>
|
||||
|
||||
fun <T> getObjectFromInt(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
serializer: (T) -> Int,
|
||||
deserializer: (Int) -> T,
|
||||
): Preference<T>
|
||||
|
||||
fun getAll(): Map<String, *>
|
||||
}
|
||||
|
||||
@@ -28,7 +35,7 @@ fun PreferenceStore.getLongArray(
|
||||
key: String,
|
||||
defaultValue: List<Long>,
|
||||
): Preference<List<Long>> {
|
||||
return getObject(
|
||||
return getObjectFromString(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
serializer = { it.joinToString(",") },
|
||||
@@ -40,7 +47,7 @@ inline fun <reified T : Enum<T>> PreferenceStore.getEnum(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
): Preference<T> {
|
||||
return getObject(
|
||||
return getObjectFromString(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
serializer = { it.name },
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user