Refactor chapters list parsing (#21)
This commit is contained in:
@@ -7,7 +7,6 @@ import android.text.InputType
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.MultiSelectListPreference
|
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import eu.kanade.tachiyomi.AppInfo
|
import eu.kanade.tachiyomi.AppInfo
|
||||||
import eu.kanade.tachiyomi.extension.all.kavita.dto.AuthenticationDto
|
import eu.kanade.tachiyomi.extension.all.kavita.dto.AuthenticationDto
|
||||||
@@ -1265,27 +1264,49 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
val seriesId = response.request.url.queryParameter("seriesId")?.toIntOrNull()
|
val seriesId = response.request.url.queryParameter("seriesId")?.toIntOrNull()
|
||||||
val libraryType = seriesId?.let { getLibraryType(it) }
|
val libraryType = seriesId?.let { getLibraryType(it) }
|
||||||
val volumes = response.parseAs<List<VolumeDto>>()
|
val volumes = response.parseAs<List<VolumeDto>>()
|
||||||
val allChapters = mutableListOf<SChapter>()
|
val chapters = mutableListOf<SChapter>()
|
||||||
|
val volumeItems = mutableListOf<SChapter>()
|
||||||
|
|
||||||
|
// First determine the content type
|
||||||
|
val hasChapters = volumes.any { volume ->
|
||||||
|
volume.chapters.any { chapter ->
|
||||||
|
ChapterType.of(chapter, volume, libraryType) != ChapterType.SingleFileVolume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val hasVolumes = volumes.any { volume ->
|
||||||
|
volume.chapters.any { chapter ->
|
||||||
|
ChapterType.of(chapter, volume, libraryType) == ChapterType.SingleFileVolume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
volumes.forEach { volume ->
|
volumes.forEach { volume ->
|
||||||
volume.chapters.forEach { chapter ->
|
volume.chapters.forEach { chapter ->
|
||||||
val sChapter = helper.chapterFromVolume(chapter, volume, libraryType = libraryType)
|
val sChapter = helper.chapterFromVolume(chapter, volume, libraryType = libraryType)
|
||||||
allChapters.add(sChapter)
|
when (ChapterType.of(chapter, volume, libraryType)) {
|
||||||
|
ChapterType.SingleFileVolume -> volumeItems.add(sChapter)
|
||||||
|
else -> chapters.add(sChapter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle single-file volumes by assigning sequential numbers
|
return when {
|
||||||
if (allChapters.all { it.scanlator == "Volume" }) {
|
// Case 1: Only chapters
|
||||||
return allChapters.mapIndexed { idx, chapter ->
|
hasChapters && !hasVolumes -> chapters.sortedByDescending { it.chapter_number }
|
||||||
chapter.apply {
|
|
||||||
chapter_number = (idx + 1).toFloat()
|
|
||||||
}
|
|
||||||
}.sortedByDescending { it.chapter_number }
|
|
||||||
}
|
|
||||||
|
|
||||||
return allChapters.sortedByDescending { it.chapter_number }
|
// Case 2: Only volumes (treat as chapters)
|
||||||
|
!hasChapters && hasVolumes -> volumeItems.sortedByDescending { it.chapter_number }
|
||||||
|
|
||||||
|
// Case 3: Mixed content
|
||||||
|
else -> {
|
||||||
|
// Chapters first (sorted descending)
|
||||||
|
val sortedChapters = chapters.sortedByDescending { it.chapter_number }
|
||||||
|
// Volumes after (sorted descending with negative numbers)
|
||||||
|
val sortedVolumes = volumeItems.sortedByDescending { it.chapter_number }
|
||||||
|
sortedChapters + sortedVolumes
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "Unhandled exception parsing chapters. Send logs to kavita devs", e)
|
Log.e(LOG_TAG, "Unhandled exception parsing chapters", e)
|
||||||
throw IOException(helper.intl["version_exceptions_chapters_parse"])
|
throw IOException(helper.intl["version_exceptions_chapters_parse"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1472,99 +1493,99 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
/**
|
/**
|
||||||
* Loads the enabled filters if they are not empty so tachiyomi can show them to the user
|
* Loads the enabled filters if they are not empty so tachiyomi can show them to the user
|
||||||
*/
|
*/
|
||||||
override fun getFilterList(): FilterList {
|
// override fun getFilterList(): FilterList {
|
||||||
val toggledFilters = getToggledFilters()
|
// val toggledFilters = getToggledFilters()
|
||||||
|
//
|
||||||
val filters = try {
|
// val filters = try {
|
||||||
val filtersLoaded = mutableListOf<Filter<*>>()
|
// val filtersLoaded = mutableListOf<Filter<*>>()
|
||||||
|
//
|
||||||
filtersLoaded.add(SortFilter(sortableList.map { it.first }.toTypedArray()))
|
// filtersLoaded.add(SortFilter(sortableList.map { it.first }.toTypedArray()))
|
||||||
|
//
|
||||||
if (smartFilters.isNotEmpty()) {
|
// if (smartFilters.isNotEmpty()) {
|
||||||
filtersLoaded.add(SmartFiltersFilter(smartFilters.map { it.name }.toTypedArray()))
|
// filtersLoaded.add(SmartFiltersFilter(smartFilters.map { it.name }.toTypedArray()))
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
filtersLoaded.add(SpecialListFilter())
|
// filtersLoaded.add(SpecialListFilter())
|
||||||
|
//
|
||||||
if (toggledFilters.contains("Read Status")) {
|
// if (toggledFilters.contains("Read Status")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
StatusFilterGroup(
|
// StatusFilterGroup(
|
||||||
listOf(
|
// listOf(
|
||||||
"notRead",
|
// "notRead",
|
||||||
"inProgress",
|
// "inProgress",
|
||||||
"read",
|
// "read",
|
||||||
).map { StatusFilter(it) },
|
// ).map { StatusFilter(it) },
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
if (toggledFilters.contains("ReleaseYearRange")) {
|
// if (toggledFilters.contains("ReleaseYearRange")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
ReleaseYearRangeGroup(
|
// ReleaseYearRangeGroup(
|
||||||
listOf("Min", "Max").map { ReleaseYearRange(it) },
|
// listOf("Min", "Max").map { ReleaseYearRange(it) },
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (genresListMeta.isNotEmpty() and toggledFilters.contains("Genres")) {
|
// if (genresListMeta.isNotEmpty() and toggledFilters.contains("Genres")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
GenreFilterGroup(genresListMeta.map { GenreFilter(it.title) }),
|
// GenreFilterGroup(genresListMeta.map { GenreFilter(it.title) }),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
if (tagsListMeta.isNotEmpty() and toggledFilters.contains("Tags")) {
|
// if (tagsListMeta.isNotEmpty() and toggledFilters.contains("Tags")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
TagFilterGroup(tagsListMeta.map { TagFilter(it.title) }),
|
// TagFilterGroup(tagsListMeta.map { TagFilter(it.title) }),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
if (ageRatingsListMeta.isNotEmpty() and toggledFilters.contains("Age Rating")) {
|
// if (ageRatingsListMeta.isNotEmpty() and toggledFilters.contains("Age Rating")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
AgeRatingFilterGroup(ageRatingsListMeta.map { AgeRatingFilter(it.title) }),
|
// AgeRatingFilterGroup(ageRatingsListMeta.map { AgeRatingFilter(it.title) }),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
if (toggledFilters.contains("Format")) {
|
// if (toggledFilters.contains("Format")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
FormatsFilterGroup(
|
// FormatsFilterGroup(
|
||||||
listOf(
|
// listOf(
|
||||||
"Image",
|
// "Image",
|
||||||
"Archive",
|
// "Archive",
|
||||||
"Pdf",
|
// "Pdf",
|
||||||
"Unknown",
|
// "Unknown",
|
||||||
).map { FormatFilter(it) },
|
// ).map { FormatFilter(it) },
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
if (collectionsListMeta.isNotEmpty() and toggledFilters.contains("Collections")) {
|
// if (collectionsListMeta.isNotEmpty() and toggledFilters.contains("Collections")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
CollectionFilterGroup(collectionsListMeta.map { CollectionFilter(it.title) }),
|
// CollectionFilterGroup(collectionsListMeta.map { CollectionFilter(it.title) }),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
if (languagesListMeta.isNotEmpty() and toggledFilters.contains("Languages")) {
|
// if (languagesListMeta.isNotEmpty() and toggledFilters.contains("Languages")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
LanguageFilterGroup(languagesListMeta.map { LanguageFilter(it.title) }),
|
// LanguageFilterGroup(languagesListMeta.map { LanguageFilter(it.title) }),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
if (libraryListMeta.isNotEmpty() and toggledFilters.contains("Libraries")) {
|
// if (libraryListMeta.isNotEmpty() and toggledFilters.contains("Libraries")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
LibrariesFilterGroup(libraryListMeta.map { LibraryFilter(it.name) }),
|
// LibrariesFilterGroup(libraryListMeta.map { LibraryFilter(it.name) }),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
if (pubStatusListMeta.isNotEmpty() and toggledFilters.contains("Publication Status")) {
|
// if (pubStatusListMeta.isNotEmpty() and toggledFilters.contains("Publication Status")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
PubStatusFilterGroup(pubStatusListMeta.map { PubStatusFilter(it.title) }),
|
// PubStatusFilterGroup(pubStatusListMeta.map { PubStatusFilter(it.title) }),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
if (pubStatusListMeta.isNotEmpty() and toggledFilters.contains("Rating")) {
|
// if (pubStatusListMeta.isNotEmpty() and toggledFilters.contains("Rating")) {
|
||||||
filtersLoaded.add(
|
// filtersLoaded.add(
|
||||||
UserRating(),
|
// UserRating(),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
filtersLoaded
|
// filtersLoaded
|
||||||
} catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "[FILTERS] Error while creating filter list", e)
|
// Log.e(LOG_TAG, "[FILTERS] Error while creating filter list", e)
|
||||||
FilterList(emptyList())
|
// FilterList(emptyList())
|
||||||
}
|
// }
|
||||||
return FilterList(filters)
|
// return FilterList(filters)
|
||||||
}
|
// }
|
||||||
|
|
||||||
class LoginErrorException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) {
|
class LoginErrorException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) {
|
||||||
constructor(cause: Throwable) : this(null, cause)
|
constructor(cause: Throwable) : this(null, cause)
|
||||||
@@ -1608,21 +1629,21 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
helper.intl["pref_opds_summary"],
|
helper.intl["pref_opds_summary"],
|
||||||
)
|
)
|
||||||
|
|
||||||
val enabledFiltersPref = MultiSelectListPreference(screen.context).apply {
|
// val enabledFiltersPref = MultiSelectListPreference(screen.context).apply {
|
||||||
key = KavitaConstants.toggledFiltersPref
|
// key = KavitaConstants.toggledFiltersPref
|
||||||
title = helper.intl["pref_filters_title"]
|
// title = helper.intl["pref_filters_title"]
|
||||||
summary = helper.intl["pref_filters_summary"]
|
// summary = helper.intl["pref_filters_summary"]
|
||||||
entries = KavitaConstants.filterPrefEntries
|
// entries = KavitaConstants.filterPrefEntries
|
||||||
entryValues = KavitaConstants.filterPrefEntriesValue
|
// entryValues = KavitaConstants.filterPrefEntriesValue
|
||||||
setDefaultValue(KavitaConstants.defaultFilterPrefEntries)
|
// setDefaultValue(KavitaConstants.defaultFilterPrefEntries)
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
// setOnPreferenceChangeListener { _, newValue ->
|
||||||
@Suppress("UNCHECKED_CAST")
|
// @Suppress("UNCHECKED_CAST")
|
||||||
val checkValue = newValue as Set<String>
|
// val checkValue = newValue as Set<String>
|
||||||
preferences.edit()
|
// preferences.edit()
|
||||||
.putStringSet(KavitaConstants.toggledFiltersPref, checkValue)
|
// .putStringSet(KavitaConstants.toggledFiltersPref, checkValue)
|
||||||
.commit()
|
// .commit()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
val customSourceNamePref = EditTextPreference(screen.context).apply {
|
val customSourceNamePref = EditTextPreference(screen.context).apply {
|
||||||
key = KavitaConstants.customSourceNamePref
|
key = KavitaConstants.customSourceNamePref
|
||||||
title = helper.intl["pref_customsource_title"]
|
title = helper.intl["pref_customsource_title"]
|
||||||
@@ -1721,7 +1742,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
}
|
}
|
||||||
}.also(screen::addPreference)
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
screen.addPreference(enabledFiltersPref)
|
// screen.addPreference(enabledFiltersPref)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun androidx.preference.PreferenceScreen.editTextPreference(
|
private fun androidx.preference.PreferenceScreen.editTextPreference(
|
||||||
@@ -1783,7 +1804,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
private fun getPrefBaseUrl(): String = preferences.getString("BASEURL", "")!!
|
private fun getPrefBaseUrl(): String = preferences.getString("BASEURL", "")!!
|
||||||
private fun getPrefApiUrl(): String = preferences.getString("APIURL", "")!!
|
private fun getPrefApiUrl(): String = preferences.getString("APIURL", "")!!
|
||||||
private fun getPrefKey(): String = preferences.getString("APIKEY", "")!!
|
private fun getPrefKey(): String = preferences.getString("APIKEY", "")!!
|
||||||
private fun getToggledFilters() = preferences.getStringSet(KavitaConstants.toggledFiltersPref, KavitaConstants.defaultFilterPrefEntries)!!
|
// private fun getToggledFilters() = preferences.getStringSet(KavitaConstants.toggledFiltersPref, KavitaConstants.defaultFilterPrefEntries)!!
|
||||||
|
|
||||||
private val SharedPreferences.score: Boolean
|
private val SharedPreferences.score: Boolean
|
||||||
get() = getBoolean(SCORE_PREF, SCORE_DEFAULT)
|
get() = getBoolean(SCORE_PREF, SCORE_DEFAULT)
|
||||||
|
|||||||
@@ -164,15 +164,23 @@ class KavitaHelper {
|
|||||||
// // }
|
// // }
|
||||||
|
|
||||||
chapter_number = when {
|
chapter_number = when {
|
||||||
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null -> singleFileVolumeNumber.toFloat()
|
// If this is a SingleFileVolume and we have an explicit number (from single-file processing)
|
||||||
type != ChapterType.SingleFileVolume -> chapter.number.toFloatOrNull() ?: 0f
|
|
||||||
else -> 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
url = when {
|
|
||||||
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
|
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
|
||||||
"volume_${volume.id}"
|
singleFileVolumeNumber.toFloat()
|
||||||
else -> chapter.id.toString()
|
|
||||||
|
// If this is a regular chapter (not a volume)
|
||||||
|
type != ChapterType.SingleFileVolume ->
|
||||||
|
chapter.number.toFloatOrNull() ?: 0f
|
||||||
|
|
||||||
|
// For volumes in mixed content, place them below chapter 0
|
||||||
|
else -> {
|
||||||
|
val volumeNum = try {
|
||||||
|
volume.number.toString().toFloatOrNull() ?: 0f
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
-volumeNum - 100000f
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// url = chapter.id.toString()
|
// url = chapter.id.toString()
|
||||||
|
|||||||
Reference in New Issue
Block a user