feat(comix): Add rating score, NSFW toggle, and filter fix (#11682)
* bump * refactor: fix **type** filter param to match api * add configurable rating score display * rearrange filter * add preference to hide NSFW content
This commit is contained in:
parent
f59060dc92
commit
6eae846dc8
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Comix'
|
extName = 'Comix'
|
||||||
extClass = '.Comix'
|
extClass = '.Comix'
|
||||||
extVersionCode = 1
|
extVersionCode = 2
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,12 +39,18 @@ class Comix : HttpSource(), ConfigurableSource {
|
|||||||
|
|
||||||
/******************************* POPULAR MANGA ************************************/
|
/******************************* POPULAR MANGA ************************************/
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
val url = apiUrl.toHttpUrl().newBuilder()
|
val url = apiUrl.toHttpUrl().newBuilder().apply {
|
||||||
.addPathSegment("manga")
|
addPathSegment("manga")
|
||||||
.addQueryParameter("order[views_30d]", "desc")
|
addQueryParameter("order[views_30d]", "desc")
|
||||||
.addQueryParameter("limit", "50")
|
addQueryParameter("limit", "50")
|
||||||
.addQueryParameter("page", page.toString())
|
addQueryParameter("page", page.toString())
|
||||||
.build()
|
|
||||||
|
if (preferences.hideNsfw()) {
|
||||||
|
NSFW_GENRE_IDS.forEach {
|
||||||
|
addQueryParameter("genres[]", "-$it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
return GET(url, headers)
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
@ -54,12 +60,18 @@ class Comix : HttpSource(), ConfigurableSource {
|
|||||||
|
|
||||||
/******************************* LATEST MANGA ************************************/
|
/******************************* LATEST MANGA ************************************/
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
val url = apiUrl.toHttpUrl().newBuilder()
|
val url = apiUrl.toHttpUrl().newBuilder().apply {
|
||||||
.addPathSegment("manga")
|
addPathSegment("manga")
|
||||||
.addQueryParameter("order[chapter_updated_at]", "desc")
|
addQueryParameter("order[chapter_updated_at]", "desc")
|
||||||
.addQueryParameter("limit", "50")
|
addQueryParameter("limit", "50")
|
||||||
.addQueryParameter("page", page.toString())
|
addQueryParameter("page", page.toString())
|
||||||
.build()
|
|
||||||
|
if (preferences.hideNsfw()) {
|
||||||
|
NSFW_GENRE_IDS.forEach {
|
||||||
|
addQueryParameter("genres[]", "-$it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
return GET(url, headers)
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
@ -71,23 +83,30 @@ class Comix : HttpSource(), ConfigurableSource {
|
|||||||
override fun getFilterList() = ComixFilters().getFilterList()
|
override fun getFilterList() = ComixFilters().getFilterList()
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val url = apiUrl.toHttpUrl().newBuilder()
|
val url = apiUrl.toHttpUrl().newBuilder().apply {
|
||||||
.addPathSegment("manga")
|
addPathSegment("manga")
|
||||||
|
|
||||||
filters.filterIsInstance<ComixFilters.UriFilter>()
|
filters.filterIsInstance<ComixFilters.UriFilter>()
|
||||||
.forEach { it.addToUri(url) }
|
.forEach { it.addToUri(this) }
|
||||||
|
|
||||||
// Make searches accurate
|
// Make searches accurate
|
||||||
if (query.isNotBlank()) {
|
if (query.isNotBlank()) {
|
||||||
url.addQueryParameter("keyword", query)
|
addQueryParameter("keyword", query)
|
||||||
url.removeAllQueryParameters("order[views_30d]")
|
removeAllQueryParameters("order[views_30d]")
|
||||||
url.setQueryParameter("order[relevance]", "desc")
|
setQueryParameter("order[relevance]", "desc")
|
||||||
}
|
}
|
||||||
|
|
||||||
url.addQueryParameter("limit", "50")
|
if (preferences.hideNsfw()) {
|
||||||
.addQueryParameter("page", page.toString())
|
NSFW_GENRE_IDS.forEach {
|
||||||
|
addQueryParameter("genres[]", "-$it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return GET(url.build(), headers)
|
addQueryParameter("limit", "50")
|
||||||
|
addQueryParameter("page", page.toString())
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
@ -121,6 +140,7 @@ class Comix : HttpSource(), ConfigurableSource {
|
|||||||
return mangaResponse.result.toSManga(
|
return mangaResponse.result.toSManga(
|
||||||
preferences.posterQuality(),
|
preferences.posterQuality(),
|
||||||
preferences.alternativeNamesInDescription(),
|
preferences.alternativeNamesInDescription(),
|
||||||
|
preferences.scorePosition(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,6 +273,13 @@ class Comix : HttpSource(), ConfigurableSource {
|
|||||||
setDefaultValue("large")
|
setDefaultValue("large")
|
||||||
}.let(screen::addPreference)
|
}.let(screen::addPreference)
|
||||||
|
|
||||||
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
|
key = NSFW_PREF
|
||||||
|
title = "Hide NSFW content"
|
||||||
|
summary = "Hides NSFW content from popular, latest, and search lists."
|
||||||
|
setDefaultValue(false)
|
||||||
|
}.let(screen::addPreference)
|
||||||
|
|
||||||
SwitchPreferenceCompat(screen.context).apply {
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
key = DEDUPLICATE_CHAPTERS
|
key = DEDUPLICATE_CHAPTERS
|
||||||
title = "Deduplicate Chapters"
|
title = "Deduplicate Chapters"
|
||||||
@ -269,6 +296,15 @@ class Comix : HttpSource(), ConfigurableSource {
|
|||||||
|
|
||||||
setDefaultValue(false)
|
setDefaultValue(false)
|
||||||
}.let(screen::addPreference)
|
}.let(screen::addPreference)
|
||||||
|
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_SCORE_POSITION
|
||||||
|
title = "Score display position"
|
||||||
|
summary = "%s"
|
||||||
|
entries = arrayOf("Top of description", "Bottom of description", "Don't show")
|
||||||
|
entryValues = arrayOf("top", "bottom", "none")
|
||||||
|
setDefaultValue("top")
|
||||||
|
}.let(screen::addPreference)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SharedPreferences.posterQuality() =
|
private fun SharedPreferences.posterQuality() =
|
||||||
@ -280,9 +316,19 @@ class Comix : HttpSource(), ConfigurableSource {
|
|||||||
private fun SharedPreferences.alternativeNamesInDescription() =
|
private fun SharedPreferences.alternativeNamesInDescription() =
|
||||||
getBoolean(ALTERNATIVE_NAMES_IN_DESCRIPTION, false)
|
getBoolean(ALTERNATIVE_NAMES_IN_DESCRIPTION, false)
|
||||||
|
|
||||||
|
private fun SharedPreferences.scorePosition() =
|
||||||
|
getString(PREF_SCORE_POSITION, "top") ?: "top"
|
||||||
|
|
||||||
|
private fun SharedPreferences.hideNsfw() =
|
||||||
|
getBoolean(NSFW_PREF, false)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PREF_POSTER_QUALITY = "pref_poster_quality"
|
private const val PREF_POSTER_QUALITY = "pref_poster_quality"
|
||||||
|
private const val NSFW_PREF = "nsfw_pref"
|
||||||
private const val DEDUPLICATE_CHAPTERS = "pref_deduplicate_chapters"
|
private const val DEDUPLICATE_CHAPTERS = "pref_deduplicate_chapters"
|
||||||
private const val ALTERNATIVE_NAMES_IN_DESCRIPTION = "pref_alt_names_in_description"
|
private const val ALTERNATIVE_NAMES_IN_DESCRIPTION = "pref_alt_names_in_description"
|
||||||
|
private const val PREF_SCORE_POSITION = "pref_score_position"
|
||||||
|
|
||||||
|
private val NSFW_GENRE_IDS = listOf("87264", "8", "87265", "13", "87266", "87268")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Term(
|
data class Term(
|
||||||
@ -33,6 +35,8 @@ class Manga(
|
|||||||
private val genre: List<Term>?,
|
private val genre: List<Term>?,
|
||||||
private val theme: List<Term>?,
|
private val theme: List<Term>?,
|
||||||
private val demographic: List<Term>?,
|
private val demographic: List<Term>?,
|
||||||
|
@SerialName("rated_avg")
|
||||||
|
private val ratedAvg: Double = 0.0,
|
||||||
) {
|
) {
|
||||||
@Serializable
|
@Serializable
|
||||||
class Poster(
|
class Poster(
|
||||||
@ -47,15 +51,44 @@ class Manga(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val fancyScore: String
|
||||||
|
get() {
|
||||||
|
if (ratedAvg == 0.0) return ""
|
||||||
|
|
||||||
|
val score = ratedAvg.toBigDecimal()
|
||||||
|
val stars = score.div(BigDecimal(2))
|
||||||
|
.setScale(0, RoundingMode.HALF_UP).toInt()
|
||||||
|
|
||||||
|
val scoreString = if (score.scale() == 0) {
|
||||||
|
score.toPlainString()
|
||||||
|
} else {
|
||||||
|
score.stripTrailingZeros().toPlainString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildString {
|
||||||
|
append("★".repeat(stars))
|
||||||
|
if (stars < 5) append("☆".repeat(5 - stars))
|
||||||
|
append(" $scoreString")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun toSManga(
|
fun toSManga(
|
||||||
posterQuality: String?,
|
posterQuality: String?,
|
||||||
altTitlesInDesc: Boolean = false,
|
altTitlesInDesc: Boolean = false,
|
||||||
|
scorePosition: String,
|
||||||
) = SManga.create().apply {
|
) = SManga.create().apply {
|
||||||
url = "/$hashId"
|
url = "/$hashId"
|
||||||
title = this@Manga.title
|
title = this@Manga.title
|
||||||
author = this@Manga.author.takeUnless { it.isNullOrEmpty() }?.joinToString { it.title }
|
author = this@Manga.author.takeUnless { it.isNullOrEmpty() }?.joinToString { it.title }
|
||||||
artist = this@Manga.artist.takeUnless { it.isNullOrEmpty() }?.joinToString { it.title }
|
artist = this@Manga.artist.takeUnless { it.isNullOrEmpty() }?.joinToString { it.title }
|
||||||
description = buildString {
|
description = buildString {
|
||||||
|
if (scorePosition == "top") {
|
||||||
|
fancyScore.takeIf { it.isNotEmpty() }?.let {
|
||||||
|
append(it)
|
||||||
|
append("\n\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
synopsis.takeUnless { it.isNullOrEmpty() }
|
synopsis.takeUnless { it.isNullOrEmpty() }
|
||||||
?.let { append(it) }
|
?.let { append(it) }
|
||||||
altTitles.takeIf { altTitlesInDesc && it.isNotEmpty() }
|
altTitles.takeIf { altTitlesInDesc && it.isNotEmpty() }
|
||||||
@ -64,6 +97,13 @@ class Manga(
|
|||||||
append("Alternative Names:\n")
|
append("Alternative Names:\n")
|
||||||
append(altName.joinToString("\n"))
|
append(altName.joinToString("\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (scorePosition == "bottom") {
|
||||||
|
fancyScore.takeIf { it.isNotEmpty() }?.let {
|
||||||
|
if (isNotEmpty()) append("\n\n")
|
||||||
|
append(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
initialized = true
|
initialized = true
|
||||||
status = when (this@Manga.status) {
|
status = when (this@Manga.status) {
|
||||||
|
|||||||
@ -105,10 +105,10 @@ class ComixFilters {
|
|||||||
fun getFilterList() = FilterList(
|
fun getFilterList() = FilterList(
|
||||||
SortFilter(getSortables()),
|
SortFilter(getSortables()),
|
||||||
StatusFilter(),
|
StatusFilter(),
|
||||||
MinChapterFilter(),
|
|
||||||
GenreFilter(getGenres()),
|
GenreFilter(getGenres()),
|
||||||
TypeFilter(),
|
TypeFilter(),
|
||||||
DemographicFilter(getDemographics()),
|
DemographicFilter(getDemographics()),
|
||||||
|
MinChapterFilter(),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
Filter.Header("Release Year"),
|
Filter.Header("Release Year"),
|
||||||
YearFromFilter(),
|
YearFromFilter(),
|
||||||
@ -180,7 +180,7 @@ class ComixFilters {
|
|||||||
|
|
||||||
private class TypeFilter : UriMultiSelectFilter(
|
private class TypeFilter : UriMultiSelectFilter(
|
||||||
"Type",
|
"Type",
|
||||||
"type",
|
"types[]",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
Pair("Manga", "manga"),
|
Pair("Manga", "manga"),
|
||||||
Pair("Manhwa", "manhwa"),
|
Pair("Manhwa", "manhwa"),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user