parent
ef58b57c65
commit
97f21bc28b
2
src/en/flixscans/AndroidManifest.xml
Normal file
2
src/en/flixscans/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="eu.kanade.tachiyomi.extension" />
|
12
src/en/flixscans/build.gradle
Normal file
12
src/en/flixscans/build.gradle
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'Flix Scans'
|
||||||
|
pkgNameSuffix = 'en.flixscans'
|
||||||
|
extClass = '.FlixScans'
|
||||||
|
extVersionCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/en/flixscans/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/en/flixscans/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
src/en/flixscans/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/en/flixscans/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
src/en/flixscans/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/en/flixscans/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
src/en/flixscans/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/en/flixscans/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
BIN
src/en/flixscans/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/en/flixscans/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
src/en/flixscans/res/web_hi_res_512.png
Normal file
BIN
src/en/flixscans/res/web_hi_res_512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
@ -0,0 +1,317 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.flixscans
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class FlixScans : HttpSource() {
|
||||||
|
|
||||||
|
override val name = "Flix Scans"
|
||||||
|
|
||||||
|
override val lang = "en"
|
||||||
|
|
||||||
|
override val baseUrl = "https://flixscans.net"
|
||||||
|
|
||||||
|
private val apiUrl = "https://api.flixscans.net/api/v1"
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
|
.rateLimit(2)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// only returns 15 chapters each request, so using higher rate limit
|
||||||
|
private val chapterClient = network.cloudflareClient.newBuilder()
|
||||||
|
.rateLimitHost(apiUrl.toHttpUrl(), 1, 2)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
|
.add("Referer", baseUrl)
|
||||||
|
|
||||||
|
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||||
|
runCatching { fetchGenre() }
|
||||||
|
|
||||||
|
return super.fetchPopularManga(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
return GET("$apiUrl/webtoon/homepage/home", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val result = response.parseAs<HomeDto>()
|
||||||
|
|
||||||
|
val entries = (result.hot + result.topAll + result.topMonth + result.topWeek)
|
||||||
|
.distinctBy { it.id }
|
||||||
|
.map(BrowseSeries::toSManga)
|
||||||
|
|
||||||
|
return MangasPage(entries, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||||
|
runCatching { fetchGenre() }
|
||||||
|
|
||||||
|
return super.fetchLatestUpdates(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
return GET("$apiUrl/search/advance?page=$page&serie_type=webtoon", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
|
val result = response.parseAs<ApiResponse<BrowseSeries>>()
|
||||||
|
val currentPage = response.request.url.queryParameter("page")
|
||||||
|
?.toIntOrNull() ?: 1
|
||||||
|
|
||||||
|
val entries = result.data.map(BrowseSeries::toSManga)
|
||||||
|
val hasNextPage = result.meta.lastPage > currentPage
|
||||||
|
|
||||||
|
return MangasPage(entries, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var fetchGenreList: List<GenreHolder> = emptyList()
|
||||||
|
private var fetchGenreCallOngoing = false
|
||||||
|
private var fetchGenreFailed = false
|
||||||
|
private var fetchGenreAttempt = 0
|
||||||
|
|
||||||
|
private fun fetchGenre() {
|
||||||
|
if (fetchGenreAttempt < 3 && (fetchGenreList.isEmpty() || fetchGenreFailed) && !fetchGenreCallOngoing) {
|
||||||
|
fetchGenreCallOngoing = true
|
||||||
|
|
||||||
|
// fetch genre asynchronously as it sometimes hangs
|
||||||
|
client.newCall(fetchGenreRequest()).enqueue(fetchGenreCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val fetchGenreCallback = object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: okio.IOException) {
|
||||||
|
fetchGenreAttempt++
|
||||||
|
fetchGenreFailed = true
|
||||||
|
fetchGenreCallOngoing = false
|
||||||
|
|
||||||
|
e.message?.let { Log.e("$name Filters", it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
fetchGenreCallOngoing = false
|
||||||
|
fetchGenreAttempt++
|
||||||
|
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
fetchGenreFailed = true
|
||||||
|
response.close()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val parsed = runCatching {
|
||||||
|
response.use(::fetchGenreParse)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchGenreFailed = parsed.isFailure
|
||||||
|
fetchGenreList = parsed.getOrElse {
|
||||||
|
Log.e("$name Filters", it.stackTraceToString())
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchGenreRequest(): Request {
|
||||||
|
return GET("$apiUrl/search/genres", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchGenreParse(response: Response): List<GenreHolder> {
|
||||||
|
return response.parseAs<List<GenreHolder>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList(): FilterList {
|
||||||
|
val filters: MutableList<Filter<*>> = mutableListOf(
|
||||||
|
Filter.Header("Ignored when using Text Search"),
|
||||||
|
MainGenreFilter(),
|
||||||
|
TypeFilter(),
|
||||||
|
StatusFilter(),
|
||||||
|
)
|
||||||
|
|
||||||
|
filters += if (fetchGenreList.isNotEmpty()) {
|
||||||
|
listOf(
|
||||||
|
GenreFilter("Genre", fetchGenreList),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
listOf(
|
||||||
|
Filter.Separator(),
|
||||||
|
Filter.Header("Press 'reset' to attempt to show Genres"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return FilterList(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
|
runCatching { fetchGenre() }
|
||||||
|
|
||||||
|
return super.fetchSearchManga(page, query, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
if (query.isNotEmpty()) {
|
||||||
|
val requestBody = SearchInput(query.trim())
|
||||||
|
.let(json::encodeToString)
|
||||||
|
.toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
|
val newHeaders = headersBuilder()
|
||||||
|
.add("Content-Length", requestBody.contentLength().toString())
|
||||||
|
.add("Content-Type", requestBody.contentType().toString())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST("$apiUrl/search/serie?page=$page", newHeaders, requestBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
val advSearchUrl = apiUrl.toHttpUrl().newBuilder().apply {
|
||||||
|
addPathSegments("search/advance")
|
||||||
|
addQueryParameter("page", page.toString())
|
||||||
|
addQueryParameter("serie_type", "webtoon")
|
||||||
|
|
||||||
|
filters.forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
is GenreFilter -> {
|
||||||
|
filter.checked.let {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
addQueryParameter("genres", it.joinToString(","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is MainGenreFilter -> {
|
||||||
|
if (filter.state > 0) {
|
||||||
|
addQueryParameter("main_genres", filter.selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TypeFilter -> {
|
||||||
|
if (filter.state > 0) {
|
||||||
|
addQueryParameter("type", filter.selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is StatusFilter -> {
|
||||||
|
if (filter.state > 0) {
|
||||||
|
addQueryParameter("status", filter.selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
return GET(advSearchUrl, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response) = latestUpdatesParse(response)
|
||||||
|
|
||||||
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
|
val id = manga.url.split("-")[1]
|
||||||
|
|
||||||
|
return GET("$apiUrl/webtoon/series/$id", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val result = response.parseAs<SeriesResponse>()
|
||||||
|
|
||||||
|
return result.serie.toSManga()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
|
return chapterClient.newCall(chapterListRequest(manga))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map(::chapterListParse)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
|
val id = manga.url.split("-")[1]
|
||||||
|
|
||||||
|
return paginatedChapterListRequest(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun paginatedChapterListRequest(seriesID: String, page: Int = 1): Request {
|
||||||
|
return GET("$apiUrl/webtoon/chapters/$seriesID-asc?page=$page", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val result = response.parseAs<ApiResponse<Chapter>>()
|
||||||
|
|
||||||
|
val id = response.request.url.toString()
|
||||||
|
.substringAfterLast("/")
|
||||||
|
.substringBefore("-")
|
||||||
|
|
||||||
|
val chapters = result.data.toMutableList()
|
||||||
|
|
||||||
|
var page = 1
|
||||||
|
|
||||||
|
while (page < result.meta.lastPage) {
|
||||||
|
page++
|
||||||
|
|
||||||
|
val newResponse = chapterClient.newCall(paginatedChapterListRequest(id, page)).execute()
|
||||||
|
|
||||||
|
if (!newResponse.isSuccessful) {
|
||||||
|
newResponse.close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val newResult = newResponse.parseAs<ApiResponse<Chapter>>()
|
||||||
|
|
||||||
|
chapters.addAll(newResult.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapters.map(Chapter::toSChapter).reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
val id = chapter.url
|
||||||
|
.substringAfterLast("/")
|
||||||
|
.substringBefore("-")
|
||||||
|
|
||||||
|
return GET("$apiUrl/webtoon/chapters/chapter/$id", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val result = response.parseAs<PageListResponse>()
|
||||||
|
|
||||||
|
return result.chapter.chapterData.webtoon.mapIndexed { i, img ->
|
||||||
|
Page(i, "", cdnUrl + img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not Used")
|
||||||
|
|
||||||
|
private inline fun <reified T> Response.parseAs(): T =
|
||||||
|
use { body.string() }.let(json::decodeFromString)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
|
||||||
|
const val cdnUrl = "https://media.flixscans.net/"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.flixscans
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ApiResponse<T>(
|
||||||
|
val data: List<T>,
|
||||||
|
val meta: PageInfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PageInfo(
|
||||||
|
@SerialName("last_page") val lastPage: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HomeDto(
|
||||||
|
val hot: List<BrowseSeries>,
|
||||||
|
val topWeek: List<BrowseSeries>,
|
||||||
|
val topMonth: List<BrowseSeries>,
|
||||||
|
val topAll: List<BrowseSeries>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BrowseSeries(
|
||||||
|
val id: Int,
|
||||||
|
val title: String,
|
||||||
|
val slug: String,
|
||||||
|
val prefix: Int,
|
||||||
|
val thumbnail: String?,
|
||||||
|
) {
|
||||||
|
fun toSManga() = SManga.create().apply {
|
||||||
|
title = this@BrowseSeries.title
|
||||||
|
url = "/series/$prefix-$id-$slug"
|
||||||
|
thumbnail_url = thumbnail?.let { FlixScans.cdnUrl + it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchInput(
|
||||||
|
val title: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GenreHolder(
|
||||||
|
val name: String,
|
||||||
|
val id: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SeriesResponse(
|
||||||
|
val serie: Series,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Series(
|
||||||
|
val id: Int,
|
||||||
|
val title: String,
|
||||||
|
val slug: String,
|
||||||
|
val prefix: Int,
|
||||||
|
val thumbnail: String?,
|
||||||
|
val story: String?,
|
||||||
|
val serieType: String?,
|
||||||
|
val mainGenres: String?,
|
||||||
|
val otherNames: List<String>? = emptyList(),
|
||||||
|
val status: String?,
|
||||||
|
val type: String?,
|
||||||
|
val authors: List<GenreHolder>? = emptyList(),
|
||||||
|
val artists: List<GenreHolder>? = emptyList(),
|
||||||
|
val genres: List<GenreHolder>? = emptyList(),
|
||||||
|
) {
|
||||||
|
fun toSManga() = SManga.create().apply {
|
||||||
|
title = this@Series.title
|
||||||
|
url = "/series/$prefix-$id-$slug"
|
||||||
|
thumbnail_url = FlixScans.cdnUrl + thumbnail
|
||||||
|
author = authors?.joinToString { it.name.trim() }
|
||||||
|
artist = artists?.joinToString { it.name.trim() }
|
||||||
|
genre = (otherGenres + genres?.map { it.name.trim() }.orEmpty())
|
||||||
|
.distinct().joinToString { it.trim() }
|
||||||
|
description = story
|
||||||
|
if (otherNames?.isNotEmpty() == true) {
|
||||||
|
if (description.isNullOrEmpty()) {
|
||||||
|
description = "Alternative Names:\n"
|
||||||
|
} else {
|
||||||
|
description += "\n\nAlternative Names:\n"
|
||||||
|
}
|
||||||
|
description += otherNames.joinToString("\n") { "• ${it.trim()}" }
|
||||||
|
}
|
||||||
|
status = when (this@Series.status?.trim()) {
|
||||||
|
"ongoing" -> SManga.ONGOING
|
||||||
|
"completed" -> SManga.COMPLETED
|
||||||
|
"onhold" -> SManga.ON_HIATUS
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val otherGenres = listOfNotNull(serieType, mainGenres, type)
|
||||||
|
.map { word ->
|
||||||
|
word.trim().replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) {
|
||||||
|
it.titlecase(Locale.getDefault())
|
||||||
|
} else {
|
||||||
|
it.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Chapter(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val slug: String,
|
||||||
|
val createdAt: String? = null,
|
||||||
|
) {
|
||||||
|
fun toSChapter() = SChapter.create().apply {
|
||||||
|
url = "/read/webtoon/$id-$slug"
|
||||||
|
name = this@Chapter.name
|
||||||
|
date_upload = runCatching { dateFormat.parse(createdAt!!)!!.time }.getOrDefault(0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.ENGLISH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PageListResponse(
|
||||||
|
val chapter: ChapterPages,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChapterPages(
|
||||||
|
val chapterData: ChapterPageData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChapterPageData(
|
||||||
|
val webtoon: List<String>,
|
||||||
|
)
|
@ -0,0 +1,62 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.flixscans
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
|
||||||
|
abstract class SelectFilter(
|
||||||
|
name: String,
|
||||||
|
private val options: List<String>,
|
||||||
|
) : Filter.Select<String>(
|
||||||
|
name,
|
||||||
|
options.toTypedArray(),
|
||||||
|
) {
|
||||||
|
val selected get() = options[state]
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckBoxFilter(
|
||||||
|
name: String,
|
||||||
|
val id: String,
|
||||||
|
) : Filter.CheckBox(name)
|
||||||
|
|
||||||
|
class GenreFilter(
|
||||||
|
name: String,
|
||||||
|
private val genres: List<GenreHolder>,
|
||||||
|
) : Filter.Group<CheckBoxFilter>(
|
||||||
|
name,
|
||||||
|
genres.map { CheckBoxFilter(it.name.trim(), it.id.toString()) },
|
||||||
|
) {
|
||||||
|
val checked get() = state.filter { it.state }.map { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainGenreFilter : SelectFilter(
|
||||||
|
"Main Genre",
|
||||||
|
listOf(
|
||||||
|
"",
|
||||||
|
"fantasy",
|
||||||
|
"romance",
|
||||||
|
"action",
|
||||||
|
"drama",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
class TypeFilter : SelectFilter(
|
||||||
|
"Type",
|
||||||
|
listOf(
|
||||||
|
"",
|
||||||
|
"manhwa",
|
||||||
|
"manhua",
|
||||||
|
"manga",
|
||||||
|
"comic",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
class StatusFilter : SelectFilter(
|
||||||
|
"Status",
|
||||||
|
listOf(
|
||||||
|
"",
|
||||||
|
"ongoing",
|
||||||
|
"completed",
|
||||||
|
"droped",
|
||||||
|
"onhold",
|
||||||
|
"soon",
|
||||||
|
),
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user