Display total chapters on duplicates list items (mihonapp/mihon#1963)

(cherry picked from commit 12abd9938b7c235d6a1c02391624703476c1f339)
This commit is contained in:
NarwhalHorns 2025-04-07 18:33:49 +01:00 committed by Cuong-Tran
parent 5da873cac1
commit f685e6d5fc
No known key found for this signature in database
GPG key ID: 733AA7624B9315C2
12 changed files with 144 additions and 25 deletions

View file

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

View file

@ -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<Manga>,
duplicates: List<MangaWithChapterCount>,
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<Manga>): Dp {
private fun getMaximumMangaCardHeight(duplicates: List<MangaWithChapterCount>): Dp {
val density = LocalDensity.current
val typography = MaterialTheme.typography
val textMeasurer = rememberTextMeasurer()
@ -461,7 +481,7 @@ private fun getMaximumMangaCardHeight(duplicates: List<Manga>): Dp {
) {
duplicates.fastMaxOfOrNull {
calculateMangaCardHeight(
manga = it,
manga = it.manga,
density = density,
typography = typography,
textMeasurer = textMeasurer,

View file

@ -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<Int, Manga, List<Manga>>? {
private suspend fun getDuplicateLibraryManga(startIdx: Int = 0): Triple<Int, Manga, List<MangaWithChapterCount>>? {
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<Manga>) : Dialog
data class BulkAllowDuplicate(val manga: Manga, val duplicates: List<Manga>, val currentIdx: Int) : Dialog
data class AddDuplicateManga(val manga: Manga, val duplicates: List<MangaWithChapterCount>) : Dialog
data class BulkAllowDuplicate(val manga: Manga, val duplicates: List<MangaWithChapterCount>, val currentIdx: Int) : Dialog
data class RemoveManga(val manga: Manga) : Dialog
data class ChangeMangasCategory(
val mangas: List<Manga>,

View file

@ -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<Manga> {
suspend fun getDuplicateLibraryManga(manga: Manga): List<MangaWithChapterCount> {
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<Manga>) : Dialog
data class AddDuplicateManga(val manga: Manga, val duplicates: List<MangaWithChapterCount>) : Dialog
data class ChangeMangaCategory(
val manga: Manga,
val initialSelection: ImmutableList<CheckboxState.State<Category>>,

View file

@ -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<Manga>) : Dialog
data class DuplicateManga(val manga: Manga, val duplicates: List<MangaWithChapterCount>) : Dialog
data class ChangeCategory(
val manga: Manga,
val initialSelection: ImmutableList<CheckboxState<Category>>,

View file

@ -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<CheckboxState<Category>>,
) : Dialog
data class DeleteChapters(val chapters: List<Chapter>) : Dialog
data class DuplicateManga(val manga: Manga, val duplicates: List<Manga>) : Dialog
data class DuplicateManga(val manga: Manga, val duplicates: List<MangaWithChapterCount>) : Dialog
data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog
data class SetFetchInterval(val manga: Manga) : Dialog

View file

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

View file

@ -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<Manga> {
override suspend fun getDuplicateLibraryManga(id: Long, title: String): List<MangaWithChapterCount> {
return handler.awaitList {
mangasQueries.getDuplicateLibraryManga(title, id, MangaMapper::mapManga)
mangasQueries.getDuplicateLibraryManga(id, title, MangaMapper::mapMangaWithChapterCount)
}
}

View file

@ -117,11 +117,33 @@ WHERE favorite = 1
AND source = :sourceId;
getDuplicateLibraryManga:
WITH
duplicates AS (
SELECT *
FROM mangas
WHERE favorite = 1
AND _id != :id
AND lower(title) LIKE '%' || lower(:title) || '%'
AND _id != :id;
),
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 *

View file

@ -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<Manga> {
suspend operator fun invoke(manga: Manga): List<MangaWithChapterCount> {
return mangaRepository.getDuplicateLibraryManga(manga.id, manga.title.lowercase())
}
}

View file

@ -0,0 +1,6 @@
package tachiyomi.domain.manga.model
data class MangaWithChapterCount(
val manga: Manga,
val chapterCount: Long,
)

View file

@ -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<List<Manga>>
suspend fun getDuplicateLibraryManga(id: Long, title: String): List<Manga>
suspend fun getDuplicateLibraryManga(id: Long, title: String): List<MangaWithChapterCount>
suspend fun getUpcomingManga(statuses: Set<Long>): Flow<List<Manga>>