mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Display total chapters on duplicates list items (#1963)
This commit is contained in:
		@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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>>,
 | 
			
		||||
 
 | 
			
		||||
@@ -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>>,
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 *
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
package tachiyomi.domain.manga.model
 | 
			
		||||
 | 
			
		||||
data class MangaWithChapterCount(
 | 
			
		||||
    val manga: Manga,
 | 
			
		||||
    val chapterCount: Long,
 | 
			
		||||
)
 | 
			
		||||
@@ -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>>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user