Display total chapters on duplicates list items (#1963)

This commit is contained in:
NarwhalHorns
2025-04-07 18:33:49 +01:00
committed by GitHub
parent c1225a5ef9
commit 12abd9938b
11 changed files with 146 additions and 29 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

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

View File

@ -44,6 +44,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.manga.model.toMangaUpdate
import tachiyomi.domain.source.interactor.GetRemoteManga
import tachiyomi.domain.source.service.SourceManager
@ -284,7 +285,7 @@ class BrowseSourceScreenModel(
.orEmpty()
}
suspend fun getDuplicateLibraryManga(manga: Manga): List<Manga> {
suspend fun getDuplicateLibraryManga(manga: Manga): List<MangaWithChapterCount> {
return getDuplicateLibraryManga.invoke(manga)
}
@ -335,7 +336,7 @@ 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

@ -80,6 +80,7 @@ import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaWithChapterCount
import tachiyomi.domain.manga.model.applyFilter
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.source.service.SourceManager
@ -1069,7 +1070,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
data object SettingsSheet : 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(
@ -128,4 +129,62 @@ 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,
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,
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

@ -116,11 +116,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 *

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