Clean up library download chapters logic
We can probably clean up the same logic in the manga controller at some point too, but that stuff's messy. Also fixes the spacing issue that the new icon introduced. (cherry picked from commit 33e90d64497f920be825b803e1342a2e6c937111) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
This commit is contained in:
parent
35bd6233d9
commit
b2565c7f8b
@ -120,7 +120,7 @@ class SYDomainModule : InjektModule {
|
|||||||
addFactory { GetMergedManga(get()) }
|
addFactory { GetMergedManga(get()) }
|
||||||
addFactory { GetMergedMangaById(get()) }
|
addFactory { GetMergedMangaById(get()) }
|
||||||
addFactory { GetMergedReferencesById(get()) }
|
addFactory { GetMergedReferencesById(get()) }
|
||||||
addFactory { GetMergedChapterByMangaId(get()) }
|
addFactory { GetMergedChapterByMangaId(get(), get(), get()) }
|
||||||
addFactory { InsertMergedReference(get()) }
|
addFactory { InsertMergedReference(get()) }
|
||||||
addFactory { UpdateMergedSettings(get()) }
|
addFactory { UpdateMergedSettings(get()) }
|
||||||
addFactory { DeleteByMergeId(get()) }
|
addFactory { DeleteByMergeId(get()) }
|
||||||
|
@ -2,16 +2,39 @@ package eu.kanade.domain.chapter.interactor
|
|||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||||
|
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import exh.merged.sql.models.MergedMangaReference
|
||||||
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
|
||||||
class GetMergedChapterByMangaId(
|
class GetMergedChapterByMangaId(
|
||||||
private val chapterRepository: ChapterRepository,
|
private val chapterRepository: ChapterRepository,
|
||||||
|
private val getMergedReferencesById: GetMergedReferencesById,
|
||||||
|
private val sourceManager: SourceManager,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(mangaId: Long): List<Chapter> {
|
suspend fun await(mangaId: Long, editScanlators: Boolean = false, dedupe: Boolean = true): List<Chapter> {
|
||||||
|
return transformMergedChapters(mangaId, getFromDatabase(mangaId), editScanlators, dedupe)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun subscribe(mangaId: Long, editScanlators: Boolean = false, dedupe: Boolean = true): Flow<List<Chapter>> {
|
||||||
|
return try {
|
||||||
|
chapterRepository.getMergedChapterByMangaIdAsFlow(mangaId)
|
||||||
|
.map {
|
||||||
|
transformMergedChapters(mangaId, it, editScanlators, dedupe)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
flowOf(emptyList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getFromDatabase(mangaId: Long): List<Chapter> {
|
||||||
return try {
|
return try {
|
||||||
chapterRepository.getMergedChapterByMangaId(mangaId)
|
chapterRepository.getMergedChapterByMangaId(mangaId)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -20,12 +43,54 @@ class GetMergedChapterByMangaId(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun subscribe(mangaId: Long): Flow<List<Chapter>> {
|
// TODO more chapter dedupe
|
||||||
return try {
|
suspend fun transformMergedChapters(mangaId: Long, chapterList: List<Chapter>, editScanlators: Boolean, dedupe: Boolean): List<Chapter> {
|
||||||
chapterRepository.getMergedChapterByMangaIdAsFlow(mangaId)
|
val mangaReferences = getMergedReferencesById.await(mangaId)
|
||||||
} catch (e: Exception) {
|
val chapters = if (editScanlators) {
|
||||||
logcat(LogPriority.ERROR, e)
|
val sources = mangaReferences.map { sourceManager.getOrStub(it.mangaSourceId) to it.mangaId }
|
||||||
flowOf(emptyList())
|
chapterList.map { chapter ->
|
||||||
|
val source = sources.firstOrNull { chapter.mangaId == it.second }?.first
|
||||||
|
if (source != null) {
|
||||||
|
chapter.copy(
|
||||||
|
scanlator = if (chapter.scanlator.isNullOrBlank()) {
|
||||||
|
source.name
|
||||||
|
} else {
|
||||||
|
"$source: ${chapter.scanlator}"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
chapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chapterList
|
||||||
|
}
|
||||||
|
return if (dedupe) dedupeChapterList(mangaReferences, chapters) else chapters
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dedupeChapterList(mangaReferences: List<MergedMangaReference>, chapterList: List<Chapter>): List<Chapter> {
|
||||||
|
return when (mangaReferences.firstOrNull { it.mangaSourceId == MERGED_SOURCE_ID }?.chapterSortMode) {
|
||||||
|
MergedMangaReference.CHAPTER_SORT_NO_DEDUPE, MergedMangaReference.CHAPTER_SORT_NONE -> chapterList
|
||||||
|
MergedMangaReference.CHAPTER_SORT_PRIORITY -> chapterList
|
||||||
|
MergedMangaReference.CHAPTER_SORT_MOST_CHAPTERS -> {
|
||||||
|
findSourceWithMostChapters(chapterList)?.let { mangaId ->
|
||||||
|
chapterList.filter { it.mangaId == mangaId }
|
||||||
|
} ?: chapterList
|
||||||
|
}
|
||||||
|
MergedMangaReference.CHAPTER_SORT_HIGHEST_CHAPTER_NUMBER -> {
|
||||||
|
findSourceWithHighestChapterNumber(chapterList)?.let { mangaId ->
|
||||||
|
chapterList.filter { it.mangaId == mangaId }
|
||||||
|
} ?: chapterList
|
||||||
|
}
|
||||||
|
else -> chapterList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findSourceWithMostChapters(chapterList: List<Chapter>): Long? {
|
||||||
|
return chapterList.groupBy { it.mangaId }.maxByOrNull { it.value.size }?.key
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSourceWithHighestChapterNumber(chapterList: List<Chapter>): Long? {
|
||||||
|
return chapterList.maxByOrNull { it.chapterNumber }?.mangaId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ import eu.kanade.domain.download.interactor.DeleteDownload
|
|||||||
import eu.kanade.domain.download.service.DownloadPreferences
|
import eu.kanade.domain.download.service.DownloadPreferences
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
import eu.kanade.domain.manga.repository.MangaRepository
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
@ -19,7 +17,9 @@ class SetReadStatus(
|
|||||||
private val deleteDownload: DeleteDownload,
|
private val deleteDownload: DeleteDownload,
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
private val chapterRepository: ChapterRepository,
|
private val chapterRepository: ChapterRepository,
|
||||||
private val sourceManager: SourceManager,
|
// SY -->
|
||||||
|
private val getMergedChapterByMangaId: GetMergedChapterByMangaId,
|
||||||
|
// SY <--
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val mapper = { chapter: Chapter, read: Boolean ->
|
private val mapper = { chapter: Chapter, read: Boolean ->
|
||||||
@ -75,11 +75,10 @@ class SetReadStatus(
|
|||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
private suspend fun awaitMerged(mangaId: Long, read: Boolean) = withNonCancellableContext f@{
|
private suspend fun awaitMerged(mangaId: Long, read: Boolean) = withNonCancellableContext f@{
|
||||||
val mergedSource = sourceManager.get(MERGED_SOURCE_ID) as MergedSource
|
|
||||||
return@f await(
|
return@f await(
|
||||||
read = read,
|
read = read,
|
||||||
chapters = mergedSource
|
chapters = getMergedChapterByMangaId
|
||||||
.getChapters(mangaId, dedupe = false)
|
.await(mangaId, dedupe = false)
|
||||||
.toTypedArray(),
|
.toTypedArray(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
package eu.kanade.domain.history.interactor
|
package eu.kanade.domain.history.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||||
|
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
import eu.kanade.domain.history.repository.HistoryRepository
|
||||||
import eu.kanade.domain.manga.interactor.GetManga
|
import eu.kanade.domain.manga.interactor.GetManga
|
||||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||||
|
import exh.source.MERGED_SOURCE_ID
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class GetNextUnreadChapters(
|
class GetNextUnreadChapters(
|
||||||
private val getChapter: GetChapter,
|
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
private val getChapterByMangaId: GetChapterByMangaId,
|
||||||
|
// SY -->
|
||||||
|
private val getMergedChapterByMangaId: GetMergedChapterByMangaId,
|
||||||
|
// SY <--
|
||||||
private val getManga: GetManga,
|
private val getManga: GetManga,
|
||||||
private val historyRepository: HistoryRepository,
|
private val historyRepository: HistoryRepository,
|
||||||
) {
|
) {
|
||||||
@ -19,15 +23,23 @@ class GetNextUnreadChapters(
|
|||||||
return await(history.mangaId, history.chapterId).firstOrNull()
|
return await(history.mangaId, history.chapterId).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun await(mangaId: Long, chapterId: Long): List<Chapter> {
|
suspend fun await(mangaId: Long): List<Chapter> {
|
||||||
val chapter = getChapter.await(chapterId) ?: return emptyList()
|
|
||||||
val manga = getManga.await(mangaId) ?: return emptyList()
|
val manga = getManga.await(mangaId) ?: return emptyList()
|
||||||
|
// SY -->
|
||||||
val chapters = getChapterByMangaId.await(mangaId)
|
if (manga.source == MERGED_SOURCE_ID) {
|
||||||
|
return getMergedChapterByMangaId.await(mangaId)
|
||||||
|
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||||
|
.filterNot { it.read }
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
return getChapterByMangaId.await(mangaId)
|
||||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||||
val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
|
|
||||||
return chapters
|
|
||||||
.subList(currChapterIndex, chapters.size)
|
|
||||||
.filterNot { it.read }
|
.filterNot { it.read }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long, fromChapterId: Long): List<Chapter> {
|
||||||
|
val unreadChapters = await(mangaId)
|
||||||
|
val currChapterIndex = unreadChapters.indexOfFirst { it.id == fromChapterId }
|
||||||
|
return unreadChapters.subList(max(0, currChapterIndex), unreadChapters.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import androidx.compose.animation.shrinkVertically
|
|||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
@ -179,6 +178,7 @@ private fun RowScope.Button(
|
|||||||
toConfirm: Boolean,
|
toConfirm: Boolean,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
content: (@Composable () -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f)
|
val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f)
|
||||||
Column(
|
Column(
|
||||||
@ -210,6 +210,7 @@ private fun RowScope.Button(
|
|||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
content?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,15 +273,14 @@ fun LibraryBottomActionMenu(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (onDownloadClicked != null) {
|
if (onDownloadClicked != null) {
|
||||||
Box {
|
var downloadExpanded by remember { mutableStateOf(false) }
|
||||||
var downloadExpanded by remember { mutableStateOf(false) }
|
Button(
|
||||||
this@Row.Button(
|
title = stringResource(R.string.action_download),
|
||||||
title = stringResource(R.string.action_download),
|
icon = Icons.Outlined.Download,
|
||||||
icon = Icons.Outlined.Download,
|
toConfirm = confirm[3],
|
||||||
toConfirm = confirm[3],
|
onLongClick = { onLongClickItem(3) },
|
||||||
onLongClick = { onLongClickItem(3) },
|
onClick = { downloadExpanded = !downloadExpanded },
|
||||||
onClick = { downloadExpanded = !downloadExpanded },
|
) {
|
||||||
)
|
|
||||||
val onDismissRequest = { downloadExpanded = false }
|
val onDismissRequest = { downloadExpanded = false }
|
||||||
DownloadDropdownMenu(
|
DownloadDropdownMenu(
|
||||||
expanded = downloadExpanded,
|
expanded = downloadExpanded,
|
||||||
|
@ -72,7 +72,7 @@ fun LibraryComfortableGrid(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
// SY -->
|
// SY -->
|
||||||
buttonBottom = if (showStartReadingButton) {
|
buttonBottom = if (showStartReadingButton && libraryItem.unreadCount > 0) {
|
||||||
{ StartReadingButton(onOpenReader = { onOpenReader(libraryItem.libraryManga) }) }
|
{ StartReadingButton(onOpenReader = { onOpenReader(libraryItem.libraryManga) }) }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@ -73,12 +73,12 @@ fun LibraryCompactGrid(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
// SY -->
|
// SY -->
|
||||||
buttonTop = if (showStartReadingButton && showTitle) {
|
buttonTop = if (showStartReadingButton && showTitle && libraryItem.unreadCount > 0) {
|
||||||
{ StartReadingButton(onOpenReader = { onOpenReader(libraryItem.libraryManga) }) }
|
{ StartReadingButton(onOpenReader = { onOpenReader(libraryItem.libraryManga) }) }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
},
|
},
|
||||||
buttonBottom = if (showStartReadingButton && !showTitle) {
|
buttonBottom = if (showStartReadingButton && !showTitle && libraryItem.unreadCount > 0) {
|
||||||
{ StartReadingButton(onOpenReader = { onOpenReader(libraryItem.libraryManga) }) }
|
{ StartReadingButton(onOpenReader = { onOpenReader(libraryItem.libraryManga) }) }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.source.online.all
|
package eu.kanade.tachiyomi.source.online.all
|
||||||
|
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
import eu.kanade.domain.category.interactor.GetCategories
|
||||||
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
|
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
import eu.kanade.domain.chapter.model.toDbChapter
|
import eu.kanade.domain.chapter.model.toDbChapter
|
||||||
@ -27,19 +26,16 @@ import exh.source.MERGED_SOURCE_ID
|
|||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlinx.coroutines.supervisorScope
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class MergedSource : HttpSource() {
|
class MergedSource : HttpSource() {
|
||||||
private val getManga: GetManga by injectLazy()
|
private val getManga: GetManga by injectLazy()
|
||||||
private val getMergedReferencesById: GetMergedReferencesById by injectLazy()
|
private val getMergedReferencesById: GetMergedReferencesById by injectLazy()
|
||||||
private val getMergedChaptersByMangaId: GetMergedChapterByMangaId by injectLazy()
|
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
|
||||||
private val networkToLocalManga: NetworkToLocalManga by injectLazy()
|
private val networkToLocalManga: NetworkToLocalManga by injectLazy()
|
||||||
private val updateManga: UpdateManga by injectLazy()
|
private val updateManga: UpdateManga by injectLazy()
|
||||||
private val getCategories: GetCategories by injectLazy()
|
private val getCategories: GetCategories by injectLazy()
|
||||||
@ -76,18 +72,12 @@ class MergedSource : HttpSource() {
|
|||||||
|
|
||||||
override suspend fun getMangaDetails(manga: SManga): SManga {
|
override suspend fun getMangaDetails(manga: SManga): SManga {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val mergedManga = getManga.await(manga.url, id) ?: throw Exception("merged manga not in db")
|
val mergedManga = requireNotNull(getManga.await(manga.url, id)) { "merged manga not in db" }
|
||||||
val mangaReferences = getMergedReferencesById.await(mergedManga.id)
|
val mangaReferences = getMergedReferencesById.await(mergedManga.id)
|
||||||
.apply {
|
.apply {
|
||||||
if (isEmpty()) {
|
require(isNotEmpty()) { "Manga references are empty, info unavailable, merge is likely corrupted" }
|
||||||
throw IllegalArgumentException(
|
require(!(size == 1 && first().mangaSourceId == MERGED_SOURCE_ID)) {
|
||||||
"Manga references are empty, info unavailable, merge is likely corrupted",
|
"Manga references contain only the merged reference, merge is likely corrupted"
|
||||||
)
|
|
||||||
}
|
|
||||||
if (size == 1 && first().mangaSourceId == MERGED_SOURCE_ID) {
|
|
||||||
throw IllegalArgumentException(
|
|
||||||
"Manga references contain only the merged reference, merge is likely corrupted",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,77 +92,14 @@ class MergedSource : HttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO more chapter dedupe
|
suspend fun fetchChaptersForMergedManga(manga: Manga, downloadChapters: Boolean = true, editScanlators: Boolean = false, dedupe: Boolean = true) {
|
||||||
suspend fun transformMergedChapters(mangaId: Long, chapterList: List<Chapter>, editScanlators: Boolean, dedupe: Boolean): List<Chapter> {
|
fetchChaptersAndSync(manga, downloadChapters)
|
||||||
val mangaReferences = getMergedReferencesById.await(mangaId)
|
|
||||||
val chapters = if (editScanlators) {
|
|
||||||
val sources = mangaReferences.map { sourceManager.getOrStub(it.mangaSourceId) to it.mangaId }
|
|
||||||
chapterList.map { chapter ->
|
|
||||||
val source = sources.firstOrNull { chapter.mangaId == it.second }?.first
|
|
||||||
if (source != null) {
|
|
||||||
chapter.copy(
|
|
||||||
scanlator = if (chapter.scanlator.isNullOrBlank()) {
|
|
||||||
source.name
|
|
||||||
} else {
|
|
||||||
"$source: ${chapter.scanlator}"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
chapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
chapterList
|
|
||||||
}
|
|
||||||
return if (dedupe) dedupeChapterList(mangaReferences, chapters) else chapters
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getChaptersAsBlocking(mangaId: Long, editScanlators: Boolean = false, dedupe: Boolean = true): List<Chapter> {
|
|
||||||
return runBlocking { getChapters(mangaId, editScanlators, dedupe) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getChapters(mangaId: Long, editScanlators: Boolean = false, dedupe: Boolean = true): List<Chapter> {
|
|
||||||
return transformMergedChapters(mangaId, getMergedChaptersByMangaId.await(mangaId), editScanlators, dedupe)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun dedupeChapterList(mangaReferences: List<MergedMangaReference>, chapterList: List<Chapter>): List<Chapter> {
|
|
||||||
return when (mangaReferences.firstOrNull { it.mangaSourceId == MERGED_SOURCE_ID }?.chapterSortMode) {
|
|
||||||
MergedMangaReference.CHAPTER_SORT_NO_DEDUPE, MergedMangaReference.CHAPTER_SORT_NONE -> chapterList
|
|
||||||
MergedMangaReference.CHAPTER_SORT_PRIORITY -> chapterList
|
|
||||||
MergedMangaReference.CHAPTER_SORT_MOST_CHAPTERS -> {
|
|
||||||
findSourceWithMostChapters(chapterList)?.let { mangaId ->
|
|
||||||
chapterList.filter { it.mangaId == mangaId }
|
|
||||||
} ?: chapterList
|
|
||||||
}
|
|
||||||
MergedMangaReference.CHAPTER_SORT_HIGHEST_CHAPTER_NUMBER -> {
|
|
||||||
findSourceWithHighestChapterNumber(chapterList)?.let { mangaId ->
|
|
||||||
chapterList.filter { it.mangaId == mangaId }
|
|
||||||
} ?: chapterList
|
|
||||||
}
|
|
||||||
else -> chapterList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findSourceWithMostChapters(chapterList: List<Chapter>): Long? {
|
|
||||||
return chapterList.groupBy { it.mangaId }.maxByOrNull { it.value.size }?.key
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findSourceWithHighestChapterNumber(chapterList: List<Chapter>): Long? {
|
|
||||||
return chapterList.maxByOrNull { it.chapterNumber }?.mangaId
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun fetchChaptersForMergedManga(manga: Manga, downloadChapters: Boolean = true, editScanlators: Boolean = false, dedupe: Boolean = true): List<Chapter> {
|
|
||||||
return withIOContext {
|
|
||||||
fetchChaptersAndSync(manga, downloadChapters)
|
|
||||||
getChapters(manga.id, editScanlators, dedupe)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchChaptersAndSync(manga: Manga, downloadChapters: Boolean = true): List<Chapter> {
|
suspend fun fetchChaptersAndSync(manga: Manga, downloadChapters: Boolean = true): List<Chapter> {
|
||||||
val syncChaptersWithSource = Injekt.get<SyncChaptersWithSource>()
|
|
||||||
val mangaReferences = getMergedReferencesById.await(manga.id)
|
val mangaReferences = getMergedReferencesById.await(manga.id)
|
||||||
if (mangaReferences.isEmpty()) {
|
require(mangaReferences.isNotEmpty()) {
|
||||||
throw IllegalArgumentException("Manga references are empty, chapters unavailable, merge is likely corrupted")
|
"Manga references are empty, chapters unavailable, merge is likely corrupted"
|
||||||
}
|
}
|
||||||
|
|
||||||
val ifDownloadNewChapters = downloadChapters && manga.shouldDownloadNewChapters(getCategories.await(manga.id).map { it.id }, downloadPreferences)
|
val ifDownloadNewChapters = downloadChapters && manga.shouldDownloadNewChapters(getCategories.await(manga.id).map { it.id }, downloadPreferences)
|
||||||
|
@ -35,6 +35,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
|
|||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
|
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.favorites.FavoritesIntroDialog
|
import exh.favorites.FavoritesIntroDialog
|
||||||
import exh.favorites.FavoritesSyncStatus
|
import exh.favorites.FavoritesSyncStatus
|
||||||
@ -509,10 +510,14 @@ class LibraryController(
|
|||||||
|
|
||||||
private fun startReading(manga: Manga) {
|
private fun startReading(manga: Manga) {
|
||||||
val activity = activity ?: return
|
val activity = activity ?: return
|
||||||
val chapter = presenter.getFirstUnread(manga) ?: return
|
viewScope.launchIO {
|
||||||
val intent = ReaderActivity.newIntent(activity, manga.id, chapter.id)
|
val chapter = presenter.getFirstUnread(manga) ?: return@launchIO
|
||||||
presenter.clearSelection()
|
val intent = ReaderActivity.newIntent(activity, manga.id, chapter.id)
|
||||||
startActivity(intent)
|
presenter.clearSelection()
|
||||||
|
withUIContext {
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// <-- EXH
|
// <-- EXH
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,11 @@ import eu.kanade.domain.base.BasePreferences
|
|||||||
import eu.kanade.domain.category.interactor.GetCategories
|
import eu.kanade.domain.category.interactor.GetCategories
|
||||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||||
import eu.kanade.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
|
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
|
||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
import eu.kanade.domain.chapter.model.toDbChapter
|
import eu.kanade.domain.chapter.model.toDbChapter
|
||||||
|
import eu.kanade.domain.history.interactor.GetNextUnreadChapters
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
import eu.kanade.domain.library.model.LibraryDisplayMode
|
||||||
import eu.kanade.domain.library.model.LibraryGroup
|
import eu.kanade.domain.library.model.LibraryGroup
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
import eu.kanade.domain.library.model.LibraryManga
|
||||||
@ -54,7 +54,6 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
|
|||||||
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
|
||||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.track.TrackStatus
|
import eu.kanade.tachiyomi.data.track.TrackStatus
|
||||||
@ -65,7 +64,6 @@ import eu.kanade.tachiyomi.source.model.SManga
|
|||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
@ -82,7 +80,6 @@ import exh.search.SearchEngine
|
|||||||
import exh.search.Text
|
import exh.search.Text
|
||||||
import exh.source.EH_SOURCE_ID
|
import exh.source.EH_SOURCE_ID
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import exh.source.isEhBasedManga
|
|
||||||
import exh.source.isMetadataSource
|
import exh.source.isMetadataSource
|
||||||
import exh.util.cancellable
|
import exh.util.cancellable
|
||||||
import exh.util.isLewd
|
import exh.util.isLewd
|
||||||
@ -123,7 +120,7 @@ class LibraryPresenter(
|
|||||||
private val getLibraryManga: GetLibraryManga = Injekt.get(),
|
private val getLibraryManga: GetLibraryManga = Injekt.get(),
|
||||||
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
|
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
|
||||||
private val getCategories: GetCategories = Injekt.get(),
|
private val getCategories: GetCategories = Injekt.get(),
|
||||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
private val getNextUnreadChapters: GetNextUnreadChapters = Injekt.get(),
|
||||||
private val setReadStatus: SetReadStatus = Injekt.get(),
|
private val setReadStatus: SetReadStatus = Injekt.get(),
|
||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||||
@ -602,25 +599,6 @@ class LibraryPresenter(
|
|||||||
return mangaCategories.flatten().distinct().subtract(common)
|
return mangaCategories.flatten().distinct().subtract(common)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shouldDownloadChapter(manga: Manga, chapter: Chapter): Boolean {
|
|
||||||
val activeDownload = downloadManager.queue.find { chapter.id == it.chapter.id }
|
|
||||||
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
|
|
||||||
val state = when {
|
|
||||||
activeDownload != null -> activeDownload.status
|
|
||||||
downloaded -> Download.State.DOWNLOADED
|
|
||||||
else -> Download.State.NOT_DOWNLOADED
|
|
||||||
}
|
|
||||||
return state == Download.State.NOT_DOWNLOADED
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getNotDownloadedUnreadChapters(manga: Manga): List<Chapter> {
|
|
||||||
return getChapterByMangaId.await(manga.id)
|
|
||||||
.filter { chapter ->
|
|
||||||
!chapter.read && shouldDownloadChapter(manga, chapter)
|
|
||||||
}
|
|
||||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues the amount specified of unread chapters from the list of mangas given.
|
* Queues the amount specified of unread chapters from the list of mangas given.
|
||||||
*
|
*
|
||||||
@ -630,31 +608,48 @@ class LibraryPresenter(
|
|||||||
fun downloadUnreadChapters(mangas: List<Manga>, amount: Int?) {
|
fun downloadUnreadChapters(mangas: List<Manga>, amount: Int?) {
|
||||||
presenterScope.launchNonCancellable {
|
presenterScope.launchNonCancellable {
|
||||||
mangas.forEach { manga ->
|
mangas.forEach { manga ->
|
||||||
|
// SY -->
|
||||||
if (manga.source == MERGED_SOURCE_ID) {
|
if (manga.source == MERGED_SOURCE_ID) {
|
||||||
val mergedSource = sourceManager.get(MERGED_SOURCE_ID) as MergedSource
|
|
||||||
val mergedMangas = getMergedMangaById.await(manga.id)
|
val mergedMangas = getMergedMangaById.await(manga.id)
|
||||||
mergedSource
|
.associateBy { it.id }
|
||||||
.getChapters(manga.id)
|
getNextUnreadChapters.await(manga.id)
|
||||||
.filter { !it.read }
|
|
||||||
.let { if (amount != null) it.take(amount) else it }
|
.let { if (amount != null) it.take(amount) else it }
|
||||||
.groupBy { it.mangaId }
|
.groupBy { it.mangaId }
|
||||||
.forEach ab@{ (mangaId, chapters) ->
|
.forEach ab@{ (mangaId, chapters) ->
|
||||||
val mergedManga = mergedMangas.firstOrNull { it.id == mangaId } ?: return@ab
|
val mergedManga = mergedMangas[mangaId] ?: return@ab
|
||||||
downloadManager.downloadChapters(mergedManga, chapters.map(Chapter::toDbChapter))
|
val downloadChapters = chapters.filterNot { chapter ->
|
||||||
}
|
downloadManager.queue.any { chapter.id == it.chapter.id } ||
|
||||||
} else {
|
downloadManager.isChapterDownloaded(
|
||||||
/* SY --> */
|
chapter.name,
|
||||||
val chapters = if (manga.isEhBasedManga()) {
|
chapter.scanlator,
|
||||||
getChapterByMangaId.await(manga.id).minByOrNull { it.sourceOrder }
|
mergedManga.ogTitle,
|
||||||
?.takeUnless { it.read }
|
mergedManga.source,
|
||||||
.let(::listOfNotNull)
|
)
|
||||||
} else {
|
}
|
||||||
/* SY <-- */ getNotDownloadedUnreadChapters(manga)
|
|
||||||
.let { if (amount != null) it.take(amount) else it }
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadManager.downloadChapters(manga, chapters.map { it.toDbChapter() })
|
downloadManager.downloadChapters(mergedManga, downloadChapters.map(Chapter::toDbChapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
return@forEach
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY <--
|
||||||
|
val chapters = getNextUnreadChapters.await(manga.id)
|
||||||
|
.filterNot { chapter ->
|
||||||
|
downloadManager.queue.any { chapter.id == it.chapter.id } ||
|
||||||
|
downloadManager.isChapterDownloaded(
|
||||||
|
chapter.name,
|
||||||
|
chapter.scanlator,
|
||||||
|
// SY -->
|
||||||
|
manga.ogTitle,
|
||||||
|
// SY <--
|
||||||
|
manga.source,
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.let { if (amount != null) it.take(amount) else it }
|
||||||
|
|
||||||
|
downloadManager.downloadChapters(manga, chapters.map { it.toDbChapter() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1082,18 +1077,8 @@ class LibraryPresenter(
|
|||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
/** Returns first unread chapter of a manga */
|
/** Returns first unread chapter of a manga */
|
||||||
fun getFirstUnread(manga: Manga): Chapter? {
|
suspend fun getFirstUnread(manga: Manga): Chapter? {
|
||||||
val chapters = if (manga.source == MERGED_SOURCE_ID) {
|
return getNextUnreadChapters.await(manga.id).firstOrNull()
|
||||||
(sourceManager.get(MERGED_SOURCE_ID) as MergedSource).getChaptersAsBlocking(manga.id)
|
|
||||||
} else {
|
|
||||||
runBlocking { getChapterByMangaId.await(manga.id) }
|
|
||||||
}
|
|
||||||
return if (manga.isEhBasedManga()) {
|
|
||||||
val chapter = chapters.sortedBy { it.sourceOrder }.getOrNull(0)
|
|
||||||
if (chapter?.read == false) chapter else null
|
|
||||||
} else {
|
|
||||||
chapters.sortedByDescending { it.sourceOrder }.find { !it.read }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGroupedMangaItems(groupType: Int, libraryManga: List<LibraryItem>): Pair<LibraryMap, List<Category>> {
|
private fun getGroupedMangaItems(groupType: Int, libraryManga: List<LibraryItem>): Pair<LibraryMap, List<Category>> {
|
||||||
|
@ -266,7 +266,7 @@ class MangaPresenter(
|
|||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
// SY -->
|
// SY -->
|
||||||
.combine(
|
.combine(
|
||||||
getMergedChapterByMangaId.subscribe(mangaId)
|
getMergedChapterByMangaId.subscribe(mangaId, true)
|
||||||
.distinctUntilChanged(),
|
.distinctUntilChanged(),
|
||||||
) { (manga, chapters), mergedChapters ->
|
) { (manga, chapters), mergedChapters ->
|
||||||
if (manga.source == MERGED_SOURCE_ID) {
|
if (manga.source == MERGED_SOURCE_ID) {
|
||||||
@ -351,7 +351,7 @@ class MangaPresenter(
|
|||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
val manga = getMangaAndChapters.awaitManga(mangaId)
|
val manga = getMangaAndChapters.awaitManga(mangaId)
|
||||||
// SY -->
|
// SY -->
|
||||||
val chapters = (if (manga.source == MERGED_SOURCE_ID) getMergedChapterByMangaId.await(mangaId) else getMangaAndChapters.awaitChapters(mangaId))
|
val chapters = (if (manga.source == MERGED_SOURCE_ID) getMergedChapterByMangaId.await(mangaId, true) else getMangaAndChapters.awaitChapters(mangaId))
|
||||||
.toChapterItemsParams(manga, null)
|
.toChapterItemsParams(manga, null)
|
||||||
val mergedData = getMergedReferencesById.await(mangaId).takeIf { it.isNotEmpty() }?.let { references ->
|
val mergedData = getMergedReferencesById.await(mangaId).takeIf { it.isNotEmpty() }?.let { references ->
|
||||||
MergedMangaData(
|
MergedMangaData(
|
||||||
@ -1041,7 +1041,6 @@ class MangaPresenter(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
successState.source.fetchChaptersForMergedManga(successState.manga, manualFetch, true, dedupe)
|
successState.source.fetchChaptersForMergedManga(successState.manga, manualFetch, true, dedupe)
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
@ -8,6 +8,7 @@ import androidx.annotation.ColorInt
|
|||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||||
|
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
|
||||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||||
import eu.kanade.domain.chapter.model.toDbChapter
|
import eu.kanade.domain.chapter.model.toDbChapter
|
||||||
@ -121,6 +122,7 @@ class ReaderPresenter(
|
|||||||
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
|
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
|
||||||
private val getMergedManga: GetMergedManga = Injekt.get(),
|
private val getMergedManga: GetMergedManga = Injekt.get(),
|
||||||
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
|
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
|
||||||
|
private val getMergedChapterByMangaId: GetMergedChapterByMangaId = Injekt.get(),
|
||||||
// SY <--
|
// SY <--
|
||||||
) : BasePresenter<ReaderActivity>() {
|
) : BasePresenter<ReaderActivity>() {
|
||||||
|
|
||||||
@ -183,8 +185,7 @@ class ReaderPresenter(
|
|||||||
// SY <--
|
// SY <--
|
||||||
val chapters = runBlocking {
|
val chapters = runBlocking {
|
||||||
/* SY --> */ if (manga.source == MERGED_SOURCE_ID) {
|
/* SY --> */ if (manga.source == MERGED_SOURCE_ID) {
|
||||||
(sourceManager.get(MERGED_SOURCE_ID) as MergedSource)
|
getMergedChapterByMangaId.await(manga.id!!)
|
||||||
.getChapters(manga.id!!)
|
|
||||||
} else {
|
} else {
|
||||||
/* SY <-- */ getChapterByMangaId.await(manga.id!!)
|
/* SY <-- */ getChapterByMangaId.await(manga.id!!)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user