diff --git a/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt b/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt index bdce7cfeb..da5b03dc2 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt @@ -3,6 +3,7 @@ package eu.kanade.presentation.manga import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -63,10 +64,14 @@ import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.model.SManga import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.service.SourceManager import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.Badge +import tachiyomi.presentation.core.components.BadgeGroup import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.secondaryItemAlpha import uy.kohesive.injekt.Injekt @@ -74,7 +79,7 @@ import uy.kohesive.injekt.api.get @Composable fun DuplicateMangaDialog( - duplicates: List, + duplicates: List, onDismissRequest: () -> Unit, onConfirm: () -> Unit, onOpenManga: (manga: Manga) -> Unit, @@ -118,14 +123,14 @@ fun DuplicateMangaDialog( ) { items( items = duplicates, - key = { it.id }, + key = { it.manga.id }, ) { DuplicateMangaListItem( - manga = it, - getSource = { sourceManager.getOrStub(it.source) }, - onMigrate = { onMigrate(it) }, + duplicate = it, + getSource = { sourceManager.getOrStub(it.manga.source) }, + onMigrate = { onMigrate(it.manga) }, onDismissRequest = onDismissRequest, - onOpenManga = { onOpenManga(it) }, + onOpenManga = { onOpenManga(it.manga) }, ) } } @@ -165,13 +170,14 @@ fun DuplicateMangaDialog( @Composable private fun DuplicateMangaListItem( - manga: Manga, + duplicate: MangaWithChapterCount, getSource: () -> Source, onDismissRequest: () -> Unit, onOpenManga: () -> Unit, onMigrate: () -> Unit, ) { val source = getSource() + val manga = duplicate.manga Column( modifier = Modifier .width(MangaCardWidth) @@ -186,13 +192,30 @@ private fun DuplicateMangaListItem( ) .padding(MaterialTheme.padding.small), ) { - MangaCover.Book( - data = ImageRequest.Builder(LocalContext.current) - .data(manga) - .crossfade(true) - .build(), - modifier = Modifier.fillMaxWidth(), - ) + Box { + MangaCover.Book( + data = ImageRequest.Builder(LocalContext.current) + .data(manga) + .crossfade(true) + .build(), + modifier = Modifier.fillMaxWidth(), + ) + BadgeGroup( + modifier = Modifier + .padding(4.dp) + .align(Alignment.TopStart), + ) { + Badge( + color = MaterialTheme.colorScheme.secondary, + textColor = MaterialTheme.colorScheme.onSecondary, + text = pluralStringResource( + MR.plurals.manga_num_chapters, + duplicate.chapterCount.toInt(), + duplicate.chapterCount, + ), + ) + } + } Spacer(modifier = Modifier.height(MaterialTheme.padding.extraSmall)) @@ -292,7 +315,7 @@ private fun MangaDetailRow( } @Composable -private fun getMaximumMangaCardHeight(duplicates: List): Dp { +private fun getMaximumMangaCardHeight(duplicates: List): Dp { val density = LocalDensity.current val typography = MaterialTheme.typography val textMeasurer = rememberTextMeasurer() @@ -320,7 +343,7 @@ private fun getMaximumMangaCardHeight(duplicates: List): Dp { ) { duplicates.fastMaxOfOrNull { calculateMangaCardHeight( - manga = it, + manga = it.manga, density = density, typography = typography, textMeasurer = textMeasurer, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt index 3c3f4d486..be881860d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt @@ -65,6 +65,7 @@ import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.manga.interactor.GetFlatMetadataById import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.manga.model.toMangaUpdate import tachiyomi.domain.source.interactor.DeleteSavedSearchById import tachiyomi.domain.source.interactor.GetRemoteManga @@ -393,7 +394,7 @@ open class BrowseSourceScreenModel( .orEmpty() } - suspend fun getDuplicateLibraryManga(manga: Manga): List { + suspend fun getDuplicateLibraryManga(manga: Manga): List { return getDuplicateLibraryManga.invoke(manga) } @@ -444,7 +445,7 @@ open class BrowseSourceScreenModel( sealed interface Dialog { data object Filter : Dialog data class RemoveManga(val manga: Manga) : Dialog - data class AddDuplicateManga(val manga: Manga, val duplicates: List) : Dialog + data class AddDuplicateManga(val manga: Manga, val duplicates: List) : Dialog data class ChangeMangaCategory( val manga: Manga, val initialSelection: ImmutableList>, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt index b1aeb978c..bdbbbe8b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt @@ -40,6 +40,7 @@ import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -246,7 +247,7 @@ class HistoryScreenModel( sealed interface Dialog { data object DeleteAll : Dialog data class Delete(val history: HistoryWithRelations) : Dialog - data class DuplicateManga(val manga: Manga, val duplicates: List) : Dialog + data class DuplicateManga(val manga: Manga, val duplicates: List) : Dialog data class ChangeCategory( val manga: Manga, val initialSelection: ImmutableList>, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index 832c0ae09..396e73c0a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -125,6 +125,7 @@ import tachiyomi.domain.manga.interactor.UpdateMergedSettings import tachiyomi.domain.manga.model.CustomMangaInfo import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaUpdate +import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.manga.model.MergeMangaSettingsUpdate import tachiyomi.domain.manga.model.MergedMangaReference import tachiyomi.domain.manga.model.applyFilter @@ -1653,7 +1654,7 @@ class MangaScreenModel( val initialSelection: ImmutableList>, ) : Dialog data class DeleteChapters(val chapters: List) : Dialog - data class DuplicateManga(val manga: Manga, val duplicates: List) : Dialog + data class DuplicateManga(val manga: Manga, val duplicates: List) : Dialog /* SY --> data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog diff --git a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt index 6d65dd66f..8f0c7531a 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt @@ -3,6 +3,7 @@ package tachiyomi.data.manga import eu.kanade.tachiyomi.source.model.UpdateStrategy import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.view.LibraryView object MangaMapper { @@ -143,6 +144,71 @@ object MangaMapper { lastRead = lastRead, ) + fun mapMangaWithChapterCount( + id: Long, + source: Long, + url: String, + artist: String?, + author: String?, + description: String?, + genre: List?, + title: String, + status: Long, + thumbnailUrl: String?, + favorite: Boolean, + lastUpdate: Long?, + nextUpdate: Long?, + initialized: Boolean, + viewerFlags: Long, + chapterFlags: Long, + coverLastModified: Long, + dateAdded: Long, + // SY --> + @Suppress("UNUSED_PARAMETER") + filteredScanlators: String?, + // SY <-- + updateStrategy: UpdateStrategy, + calculateInterval: Long, + lastModifiedAt: Long, + favoriteModifiedAt: Long?, + version: Long, + isSyncing: Long, + notes: String, + totalCount: Long, + ): MangaWithChapterCount = MangaWithChapterCount( + manga = mapManga( + id, + source, + url, + artist, + author, + description, + genre, + title, + status, + thumbnailUrl, + favorite, + lastUpdate, + nextUpdate, + initialized, + viewerFlags, + chapterFlags, + coverLastModified, + dateAdded, + // SY --> + null, + // SY <-- + updateStrategy, + calculateInterval, + lastModifiedAt, + favoriteModifiedAt, + version, + isSyncing, + notes, + ), + chapterCount = totalCount, + ) + fun mapLibraryView(libraryView: LibraryView): LibraryManga { return LibraryManga( manga = Manga( diff --git a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt index bc7eb9e91..237f9cff9 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt @@ -11,6 +11,7 @@ import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaUpdate +import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.manga.repository.MangaRepository import java.time.LocalDate import java.time.ZoneId @@ -73,9 +74,9 @@ class MangaRepositoryImpl( return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, MangaMapper::mapManga) } } - override suspend fun getDuplicateLibraryManga(id: Long, title: String): List { + override suspend fun getDuplicateLibraryManga(id: Long, title: String): List { return handler.awaitList { - mangasQueries.getDuplicateLibraryManga(title, id, MangaMapper::mapManga) + mangasQueries.getDuplicateLibraryManga(id, title, MangaMapper::mapMangaWithChapterCount) } } diff --git a/data/src/main/sqldelight/tachiyomi/data/mangas.sq b/data/src/main/sqldelight/tachiyomi/data/mangas.sq index 3ff054d91..87743b15a 100644 --- a/data/src/main/sqldelight/tachiyomi/data/mangas.sq +++ b/data/src/main/sqldelight/tachiyomi/data/mangas.sq @@ -117,11 +117,33 @@ WHERE favorite = 1 AND source = :sourceId; getDuplicateLibraryManga: -SELECT * -FROM mangas -WHERE favorite = 1 -AND lower(title) LIKE '%' || lower(:title) || '%' -AND _id != :id; +WITH +duplicates AS ( + SELECT * + FROM mangas + WHERE favorite = 1 + AND _id != :id + AND lower(title) LIKE '%' || lower(:title) || '%' +), +chapter_counts AS ( + SELECT + M._id AS manga_id, + count(*) AS chapter_count + FROM duplicates M + JOIN chapters C + ON M._id = C.manga_id + LEFT JOIN excluded_scanlators ES + ON C.manga_id = ES.manga_id + AND C.scanlator = ES.scanlator + WHERE ES.scanlator IS NULL + GROUP BY M._id +) +SELECT + M.*, + coalesce(CC.chapter_count, 0) AS chapter_count +FROM duplicates M +LEFT JOIN chapter_counts CC +ON M._id = CC.manga_id; getUpcomingManga: SELECT * diff --git a/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt b/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt index b81d73b6c..575f68947 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt @@ -1,13 +1,14 @@ package tachiyomi.domain.manga.interactor import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.manga.repository.MangaRepository class GetDuplicateLibraryManga( private val mangaRepository: MangaRepository, ) { - suspend operator fun invoke(manga: Manga): List { + suspend operator fun invoke(manga: Manga): List { return mangaRepository.getDuplicateLibraryManga(manga.id, manga.title.lowercase()) } } diff --git a/domain/src/main/java/tachiyomi/domain/manga/model/MangaWithChapterCount.kt b/domain/src/main/java/tachiyomi/domain/manga/model/MangaWithChapterCount.kt new file mode 100644 index 000000000..23e9b7e7c --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/manga/model/MangaWithChapterCount.kt @@ -0,0 +1,6 @@ +package tachiyomi.domain.manga.model + +data class MangaWithChapterCount( + val manga: Manga, + val chapterCount: Long, +) diff --git a/domain/src/main/java/tachiyomi/domain/manga/repository/MangaRepository.kt b/domain/src/main/java/tachiyomi/domain/manga/repository/MangaRepository.kt index 317c9e156..770ca9b90 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/repository/MangaRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/repository/MangaRepository.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.flow.Flow import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaUpdate +import tachiyomi.domain.manga.model.MangaWithChapterCount interface MangaRepository { @@ -25,7 +26,7 @@ interface MangaRepository { fun getFavoritesBySourceId(sourceId: Long): Flow> - suspend fun getDuplicateLibraryManga(id: Long, title: String): List + suspend fun getDuplicateLibraryManga(id: Long, title: String): List suspend fun getUpcomingManga(statuses: Set): Flow>