[Comick] Various updates (#19118)

* Add setting for Score positioning
Localization support for settings

* Simplify score position evaluation
Reformat code

* Include backup domain in Manifest

* Refine logic for picking the first volume cover

* Officially moved their main TLD

* Fallback to default value instead of empty string
This commit is contained in:
BrutuZ 2023-12-07 10:07:43 -03:00 committed by GitHub
parent 76d9997262
commit 67c4e30577
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 118 additions and 24 deletions

View File

@ -14,7 +14,9 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="comick.ink" />
<data android:host="comick.app" /> <data android:host="comick.app" />
<data android:host="comick.fun" />
<data android:pathPattern="/comic/.*/..*" /> <data android:pathPattern="/comic/.*/..*" />
<data android:pathPattern="/comic/..*" /> <data android:pathPattern="/comic/..*" />
</intent-filter> </intent-filter>

View File

@ -0,0 +1,13 @@
ignored_groups_title=Ignored Groups
ignored_groups_summary=Chapters from these groups won't be shown.\nOne group name per line (case-insensitive)
include_tags_title=Include Tags
include_tags_on=More specific, but might contain spoilers!
include_tags_off=Only the broader genres
update_cover_title=Update Covers
update_cover_on=Keep cover updated
update_cover_off=Prefer first cover
score_position_title=Score Position in the Description
score_position_top=Top
score_position_middle=Middle
score_position_bottom=Bottom
score_position_none=Hide Score

View File

@ -0,0 +1,13 @@
ignored_groups_title=Grupos Ignorados
ignored_groups_summary=Capítulos desses grupos não aparecerão.\nUm grupo por linha
include_tags_title=Incluir Tags
include_tags_on=Mais detalhadas, mas podem conter spoilers
include_tags_off=Apenas os gêneros básicos
update_cover_title=Atualizar Capas
update_cover_on=Manter capas atualizadas
update_cover_off=Usar apenas a primeira capa
score_position_title=Posição da Nota na Descrição
score_position_top=Topo
score_position_middle=Meio
score_position_bottom=Final
score_position_none=Sem Nota

View File

@ -6,8 +6,12 @@ ext {
extName = 'Comick' extName = 'Comick'
pkgNameSuffix = 'all.comickfun' pkgNameSuffix = 'all.comickfun'
extClass = '.ComickFunFactory' extClass = '.ComickFunFactory'
extVersionCode = 39 extVersionCode = 40
isNsfw = true isNsfw = true
} }
dependencies {
implementation(project(":lib-i18n"))
}
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -3,8 +3,10 @@ package eu.kanade.tachiyomi.extension.all.comickfun
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.i18n.Intl
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
@ -36,7 +38,7 @@ abstract class ComickFun(
override val name = "Comick" override val name = "Comick"
override val baseUrl = "https://comick.app" override val baseUrl = "https://comick.ink"
private val apiUrl = "https://api.comick.fun" private val apiUrl = "https://api.comick.fun"
@ -51,6 +53,15 @@ abstract class ComickFun(
private lateinit var searchResponse: List<SearchManga> private lateinit var searchResponse: List<SearchManga>
private val intl by lazy {
Intl(
language = lang,
baseLanguage = "en",
availableLanguages = setOf("en", "pt-BR"),
classLoader = this::class.java.classLoader!!,
)
}
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
@ -58,9 +69,8 @@ abstract class ComickFun(
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
EditTextPreference(screen.context).apply { EditTextPreference(screen.context).apply {
key = IGNORED_GROUPS_PREF key = IGNORED_GROUPS_PREF
title = "Ignored Groups" title = intl["ignored_groups_title"]
summary = summary = intl["ignored_groups_summary"]
"Chapters from these groups won't be shown.\nOne group name per line (case-insensitive)"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
preferences.edit() preferences.edit()
@ -71,9 +81,9 @@ abstract class ComickFun(
SwitchPreferenceCompat(screen.context).apply { SwitchPreferenceCompat(screen.context).apply {
key = INCLUDE_MU_TAGS_PREF key = INCLUDE_MU_TAGS_PREF
title = "Include Tags" title = intl["include_tags_title"]
summaryOn = "More specific, but might contain spoilers!" summaryOn = intl["include_tags_on"]
summaryOff = "Only the broader genres" summaryOff = intl["include_tags_off"]
setDefaultValue(INCLUDE_MU_TAGS_DEFAULT) setDefaultValue(INCLUDE_MU_TAGS_DEFAULT)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -85,9 +95,9 @@ abstract class ComickFun(
SwitchPreferenceCompat(screen.context).apply { SwitchPreferenceCompat(screen.context).apply {
key = FIRST_COVER_PREF key = FIRST_COVER_PREF
title = "Update Covers" title = intl["update_cover_title"]
summaryOff = "Prefer first cover" summaryOff = intl["update_cover_off"]
summaryOn = "Keep cover updated" summaryOn = intl["update_cover_on"]
setDefaultValue(FIRST_COVER_DEFAULT) setDefaultValue(FIRST_COVER_DEFAULT)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -96,6 +106,30 @@ abstract class ComickFun(
.commit() .commit()
} }
}.also(screen::addPreference) }.also(screen::addPreference)
ListPreference(screen.context).apply {
key = SCORE_POSITION_PREF
title = intl["score_position_title"]
summary = "%s"
entries = arrayOf(
intl["score_position_top"],
intl["score_position_middle"],
intl["score_position_bottom"],
intl["score_position_none"],
)
entryValues = arrayOf(SCORE_POSITION_DEFAULT, "middle", "bottom", "none")
setDefaultValue(SCORE_POSITION_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit()
.putString(SCORE_POSITION_PREF, entry)
.commit()
}
}.also(screen::addPreference)
} }
private val SharedPreferences.ignoredGroups: Set<String> private val SharedPreferences.ignoredGroups: Set<String>
@ -114,6 +148,9 @@ abstract class ComickFun(
private val SharedPreferences.updateCover: Boolean private val SharedPreferences.updateCover: Boolean
get() = getBoolean(FIRST_COVER_PREF, FIRST_COVER_DEFAULT) get() = getBoolean(FIRST_COVER_PREF, FIRST_COVER_DEFAULT)
private val SharedPreferences.scorePosition: String
get() = getString(SCORE_POSITION_PREF, SCORE_POSITION_DEFAULT) ?: SCORE_POSITION_DEFAULT
init { init {
preferences.newLineIgnoredGroups() preferences.newLineIgnoredGroups()
} }
@ -314,13 +351,15 @@ abstract class ComickFun(
private fun mangaDetailsParse(response: Response, manga: SManga): SManga { private fun mangaDetailsParse(response: Response, manga: SManga): SManga {
val mangaData = response.parseAs<Manga>() val mangaData = response.parseAs<Manga>()
if (!preferences.updateCover && manga.thumbnail_url != mangaData.comic.cover) { if (!preferences.updateCover && manga.thumbnail_url != mangaData.comic.cover) {
if (manga.thumbnail_url.toString().endsWith("#")) { if (manga.thumbnail_url.toString().endsWith("#1")) {
return mangaData.toSManga( return mangaData.toSManga(
includeMuTags = preferences.includeMuTags, includeMuTags = preferences.includeMuTags,
scorePosition = preferences.scorePosition,
covers = listOf( covers = listOf(
MDcovers( MDcovers(
b2key = manga.thumbnail_url?.removeSuffix("#") b2key = manga.thumbnail_url?.substringBeforeLast("#")
?.substringAfterLast("/"), ?.substringAfterLast("/"),
vol = "1",
), ),
), ),
) )
@ -329,7 +368,10 @@ abstract class ComickFun(
"$apiUrl/comic/${mangaData.comic.slug ?: mangaData.comic.hid}/covers?tachiyomi=true" "$apiUrl/comic/${mangaData.comic.slug ?: mangaData.comic.hid}/covers?tachiyomi=true"
val covers = client.newCall(GET(coversUrl)).execute() val covers = client.newCall(GET(coversUrl)).execute()
.parseAs<Covers>().md_covers.reversed() .parseAs<Covers>().md_covers.reversed()
return mangaData.toSManga(includeMuTags = preferences.includeMuTags, covers = covers) return mangaData.toSManga(
includeMuTags = preferences.includeMuTags,
covers = if (covers.any { it.vol == "1" }) covers.filter { it.vol == "1" } else covers,
)
} }
return mangaData.toSManga(includeMuTags = preferences.includeMuTags) return mangaData.toSManga(includeMuTags = preferences.includeMuTags)
} }
@ -440,6 +482,8 @@ abstract class ComickFun(
private const val MIGRATED_IGNORED_GROUPS = "MigratedIgnoredGroups" private const val MIGRATED_IGNORED_GROUPS = "MigratedIgnoredGroups"
private const val FIRST_COVER_PREF = "DefaultCover" private const val FIRST_COVER_PREF = "DefaultCover"
private const val FIRST_COVER_DEFAULT = true private const val FIRST_COVER_DEFAULT = true
private const val SCORE_POSITION_PREF = "ScorePosition"
private const val SCORE_POSITION_DEFAULT = "top"
private const val limit = 20 private const val limit = 20
val dateFormat by lazy { val dateFormat by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH).apply { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH).apply {

View File

@ -13,7 +13,6 @@ data class SearchManga(
val title: String, val title: String,
@SerialName("md_covers") val mdCovers: List<MDcovers> = emptyList(), @SerialName("md_covers") val mdCovers: List<MDcovers> = emptyList(),
@SerialName("cover_url") val cover: String? = null, @SerialName("cover_url") val cover: String? = null,
) { ) {
fun toSManga() = SManga.create().apply { fun toSManga() = SManga.create().apply {
// appending # at end as part of migration from slug to hid // appending # at end as part of migration from slug to hid
@ -31,24 +30,26 @@ data class Manga(
val genres: List<Name> = emptyList(), val genres: List<Name> = emptyList(),
val demographic: String? = null, val demographic: String? = null,
) { ) {
fun toSManga(includeMuTags: Boolean = false, covers: List<MDcovers>? = null) = fun toSManga(
includeMuTags: Boolean = false,
scorePosition: String = "",
covers: List<MDcovers>? = null,
) =
SManga.create().apply { SManga.create().apply {
// appennding # at end as part of migration from slug to hid // appennding # at end as part of migration from slug to hid
url = "/comic/${comic.hid}#" url = "/comic/${comic.hid}#"
title = comic.title title = comic.title
description = buildString { description = buildString {
if (!comic.score.isNullOrEmpty()) { if (scorePosition == "top") append(comic.fancyScore)
val stars = comic.score.toBigDecimal().div(BigDecimal(2))
.setScale(0, RoundingMode.HALF_UP).toInt()
append("".repeat(stars))
if (stars < 5) append("".repeat(5 - stars))
append(" ${comic.score} ")
}
val desc = comic.desc?.beautifyDescription() val desc = comic.desc?.beautifyDescription()
if (!desc.isNullOrEmpty()) { if (!desc.isNullOrEmpty()) {
if (this.isNotEmpty()) append("\n\n") if (this.isNotEmpty()) append("\n\n")
append(desc) append(desc)
} }
if (scorePosition == "middle") {
if (this.isNotEmpty()) append("\n\n")
append(comic.fancyScore)
}
if (comic.altTitles.isNotEmpty()) { if (comic.altTitles.isNotEmpty()) {
if (this.isNotEmpty()) append("\n\n") if (this.isNotEmpty()) append("\n\n")
append("Alternative Titles:\n") append("Alternative Titles:\n")
@ -58,6 +59,10 @@ data class Manga(
}.joinToString("\n"), }.joinToString("\n"),
) )
} }
if (scorePosition == "bottom") {
if (this.isNotEmpty()) append("\n\n")
append(comic.fancyScore)
}
} }
status = comic.status.parseStatus(comic.translationComplete) status = comic.status.parseStatus(comic.translationComplete)
@ -106,6 +111,17 @@ data class Comic(
"cn" -> Name("Manhua") "cn" -> Name("Manhua")
else -> null else -> null
} }
val fancyScore: String = if (score.isNullOrEmpty()) {
""
} else {
val stars = score.toBigDecimal().div(BigDecimal(2))
.setScale(0, RoundingMode.HALF_UP).toInt()
buildString {
append("".repeat(stars))
if (stars < 5) append("".repeat(5 - stars))
append(" $score")
}
}
} }
@Serializable @Serializable
@ -131,6 +147,7 @@ data class Covers(
@Serializable @Serializable
data class MDcovers( data class MDcovers(
val b2key: String?, val b2key: String?,
val vol: String? = null,
) )
@Serializable @Serializable

View File

@ -37,8 +37,9 @@ internal fun Int?.parseStatus(translationComplete: Boolean?): Int {
internal fun parseCover(thumbnailUrl: String?, mdCovers: List<MDcovers>): String? { internal fun parseCover(thumbnailUrl: String?, mdCovers: List<MDcovers>): String? {
val b2key = mdCovers.firstOrNull()?.b2key val b2key = mdCovers.firstOrNull()?.b2key
?: return thumbnailUrl ?: return thumbnailUrl
val vol = mdCovers.firstOrNull()?.vol.orEmpty()
return thumbnailUrl?.replaceAfterLast("/", "$b2key#") return thumbnailUrl?.replaceAfterLast("/", "$b2key#$vol")
} }
internal fun thumbnailIntercept(chain: Interceptor.Chain): Response { internal fun thumbnailIntercept(chain: Interceptor.Chain): Response {