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
This commit is contained in:
Tran M. Cuong 2024-08-28 18:26:00 +07:00 committed by GitHub
parent c4f54e54b3
commit eb311ee27a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 339 additions and 179 deletions

View file

@ -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<FavoritesEntryRepository> { FavoritesEntryRepositoryImpl(get()) }
addFactory { GetFavoriteEntries(get()) }

View file

@ -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<Application>()
// 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
}
}

View file

@ -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 <--
}

View file

@ -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<LibraryScreenModel.State>(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<LibraryManga>): 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,

View file

@ -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) },

View file

@ -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

View file

@ -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<MergedMangaReference>) {
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 -->

View file

@ -11,6 +11,7 @@
<string name="action_move_down">Move down</string>
<string name="action_move_bottom">Move bottom</string>
<string name="action_confirm_color">Confirm Color</string>
<string name="action_remove_merged">Remove merged entries?</string>
<!-- Preferences -->