Enhance logic for covers + add Comic Support
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user