Enhance logic for covers + add Comic Support

This commit is contained in:
Mio.
2025-07-11 20:03:21 +02:00
parent 7b103b2476
commit 2648815fc5
5 changed files with 64 additions and 78 deletions

View File

@@ -8,7 +8,7 @@ pref_group_tags_summary_on=Will prefix tags with their type
pref_group_tags_summary_off=List all tags together pref_group_tags_summary_off=List all tags together
pref_last_volume_cover_title=Keep cover updated pref_last_volume_cover_title=Keep cover updated
pref_last_volume_cover_summary_off=Keep current cover pref_last_volume_cover_summary_off=Keep current cover
pref_last_volume_cover_summary_on=Use the last volume cover when updating pref_last_volume_cover_summary_on=Update cover to current Volume/Issue
pref_show_epub_title=Show EPUB (EPUB can't be read in Mihon) pref_show_epub_title=Show EPUB (EPUB can't be read in Mihon)
pref_show_epub_summary_off=Hide EPUB titles in browsing pref_show_epub_summary_off=Hide EPUB titles in browsing
pref_show_epub_summary_on=Show EPUB titles in browsing pref_show_epub_summary_on=Show EPUB titles in browsing

View File

@@ -163,7 +163,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
// Fallback - use original URL if we don't recognize the pattern // Fallback - use original URL if we don't recognize the pattern
else -> manga.url else -> manga.url
}.also { url -> }.also { url ->
Log.d(LOG_TAG, "Generated webview URL: $url (tracking URL: ${manga.url})") // Log.d(LOG_TAG, "Generated webview URL: $url (tracking URL: ${manga.url})")
} }
} }
@@ -1012,45 +1012,69 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
manga.thumbnail_url = if (preferences.LastVolumeCover) { manga.thumbnail_url = if (preferences.LastVolumeCover) {
try { try {
try { Log.d(LOG_TAG, "Fetching volumes for series ${result.seriesId}")
Log.d(LOG_TAG, "Fetching volumes for series ${result.seriesId}") val seriesId = result.seriesId ?: 0
val volumesRequest = GET("$apiUrl/Series/volumes?seriesId=${result.seriesId}", headersBuilder().build()) val volumes = client.newCall(
val volumes = client.newCall(volumesRequest).execute().parseAs<List<VolumeDto>>() GET("$apiUrl/Series/volumes?seriesId=$seriesId", headersBuilder().build()),
).execute().parseAs<List<VolumeDto>>()
Log.d(LOG_TAG, "Found ${volumes.size} volumes for series ${result.seriesId}") val libraryType = getLibraryType(seriesId)
val isComicLibrary = libraryType == LibraryTypeEnum.Comic || libraryType == LibraryTypeEnum.ComicVine
// Find all volumes that have SingleFileVolume chapters and valid covers val coverCandidates = mutableListOf<Triple<String, Boolean, Float>>()
val validVolumes = volumes.filter { volume ->
for (volume in volumes) {
if (isComicLibrary && volume.number == -100000) {
// Issue: use chapter covers
for (chapter in volume.chapters) {
if (!chapter.coverImage.isNullOrBlank()) {
val isUnread = chapter.pagesRead < chapter.pages
val sortKey = chapter.number.toFloatOrNull() ?: 0f
coverCandidates.add(
Triple(
"$apiUrl/Image/chapter-cover?chapterId=${chapter.id}&apiKey=$apiKey",
isUnread,
sortKey,
),
)
}
}
} else if (!isComicLibrary) {
// Manga: use volume cover if it has a SingleFileVolume chapter
val hasSingleFile = volume.chapters.any { chapter -> val hasSingleFile = volume.chapters.any { chapter ->
ChapterType.of(chapter, volume) == ChapterType.SingleFileVolume ChapterType.of(chapter, volume) == ChapterType.SingleFileVolume
} }
val hasCover = volume.coverImage.isNotBlank() if (hasSingleFile && !volume.coverImage.isNullOrBlank()) {
val isUnread = volume.pagesRead < volume.pages
hasSingleFile && hasCover val volumeNumber = volume.number.toFloat()
coverCandidates.add(
Triple(
"$apiUrl/Image/volume-cover?volumeId=${volume.id}&apiKey=$apiKey",
isUnread,
volumeNumber,
),
)
}
} }
Log.d(LOG_TAG, "Found ${validVolumes.size} valid volumes with covers")
// Get the last volume with a valid cover
val lastValidVolume = validVolumes.maxByOrNull { it.number }
Log.d(LOG_TAG, "Selected last volume: ${lastValidVolume?.number ?: "NONE"} (id=${lastValidVolume?.id ?: "NONE"})")
if (lastValidVolume != null && lastValidVolume.id > 0) {
"$apiUrl/Image/volume-cover?volumeId=${lastValidVolume.id}&apiKey=$apiKey"
} else {
"$apiUrl/image/series-cover?seriesId=${result.seriesId}&apiKey=$apiKey"
}
} catch (e: Exception) {
Log.e(LOG_TAG, "Error fetching volumes for last cover", e)
"$apiUrl/image/series-cover?seriesId=${result.seriesId}&apiKey=$apiKey"
} }
Log.d(LOG_TAG, "Found ${coverCandidates.size} cover candidates")
// Prefer first unread (lowest number), else most recent (highest number)
val targetCover = coverCandidates
.filter { it.second }
.minByOrNull { it.third }
?: coverCandidates.maxByOrNull { it.third }
targetCover?.first ?: "$apiUrl/image/series-cover?seriesId=$seriesId&apiKey=$apiKey"
} catch (e: Exception) { } catch (e: Exception) {
Log.e(LOG_TAG, "Error fetching volumes for last cover", e) Log.e(LOG_TAG, "Error fetching volumes for cover selection", e)
"$apiUrl/image/series-cover?seriesId=${result.seriesId}&apiKey=$apiKey" "$apiUrl/image/series-cover?seriesId=${result.seriesId ?: 0}&apiKey=$apiKey"
} }
} else { } else {
"$apiUrl/image/series-cover?seriesId=${result.seriesId}&apiKey=$apiKey" "$apiUrl/image/series-cover?seriesId=${result.seriesId ?: 0}&apiKey=$apiKey"
} }
manga.status = when (result.publicationStatus) { manga.status = when (result.publicationStatus) {
4 -> SManga.PUBLISHING_FINISHED 4 -> SManga.PUBLISHING_FINISHED
2 -> SManga.COMPLETED 2 -> SManga.COMPLETED

View File

@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.extension.all.kavita package eu.kanade.tachiyomi.extension.all.kavita
import android.util.Log
import eu.kanade.tachiyomi.extension.all.kavita.dto.ChapterDto import eu.kanade.tachiyomi.extension.all.kavita.dto.ChapterDto
import eu.kanade.tachiyomi.extension.all.kavita.dto.ChapterType import eu.kanade.tachiyomi.extension.all.kavita.dto.ChapterType
import eu.kanade.tachiyomi.extension.all.kavita.dto.LibraryTypeEnum import eu.kanade.tachiyomi.extension.all.kavita.dto.LibraryTypeEnum
@@ -46,10 +45,10 @@ class KavitaHelper {
fun getIdFromUrl(url: String): Int { fun getIdFromUrl(url: String): Int {
return try { return try {
val id = url.substringAfterLast("/").toInt() val id = url.substringAfterLast("/").toInt()
Log.d("KavitaHelper", "Extracted ID $id from URL: $url") // Log.d("KavitaHelper", "Extracted ID $id from URL: $url")
id id
} catch (e: Exception) { } catch (e: Exception) {
Log.e("KavitaHelper", "Failed to extract ID from URL: $url", e) // Log.e("KavitaHelper", "Failed to extract ID from URL: $url", e)
-1 -1
} }
} }
@@ -65,44 +64,12 @@ class KavitaHelper {
fun createSeriesDto(obj: SeriesDto, baseUrl: String, apiUrl: String, apiKey: String): SManga = fun createSeriesDto(obj: SeriesDto, baseUrl: String, apiUrl: String, apiKey: String): SManga =
SManga.create().apply { SManga.create().apply {
// url = "$baseUrl/library/${obj.libraryId}/series/${obj.id}" // url = "$baseUrl/library/${obj.libraryId}/series/${obj.id}"
url = "$baseUrl/Series/${obj.id}" url = "$baseUrl/Series/${obj.id}"
title = obj.name title = obj.name
thumbnail_url = "$baseUrl/image/series-cover?seriesId=${obj.id}&apiKey=$apiKey" thumbnail_url = "$baseUrl/image/series-cover?seriesId=${obj.id}&apiKey=$apiKey"
// Set status based on read progress
status = when {
obj.pagesRead >= obj.pages -> SManga.COMPLETED
obj.pagesRead > 0 -> SManga.ONGOING
else -> SManga.UNKNOWN
}
} }
// class CompareChapters {
// companion object : Comparator<SChapter> {
// override fun compare(a: SChapter, b: SChapter): Int {
// if (a.chapter_number < 1.0 && b.chapter_number < 1.0) {
// // Both are volumes, multiply by 100 and do normal sort
// return if ((a.chapter_number * 100) < (b.chapter_number * 100)) {
// 1
// } else {
// -1
// }
// } else {
// if (a.chapter_number < 1.0 && b.chapter_number >= 1.0) {
// // A is volume, b is not. A should sort first
// return 1
// } else if (a.chapter_number >= 1.0 && b.chapter_number < 1.0) {
// return -1
// }
// }
// if (a.chapter_number < b.chapter_number) return 1
// if (a.chapter_number > b.chapter_number) return -1
// return 0
// }
// }
// }
fun chapterFromVolume(chapter: ChapterDto, volume: VolumeDto, singleFileVolumeNumber: Int? = null, libraryType: LibraryTypeEnum? = null): SChapter = fun chapterFromVolume(chapter: ChapterDto, volume: VolumeDto, singleFileVolumeNumber: Int? = null, libraryType: LibraryTypeEnum? = null): SChapter =
SChapter.create().apply { SChapter.create().apply {
val type = ChapterType.of(chapter, volume, libraryType) val type = ChapterType.of(chapter, volume, libraryType)
@@ -153,16 +120,6 @@ class KavitaHelper {
} }
} }
// chapter_number = try {
// // if (type == ChapterType.SingleFileVolume) {
// // volume.number.toFloat() / 10000
// // } else {
// // chapter.number.toFloatOrNull() ?: 0f
// // }
// // } catch (e: NumberFormatException) {
// // 0f
// // }
chapter_number = when { chapter_number = when {
// If this is a SingleFileVolume and we have an explicit number (from single-file processing) // If this is a SingleFileVolume and we have an explicit number (from single-file processing)
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null -> type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
@@ -183,7 +140,11 @@ class KavitaHelper {
} }
} }
// url = chapter.id.toString() url = when {
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
"volume_${volume.id}"
else -> chapter.id.toString()
}
if (chapter.fileCount > 1) { if (chapter.fileCount > 1) {
// salt/offset to recognize chapters with new merged part-chapters as new and hence unread // salt/offset to recognize chapters with new merged part-chapters as new and hence unread

View File

@@ -80,7 +80,7 @@ fun PreferenceScreen.addEditTextPreference(
val result = text.isBlank() || validate?.invoke(text) ?: true val result = text.isBlank() || validate?.invoke(text) ?: true
if (restartRequired && result) { if (restartRequired && result) {
Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() Toast.makeText(context, "Restart Mihon to apply new setting.", Toast.LENGTH_LONG).show()
} }
result result

View File

@@ -239,6 +239,7 @@ data class ChapterDto(
val titleName: String, val titleName: String,
val pagesRead: Int, val pagesRead: Int,
val coverImageLocked: Boolean, val coverImageLocked: Boolean,
val coverImage: String,
val volumeId: Int, val volumeId: Int,
val created: String, val created: String,
val lastModifiedUtc: String, val lastModifiedUtc: String,