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:
parent
c4f54e54b3
commit
eb311ee27a
8 changed files with 339 additions and 179 deletions
|
|
@ -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()) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <--
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
Loading…
Reference in a new issue