DMZJ: tweak page list parsing and remove special lists (#16427)
* remove special lists * use kotlin class for tags * adjust page list * bump version * tweak preference summary
This commit is contained in:
parent
ce08808666
commit
24147b6556
@ -116,7 +116,9 @@ class ChapterImagesDto(
|
|||||||
@ProtoNumber(3) val name: String,
|
@ProtoNumber(3) val name: String,
|
||||||
@ProtoNumber(4) val order: Int,
|
@ProtoNumber(4) val order: Int,
|
||||||
@ProtoNumber(5) val direction: Int,
|
@ProtoNumber(5) val direction: Int,
|
||||||
|
// initial letter is sometimes different from that in original URLs, see manga ID 56649
|
||||||
@ProtoNumber(6) val lowResImages: List<String>,
|
@ProtoNumber(6) val lowResImages: List<String>,
|
||||||
|
// page count of low-res images
|
||||||
@ProtoNumber(7) val pageCount: Int?,
|
@ProtoNumber(7) val pageCount: Int?,
|
||||||
@ProtoNumber(8) val images: List<String>,
|
@ProtoNumber(8) val images: List<String>,
|
||||||
@ProtoNumber(9) val commentCount: Int,
|
@ProtoNumber(9) val commentCount: Int,
|
||||||
|
@ -6,7 +6,7 @@ ext {
|
|||||||
extName = 'DMZJ'
|
extName = 'DMZJ'
|
||||||
pkgNameSuffix = 'zh.dmzj'
|
pkgNameSuffix = 'zh.dmzj'
|
||||||
extClass = '.Dmzj'
|
extClass = '.Dmzj'
|
||||||
extVersionCode = 38
|
extVersionCode = 39
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -97,11 +97,9 @@ object ApiV3 {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ChapterImagesDto(
|
class ChapterImagesDto(
|
||||||
private val id: Int,
|
|
||||||
private val comic_id: Int,
|
|
||||||
private val page_url: List<String>,
|
private val page_url: List<String>,
|
||||||
) {
|
) {
|
||||||
fun toPageList() = parsePageList(comic_id, id, page_url, emptyList())
|
fun toPageList() = parsePageList(page_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -20,20 +20,17 @@ object ApiV4 {
|
|||||||
|
|
||||||
fun mangaInfoUrl(id: String) = "$v4apiUrl/comic/detail/$id?uid=2665531"
|
fun mangaInfoUrl(id: String) = "$v4apiUrl/comic/detail/$id?uid=2665531"
|
||||||
|
|
||||||
fun parseMangaInfo(response: Response): ParseResult {
|
fun parseMangaInfo(response: Response): MangaDto? {
|
||||||
val result: ResponseDto<MangaDto> = response.decrypt()
|
val result: ResponseDto<MangaDto> = response.decrypt()
|
||||||
return when (val manga = result.data) {
|
return result.data
|
||||||
null -> ParseResult.Error(result.message)
|
|
||||||
else -> ParseResult.Ok(manga)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// path = "mangaId/chapterId"
|
// path = "mangaId/chapterId"
|
||||||
fun chapterImagesUrl(path: String) = "$v4apiUrl/comic/chapter/$path"
|
fun chapterImagesUrl(path: String) = "$v4apiUrl/comic/chapter/$path"
|
||||||
|
|
||||||
fun parseChapterImages(response: Response): ArrayList<Page> {
|
fun parseChapterImages(response: Response, isLowRes: Boolean): ArrayList<Page> {
|
||||||
val result: ResponseDto<ChapterImagesDto> = response.decrypt()
|
val result: ResponseDto<ChapterImagesDto> = response.decrypt()
|
||||||
return result.data!!.toPageList()
|
return result.data!!.toPageList(isLowRes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rankingUrl(page: Int, filters: RankingGroup) =
|
fun rankingUrl(page: Int, filters: RankingGroup) =
|
||||||
@ -128,12 +125,18 @@ object ApiV4 {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ChapterImagesDto(
|
class ChapterImagesDto(
|
||||||
@ProtoNumber(1) private val id: Int,
|
|
||||||
@ProtoNumber(2) private val mangaId: Int,
|
|
||||||
@ProtoNumber(6) private val lowResImages: List<String>,
|
@ProtoNumber(6) private val lowResImages: List<String>,
|
||||||
@ProtoNumber(8) private val images: List<String>,
|
@ProtoNumber(8) private val images: List<String>,
|
||||||
) {
|
) {
|
||||||
fun toPageList() = parsePageList(mangaId, id, images, lowResImages)
|
fun toPageList(isLowRes: Boolean) =
|
||||||
|
// page count can be messy, see manga ID 55847 chapters 107-109
|
||||||
|
if (images.size == lowResImages.size) {
|
||||||
|
parsePageList(images, lowResImages)
|
||||||
|
} else if (isLowRes) {
|
||||||
|
parsePageList(lowResImages, lowResImages)
|
||||||
|
} else {
|
||||||
|
parsePageList(images)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// same as ApiV3.MangaDto
|
// same as ApiV3.MangaDto
|
||||||
@ -167,10 +170,5 @@ object ApiV4 {
|
|||||||
@ProtoNumber(3) val data: T?,
|
@ProtoNumber(3) val data: T?,
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed interface ParseResult {
|
|
||||||
class Ok(val manga: MangaDto) : ParseResult
|
|
||||||
class Error(val message: String?) : ParseResult
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cipher by lazy { RSA.getPrivateKey("MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAK8nNR1lTnIfIes6oRWJNj3mB6OssDGx0uGMpgpbVCpf6+VwnuI2stmhZNoQcM417Iz7WqlPzbUmu9R4dEKmLGEEqOhOdVaeh9Xk2IPPjqIu5TbkLZRxkY3dJM1htbz57d/roesJLkZXqssfG5EJauNc+RcABTfLb4IiFjSMlTsnAgMBAAECgYEAiz/pi2hKOJKlvcTL4jpHJGjn8+lL3wZX+LeAHkXDoTjHa47g0knYYQteCbv+YwMeAGupBWiLy5RyyhXFoGNKbbnvftMYK56hH+iqxjtDLnjSDKWnhcB7089sNKaEM9Ilil6uxWMrMMBH9v2PLdYsqMBHqPutKu/SigeGPeiB7VECQQDizVlNv67go99QAIv2n/ga4e0wLizVuaNBXE88AdOnaZ0LOTeniVEqvPtgUk63zbjl0P/pzQzyjitwe6HoCAIpAkEAxbOtnCm1uKEp5HsNaXEJTwE7WQf7PrLD4+BpGtNKkgja6f6F4ld4QZ2TQ6qvsCizSGJrjOpNdjVGJ7bgYMcczwJBALvJWPLmDi7ToFfGTB0EsNHZVKE66kZ/8Stx+ezueke4S556XplqOflQBjbnj2PigwBN/0afT+QZUOBOjWzoDJkCQClzo+oDQMvGVs9GEajS/32mJ3hiWQZrWvEzgzYRqSf3XVcEe7PaXSd8z3y3lACeeACsShqQoc8wGlaHXIJOHTcCQQCZw5127ZGs8ZDTSrogrH73Kw/HvX55wGAeirKYcv28eauveCG7iyFR0PFB/P/EDZnyb+ifvyEFlucPUI0+Y87F") }
|
private val cipher by lazy { RSA.getPrivateKey("MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAK8nNR1lTnIfIes6oRWJNj3mB6OssDGx0uGMpgpbVCpf6+VwnuI2stmhZNoQcM417Iz7WqlPzbUmu9R4dEKmLGEEqOhOdVaeh9Xk2IPPjqIu5TbkLZRxkY3dJM1htbz57d/roesJLkZXqssfG5EJauNc+RcABTfLb4IiFjSMlTsnAgMBAAECgYEAiz/pi2hKOJKlvcTL4jpHJGjn8+lL3wZX+LeAHkXDoTjHa47g0knYYQteCbv+YwMeAGupBWiLy5RyyhXFoGNKbbnvftMYK56hH+iqxjtDLnjSDKWnhcB7089sNKaEM9Ilil6uxWMrMMBH9v2PLdYsqMBHqPutKu/SigeGPeiB7VECQQDizVlNv67go99QAIv2n/ga4e0wLizVuaNBXE88AdOnaZ0LOTeniVEqvPtgUk63zbjl0P/pzQzyjitwe6HoCAIpAkEAxbOtnCm1uKEp5HsNaXEJTwE7WQf7PrLD4+BpGtNKkgja6f6F4ld4QZ2TQ6qvsCizSGJrjOpNdjVGJ7bgYMcczwJBALvJWPLmDi7ToFfGTB0EsNHZVKE66kZ/8Stx+ezueke4S556XplqOflQBjbnj2PigwBN/0afT+QZUOBOjWzoDJkCQClzo+oDQMvGVs9GEajS/32mJ3hiWQZrWvEzgzYRqSf3XVcEe7PaXSd8z3y3lACeeACsShqQoc8wGlaHXIJOHTcCQQCZw5127ZGs8ZDTSrogrH73Kw/HvX55wGAeirKYcv28eauveCG7iyFR0PFB/P/EDZnyb+ifvyEFlucPUI0+Y87F") }
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ object CommentsInterceptor : Interceptor {
|
|||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
val response = chain.proceed(request)
|
val response = chain.proceed(request)
|
||||||
if (request.tag(Tag::class.java) == null) return response
|
if (request.tag(Tag::class) == null) return response
|
||||||
|
|
||||||
val comments = ApiV3.parseChapterComments(response)
|
val comments = ApiV3.parseChapterComments(response)
|
||||||
.take(MAX_HEIGHT / (UNIT * 2))
|
.take(MAX_HEIGHT / (UNIT * 2))
|
||||||
|
@ -4,11 +4,9 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
const val PREFIX_ID_SEARCH = "id:"
|
const val PREFIX_ID_SEARCH = "id:"
|
||||||
|
|
||||||
@ -42,34 +40,18 @@ fun String.formatChapterName(): String {
|
|||||||
return "第$number$type"
|
return "第$number$type"
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val imageSmallUrl = "https://imgsmall.idmzj.com"
|
|
||||||
|
|
||||||
fun parsePageList(
|
fun parsePageList(
|
||||||
mangaId: Int,
|
|
||||||
chapterId: Int,
|
|
||||||
images: List<String>,
|
images: List<String>,
|
||||||
lowResImages: List<String>,
|
lowResImages: List<String> = List(images.size) { "" },
|
||||||
): ArrayList<Page> {
|
): ArrayList<Page> {
|
||||||
// page count can be messy, see manga ID 55847 chapters 107-109
|
val pageCount = images.size
|
||||||
val pageCount = max(images.size, lowResImages.size)
|
|
||||||
val list = ArrayList<Page>(pageCount + 1) // for comments page
|
val list = ArrayList<Page>(pageCount + 1) // for comments page
|
||||||
for (i in 0 until pageCount) {
|
for (i in 0 until pageCount) {
|
||||||
val imageUrl = images.getOrNull(i)?.fixFilename()?.toHttps()
|
list.add(Page(i, lowResImages[i], images[i]))
|
||||||
val lowResUrl = lowResImages.getOrElse(i) {
|
|
||||||
// this is sometimes different in low-res URLs and might fail, see manga ID 56649
|
|
||||||
val initial = imageUrl!!.decodePath().toHttpUrl().pathSegments[0]
|
|
||||||
"$imageSmallUrl/$initial/$mangaId/$chapterId/$i.jpg"
|
|
||||||
}.toHttps()
|
|
||||||
list.add(Page(i, url = lowResUrl, imageUrl = imageUrl ?: lowResUrl))
|
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.toHttps() = "https:" + substringAfter(':')
|
|
||||||
|
|
||||||
// see https://github.com/tachiyomiorg/tachiyomi-extensions/issues/3457
|
|
||||||
fun String.fixFilename() = if (endsWith(".jp")) this + 'g' else this
|
|
||||||
|
|
||||||
fun String.decodePath(): String = URLDecoder.decode(this, "UTF-8")
|
fun String.decodePath(): String = URLDecoder.decode(this, "UTF-8")
|
||||||
|
|
||||||
const val COMMENTS_FLAG = "COMMENTS"
|
const val COMMENTS_FLAG = "COMMENTS"
|
||||||
|
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.zh.dmzj
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Log
|
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
@ -32,7 +31,6 @@ class Dmzj : ConfigurableSource, HttpSource() {
|
|||||||
|
|
||||||
private val preferences: SharedPreferences =
|
private val preferences: SharedPreferences =
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
.migrate()
|
|
||||||
|
|
||||||
override val client: OkHttpClient = network.client.newBuilder()
|
override val client: OkHttpClient = network.client.newBuilder()
|
||||||
.addInterceptor(ImageUrlInterceptor)
|
.addInterceptor(ImageUrlInterceptor)
|
||||||
@ -57,18 +55,7 @@ class Dmzj : ConfigurableSource, HttpSource() {
|
|||||||
|
|
||||||
private fun fetchMangaInfoV4(id: String): ApiV4.MangaDto? {
|
private fun fetchMangaInfoV4(id: String): ApiV4.MangaDto? {
|
||||||
val response = retryClient.newCall(GET(ApiV4.mangaInfoUrl(id), headers)).execute()
|
val response = retryClient.newCall(GET(ApiV4.mangaInfoUrl(id), headers)).execute()
|
||||||
return when (val result = ApiV4.parseMangaInfo(response)) {
|
return ApiV4.parseMangaInfo(response)
|
||||||
is ApiV4.ParseResult.Ok -> {
|
|
||||||
val manga = result.manga
|
|
||||||
if (manga.isLicensed) preferences.addLicensed(id)
|
|
||||||
manga
|
|
||||||
}
|
|
||||||
is ApiV4.ParseResult.Error -> {
|
|
||||||
Log.e("DMZJ", "no data for manga $id: ${result.message}")
|
|
||||||
preferences.addHidden(id)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET(ApiV3.popularMangaUrl(page), headers)
|
override fun popularMangaRequest(page: Int) = GET(ApiV3.popularMangaUrl(page), headers)
|
||||||
@ -139,9 +126,7 @@ class Dmzj : ConfigurableSource, HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchMangaDetails(id: String): SManga {
|
private fun fetchMangaDetails(id: String): SManga {
|
||||||
if (id !in preferences.hiddenList) {
|
fetchMangaInfoV4(id)?.run { return toSManga() }
|
||||||
fetchMangaInfoV4(id)?.run { return toSManga() }
|
|
||||||
}
|
|
||||||
val response = client.newCall(GET(ApiV3.mangaInfoUrlV1(id), headers)).execute()
|
val response = client.newCall(GET(ApiV3.mangaInfoUrlV1(id), headers)).execute()
|
||||||
return ApiV3.parseMangaDetailsV1(response)
|
return ApiV3.parseMangaDetailsV1(response)
|
||||||
}
|
}
|
||||||
@ -159,16 +144,14 @@ class Dmzj : ConfigurableSource, HttpSource() {
|
|||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request = throw UnsupportedOperationException("Not used.")
|
override fun chapterListRequest(manga: SManga): Request = throw UnsupportedOperationException()
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
return Observable.fromCallable {
|
return Observable.fromCallable {
|
||||||
val id = manga.url.extractMangaId()
|
val id = manga.url.extractMangaId()
|
||||||
if (id !in preferences.licensedList && id !in preferences.hiddenList) {
|
val result = fetchMangaInfoV4(id)
|
||||||
val result = fetchMangaInfoV4(id)
|
if (result != null && !result.isLicensed) {
|
||||||
if (result != null && !result.isLicensed) {
|
return@fromCallable result.parseChapterList()
|
||||||
return@fromCallable result.parseChapterList()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val response = client.newCall(GET(ApiV3.mangaInfoUrlV1(id), headers)).execute()
|
val response = client.newCall(GET(ApiV3.mangaInfoUrlV1(id), headers)).execute()
|
||||||
ApiV3.parseChapterListV1(response)
|
ApiV3.parseChapterListV1(response)
|
||||||
@ -186,7 +169,7 @@ class Dmzj : ConfigurableSource, HttpSource() {
|
|||||||
return Observable.fromCallable {
|
return Observable.fromCallable {
|
||||||
val response = retryClient.newCall(GET(ApiV4.chapterImagesUrl(path), headers)).execute()
|
val response = retryClient.newCall(GET(ApiV4.chapterImagesUrl(path), headers)).execute()
|
||||||
val result = try {
|
val result = try {
|
||||||
ApiV4.parseChapterImages(response)
|
ApiV4.parseChapterImages(response, preferences.imageQuality == LOW_RES)
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
client.newCall(GET(ApiV3.chapterImagesUrlV1(path), headers)).execute()
|
client.newCall(GET(ApiV3.chapterImagesUrlV1(path), headers)).execute()
|
||||||
.let(ApiV3::parseChapterImagesV1)
|
.let(ApiV3::parseChapterImagesV1)
|
||||||
@ -204,31 +187,31 @@ class Dmzj : ConfigurableSource, HttpSource() {
|
|||||||
|
|
||||||
// see https://github.com/tachiyomiorg/tachiyomi-extensions/issues/10475
|
// see https://github.com/tachiyomiorg/tachiyomi-extensions/issues/10475
|
||||||
override fun imageRequest(page: Page): Request {
|
override fun imageRequest(page: Page): Request {
|
||||||
val url = page.url
|
val url = page.url.takeIf { it.isNotEmpty() }
|
||||||
val imageUrl = page.imageUrl!!
|
val imageUrl = page.imageUrl!!
|
||||||
if (url == COMMENTS_FLAG) {
|
if (url == COMMENTS_FLAG) {
|
||||||
return GET(imageUrl, headers).newBuilder()
|
return GET(imageUrl, headers).newBuilder()
|
||||||
.tag(CommentsInterceptor.Tag::class.java, CommentsInterceptor.Tag())
|
.tag(CommentsInterceptor.Tag::class, CommentsInterceptor.Tag())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
val fallbackUrl = when (preferences.imageQuality) {
|
val fallbackUrl = when (preferences.imageQuality) {
|
||||||
AUTO_RES -> url
|
AUTO_RES -> url
|
||||||
ORIGINAL_RES -> null
|
ORIGINAL_RES -> null
|
||||||
LOW_RES -> return GET(url, headers)
|
LOW_RES -> if (url == null) null else return GET(url, headers)
|
||||||
else -> url
|
else -> url
|
||||||
}
|
}
|
||||||
return GET(imageUrl, headers).newBuilder()
|
return GET(imageUrl, headers).newBuilder()
|
||||||
.tag(ImageUrlInterceptor.Tag::class.java, ImageUrlInterceptor.Tag(fallbackUrl))
|
.tag(ImageUrlInterceptor.Tag::class, ImageUrlInterceptor.Tag(fallbackUrl))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unused, we can get image urls directly from the chapter page
|
// Unused, we can get image urls directly from the chapter page
|
||||||
override fun imageUrlParse(response: Response) =
|
override fun imageUrlParse(response: Response) =
|
||||||
throw UnsupportedOperationException("This method should not be called!")
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
override fun getFilterList() = getFilterListInternal(preferences.isMultiGenreFilter)
|
override fun getFilterList() = getFilterListInternal(preferences.isMultiGenreFilter)
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
getPreferencesInternal(screen.context, preferences).forEach(screen::addPreference)
|
getPreferencesInternal(screen.context).forEach(screen::addPreference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ object ImageUrlInterceptor : Interceptor {
|
|||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
val tag = request.tag(Tag::class.java) ?: return chain.proceed(request)
|
val tag = request.tag(Tag::class) ?: return chain.proceed(request)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val response = chain.proceed(request)
|
val response = chain.proceed(request)
|
||||||
|
@ -3,20 +3,21 @@ package eu.kanade.tachiyomi.extension.zh.dmzj
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.MultiSelectListPreference
|
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
|
|
||||||
// Legacy preferences:
|
// Legacy preferences:
|
||||||
// "apiRatelimitPreference" -> 1..10 default "5"
|
// "apiRatelimitPreference" -> 1..10 default "5"
|
||||||
// "imgCDNRatelimitPreference" -> 1..10 default "5"
|
// "imgCDNRatelimitPreference" -> 1..10 default "5"
|
||||||
|
// "licensedList" -> StringSet of manga ID
|
||||||
|
// "hiddenList" -> StringSet of manga ID
|
||||||
|
|
||||||
fun getPreferencesInternal(context: Context, preferences: SharedPreferences) = arrayOf(
|
fun getPreferencesInternal(context: Context) = arrayOf(
|
||||||
|
|
||||||
ListPreference(context).apply {
|
ListPreference(context).apply {
|
||||||
key = IMAGE_QUALITY_PREF
|
key = IMAGE_QUALITY_PREF
|
||||||
title = "图片质量"
|
title = "图片质量"
|
||||||
summary = "%s\n如果选择“只用原图”可能会有部分图片无法加载。"
|
summary = "%s\n修改后,已加载的章节需要清除章节缓存才能生效。"
|
||||||
entries = arrayOf("优先原图", "只用原图", "只用低清")
|
entries = arrayOf("优先原图", "只用原图 (加载出错概率更高)", "优先低清")
|
||||||
entryValues = arrayOf(AUTO_RES, ORIGINAL_RES, LOW_RES)
|
entryValues = arrayOf(AUTO_RES, ORIGINAL_RES, LOW_RES)
|
||||||
setDefaultValue(AUTO_RES)
|
setDefaultValue(AUTO_RES)
|
||||||
},
|
},
|
||||||
@ -34,18 +35,6 @@ fun getPreferencesInternal(context: Context, preferences: SharedPreferences) = a
|
|||||||
summary = "可以更精细地筛选出同时符合多个题材的作品。"
|
summary = "可以更精细地筛选出同时符合多个题材的作品。"
|
||||||
setDefaultValue(false)
|
setDefaultValue(false)
|
||||||
},
|
},
|
||||||
|
|
||||||
MultiSelectListPreference(context).setupIdList(
|
|
||||||
LICENSED_LIST_PREF,
|
|
||||||
"特殊漫画 ID 列表 (1)",
|
|
||||||
preferences.licensedList.toTypedArray(),
|
|
||||||
),
|
|
||||||
|
|
||||||
MultiSelectListPreference(context).setupIdList(
|
|
||||||
HIDDEN_LIST_PREF,
|
|
||||||
"特殊漫画 ID 列表 (2)",
|
|
||||||
preferences.hiddenList.toTypedArray(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val SharedPreferences.imageQuality get() = getString(IMAGE_QUALITY_PREF, AUTO_RES)!!
|
val SharedPreferences.imageQuality get() = getString(IMAGE_QUALITY_PREF, AUTO_RES)!!
|
||||||
@ -54,50 +43,6 @@ val SharedPreferences.showChapterComments get() = getBoolean(CHAPTER_COMMENTS_PR
|
|||||||
|
|
||||||
val SharedPreferences.isMultiGenreFilter get() = getBoolean(MULTI_GENRE_FILTER_PREF, false)
|
val SharedPreferences.isMultiGenreFilter get() = getBoolean(MULTI_GENRE_FILTER_PREF, false)
|
||||||
|
|
||||||
val SharedPreferences.licensedList: Set<String> get() = getStringSet(LICENSED_LIST_PREF, emptySet())!!
|
|
||||||
val SharedPreferences.hiddenList: Set<String> get() = getStringSet(HIDDEN_LIST_PREF, emptySet())!!
|
|
||||||
|
|
||||||
fun SharedPreferences.addLicensed(id: String) = addToSet(LICENSED_LIST_PREF, id, licensedList)
|
|
||||||
fun SharedPreferences.addHidden(id: String) = addToSet(HIDDEN_LIST_PREF, id, hiddenList)
|
|
||||||
|
|
||||||
private fun MultiSelectListPreference.setupIdList(
|
|
||||||
key: String,
|
|
||||||
title: String,
|
|
||||||
values: Array<String>,
|
|
||||||
): MultiSelectListPreference {
|
|
||||||
this.key = key
|
|
||||||
this.title = title
|
|
||||||
summary = "如果漫画网页版可以正常访问,但是应用内章节目录加载异常,可以点开列表删除记录。" +
|
|
||||||
"删除方法是【取消勾选】要删除的 ID 再点击确定,勾选的项目会保留。" +
|
|
||||||
"如果点开为空,就表示没有记录。刷新漫画页并展开简介即可查看 ID。"
|
|
||||||
entries = values
|
|
||||||
entryValues = values
|
|
||||||
setDefaultValue(emptySet<Nothing>())
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun SharedPreferences.addToSet(key: String, id: String, oldSet: Set<String>) {
|
|
||||||
if (id in oldSet) return
|
|
||||||
val newSet = HashSet<String>((oldSet.size + 1) * 2)
|
|
||||||
newSet.addAll(oldSet)
|
|
||||||
newSet.add(id)
|
|
||||||
edit().putStringSet(key, newSet).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun SharedPreferences.migrate(): SharedPreferences {
|
|
||||||
val currentVersion = 1
|
|
||||||
val versionPref = "version"
|
|
||||||
val oldVersion = getInt(versionPref, 0)
|
|
||||||
if (oldVersion >= currentVersion) return this
|
|
||||||
val editor = edit()
|
|
||||||
if (oldVersion < 1) {
|
|
||||||
editor.remove(LICENSED_LIST_PREF).remove(HIDDEN_LIST_PREF)
|
|
||||||
}
|
|
||||||
editor.putInt(versionPref, currentVersion).apply()
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val IMAGE_QUALITY_PREF = "imageSourcePreference"
|
private const val IMAGE_QUALITY_PREF = "imageSourcePreference"
|
||||||
const val AUTO_RES = "PREFER_ORIG_RES"
|
const val AUTO_RES = "PREFER_ORIG_RES"
|
||||||
const val ORIGINAL_RES = "ORIG_RES_ONLY"
|
const val ORIGINAL_RES = "ORIG_RES_ONLY"
|
||||||
@ -105,6 +50,3 @@ const val LOW_RES = "LOW_RES_ONLY"
|
|||||||
|
|
||||||
private const val CHAPTER_COMMENTS_PREF = "chapterComments"
|
private const val CHAPTER_COMMENTS_PREF = "chapterComments"
|
||||||
private const val MULTI_GENRE_FILTER_PREF = "multiGenreFilter"
|
private const val MULTI_GENRE_FILTER_PREF = "multiGenreFilter"
|
||||||
|
|
||||||
private const val LICENSED_LIST_PREF = "licensedList"
|
|
||||||
private const val HIDDEN_LIST_PREF = "hiddenList"
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user