From f685e6d5fc962f5c546444f1445ce4d350faf197 Mon Sep 17 00:00:00 2001 From: NarwhalHorns Date: Mon, 7 Apr 2025 18:33:49 +0100 Subject: [PATCH] Display total chapters on duplicates list items (mihonapp/mihon#1963) (cherry picked from commit 12abd9938b7c235d6a1c02391624703476c1f339) --- CHANGELOG.md | 1 + .../manga/DuplicateMangaDialog.kt | 38 ++++++++--- .../ui/browse/BulkFavoriteScreenModel.kt | 7 ++- .../source/browse/BrowseSourceScreenModel.kt | 5 +- .../ui/history/HistoryScreenModel.kt | 3 +- .../tachiyomi/ui/manga/MangaScreenModel.kt | 3 +- .../java/tachiyomi/data/manga/MangaMapper.kt | 63 +++++++++++++++++++ .../data/manga/MangaRepositoryImpl.kt | 5 +- .../main/sqldelight/tachiyomi/data/mangas.sq | 32 ++++++++-- .../interactor/GetDuplicateLibraryManga.kt | 3 +- .../manga/model/MangaWithChapterCount.kt | 6 ++ .../manga/repository/MangaRepository.kt | 3 +- 12 files changed, 144 insertions(+), 25 deletions(-) create mode 100644 domain/src/main/java/tachiyomi/domain/manga/model/MangaWithChapterCount.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index f48b3f6ce..25ab8b4dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ### Changed - Display all similarly named duplicates in duplicate manga dialogue ([@NarwhalHorns](https://github.com/NarwhalHorns), [@AntsyLich](https://github.com/AntsyLich)) ([#1861](https://github.com/mihonapp/mihon/pull/1861)) + - Display chapter count on items in duplicate manga dialogue ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#1963](https://github.com/mihonapp/mihon/pull/1963)) ### Fixes - Fix Bangumi search results including novels ([@MajorTanya](https://github.com/MajorTanya)) ([#1885](https://github.com/mihonapp/mihon/pull/1885)) 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 6a07b8bc8..ee8dac161 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt @@ -68,11 +68,15 @@ 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.i18n.kmk.KMR +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.collectAsState import tachiyomi.presentation.core.util.secondaryItemAlpha @@ -81,7 +85,7 @@ import uy.kohesive.injekt.api.get @Composable fun DuplicateMangaDialog( - duplicates: List, + duplicates: List, onDismissRequest: () -> Unit, onConfirm: () -> Unit, onOpenManga: (manga: Manga) -> Unit, @@ -142,13 +146,13 @@ fun DuplicateMangaDialog( itemsIndexed( // KMK <-- items = duplicates, - key = { _, it -> it.id }, + key = { _, it -> it.manga.id }, ) { index, it -> DuplicateMangaListItem( - manga = it, - getSource = { sourceManager.getOrStub(it.source) }, - onMigrate = { onMigrate(it) }, - onOpenManga = { onOpenManga(it) }, + duplicate = it, + getSource = { sourceManager.getOrStub(it.manga.source) }, + onMigrate = { onMigrate(it.manga) }, + onOpenManga = { onOpenManga(it.manga) }, ) // KMK --> @@ -275,7 +279,7 @@ fun DuplicateMangaDialog( @Composable private fun DuplicateMangaListItem( - manga: Manga, + duplicate: MangaWithChapterCount, getSource: () -> Source, onOpenManga: () -> Unit, onMigrate: () -> Unit, @@ -287,6 +291,7 @@ private fun DuplicateMangaListItem( // KMK <-- val source = getSource() + val manga = duplicate.manga Column( modifier = Modifier // KMK --> @@ -332,6 +337,21 @@ private fun DuplicateMangaListItem( }, ) } + 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, + ), + ) + } } // KMK <-- @@ -433,7 +453,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() @@ -461,7 +481,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/BulkFavoriteScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BulkFavoriteScreenModel.kt index f6481dc4f..bb9d2f8cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BulkFavoriteScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BulkFavoriteScreenModel.kt @@ -35,6 +35,7 @@ import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.manga.model.toMangaUpdate import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.Injekt @@ -193,7 +194,7 @@ class BulkFavoriteScreenModel( } } - private suspend fun getDuplicateLibraryManga(startIdx: Int = 0): Triple>? { + private suspend fun getDuplicateLibraryManga(startIdx: Int = 0): Triple>? { val mangas = state.value.selection mangas.fastForEachIndexed { index, manga -> if (index < startIdx) return@fastForEachIndexed @@ -406,8 +407,8 @@ class BulkFavoriteScreenModel( sealed interface Dialog { data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog - data class AddDuplicateManga(val manga: Manga, val duplicates: List) : Dialog - data class BulkAllowDuplicate(val manga: Manga, val duplicates: List, val currentIdx: Int) : Dialog + data class AddDuplicateManga(val manga: Manga, val duplicates: List) : Dialog + data class BulkAllowDuplicate(val manga: Manga, val duplicates: List, val currentIdx: Int) : Dialog data class RemoveManga(val manga: Manga) : Dialog data class ChangeMangasCategory( val mangas: List, 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 79e983a24..65d0d6a38 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 @@ -434,7 +435,7 @@ open class BrowseSourceScreenModel( .orEmpty() } - suspend fun getDuplicateLibraryManga(manga: Manga): List { + suspend fun getDuplicateLibraryManga(manga: Manga): List { return getDuplicateLibraryManga.invoke(manga) } @@ -488,7 +489,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 a84771d65..5f9fe9967 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 6b4a7bddc..499414468 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 @@ -141,6 +141,7 @@ import tachiyomi.domain.manga.model.CustomMangaInfo import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaCover 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 @@ -1737,7 +1738,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 data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog data class SetFetchInterval(val manga: 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 1df820c36..bbe09c66a 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 object MangaMapper { fun mapManga( @@ -141,4 +142,66 @@ object MangaMapper { chapterFetchedAt = chapterFetchedAt, 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 --> + 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, + filteredScanlators, + updateStrategy, + calculateInterval, + lastModifiedAt, + favoriteModifiedAt, + version, + isSyncing, + notes, + ), + chapterCount = totalCount, + ) } diff --git a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt index 63c8f35c9..1bd804799 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt @@ -9,6 +9,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 @@ -65,9 +66,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>