From eb311ee27a21f56ee6afb73797e841fb8665816e Mon Sep 17 00:00:00 2001 From: "Tran M. Cuong" Date: Wed, 28 Aug 2024 18:26:00 +0700 Subject: [PATCH] feat: merge multiple library entries (#302) * extract SmartSearchMerge to a separated class * multiple select & merging from library * fix various case when merging an already merged entry * allow run 1 manga search-merging directly from Library screen * allow remove merged entries after merging * Open the newly-created merged manga after merging * don't expand description on the merged manga being opened after merging --- .../java/eu/kanade/domain/SYDomainModule.kt | 4 + .../manga/interactor/SmartSearchMerge.kt | 176 ++++++++++++++++++ .../manga/components/MangaBottomActionMenu.kt | 111 +++++++---- .../ui/library/LibraryScreenModel.kt | 24 +++ .../kanade/tachiyomi/ui/library/LibraryTab.kt | 43 +++++ .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 8 +- .../tachiyomi/ui/manga/MangaScreenModel.kt | 151 +-------------- .../moko-resources/base/strings.xml | 1 + 8 files changed, 339 insertions(+), 179 deletions(-) create mode 100644 app/src/main/java/eu/kanade/domain/manga/interactor/SmartSearchMerge.kt diff --git a/app/src/main/java/eu/kanade/domain/SYDomainModule.kt b/app/src/main/java/eu/kanade/domain/SYDomainModule.kt index 21a59726b..61e62a2f6 100644 --- a/app/src/main/java/eu/kanade/domain/SYDomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/SYDomainModule.kt @@ -6,6 +6,7 @@ import eu.kanade.domain.manga.interactor.DeleteSortTag import eu.kanade.domain.manga.interactor.GetPagePreviews import eu.kanade.domain.manga.interactor.GetSortTag import eu.kanade.domain.manga.interactor.ReorderSortTag +import eu.kanade.domain.manga.interactor.SmartSearchMerge import eu.kanade.domain.source.interactor.CreateSourceCategory import eu.kanade.domain.source.interactor.DeleteSourceCategory import eu.kanade.domain.source.interactor.GetExhSavedSearch @@ -129,6 +130,9 @@ class SYDomainModule : InjektModule { addFactory { DeleteByMergeId(get()) } addFactory { DeleteMergeById(get()) } addFactory { GetMergedMangaForDownloading(get()) } + // KMK --> + addFactory { SmartSearchMerge(get()) } + // KMK <-- addSingletonFactory { FavoritesEntryRepositoryImpl(get()) } addFactory { GetFavoriteEntries(get()) } diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/SmartSearchMerge.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/SmartSearchMerge.kt new file mode 100644 index 000000000..9d46599bc --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/SmartSearchMerge.kt @@ -0,0 +1,176 @@ +package eu.kanade.domain.manga.interactor + +import android.app.Application +import eu.kanade.domain.manga.model.copyFrom +import eu.kanade.domain.manga.model.toSManga +import exh.source.MERGED_SOURCE_ID +import tachiyomi.core.common.i18n.stringResource +import tachiyomi.core.common.util.lang.withNonCancellableContext +import tachiyomi.domain.category.interactor.GetCategories +import tachiyomi.domain.category.interactor.SetMangaCategories +import tachiyomi.domain.manga.interactor.DeleteByMergeId +import tachiyomi.domain.manga.interactor.DeleteMangaById +import tachiyomi.domain.manga.interactor.GetManga +import tachiyomi.domain.manga.interactor.GetMergedReferencesById +import tachiyomi.domain.manga.interactor.InsertMergedReference +import tachiyomi.domain.manga.interactor.NetworkToLocalManga +import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.manga.model.MergedMangaReference +import tachiyomi.i18n.sy.SYMR +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class SmartSearchMerge( + private val getManga: GetManga = Injekt.get(), + private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(), + private val insertMergedReference: InsertMergedReference = Injekt.get(), + private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), + private val deleteMangaById: DeleteMangaById = Injekt.get(), + private val deleteByMergeId: DeleteByMergeId = Injekt.get(), + private val getCategories: GetCategories = Injekt.get(), + private val setMangaCategories: SetMangaCategories = Injekt.get(), +) { + suspend fun smartSearchMerge(manga: Manga, originalMangaId: Long): Manga { + // KMK --> + val context = Injekt.get() + // KMK <-- + val originalManga = getManga.await(originalMangaId) + ?: throw IllegalArgumentException(context.stringResource(SYMR.strings.merge_unknown_entry, originalMangaId)) + if (originalManga.source == MERGED_SOURCE_ID) { + val children = getMergedReferencesById.await(originalMangaId) + if (children.any { it.mangaSourceId == manga.source && it.mangaUrl == manga.url }) { + // Merged already + return originalManga + } + + val mangaReferences = mutableListOf( + MergedMangaReference( + id = -1, + isInfoManga = false, + getChapterUpdates = true, + chapterSortMode = 0, + chapterPriority = 0, + downloadChapters = true, + mergeId = originalManga.id, + mergeUrl = originalManga.url, + mangaId = manga.id, + mangaUrl = manga.url, + mangaSourceId = manga.source, + ), + ) + + if (children.isEmpty() || children.all { it.mangaSourceId != MERGED_SOURCE_ID }) { + mangaReferences += MergedMangaReference( + id = -1, + isInfoManga = false, + getChapterUpdates = false, + chapterSortMode = 0, + chapterPriority = -1, + downloadChapters = false, + mergeId = originalManga.id, + mergeUrl = originalManga.url, + mangaId = originalManga.id, + mangaUrl = originalManga.url, + mangaSourceId = MERGED_SOURCE_ID, + ) + } + + // todo + insertMergedReference.awaitAll(mangaReferences) + + return originalManga + } else { + if (manga.id == originalMangaId) { + // Merged already + return originalManga + } + var mergedManga = Manga.create() + .copy( + url = originalManga.url, + ogTitle = originalManga.title, + source = MERGED_SOURCE_ID, + ) + .copyFrom(originalManga.toSManga()) + .copy( + favorite = true, + lastUpdate = originalManga.lastUpdate, + viewerFlags = originalManga.viewerFlags, + chapterFlags = originalManga.chapterFlags, + dateAdded = System.currentTimeMillis(), + ) + + var existingManga = getManga.await(mergedManga.url, mergedManga.source) + while (existingManga != null) { + if (existingManga.favorite) { + // Duplicate entry found -> use it instead + mergedManga = existingManga + break + } else { + withNonCancellableContext { + existingManga?.id?.let { + deleteByMergeId.await(it) + deleteMangaById.await(it) + } + } + } + // Remove previously merged entry from database (user already removed from favorites) + existingManga = getManga.await(mergedManga.url, mergedManga.source) + } + + mergedManga = networkToLocalManga.await(mergedManga) + + getCategories.await(originalMangaId) + .let { categories -> + setMangaCategories.await(mergedManga.id, categories.map { it.id }) + } + + val originalMangaReference = MergedMangaReference( + id = -1, + isInfoManga = true, + getChapterUpdates = true, + chapterSortMode = 0, + chapterPriority = 0, + downloadChapters = true, + mergeId = mergedManga.id, + mergeUrl = mergedManga.url, + mangaId = originalManga.id, + mangaUrl = originalManga.url, + mangaSourceId = originalManga.source, + ) + + val newMangaReference = MergedMangaReference( + id = -1, + isInfoManga = false, + getChapterUpdates = true, + chapterSortMode = 0, + chapterPriority = 0, + downloadChapters = true, + mergeId = mergedManga.id, + mergeUrl = mergedManga.url, + mangaId = manga.id, + mangaUrl = manga.url, + mangaSourceId = manga.source, + ) + + val mergedMangaReference = MergedMangaReference( + id = -1, + isInfoManga = false, + getChapterUpdates = false, + chapterSortMode = 0, + chapterPriority = -1, + downloadChapters = false, + mergeId = mergedManga.id, + mergeUrl = mergedManga.url, + mangaId = mergedManga.id, + mangaUrl = mergedManga.url, + mangaSourceId = MERGED_SOURCE_ID, + ) + + insertMergedReference.awaitAll(listOf(originalMangaReference, newMangaReference, mergedMangaReference)) + + return mergedManga + } + + // Note that if the manga are merged in a different order, this won't trigger, but I don't care lol + } +} diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt index 4ff793623..60a845069 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt @@ -28,7 +28,7 @@ import androidx.compose.material.icons.outlined.BookmarkRemove import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.DoneAll import androidx.compose.material.icons.outlined.Download -import androidx.compose.material.icons.outlined.Label +import androidx.compose.material.icons.outlined.Merge import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.RemoveDone import androidx.compose.material.icons.outlined.SwapCalls @@ -53,6 +53,7 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import eu.kanade.presentation.components.DownloadDropdownMenu import eu.kanade.presentation.components.DropdownMenu @@ -240,6 +241,9 @@ fun LibraryBottomActionMenu( onClickAddToMangaDex: (() -> Unit)?, onClickResetInfo: (() -> Unit)?, // SY <-- + // KMK --> + onClickMerge: (() -> Unit)?, + // KMK <-- modifier: Modifier = Modifier, ) { AnimatedVisibility( @@ -255,11 +259,13 @@ fun LibraryBottomActionMenu( ) { val haptic = LocalHapticFeedback.current val confirm = - remember { mutableStateListOf(false, false, false, false, false /* SY --> */, false /* SY <-- */) } + remember { + mutableStateListOf(false, false, false, false, false /* SY --> */, false, false, false /* SY <-- */) + } var resetJob: Job? = remember { null } val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> haptic.performHapticFeedback(HapticFeedbackType.LongPress) - (0..<5).forEach { i -> confirm[i] = i == toConfirmIndex } + (0..<8).forEach { i -> confirm[i] = i == toConfirmIndex } resetJob?.cancel() resetJob = scope.launch { delay(1.seconds) @@ -267,9 +273,15 @@ fun LibraryBottomActionMenu( } } // SY --> - val showOverflow = onClickCleanTitles != null || onClickAddToMangaDex != null || onClickResetInfo != null + val showOverflow = onClickCleanTitles != null || + onClickAddToMangaDex != null || + onClickResetInfo != null || + // KMK --> + onClickMigrate != null || + onClickMerge != null + // KMK <-- val configuration = LocalConfiguration.current - val moveMarkPrev = remember { !configuration.isTabletUi() } + val isTabletUi = remember { configuration.isTabletUi() } var overFlowOpen by remember { mutableStateOf(false) } // SY <-- Row( @@ -311,7 +323,6 @@ fun LibraryBottomActionMenu( onLongClick = { onLongClickItem(4) }, onClick = onDeleteClicked, ) - // SY --> Button( title = stringResource(MR.strings.action_mark_as_read), icon = Icons.Outlined.DoneAll, @@ -319,15 +330,36 @@ fun LibraryBottomActionMenu( onLongClick = { onLongClickItem(1) }, onClick = onMarkAsReadClicked, ) + Button( + title = stringResource(MR.strings.action_mark_as_unread), + icon = Icons.Outlined.RemoveDone, + toConfirm = confirm[2], + onLongClick = { onLongClickItem(2) }, + onClick = onMarkAsUnreadClicked, + ) + // SY --> if (showOverflow) { - if (!moveMarkPrev) { - Button( - title = stringResource(MR.strings.action_mark_as_unread), - icon = Icons.Outlined.RemoveDone, - toConfirm = confirm[2], - onLongClick = { onLongClickItem(2) }, - onClick = onMarkAsUnreadClicked, - ) + if (isTabletUi) { + if (onClickMigrate != null) { + Button( + title = stringResource(MR.strings.migrate), + icon = Icons.Outlined.SwapCalls, + toConfirm = confirm[6], + onLongClick = { onLongClickItem(6) }, + onClick = onClickMigrate, + ) + } + // KMK --> + if (onClickMerge != null) { + Button( + title = stringResource(SYMR.strings.merge), + icon = Icons.Outlined.Merge, + toConfirm = confirm[7], + onLongClick = { onLongClickItem(7) }, + onClick = onClickMerge, + ) + } + // KMK <-- } Button( title = stringResource(MR.strings.label_more), @@ -339,12 +371,25 @@ fun LibraryBottomActionMenu( DropdownMenu( expanded = overFlowOpen, onDismissRequest = { overFlowOpen = false }, + // KMK --> + offset = DpOffset((-10).dp, 0.dp), + // KMK <-- ) { - if (moveMarkPrev) { - DropdownMenuItem( - text = { Text(stringResource(MR.strings.action_mark_as_unread)) }, - onClick = onMarkAsUnreadClicked, - ) + if (!isTabletUi) { + if (onClickMigrate != null) { + DropdownMenuItem( + text = { Text(stringResource(MR.strings.migrate)) }, + onClick = onClickMigrate, + ) + } + // KMK --> + if (onClickMerge != null) { + DropdownMenuItem( + text = { Text(stringResource(SYMR.strings.merge)) }, + onClick = onClickMerge, + ) + } + // KMK <-- } if (onClickCleanTitles != null) { DropdownMenuItem( @@ -352,12 +397,6 @@ fun LibraryBottomActionMenu( onClick = onClickCleanTitles, ) } - if (onClickMigrate != null) { - DropdownMenuItem( - text = { Text(stringResource(MR.strings.migrate)) }, - onClick = onClickMigrate, - ) - } if (onClickAddToMangaDex != null) { DropdownMenuItem( text = { Text(stringResource(SYMR.strings.mangadex_add_to_follows)) }, @@ -371,23 +410,27 @@ fun LibraryBottomActionMenu( ) } } + /* KMK --> } else { - Button( - title = stringResource(MR.strings.action_mark_as_unread), - icon = Icons.Outlined.RemoveDone, - toConfirm = confirm[2], - onLongClick = { onLongClickItem(2) }, - onClick = onMarkAsUnreadClicked, - ) if (onClickMigrate != null) { Button( title = stringResource(MR.strings.migrate), icon = Icons.Outlined.SwapCalls, - toConfirm = confirm[5], - onLongClick = { onLongClickItem(5) }, + toConfirm = confirm[6], + onLongClick = { onLongClickItem(6) }, onClick = onClickMigrate, ) } + if (onClickMerge != null) { + Button( + title = stringResource(SYMR.strings.merge), + icon = Icons.Outlined.Merge, + toConfirm = confirm[7], + onLongClick = { onLongClickItem(7) }, + onClick = onClickMerge, + ) + } + // KMK <-- */ } // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt index fe189e700..baa52bf66 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt @@ -20,6 +20,7 @@ import eu.kanade.core.util.fastMapNotNull import eu.kanade.core.util.fastPartition import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.chapter.interactor.SetReadStatus +import eu.kanade.domain.manga.interactor.SmartSearchMerge import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.components.SEARCH_DEBOUNCE_MILLIS @@ -154,6 +155,9 @@ class LibraryScreenModel( private val setCustomMangaInfo: SetCustomMangaInfo = Injekt.get(), private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId = Injekt.get(), // SY <-- + // KMK --> + private val smartSearchMerge: SmartSearchMerge = Injekt.get(), + // KMK <-- ) : StateScreenModel(State()) { var activeCategoryIndex: Int by libraryPreferences.lastUsedCategory().asState(screenModelScope) @@ -1359,6 +1363,26 @@ class LibraryScreenModel( } // SY <-- + // KMK --> + /** + * Will get first merged manga in the list as target merging. + * If there is no merged manga, then it will use the first one in list to create a new target. + */ + suspend fun smartSearchMerge(selectedMangas: PersistentList): Long? { + val mergedManga = selectedMangas.firstOrNull { it.manga.source == MERGED_SOURCE_ID }?.let { listOf(it) } + ?: emptyList() + val mergingMangas = selectedMangas.filterNot { it.manga.source == MERGED_SOURCE_ID } + val toMergeMangas = mergedManga + mergingMangas + if (toMergeMangas.size <= 1) return null + + var mergingMangaId = toMergeMangas.first().manga.id + for (manga in toMergeMangas.drop(1)) { + mergingMangaId = smartSearchMerge.smartSearchMerge(manga.manga, mergingMangaId).id + } + return mergingMangaId + } + // KMK <-- + @Immutable private data class ItemPreferences( val downloadBadge: Boolean, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt index f483f5044..ef60d5bdb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt @@ -9,6 +9,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.HelpOutline import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -43,6 +44,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.sync.SyncDataJob 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.globalsearch.GlobalSearchScreen import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.home.HomeScreen @@ -65,6 +67,7 @@ import tachiyomi.domain.library.model.LibraryGroup import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.manga.model.Manga import tachiyomi.i18n.MR +import tachiyomi.i18n.kmk.KMR import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource @@ -208,6 +211,46 @@ object LibraryTab : Tab { onClickAddToMangaDex = screenModel::syncMangaToDex.takeIf { state.showAddToMangadex }, onClickResetInfo = screenModel::resetInfo.takeIf { state.showResetInfo }, // SY <-- + // KMK --> + onClickMerge = { + if (state.selection.size == 1) { + val manga = state.selection.first().manga + // Invoke merging for this manga + screenModel.clearSelection() + val smartSearchConfig = SourcesScreen.SmartSearchConfig(manga.title, manga.id) + navigator.push(SourcesScreen(smartSearchConfig)) + } else if (state.selection.isNotEmpty()) { + // Invoke multiple merge + val selection = state.selection + screenModel.clearSelection() + scope.launchIO { + val mergingMangas = selection.filterNot { it.manga.source == MERGED_SOURCE_ID } + val mergedMangaId = screenModel.smartSearchMerge(selection) + snackbarHostState.showSnackbar(context.stringResource(SYMR.strings.entry_merged)) + if (mergedMangaId != null) { + val result = snackbarHostState.showSnackbar( + message = context.stringResource(KMR.strings.action_remove_merged), + actionLabel = context.stringResource(MR.strings.action_remove), + withDismissAction = true, + ) + if (result == SnackbarResult.ActionPerformed) { + screenModel.removeMangas( + mangaList = mergingMangas.map { it.manga }, + deleteFromLibrary = true, + deleteChapters = false, + ) + } + navigator.push(MangaScreen(mergedMangaId)) + } else { + snackbarHostState.showSnackbar(context.stringResource(SYMR.strings.merged_references_invalid)) + } + } + } else { + screenModel.clearSelection() + context.toast(SYMR.strings.no_valid_entry) + } + }, + // KMK <-- ) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, 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 c4123fa12..1bf52cfbe 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 @@ -776,7 +776,13 @@ class MangaScreen( navigator.popUntil { it is SourcesScreen } navigator.pop() - navigator replace MangaScreen(mergedManga.id, true) + // KMK --> + if (navigator.lastItem !is MangaScreen) { + navigator push MangaScreen(mergedManga.id) + } else { + // KMK <-- + navigator replace MangaScreen(mergedManga.id) + } context.toast(SYMR.strings.entry_merged) } catch (e: Exception) { if (e is CancellationException) throw e 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 5cbcdefcb..eea8ba91c 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 @@ -28,10 +28,10 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.manga.interactor.GetExcludedScanlators import eu.kanade.domain.manga.interactor.GetPagePreviews import eu.kanade.domain.manga.interactor.SetExcludedScanlators +import eu.kanade.domain.manga.interactor.SmartSearchMerge import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.PagePreview import eu.kanade.domain.manga.model.chaptersFiltered -import eu.kanade.domain.manga.model.copyFrom import eu.kanade.domain.manga.model.downloadedFilter import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.manga.model.toSManga @@ -109,7 +109,6 @@ import tachiyomi.core.common.preference.mapAsCheckboxState import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchNonCancellable import tachiyomi.core.common.util.lang.withIOContext -import tachiyomi.core.common.util.lang.withNonCancellableContext import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.category.interactor.GetCategories @@ -124,8 +123,6 @@ import tachiyomi.domain.chapter.model.NoChaptersException import tachiyomi.domain.chapter.service.calculateChapterGap import tachiyomi.domain.chapter.service.getChapterSort import tachiyomi.domain.library.service.LibraryPreferences -import tachiyomi.domain.manga.interactor.DeleteByMergeId -import tachiyomi.domain.manga.interactor.DeleteMangaById import tachiyomi.domain.manga.interactor.DeleteMergeById import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.manga.interactor.GetFlatMetadataById @@ -133,7 +130,6 @@ import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.interactor.GetMangaWithChapters import tachiyomi.domain.manga.interactor.GetMergedMangaById import tachiyomi.domain.manga.interactor.GetMergedReferencesById -import tachiyomi.domain.manga.interactor.InsertMergedReference import tachiyomi.domain.manga.interactor.NetworkToLocalManga import tachiyomi.domain.manga.interactor.SetCustomMangaInfo import tachiyomi.domain.manga.interactor.SetMangaChapterFlags @@ -153,7 +149,6 @@ import tachiyomi.domain.track.interactor.GetTracks import tachiyomi.domain.track.interactor.InsertTrack import tachiyomi.domain.track.model.Track import tachiyomi.i18n.MR -import tachiyomi.i18n.sy.SYMR import tachiyomi.source.local.LocalSource import tachiyomi.source.local.isLocal import uy.kohesive.injekt.Injekt @@ -187,11 +182,11 @@ class MangaScreenModel( private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId = Injekt.get(), private val getMergedMangaById: GetMergedMangaById = Injekt.get(), private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(), - private val insertMergedReference: InsertMergedReference = Injekt.get(), + // KMK --> + private val smartSearchMerge: SmartSearchMerge = Injekt.get(), + // KMK <-- private val updateMergedSettings: UpdateMergedSettings = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), - private val deleteMangaById: DeleteMangaById = Injekt.get(), - private val deleteByMergeId: DeleteByMergeId = Injekt.get(), private val deleteMergeById: DeleteMergeById = Injekt.get(), private val getFlatMetadata: GetFlatMetadataById = Injekt.get(), private val getPagePreviews: GetPagePreviews = Injekt.get(), @@ -704,143 +699,11 @@ class MangaScreenModel( } } } - // KMK <-- suspend fun smartSearchMerge(manga: Manga, originalMangaId: Long): Manga { - val originalManga = getManga.await(originalMangaId) - ?: throw IllegalArgumentException(context.stringResource(SYMR.strings.merge_unknown_entry, originalMangaId)) - if (originalManga.source == MERGED_SOURCE_ID) { - val children = getMergedReferencesById.await(originalMangaId) - if (children.any { it.mangaSourceId == manga.source && it.mangaUrl == manga.url }) { - throw IllegalArgumentException(context.stringResource(SYMR.strings.merged_already)) - } - - val mangaReferences = mutableListOf( - MergedMangaReference( - id = -1, - isInfoManga = false, - getChapterUpdates = true, - chapterSortMode = 0, - chapterPriority = 0, - downloadChapters = true, - mergeId = originalManga.id, - mergeUrl = originalManga.url, - mangaId = manga.id, - mangaUrl = manga.url, - mangaSourceId = manga.source, - ), - ) - - if (children.isEmpty() || children.all { it.mangaSourceId != MERGED_SOURCE_ID }) { - mangaReferences += MergedMangaReference( - id = -1, - isInfoManga = false, - getChapterUpdates = false, - chapterSortMode = 0, - chapterPriority = -1, - downloadChapters = false, - mergeId = originalManga.id, - mergeUrl = originalManga.url, - mangaId = originalManga.id, - mangaUrl = originalManga.url, - mangaSourceId = MERGED_SOURCE_ID, - ) - } - - // todo - insertMergedReference.awaitAll(mangaReferences) - - return originalManga - } else { - if (manga.id == originalMangaId) { - throw IllegalArgumentException(context.stringResource(SYMR.strings.merged_already)) - } - var mergedManga = Manga.create() - .copy( - url = originalManga.url, - ogTitle = originalManga.title, - source = MERGED_SOURCE_ID, - ) - .copyFrom(originalManga.toSManga()) - .copy( - favorite = true, - lastUpdate = originalManga.lastUpdate, - viewerFlags = originalManga.viewerFlags, - chapterFlags = originalManga.chapterFlags, - dateAdded = System.currentTimeMillis(), - ) - - var existingManga = getManga.await(mergedManga.url, mergedManga.source) - while (existingManga != null) { - if (existingManga.favorite) { - throw IllegalArgumentException(context.stringResource(SYMR.strings.merge_duplicate)) - } else { - withNonCancellableContext { - existingManga?.id?.let { - deleteByMergeId.await(it) - deleteMangaById.await(it) - } - } - } - existingManga = getManga.await(mergedManga.url, mergedManga.source) - } - - mergedManga = networkToLocalManga.await(mergedManga) - - getCategories.await(originalMangaId) - .let { categories -> - setMangaCategories.await(mergedManga.id, categories.map { it.id }) - } - - val originalMangaReference = MergedMangaReference( - id = -1, - isInfoManga = true, - getChapterUpdates = true, - chapterSortMode = 0, - chapterPriority = 0, - downloadChapters = true, - mergeId = mergedManga.id, - mergeUrl = mergedManga.url, - mangaId = originalManga.id, - mangaUrl = originalManga.url, - mangaSourceId = originalManga.source, - ) - - val newMangaReference = MergedMangaReference( - id = -1, - isInfoManga = false, - getChapterUpdates = true, - chapterSortMode = 0, - chapterPriority = 0, - downloadChapters = true, - mergeId = mergedManga.id, - mergeUrl = mergedManga.url, - mangaId = manga.id, - mangaUrl = manga.url, - mangaSourceId = manga.source, - ) - - val mergedMangaReference = MergedMangaReference( - id = -1, - isInfoManga = false, - getChapterUpdates = false, - chapterSortMode = 0, - chapterPriority = -1, - downloadChapters = false, - mergeId = mergedManga.id, - mergeUrl = mergedManga.url, - mangaId = mergedManga.id, - mangaUrl = mergedManga.url, - mangaSourceId = MERGED_SOURCE_ID, - ) - - insertMergedReference.awaitAll(listOf(originalMangaReference, newMangaReference, mergedMangaReference)) - - return mergedManga - } - - // Note that if the manga are merged in a different order, this won't trigger, but I don't care lol + return smartSearchMerge.smartSearchMerge(manga, originalMangaId) } + // KMK <-- fun updateMergeSettings(mergedMangaReferences: List) { screenModelScope.launchNonCancellable { @@ -1925,7 +1788,7 @@ class MangaScreenModel( val trackingCount: Int = 0, val hasLoggedInTrackers: Boolean = false, val isRefreshingData: Boolean = false, - val dialog: MangaScreenModel.Dialog? = null, + val dialog: Dialog? = null, val hasPromptedToAddBefore: Boolean = false, // SY --> diff --git a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml index 19cc2fb86..4bb6a4dc0 100644 --- a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml @@ -11,6 +11,7 @@ Move down Move bottom Confirm Color + Remove merged entries?