From d3e9200a7f82f7a6d463487b39016b739bfe1026 Mon Sep 17 00:00:00 2001 From: paronos Date: Mon, 2 Jan 2017 18:30:10 +0100 Subject: [PATCH] Improve catalog search filters (#615) * Add three state (include/exclude/ignore) search filters (works for now only on MangaFox and MangaHere) * checkbox icons in xml format * fix checkbox icons referencing * fix three states filters in remaining catalogs * use Spinner for filter with more than three states (Mangasee) * use EditText for freetext filters (Mangasee) * remove pngs * Filter class/subclass * add Filter.Header * English catalogs --- .../data/source/online/OnlineSource.kt | 35 ++-- .../data/source/online/ParsedOnlineSource.kt | 2 +- .../data/source/online/YamlOnlineSource.kt | 8 +- .../data/source/online/english/Batoto.kt | 144 +++++++++++------ .../data/source/online/english/Kissmanga.kt | 116 ++++++------- .../data/source/online/english/Mangafox.kt | 112 ++++++++----- .../data/source/online/english/Mangahere.kt | 107 +++++++----- .../data/source/online/english/Mangasee.kt | 130 +++++++++------ .../source/online/english/Readmangatoday.kt | 117 ++++++++------ .../data/source/online/german/WieManga.kt | 11 +- .../data/source/online/russian/Mangachan.kt | 145 +++++++++-------- .../data/source/online/russian/Mintmanga.kt | 97 +++++------ .../data/source/online/russian/Readmanga.kt | 95 +++++------ .../ui/catalogue/CatalogueFragment.kt | 22 +-- .../tachiyomi/ui/catalogue/CataloguePager.kt | 2 +- .../ui/catalogue/CataloguePresenter.kt | 18 +-- .../tachiyomi/ui/catalogue/FilterAdapter.kt | 153 ++++++++++++++++++ .../latest_updates/LatestUpdatesPresenter.kt | 2 +- .../main/res/drawable/ic_check_box_24dp.xml | 9 ++ .../ic_check_box_outline_blank_24dp.xml | 9 ++ .../main/res/drawable/ic_check_box_set.xml | 5 + .../main/res/drawable/ic_check_box_x_24dp.xml | 9 ++ 22 files changed, 853 insertions(+), 495 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt create mode 100644 app/src/main/res/drawable/ic_check_box_24dp.xml create mode 100644 app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml create mode 100644 app/src/main/res/drawable/ic_check_box_set.xml create mode 100644 app/src/main/res/drawable/ic_check_box_x_24dp.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt index 611fcc06d3..5b89368387 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt @@ -53,7 +53,7 @@ abstract class OnlineSource() : Source { /** * Whether the source has support for latest updates. */ - abstract val supportsLatest : Boolean + abstract val supportsLatest: Boolean /** * Headers used for requests. @@ -133,7 +133,7 @@ abstract class OnlineSource() : Source { * the current page and the next page url. * @param query the search query. */ - open fun fetchSearchManga(page: MangasPage, query: String, filters: List): Observable = client + open fun fetchSearchManga(page: MangasPage, query: String, filters: List>): Observable = client .newCall(searchMangaRequest(page, query, filters)) .asObservableSuccess() .map { response -> @@ -148,7 +148,7 @@ abstract class OnlineSource() : Source { * @param page the page object. * @param query the search query. */ - open protected fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + open protected fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } @@ -160,7 +160,7 @@ abstract class OnlineSource() : Source { * * @param query the search query. */ - abstract protected fun searchMangaInitialUrl(query: String, filters: List): String + abstract protected fun searchMangaInitialUrl(query: String, filters: List>): String /** * Parse the response from the site. It should add a list of manga and the absolute url to the @@ -170,7 +170,7 @@ abstract class OnlineSource() : Source { * @param page the page object to be filled. * @param query the search query. */ - abstract protected fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) + abstract protected fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) /** * Returns an observable containing a page with a list of latest manga. @@ -365,10 +365,10 @@ abstract class OnlineSource() : Source { * @param page the page whose source image has to be downloaded. */ final override fun fetchImage(page: Page): Observable = - if (page.imageUrl.isNullOrEmpty()) - fetchImageUrl(page).flatMap { getCachedImage(it) } - else - getCachedImage(page) + if (page.imageUrl.isNullOrEmpty()) + fetchImageUrl(page).flatMap { getCachedImage(it) } + else + getCachedImage(page) /** * Returns an observable with the response of the source image. @@ -460,10 +460,21 @@ abstract class OnlineSource() : Source { * @param manga the manga of the chapter. */ open fun prepareNewChapter(chapter: Chapter, manga: Manga) { - } - data class Filter(val id: String, val name: String) + sealed class Filter(val name: String, var state: T) { + open class Header(name: String) : Filter(name, 0) + abstract class List(name: String, val values: Array, state: Int = 0) : Filter(name, state) + abstract class Text(name: String, state: String = "") : Filter(name, state) + abstract class CheckBox(name: String, state: Boolean = false) : Filter(name, state) + abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter(name, state) { + companion object { + const val STATE_IGNORE = 0 + const val STATE_INCLUDE = 1 + const val STATE_EXCLUDE = 2 + } + } + } - open fun getFilterList(): List = emptyList() + open fun getFilterList(): List> = emptyList() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt index 6b0e211587..519c9e3d11 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt @@ -61,7 +61,7 @@ abstract class ParsedOnlineSource() : OnlineSource() { * @param page the page object to be filled. * @param query the search query. */ - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(searchMangaSelector())) { Manga.create(id).apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt index 4b2f3bcb2a..85efe6412a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt @@ -30,7 +30,7 @@ class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { override val supportsLatest = map.latestupdates != null - override val client = when(map.client) { + override val client = when (map.client) { "cloudflare" -> network.cloudflareClient else -> network.client } @@ -66,7 +66,7 @@ class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { } } - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } @@ -76,9 +76,9 @@ class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { } } - override fun searchMangaInitialUrl(query: String, filters: List) = map.search.url.replace("\$query", query) + override fun searchMangaInitialUrl(query: String, filters: List>) = map.search.url.replace("\$query", query) - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(map.search.manga_css)) { Manga.create(id).apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt index 19c678087c..c880023af5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.source.online.english -import android.net.Uri import android.text.Html import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga @@ -14,6 +13,7 @@ import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.selectText import okhttp3.FormBody +import okhttp3.HttpUrl import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document @@ -107,26 +107,46 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { override fun latestUpdatesNextPageSelector() = "#show_more_row" - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search_ajax?name=${Uri.encode(query)}&order_cond=views&order=desc&p=1${getFilterParams(filters)}" + override fun searchMangaInitialUrl(query: String, filters: List>) = searchMangaUrl(query, filters, 1) - private fun getFilterParams(filters: List): String { + private fun searchMangaUrl(query: String, filterStates: List>, page: Int): String { + val url = HttpUrl.parse("$baseUrl/search_ajax").newBuilder() + if (!query.isEmpty()) url.addQueryParameter("name", query).addQueryParameter("name_cond", "c") var genres = "" - var completed = "" - for (filter in filters) { - if (filter.equals(completedFilter)) completed = "&completed=c" - else genres += ";i" + filter.id + for (filter in if (filterStates.isEmpty()) filters else filterStates) { + when (filter) { + is Status -> if (filter.state != Filter.TriState.STATE_IGNORE) { + url.addQueryParameter("completed", if (filter.state == Filter.TriState.STATE_EXCLUDE) "i" else "c") + } + is Genre -> if (filter.state != Filter.TriState.STATE_IGNORE) { + genres += (if (filter.state == Filter.TriState.STATE_EXCLUDE) ";e" else ";i") + filter.id + } + is TextField -> { + if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) + } + is ListField -> { + val sel = filter.values[filter.state].value + if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) + } + is Flag -> { + val sel = if (filter.state) filter.valTrue else filter.valFalse + if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) + } + } } - return if (genres.isEmpty()) completed else "&genres=$genres&genre_cond=and$completed" + if (!genres.isEmpty()) url.addQueryParameter("genres", genres) + url.addQueryParameter("p", page.toString()) + return url.toString() } - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } return GET(page.url, headers) } - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(searchMangaSelector())) { Manga.create(id).apply { @@ -136,7 +156,7 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { } page.nextPageUrl = document.select(searchMangaNextPageSelector()).first()?.let { - "$baseUrl/search_ajax?name=${Uri.encode(query)}&order_cond=views&order=desc&p=${page.page + 1}${getFilterParams(filters)}" + searchMangaUrl(query, filters, page.page + 1) } } @@ -304,51 +324,69 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { } } - private val completedFilter = Filter("completed", "Completed") + private data class ListValue(val name: String, val value: String) { + override fun toString(): String = name + } + + private class Status() : Filter.TriState("Completed") + private class Genre(name: String, val id: Int) : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class ListField(name: String, val key: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + private class Flag(name: String, val key: String, val valTrue: String, val valFalse: String) : Filter.CheckBox(name) + // [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => { - // const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Filter("${id}", "${el.textContent.trim()}")` + // const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})` // }).join(',\n') // on https://bato.to/search - override fun getFilterList(): List = listOf( - completedFilter, - Filter("40", "4-Koma"), - Filter("1", "Action"), - Filter("2", "Adventure"), - Filter("39", "Award Winning"), - Filter("3", "Comedy"), - Filter("41", "Cooking"), - Filter("9", "Doujinshi"), - Filter("10", "Drama"), - Filter("12", "Ecchi"), - Filter("13", "Fantasy"), - Filter("15", "Gender Bender"), - Filter("17", "Harem"), - Filter("20", "Historical"), - Filter("22", "Horror"), - Filter("34", "Josei"), - Filter("27", "Martial Arts"), - Filter("30", "Mecha"), - Filter("42", "Medical"), - Filter("37", "Music"), - Filter("4", "Mystery"), - Filter("38", "Oneshot"), - Filter("5", "Psychological"), - Filter("6", "Romance"), - Filter("7", "School Life"), - Filter("8", "Sci-fi"), - Filter("32", "Seinen"), - Filter("35", "Shoujo"), - Filter("16", "Shoujo Ai"), - Filter("33", "Shounen"), - Filter("19", "Shounen Ai"), - Filter("21", "Slice of Life"), - Filter("23", "Smut"), - Filter("25", "Sports"), - Filter("26", "Supernatural"), - Filter("28", "Tragedy"), - Filter("36", "Webtoon"), - Filter("29", "Yaoi"), - Filter("31", "Yuri") + override fun getFilterList(): List> = listOf( + TextField("Author", "artist_name"), + ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))), + Status(), + Flag("Exclude mature", "mature", "m", ""), + Filter.Header(""), + ListField("Order by", "order_cond", arrayOf(ListValue("Title", "title"), ListValue("Author", "author"), ListValue("Artist", "artist"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Last Update", "update")), 4), + Flag("Ascending order", "order", "asc", "desc"), + Filter.Header("Genres"), + ListField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))), + Genre("4-Koma", 40), + Genre("Action", 1), + Genre("Adventure", 2), + Genre("Award Winning", 39), + Genre("Comedy", 3), + Genre("Cooking", 41), + Genre("Doujinshi", 9), + Genre("Drama", 10), + Genre("Ecchi", 12), + Genre("Fantasy", 13), + Genre("Gender Bender", 15), + Genre("Harem", 17), + Genre("Historical", 20), + Genre("Horror", 22), + Genre("Josei", 34), + Genre("Martial Arts", 27), + Genre("Mecha", 30), + Genre("Medical", 42), + Genre("Music", 37), + Genre("Mystery", 4), + Genre("Oneshot", 38), + Genre("Psychological", 5), + Genre("Romance", 6), + Genre("School Life", 7), + Genre("Sci-fi", 8), + Genre("Seinen", 32), + Genre("Shoujo", 35), + Genre("Shoujo Ai", 16), + Genre("Shounen", 33), + Genre("Shounen Ai", 19), + Genre("Slice of Life", 21), + Genre("Smut", 23), + Genre("Sports", 25), + Genre("Supernatural", 26), + Genre("Tragedy", 28), + Genre("Webtoon", 36), + Genre("Yaoi", 29), + Genre("Yuri", 31), + Genre("[no chapters]", 44) ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt index 767ae01372..1068f6efb2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt @@ -51,25 +51,26 @@ class Kissmanga(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)" - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } val form = FormBody.Builder().apply { - add("authorArtist", "") add("mangaName", query) - this@Kissmanga.filters.forEach { filter -> - if (filter.equals(completedFilter)) add("status", if (filter in filters) filter.id else "") - else add("genres", if (filter in filters) "1" else "0") + for (filter in if (filters.isEmpty()) this@Kissmanga.filters else filters) { + when (filter) { + is Author -> add("authorArtist", filter.state) + is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state]) + is Genre -> add("genres", filter.state.toString()) + } } } - return POST(page.url, headers, form.build()) } - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/AdvanceSearch" + override fun searchMangaInitialUrl(query: String, filters: List>) = "$baseUrl/AdvanceSearch" override fun searchMangaSelector() = popularMangaSelector() @@ -128,54 +129,59 @@ class Kissmanga(override val id: Int) : ParsedOnlineSource() { override fun imageUrlParse(document: Document) = "" - private val completedFilter = Filter("Completed", "Completed") - // $("select[name=\"genres\"]").map((i,el) => `Filter("${i}", "${$(el).next().text().trim()}")`).get().join(',\n') + private class Status() : Filter.TriState("Completed") + private class Author() : Filter.Text("Author") + private class Genre(name: String, val id: Int) : Filter.TriState(name) + + // $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n') // on http://kissmanga.com/AdvanceSearch - override fun getFilterList(): List = listOf( - completedFilter, - Filter("0", "Action"), - Filter("1", "Adult"), - Filter("2", "Adventure"), - Filter("3", "Comedy"), - Filter("4", "Comic"), - Filter("5", "Cooking"), - Filter("6", "Doujinshi"), - Filter("7", "Drama"), - Filter("8", "Ecchi"), - Filter("9", "Fantasy"), - Filter("10", "Gender Bender"), - Filter("11", "Harem"), - Filter("12", "Historical"), - Filter("13", "Horror"), - Filter("14", "Josei"), - Filter("15", "Lolicon"), - Filter("16", "Manga"), - Filter("17", "Manhua"), - Filter("18", "Manhwa"), - Filter("19", "Martial Arts"), - Filter("20", "Mature"), - Filter("21", "Mecha"), - Filter("22", "Medical"), - Filter("23", "Music"), - Filter("24", "Mystery"), - Filter("25", "One shot"), - Filter("26", "Psychological"), - Filter("27", "Romance"), - Filter("28", "School Life"), - Filter("29", "Sci-fi"), - Filter("30", "Seinen"), - Filter("31", "Shotacon"), - Filter("32", "Shoujo"), - Filter("33", "Shoujo Ai"), - Filter("34", "Shounen"), - Filter("35", "Shounen Ai"), - Filter("36", "Slice of Life"), - Filter("37", "Smut"), - Filter("38", "Sports"), - Filter("39", "Supernatural"), - Filter("40", "Tragedy"), - Filter("41", "Webtoon"), - Filter("42", "Yaoi"), - Filter("43", "Yuri") + override fun getFilterList(): List> = listOf( + Author(), + Status(), + Filter.Header("Genres"), + Genre("Action", 0), + Genre("Adult", 1), + Genre("Adventure", 2), + Genre("Comedy", 3), + Genre("Comic", 4), + Genre("Cooking", 5), + Genre("Doujinshi", 6), + Genre("Drama", 7), + Genre("Ecchi", 8), + Genre("Fantasy", 9), + Genre("Gender Bender", 10), + Genre("Harem", 11), + Genre("Historical", 12), + Genre("Horror", 13), + Genre("Josei", 14), + Genre("Lolicon", 15), + Genre("Manga", 16), + Genre("Manhua", 17), + Genre("Manhwa", 18), + Genre("Martial Arts", 19), + Genre("Mature", 20), + Genre("Mecha", 21), + Genre("Medical", 22), + Genre("Music", 23), + Genre("Mystery", 24), + Genre("One shot", 25), + Genre("Psychological", 26), + Genre("Romance", 27), + Genre("School Life", 28), + Genre("Sci-fi", 29), + Genre("Seinen", 30), + Genre("Shotacon", 31), + Genre("Shoujo", 32), + Genre("Shoujo Ai", 33), + Genre("Shounen", 34), + Genre("Shounen Ai", 35), + Genre("Slice of Life", 36), + Genre("Smut", 37), + Genre("Sports", 38), + Genre("Supernatural", 39), + Genre("Tragedy", 40), + Genre("Webtoon", 41), + Genre("Yaoi", 42), + Genre("Yuri", 43) ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt index 31b580c7a2..f0474371c4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element @@ -45,8 +46,18 @@ class Mangafox(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector() = "a:has(span.next)" - override fun searchMangaInitialUrl(query: String, filters: List) = - "$baseUrl/search.php?name_method=cw&advopts=1&order=za&sort=views&name=$query&page=1&${filters.map { it.id + "=1" }.joinToString("&")}" + override fun searchMangaInitialUrl(query: String, filters: List>): String { + val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) + for (filter in if (filters.isEmpty()) this@Mangafox.filters else filters) { + when (filter) { + is Genre -> url.addQueryParameter(filter.id, filter.state.toString()) + is TextField -> url.addQueryParameter(filter.key, filter.state) + is ListField -> url.addQueryParameter(filter.key, filter.values[filter.state].value) + is Order -> url.addQueryParameter("order", if (filter.state) "az" else "za") + } + } + return url.toString() + } override fun searchMangaSelector() = "div#mangalist > ul.list > li" @@ -123,49 +134,66 @@ class Mangafox(override val id: Int) : ParsedOnlineSource() { } // Not used, overrides parent. - override fun pageListParse(document: Document, pages: MutableList) {} + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") - // $('select.genres').map((i,el)=>`Filter("${$(el).attr('name')}", "${$(el).next().text().trim()}")`).get().join(',\n') - // on http://kissmanga.com/AdvanceSearch - override fun getFilterList(): List = listOf( - Filter("is_completed", "Completed"), - Filter("genres[Action]", "Action"), - Filter("genres[Adult]", "Adult"), - Filter("genres[Adventure]", "Adventure"), - Filter("genres[Comedy]", "Comedy"), - Filter("genres[Doujinshi]", "Doujinshi"), - Filter("genres[Drama]", "Drama"), - Filter("genres[Ecchi]", "Ecchi"), - Filter("genres[Fantasy]", "Fantasy"), - Filter("genres[Gender Bender]", "Gender Bender"), - Filter("genres[Harem]", "Harem"), - Filter("genres[Historical]", "Historical"), - Filter("genres[Horror]", "Horror"), - Filter("genres[Josei]", "Josei"), - Filter("genres[Martial Arts]", "Martial Arts"), - Filter("genres[Mature]", "Mature"), - Filter("genres[Mecha]", "Mecha"), - Filter("genres[Mystery]", "Mystery"), - Filter("genres[One Shot]", "One Shot"), - Filter("genres[Psychological]", "Psychological"), - Filter("genres[Romance]", "Romance"), - Filter("genres[School Life]", "School Life"), - Filter("genres[Sci-fi]", "Sci-fi"), - Filter("genres[Seinen]", "Seinen"), - Filter("genres[Shoujo]", "Shoujo"), - Filter("genres[Shoujo Ai]", "Shoujo Ai"), - Filter("genres[Shounen]", "Shounen"), - Filter("genres[Shounen Ai]", "Shounen Ai"), - Filter("genres[Slice of Life]", "Slice of Life"), - Filter("genres[Smut]", "Smut"), - Filter("genres[Sports]", "Sports"), - Filter("genres[Supernatural]", "Supernatural"), - Filter("genres[Tragedy]", "Tragedy"), - Filter("genres[Webtoons]", "Webtoons"), - Filter("genres[Yaoi]", "Yaoi"), - Filter("genres[Yuri]", "Yuri") + private data class ListValue(val name: String, val value: String) { + override fun toString(): String = name + } + + private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class ListField(name: String, val key: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + private class Order() : Filter.CheckBox("Ascending order") + + // $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n') + // on http://mangafox.me/search.php + override fun getFilterList(): List> = listOf( + TextField("Author", "author"), + TextField("Artist", "artist"), + ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga", "1"), ListValue("Korean Manhwa", "2"), ListValue("Chinese Manhua", "3"))), + Genre("Completed", "is_completed"), + Filter.Header(""), + ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), + Order(), + Filter.Header("Genres"), + Genre("Action"), + Genre("Adult"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("One Shot"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Smut"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Webtoons"), + Genre("Yaoi"), + Genre("Yuri") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt index b5d8c9989a..83b93b6fc1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt @@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource +import okhttp3.HttpUrl import org.jsoup.nodes.Document import org.jsoup.nodes.Element import java.text.ParseException @@ -47,7 +48,20 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector() = "div.next-page > a.next" - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search.php?name=$query&page=1&sort=views&order=za&${filters.map { it.id + "=1" }.joinToString("&")}&advopts=1" + override fun searchMangaInitialUrl(query: String, filters: List>): String { + val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) + for (filter in if (filters.isEmpty()) this@Mangahere.filters else filters) { + when (filter) { + is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state]) + is Genre -> url.addQueryParameter(filter.id, filter.state.toString()) + is TextField -> url.addQueryParameter(filter.key, filter.state) + is ListField -> url.addQueryParameter(filter.key, filter.values[filter.state].value) + is Order -> url.addQueryParameter("order", if (filter.state) "az" else "za") + } + } + return url.toString() + } + override fun searchMangaSelector() = "div.result_search > dl:has(dt)" @@ -82,12 +96,12 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { val urlElement = parentEl.select("a").first() - var volume = parentEl.select("span.mr6")?.first()?.text()?.trim()?:"" + var volume = parentEl.select("span.mr6")?.first()?.text()?.trim() ?: "" if (volume.length > 0) { volume = " - " + volume } - var title = parentEl?.textNodes()?.last()?.text()?.trim()?:"" + var title = parentEl?.textNodes()?.last()?.text()?.trim() ?: "" if (title.length > 0) { title = " - " + title } @@ -131,42 +145,59 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") - // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Filter("${el.getAttribute('name')}", "${el.nextSibling.nextSibling.textContent.trim()}")`).join(',\n') + private data class ListValue(val name: String, val value: String) { + override fun toString(): String = name + } + + private class Status() : Filter.TriState("Completed") + private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class ListField(name: String, val key: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + private class Order() : Filter.CheckBox("Ascending order") + + // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n') // http://www.mangahere.co/advsearch.htm - override fun getFilterList(): List = listOf( - Filter("is_completed", "Completed"), - Filter("genres[Action]", "Action"), - Filter("genres[Adventure]", "Adventure"), - Filter("genres[Comedy]", "Comedy"), - Filter("genres[Doujinshi]", "Doujinshi"), - Filter("genres[Drama]", "Drama"), - Filter("genres[Ecchi]", "Ecchi"), - Filter("genres[Fantasy]", "Fantasy"), - Filter("genres[Gender Bender]", "Gender Bender"), - Filter("genres[Harem]", "Harem"), - Filter("genres[Historical]", "Historical"), - Filter("genres[Horror]", "Horror"), - Filter("genres[Josei]", "Josei"), - Filter("genres[Martial Arts]", "Martial Arts"), - Filter("genres[Mature]", "Mature"), - Filter("genres[Mecha]", "Mecha"), - Filter("genres[Mystery]", "Mystery"), - Filter("genres[One Shot]", "One Shot"), - Filter("genres[Psychological]", "Psychological"), - Filter("genres[Romance]", "Romance"), - Filter("genres[School Life]", "School Life"), - Filter("genres[Sci-fi]", "Sci-fi"), - Filter("genres[Seinen]", "Seinen"), - Filter("genres[Shoujo]", "Shoujo"), - Filter("genres[Shoujo Ai]", "Shoujo Ai"), - Filter("genres[Shounen]", "Shounen"), - Filter("genres[Shounen Ai]", "Shounen Ai"), - Filter("genres[Slice of Life]", "Slice of Life"), - Filter("genres[Sports]", "Sports"), - Filter("genres[Supernatural]", "Supernatural"), - Filter("genres[Tragedy]", "Tragedy"), - Filter("genres[Yaoi]", "Yaoi"), - Filter("genres[Yuri]", "Yuri") + override fun getFilterList(): List> = listOf( + TextField("Author", "author"), + TextField("Artist", "artist"), + ListField("Type", "direction", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga (read from right to left)", "rl"), ListValue("Korean Manhwa (read from left to right)", "lr"))), + Status(), + Filter.Header(""), + ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), + Order(), + Filter.Header("Genres"), + Genre("Action"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("One Shot"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Yaoi"), + Genre("Yuri") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt index 6e29363bd5..c4e56d6391 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt @@ -30,7 +30,7 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { private val indexPattern = Pattern.compile("-index-(.*?)-") - override fun popularMangaInitialUrl() = "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending" + override fun popularMangaInitialUrl() = "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending&todo=1" override fun popularMangaSelector() = "div.requested > div.row" @@ -64,20 +64,32 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { // Not used, overrides parent. override fun popularMangaNextPageSelector() = "" - override fun searchMangaInitialUrl(query: String, filters: List): String { - var url = "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending&keyword=$query" + override fun searchMangaInitialUrl(query: String, filters: List>): String { + val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder() + if (!query.isEmpty()) url.addQueryParameter("keyword", query) var genres: String? = null - for (filter in filters) { - if (filter.equals(completedFilter)) url += "&status=Complete" - else if (genres == null) genres = filter.id - else genres += "," + filter.id + var genresNo: String? = null + for (filter in if (filters.isEmpty()) this@Mangasee.filters else filters) { + when (filter) { + is Sort -> filter.values[filter.state].keys.forEachIndexed { i, s -> + url.addQueryParameter(s, filter.values[filter.state].values[i]) + } + is ListField -> if (filter.state != 0) url.addQueryParameter(filter.key, filter.values[filter.state]) + is TextField -> if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) + is Genre -> when (filter.state) { + Filter.TriState.STATE_INCLUDE -> genres = if (genres == null) filter.id else genres + "," + filter.id + Filter.TriState.STATE_EXCLUDE -> genresNo = if (genresNo == null) filter.id else genresNo + "," + filter.id + } + } } - return if (genres == null) url else url + "&genre=$genres" + if (genres != null) url.addQueryParameter("genre", genres) + if (genresNo != null) url.addQueryParameter("genreNo", genresNo) + return url.toString() } override fun searchMangaSelector() = "div.searchResults > div.requested > div.row" - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } @@ -95,7 +107,7 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { return Pair(body, requestUrl) } - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(popularMangaSelector())) { Manga.create(id).apply { @@ -174,47 +186,67 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src") - private val completedFilter = Filter("Complete", "Completed") + private data class SortOption(val name: String, val keys: Array, val values: Array) { + override fun toString(): String = name + } + + private class Sort(name: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class ListField(name: String, val key: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + // [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') // http://mangasee.co/advanced-search/ - override fun getFilterList(): List = listOf( - completedFilter, - Filter("Action", "Action"), - Filter("Adult", "Adult"), - Filter("Adventure", "Adventure"), - Filter("Comedy", "Comedy"), - Filter("Doujinshi", "Doujinshi"), - Filter("Drama", "Drama"), - Filter("Ecchi", "Ecchi"), - Filter("Fantasy", "Fantasy"), - Filter("Gender_Bender", "Gender Bender"), - Filter("Harem", "Harem"), - Filter("Hentai", "Hentai"), - Filter("Historical", "Historical"), - Filter("Horror", "Horror"), - Filter("Josei", "Josei"), - Filter("Lolicon", "Lolicon"), - Filter("Martial_Arts", "Martial Arts"), - Filter("Mature", "Mature"), - Filter("Mecha", "Mecha"), - Filter("Mystery", "Mystery"), - Filter("Psychological", "Psychological"), - Filter("Romance", "Romance"), - Filter("School_Life", "School Life"), - Filter("Sci-fi", "Sci-fi"), - Filter("Seinen", "Seinen"), - Filter("Shotacon", "Shotacon"), - Filter("Shoujo", "Shoujo"), - Filter("Shoujo_Ai", "Shoujo Ai"), - Filter("Shounen", "Shounen"), - Filter("Shounen_Ai", "Shounen Ai"), - Filter("Slice_of_Life", "Slice of Life"), - Filter("Smut", "Smut"), - Filter("Sports", "Sports"), - Filter("Supernatural", "Supernatural"), - Filter("Tragedy", "Tragedy"), - Filter("Yaoi", "Yaoi"), - Filter("Yuri", "Yuri") + override fun getFilterList(): List> = listOf( + TextField("Years", "year"), + TextField("Author", "author"), + Sort("Sort By", arrayOf(SortOption("Alphabetical A-Z", emptyArray(), emptyArray()), + SortOption("Alphabetical Z-A", arrayOf("sortOrder"), arrayOf("descending")), + SortOption("Newest", arrayOf("sortBy", "sortOrder"), arrayOf("dateUpdated", "descending")), + SortOption("Oldest", arrayOf("sortBy"), arrayOf("dateUpdated")), + SortOption("Most Popular", arrayOf("sortBy", "sortOrder"), arrayOf("popularity", "descending")), + SortOption("Least Popular", arrayOf("sortBy"), arrayOf("popularity")) + ), 4), + ListField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")), + ListField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")), + ListField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")), + Filter.Header("Genres"), + Genre("Action"), + Genre("Adult"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Hentai"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Lolicon"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shotacon"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Smut"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Yaoi"), + Genre("Yuri") ) override fun latestUpdatesInitialUrl(): String = "http://mangaseeonline.net/home/latest.request.php" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt index 5422c7ddb0..09b520d140 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import okhttp3.Headers import okhttp3.OkHttpClient @@ -57,25 +56,29 @@ class Readmangatoday(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector(): String = "div.hot-manga > ul.pagination > li > a:contains(»)" - override fun searchMangaInitialUrl(query: String, filters: List) = + override fun searchMangaInitialUrl(query: String, filters: List>) = "$baseUrl/service/advanced_search" - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } val builder = okhttp3.FormBody.Builder() builder.add("manga-name", query) - builder.add("type", "all") - var status = "both" - for (filter in filters) { - if (filter.equals(completedFilter)) status = filter.id - else builder.add("include[]", filter.id) - } - builder.add("status", status) + for (filter in if (filters.isEmpty()) this@Readmangatoday.filters else filters) { + when (filter) { + is TextField -> builder.add(filter.key, filter.state) + is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state]) + is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state]) + is Genre -> when (filter.state) { + Filter.TriState.STATE_INCLUDE -> builder.add("include[]", filter.id.toString()) + Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", filter.id.toString()) + } + } + } return POST(page.url, headers, builder.build()) } @@ -118,16 +121,16 @@ class Readmangatoday(override val id: Int) : ParsedOnlineSource() { } private fun parseChapterDate(date: String): Long { - val dateWords : List = date.split(" ") + val dateWords: List = date.split(" ") if (dateWords.size == 3) { val timeAgo = Integer.parseInt(dateWords[0]) - var date : Calendar = Calendar.getInstance() + var date: Calendar = Calendar.getInstance() if (dateWords[1].contains("Minute")) { - date.add(Calendar.MINUTE, - timeAgo) + date.add(Calendar.MINUTE, -timeAgo) } else if (dateWords[1].contains("Hour")) { - date.add(Calendar.HOUR_OF_DAY, - timeAgo) + date.add(Calendar.HOUR_OF_DAY, -timeAgo) } else if (dateWords[1].contains("Day")) { date.add(Calendar.DAY_OF_YEAR, -timeAgo) } else if (dateWords[1].contains("Week")) { @@ -153,45 +156,53 @@ class Readmangatoday(override val id: Int) : ParsedOnlineSource() { override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src") - private val completedFilter = Filter("completed", "Completed") - // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Filter("${el.getAttribute('data-id')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') + private class Status() : Filter.TriState("Completed") + private class Genre(name: String, val id: Int) : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class Type() : Filter.List("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) + + // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n') // http://www.readmanga.today/advanced-search - override fun getFilterList(): List = listOf( - completedFilter, - Filter("2", "Action"), - Filter("4", "Adventure"), - Filter("5", "Comedy"), - Filter("6", "Doujinshi"), - Filter("7", "Drama"), - Filter("8", "Ecchi"), - Filter("9", "Fantasy"), - Filter("10", "Gender Bender"), - Filter("11", "Harem"), - Filter("12", "Historical"), - Filter("13", "Horror"), - Filter("14", "Josei"), - Filter("15", "Lolicon"), - Filter("16", "Martial Arts"), - Filter("17", "Mature"), - Filter("18", "Mecha"), - Filter("19", "Mystery"), - Filter("20", "One shot"), - Filter("21", "Psychological"), - Filter("22", "Romance"), - Filter("23", "School Life"), - Filter("24", "Sci-fi"), - Filter("25", "Seinen"), - Filter("26", "Shotacon"), - Filter("27", "Shoujo"), - Filter("28", "Shoujo Ai"), - Filter("29", "Shounen"), - Filter("30", "Shounen Ai"), - Filter("31", "Slice of Life"), - Filter("32", "Smut"), - Filter("33", "Sports"), - Filter("34", "Supernatural"), - Filter("35", "Tragedy"), - Filter("36", "Yaoi"), - Filter("37", "Yuri") + override fun getFilterList(): List> = listOf( + TextField("Author", "author-name"), + TextField("Artist", "artist-name"), + Type(), + Status(), + Filter.Header("Genres"), + Genre("Action", 2), + Genre("Adventure", 4), + Genre("Comedy", 5), + Genre("Doujinshi", 6), + Genre("Drama", 7), + Genre("Ecchi", 8), + Genre("Fantasy", 9), + Genre("Gender Bender", 10), + Genre("Harem", 11), + Genre("Historical", 12), + Genre("Horror", 13), + Genre("Josei", 14), + Genre("Lolicon", 15), + Genre("Martial Arts", 16), + Genre("Mature", 17), + Genre("Mecha", 18), + Genre("Mystery", 19), + Genre("One shot", 20), + Genre("Psychological", 21), + Genre("Romance", 22), + Genre("School Life", 23), + Genre("Sci-fi", 24), + Genre("Seinen", 25), + Genre("Shotacon", 26), + Genre("Shoujo", 27), + Genre("Shoujo Ai", 28), + Genre("Shounen", 29), + Genre("Shounen Ai", 30), + Genre("Slice of Life", 31), + Genre("Smut", 32), + Genre("Sports", 33), + Genre("Supernatural", 34), + Genre("Tragedy", 35), + Genre("Yaoi", 36), + Genre("Yuri", 37) ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt index 9958792dc5..25c3dcf6ae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt @@ -45,7 +45,7 @@ class WieManga(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector() = null - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search/?wd=$query" + override fun searchMangaInitialUrl(query: String, filters: List>) = "$baseUrl/search/?wd=$query" override fun searchMangaSelector() = ".searchresult td > div" @@ -70,10 +70,10 @@ class WieManga(override val id: Int) : ParsedOnlineSource() { manga.thumbnail_url = imageElement.select("img").first()?.attr("src") if (manga.author == "RSS") - manga.author = null + manga.author = null if (manga.artist == "RSS") - manga.artist = null + manga.artist = null } override fun chapterListSelector() = ".chapterlist tr:not(:first-child)" @@ -95,11 +95,12 @@ class WieManga(override val id: Int) : ParsedOnlineSource() { val document = response.asJsoup() document.select("select#page").first().select("option").forEach { - pages.add(Page(pages.size, it.attr("value"))) + pages.add(Page(pages.size, it.attr("value"))) } } - override fun pageListParse(document: Document, pages: MutableList) {} + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src") diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt index 9075fa6f8c..e26317a93d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt @@ -26,15 +26,18 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesInitialUrl() = "$baseUrl/newestch" - override fun searchMangaInitialUrl(query: String, filters: List): String { + override fun searchMangaInitialUrl(query: String, filters: List>): String { if (query.isNotEmpty()) { return "$baseUrl/?do=search&subaction=search&story=$query" - } else if (filters.isNotEmpty()) { - var genres = "" - filters.forEach { genres = genres + it.name + '+' } - return "$baseUrl/tags/${genres.dropLast(1)}" } else { - return "$baseUrl/?do=search&subaction=search&story=$query" + val filt = filters.filter { it.state != Filter.TriState.STATE_IGNORE } + if (filt.isNotEmpty()) { + var genres = "" + filt.forEach { genres += (if (it.state == Filter.TriState.STATE_EXCLUDE) "-" else "") + (it as Genre).id + '+' } + return "$baseUrl/tags/${genres.dropLast(1)}" + } else { + return "$baseUrl/?do=search&subaction=search&story=$query" + } } } @@ -70,7 +73,7 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(searchMangaSelector())) { Manga.create(id).apply { @@ -78,9 +81,9 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { page.mangas.add(this) } } - + val allIgnore = filters.all { it.state == Filter.TriState.STATE_IGNORE } searchMangaNextPageSelector().let { selector -> - if (page.nextPageUrl.isNullOrEmpty() && filters.isEmpty()) { + if (page.nextPageUrl.isNullOrEmpty() && allIgnore) { val onClick = document.select(selector).first()?.attr("onclick") val pageNum = onClick?.substring(23, onClick.indexOf("); return(false)")) page.nextPageUrl = searchMangaInitialUrl(query, emptyList()) + "&search_start=" + pageNum @@ -88,7 +91,7 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { } searchGenresNextPageSelector().let { selector -> - if (page.nextPageUrl.isNullOrEmpty() && filters.isNotEmpty()) { + if (page.nextPageUrl.isNullOrEmpty() && !allIgnore) { val url = document.select(selector).first()?.attr("href") page.nextPageUrl = searchMangaInitialUrl(query, filters) + url } @@ -137,71 +140,75 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { pageUrls.mapIndexedTo(pages) { i, url -> Page(i, "", url) } } - override fun pageListParse(document: Document, pages: MutableList) { } + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = "" + private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) + /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) => * { const link=el.getAttribute('href');const id=link.substr(6,link.length); - * return `Filter("${id}", "${id}")` }).join(',\n') + * return `Genre("${id.replace("_", " ")}")` }).join(',\n') * on http://mangachan.me/ */ - override fun getFilterList(): List = listOf( - Filter("18_плюс", "18_плюс"), - Filter("bdsm", "bdsm"), - Filter("арт", "арт"), - Filter("биография", "биография"), - Filter("боевик", "боевик"), - Filter("боевые_искусства", "боевые_искусства"), - Filter("вампиры", "вампиры"), - Filter("веб", "веб"), - Filter("гарем", "гарем"), - Filter("гендерная_интрига", "гендерная_интрига"), - Filter("героическое_фэнтези", "героическое_фэнтези"), - Filter("детектив", "детектив"), - Filter("дзёсэй", "дзёсэй"), - Filter("додзинси", "додзинси"), - Filter("драма", "драма"), - Filter("игра", "игра"), - Filter("инцест", "инцест"), - Filter("искусство", "искусство"), - Filter("история", "история"), - Filter("киберпанк", "киберпанк"), - Filter("кодомо", "кодомо"), - Filter("комедия", "комедия"), - Filter("литРПГ", "литРПГ"), - Filter("махо-сёдзё", "махо-сёдзё"), - Filter("меха", "меха"), - Filter("мистика", "мистика"), - Filter("музыка", "музыка"), - Filter("научная_фантастика", "научная_фантастика"), - Filter("повседневность", "повседневность"), - Filter("постапокалиптика", "постапокалиптика"), - Filter("приключения", "приключения"), - Filter("психология", "психология"), - Filter("романтика", "романтика"), - Filter("самурайский_боевик", "самурайский_боевик"), - Filter("сборник", "сборник"), - Filter("сверхъестественное", "сверхъестественное"), - Filter("сказка", "сказка"), - Filter("спорт", "спорт"), - Filter("супергерои", "супергерои"), - Filter("сэйнэн", "сэйнэн"), - Filter("сёдзё", "сёдзё"), - Filter("сёдзё-ай", "сёдзё-ай"), - Filter("сёнэн", "сёнэн"), - Filter("сёнэн-ай", "сёнэн-ай"), - Filter("тентакли", "тентакли"), - Filter("трагедия", "трагедия"), - Filter("триллер", "триллер"), - Filter("ужасы", "ужасы"), - Filter("фантастика", "фантастика"), - Filter("фурри", "фурри"), - Filter("фэнтези", "фэнтези"), - Filter("школа", "школа"), - Filter("эротика", "эротика"), - Filter("юри", "юри"), - Filter("яой", "яой"), - Filter("ёнкома", "ёнкома") + override fun getFilterList(): List> = listOf( + Genre("18 плюс"), + Genre("bdsm"), + Genre("арт"), + Genre("биография"), + Genre("боевик"), + Genre("боевые искусства"), + Genre("вампиры"), + Genre("веб"), + Genre("гарем"), + Genre("гендерная интрига"), + Genre("героическое фэнтези"), + Genre("детектив"), + Genre("дзёсэй"), + Genre("додзинси"), + Genre("драма"), + Genre("игра"), + Genre("инцест"), + Genre("искусство"), + Genre("история"), + Genre("киберпанк"), + Genre("кодомо"), + Genre("комедия"), + Genre("литРПГ"), + Genre("магия"), + Genre("махо-сёдзё"), + Genre("меха"), + Genre("мистика"), + Genre("музыка"), + Genre("научная фантастика"), + Genre("повседневность"), + Genre("постапокалиптика"), + Genre("приключения"), + Genre("психология"), + Genre("романтика"), + Genre("самурайский боевик"), + Genre("сборник"), + Genre("сверхъестественное"), + Genre("сказка"), + Genre("спорт"), + Genre("супергерои"), + Genre("сэйнэн"), + Genre("сёдзё"), + Genre("сёдзё-ай"), + Genre("сёнэн"), + Genre("сёнэн-ай"), + Genre("тентакли"), + Genre("трагедия"), + Genre("триллер"), + Genre("ужасы"), + Genre("фантастика"), + Genre("фурри"), + Genre("фэнтези"), + Genre("школа"), + Genre("эротика"), + Genre("юри"), + Genre("яой"), + Genre("ёнкома") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt index 2e1755676f..cab2def3f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt @@ -25,8 +25,8 @@ class Mintmanga(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated" - override fun searchMangaInitialUrl(query: String, filters: List) = - "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" + override fun searchMangaInitialUrl(query: String, filters: List>) = + "$baseUrl/search?q=$query&${filters.map { (it as Genre).id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")}" override fun popularMangaSelector() = "div.desc" @@ -107,57 +107,60 @@ class Mintmanga(override val id: Int) : ParsedOnlineSource() { } } - override fun pageListParse(document: Document, pages: MutableList) { } + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = "" + private class Genre(name: String, val id: String) : Filter.TriState(name) + /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); - * return `Filter("${id}", "${el.textContent.trim()}")` }).join(',\n') + * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') * on http://mintmanga.com/search */ - override fun getFilterList(): List = listOf( - Filter("el_2220", "арт"), - Filter("el_1353", "бара"), - Filter("el_1346", "боевик"), - Filter("el_1334", "боевые искусства"), - Filter("el_1339", "вампиры"), - Filter("el_1333", "гарем"), - Filter("el_1347", "гендерная интрига"), - Filter("el_1337", "героическое фэнтези"), - Filter("el_1343", "детектив"), - Filter("el_1349", "дзёсэй"), - Filter("el_1332", "додзинси"), - Filter("el_1310", "драма"), - Filter("el_5229", "игра"), - Filter("el_1311", "история"), - Filter("el_1351", "киберпанк"), - Filter("el_1328", "комедия"), - Filter("el_1318", "меха"), - Filter("el_1324", "мистика"), - Filter("el_1325", "научная фантастика"), - Filter("el_1327", "повседневность"), - Filter("el_1342", "постапокалиптика"), - Filter("el_1322", "приключения"), - Filter("el_1335", "психология"), - Filter("el_1313", "романтика"), - Filter("el_1316", "самурайский боевик"), - Filter("el_1350", "сверхъестественное"), - Filter("el_1314", "сёдзё"), - Filter("el_1320", "сёдзё-ай"), - Filter("el_1326", "сёнэн"), - Filter("el_1330", "сёнэн-ай"), - Filter("el_1321", "спорт"), - Filter("el_1329", "сэйнэн"), - Filter("el_1344", "трагедия"), - Filter("el_1341", "триллер"), - Filter("el_1317", "ужасы"), - Filter("el_1331", "фантастика"), - Filter("el_1323", "фэнтези"), - Filter("el_1319", "школа"), - Filter("el_1340", "эротика"), - Filter("el_1354", "этти"), - Filter("el_1315", "юри"), - Filter("el_1336", "яой") + override fun getFilterList(): List> = listOf( + Genre("арт", "el_2220"), + Genre("бара", "el_1353"), + Genre("боевик", "el_1346"), + Genre("боевые искусства", "el_1334"), + Genre("вампиры", "el_1339"), + Genre("гарем", "el_1333"), + Genre("гендерная интрига", "el_1347"), + Genre("героическое фэнтези", "el_1337"), + Genre("детектив", "el_1343"), + Genre("дзёсэй", "el_1349"), + Genre("додзинси", "el_1332"), + Genre("драма", "el_1310"), + Genre("игра", "el_5229"), + Genre("история", "el_1311"), + Genre("киберпанк", "el_1351"), + Genre("комедия", "el_1328"), + Genre("меха", "el_1318"), + Genre("мистика", "el_1324"), + Genre("научная фантастика", "el_1325"), + Genre("повседневность", "el_1327"), + Genre("постапокалиптика", "el_1342"), + Genre("приключения", "el_1322"), + Genre("психология", "el_1335"), + Genre("романтика", "el_1313"), + Genre("самурайский боевик", "el_1316"), + Genre("сверхъестественное", "el_1350"), + Genre("сёдзё", "el_1314"), + Genre("сёдзё-ай", "el_1320"), + Genre("сёнэн", "el_1326"), + Genre("сёнэн-ай", "el_1330"), + Genre("спорт", "el_1321"), + Genre("сэйнэн", "el_1329"), + Genre("трагедия", "el_1344"), + Genre("триллер", "el_1341"), + Genre("ужасы", "el_1317"), + Genre("фантастика", "el_1331"), + Genre("фэнтези", "el_1323"), + Genre("школа", "el_1319"), + Genre("эротика", "el_1340"), + Genre("этти", "el_1354"), + Genre("юри", "el_1315"), + Genre("яой", "el_1336") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt index f3005a502d..0b75bc0da5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt @@ -25,8 +25,8 @@ class Readmanga(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated" - override fun searchMangaInitialUrl(query: String, filters: List) = - "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" + override fun searchMangaInitialUrl(query: String, filters: List>) = + "$baseUrl/search?q=$query&${filters.map { (it as Genre).id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")}" override fun popularMangaSelector() = "div.desc" @@ -107,56 +107,59 @@ class Readmanga(override val id: Int) : ParsedOnlineSource() { } } - override fun pageListParse(document: Document, pages: MutableList) { } + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = "" + private class Genre(name: String, val id: String) : Filter.TriState(name) + /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); - * return `Filter("${id}", "${el.textContent.trim()}")` }).join(',\n') + * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') * on http://readmanga.me/search */ - override fun getFilterList(): List = listOf( - Filter("el_5685", "арт"), - Filter("el_2155", "боевик"), - Filter("el_2143", "боевые искусства"), - Filter("el_2148", "вампиры"), - Filter("el_2142", "гарем"), - Filter("el_2156", "гендерная интрига"), - Filter("el_2146", "героическое фэнтези"), - Filter("el_2152", "детектив"), - Filter("el_2158", "дзёсэй"), - Filter("el_2141", "додзинси"), - Filter("el_2118", "драма"), - Filter("el_2154", "игра"), - Filter("el_2119", "история"), - Filter("el_8032", "киберпанк"), - Filter("el_2137", "кодомо"), - Filter("el_2136", "комедия"), - Filter("el_2147", "махо-сёдзё"), - Filter("el_2126", "меха"), - Filter("el_2132", "мистика"), - Filter("el_2133", "научная фантастика"), - Filter("el_2135", "повседневность"), - Filter("el_2151", "постапокалиптика"), - Filter("el_2130", "приключения"), - Filter("el_2144", "психология"), - Filter("el_2121", "романтика"), - Filter("el_2124", "самурайский боевик"), - Filter("el_2159", "сверхъестественное"), - Filter("el_2122", "сёдзё"), - Filter("el_2128", "сёдзё-ай"), - Filter("el_2134", "сёнэн"), - Filter("el_2139", "сёнэн-ай"), - Filter("el_2129", "спорт"), - Filter("el_2138", "сэйнэн"), - Filter("el_2153", "трагедия"), - Filter("el_2150", "триллер"), - Filter("el_2125", "ужасы"), - Filter("el_2140", "фантастика"), - Filter("el_2131", "фэнтези"), - Filter("el_2127", "школа"), - Filter("el_2149", "этти"), - Filter("el_2123", "юри") + override fun getFilterList(): List> = listOf( + Genre("арт", "el_5685"), + Genre("боевик", "el_2155"), + Genre("боевые искусства", "el_2143"), + Genre("вампиры", "el_2148"), + Genre("гарем", "el_2142"), + Genre("гендерная интрига", "el_2156"), + Genre("героическое фэнтези", "el_2146"), + Genre("детектив", "el_2152"), + Genre("дзёсэй", "el_2158"), + Genre("додзинси", "el_2141"), + Genre("драма", "el_2118"), + Genre("игра", "el_2154"), + Genre("история", "el_2119"), + Genre("киберпанк", "el_8032"), + Genre("кодомо", "el_2137"), + Genre("комедия", "el_2136"), + Genre("махо-сёдзё", "el_2147"), + Genre("меха", "el_2126"), + Genre("мистика", "el_2132"), + Genre("научная фантастика", "el_2133"), + Genre("повседневность", "el_2135"), + Genre("постапокалиптика", "el_2151"), + Genre("приключения", "el_2130"), + Genre("психология", "el_2144"), + Genre("романтика", "el_2121"), + Genre("самурайский боевик", "el_2124"), + Genre("сверхъестественное", "el_2159"), + Genre("сёдзё", "el_2122"), + Genre("сёдзё-ай", "el_2128"), + Genre("сёнэн", "el_2134"), + Genre("сёнэн-ай", "el_2139"), + Genre("спорт", "el_2129"), + Genre("сэйнэн", "el_2138"), + Genre("трагедия", "el_2153"), + Genre("триллер", "el_2150"), + Genre("ужасы", "el_2125"), + Genre("фантастика", "el_2140"), + Genre("фэнтези", "el_2131"), + Genre("школа", "el_2127"), + Genre("этти", "el_2149"), + Genre("юри", "el_2123") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt index 68f2ee1283..8ee0458755 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt @@ -452,19 +452,21 @@ open class CatalogueFragment : BaseRxFragment(), FlexibleVie * Show the filter dialog for the source. */ private fun showFiltersDialog() { - val allFilters = presenter.source.filters - val selectedFilters = presenter.filters - .map { filter -> allFilters.indexOf(filter) } - .toTypedArray() - + val adapter = FilterAdapter(if (presenter.filters.isEmpty()) presenter.source.getFilterList() // make a copy + else presenter.filters) MaterialDialog.Builder(context) .title(R.string.action_set_filter) - .items(allFilters.map { it.name }) - .itemsCallbackMultiChoice(selectedFilters) { dialog, positions, text -> - val newFilters = positions.map { allFilters[it] } + .adapter(adapter, null) + .onPositive() { dialog, which -> showProgressBar() - presenter.setSourceFilter(newFilters) - true + var allDefault = true + for (i in 0..adapter.filters.lastIndex) { + if (adapter.filters[i].state != presenter.source.filters[i].state) { + allDefault = false + break + } + } + presenter.setSourceFilter(if (allDefault) emptyList() else adapter.filters) } .positiveText(android.R.string.ok) .negativeText(android.R.string.cancel) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt index 3019604140..3653f91c6b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt @@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter import rx.Observable -open class CataloguePager(val source: OnlineSource, val query: String, val filters: List): Pager() { +open class CataloguePager(val source: OnlineSource, val query: String, val filters: List>) : Pager() { override fun requestNext(transformer: (Observable) -> Observable): Observable { val lastPage = lastPage diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index a355073768..e0ec27c8e1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -65,9 +65,9 @@ open class CataloguePresenter : BasePresenter() { private set /** - * Active filters. + * Filters states. */ - var filters: List = emptyList() + var filters: List> = emptyList() /** * Pager containing a list of manga results. @@ -128,9 +128,9 @@ open class CataloguePresenter : BasePresenter() { * Restarts the pager for the active source with the provided query and filters. * * @param query the query. - * @param filters the list of active filters (for search mode). + * @param filters the current state of the filters (for search mode). */ - fun restartPager(query: String = this.query, filters: List = this.filters) { + fun restartPager(query: String = this.query, filters: List> = this.filters) { this.query = query this.filters = filters @@ -362,15 +362,15 @@ open class CataloguePresenter : BasePresenter() { } /** - * Set the active filters for the current source. + * Set the filter states for the current source. * - * @param selectedFilters a list of active filters. + * @param filterStates a list of active filters. */ - fun setSourceFilter(selectedFilters: List) { - restartPager(filters = selectedFilters) + fun setSourceFilter(filters: List>) { + restartPager(filters = filters) } - open fun createPager(query: String, filters: List): Pager { + open fun createPager(query: String, filters: List>): Pager { return CataloguePager(source, query, filters) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt new file mode 100644 index 0000000000..bf83dffba9 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt @@ -0,0 +1,153 @@ +package eu.kanade.tachiyomi.ui.catalogue + +import android.content.Context +import android.graphics.Typeface +import android.support.graphics.drawable.VectorDrawableCompat +import android.support.v7.widget.RecyclerView +import android.view.View +import android.view.ViewGroup +import android.widget.* +import android.widget.AdapterView.OnItemSelectedListener +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter +import android.text.TextWatcher +import android.text.Editable +import android.view.inputmethod.EditorInfo +import android.widget.TextView +import eu.kanade.tachiyomi.util.inflate + + +class FilterAdapter(val filters: List>) : RecyclerView.Adapter() { + private companion object { + const val HEADER = 0 + const val CHECKBOX = 1 + const val TRISTATE = 2 + const val LIST = 3 + const val TEXT = 4 + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterAdapter.ViewHolder { + return when (viewType) { + HEADER -> ViewHolder(SepText(parent)) + LIST -> ViewHolder(TextSpinner(parent.context)) + TEXT -> ViewHolder(TextEditText(parent.context)) + else -> ViewHolder(CheckBox(parent.context)) + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val filter = filters[position] + when (filter) { + is Filter.Header -> { + if (filter.name.isEmpty()) (holder.view as SepText).textView.visibility = View.GONE + else (holder.view as SepText).textView.text = filter.name + } + is Filter.CheckBox -> { + var checkBox = holder.view as CheckBox + checkBox.text = filter.name + checkBox.isChecked = filter.state + checkBox.setButtonDrawable(VectorDrawableCompat.create(checkBox.getResources(), R.drawable.ic_check_box_set, null)) + checkBox.setOnCheckedChangeListener { buttonView, isChecked -> + filter.state = isChecked + } + } + is Filter.TriState -> { + var triCheckBox = holder.view as CheckBox + triCheckBox.text = filter.name + val icons = arrayOf(VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_outline_blank_24dp, null), + VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_24dp, null), + VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_x_24dp, null)) + triCheckBox.setButtonDrawable(icons[filter.state]) + triCheckBox.invalidate() + triCheckBox.setOnCheckedChangeListener { buttonView, isChecked -> + filter.state = (filter.state + 1) % 3 + triCheckBox.setButtonDrawable(icons[filter.state]) + triCheckBox.invalidate() + } + } + is Filter.List<*> -> { + var txtSpin = holder.view as TextSpinner + if (filter.name.isEmpty()) txtSpin.textView.visibility = View.GONE + else txtSpin.textView.text = filter.name + ":" + txtSpin.spinner.adapter = ArrayAdapter(holder.view.context, + android.R.layout.simple_spinner_item, filter.values) + txtSpin.spinner.setSelection(filter.state) + txtSpin.spinner.onItemSelectedListener = object : OnItemSelectedListener { + override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View, pos: Int, id: Long) { + filter.state = pos + } + + override fun onNothingSelected(parentView: AdapterView<*>) { + } + } + } + is Filter.Text -> { + var txtEdTx = holder.view as TextEditText + if (filter.name.isEmpty()) txtEdTx.textView.visibility = View.GONE + else txtEdTx.textView.text = filter.name + ":" + txtEdTx.editText.setText(filter.state) + txtEdTx.editText.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable) { + } + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + filter.state = s.toString() + } + }) + } + } + } + + override fun getItemCount(): Int { + return filters.size + } + + override fun getItemViewType(position: Int): Int { + return when (filters[position]) { + is Filter.Header -> HEADER + is Filter.CheckBox -> CHECKBOX + is Filter.TriState -> TRISTATE + is Filter.List<*> -> LIST + is Filter.Text -> TEXT + } + } + + class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) + + private class SepText(parent: ViewGroup) : LinearLayout(parent.context) { + val separator: View = parent.inflate(R.layout.design_navigation_item_separator) + val textView: TextView = TextView(context) + + init { + orientation = LinearLayout.VERTICAL + textView.setTypeface(null, Typeface.BOLD); + addView(separator) + addView(textView) + } + } + + private class TextSpinner(context: Context?) : LinearLayout(context) { + val textView: TextView = TextView(context) + val spinner: Spinner = Spinner(context) + + init { + addView(textView) + addView(spinner) + } + } + + private class TextEditText(context: Context?) : LinearLayout(context) { + val textView: TextView = TextView(context) + val editText: EditText = EditText(context) + + init { + addView(textView) + editText.setSingleLine() + editText.setImeOptions(EditorInfo.IME_ACTION_DONE); + addView(editText) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt index 8df1196e6d..1d224bdd13 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt @@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter */ class LatestUpdatesPresenter : CataloguePresenter() { - override fun createPager(query: String, filters: List): Pager { + override fun createPager(query: String, filters: List>): Pager { return LatestUpdatesPager(source) } diff --git a/app/src/main/res/drawable/ic_check_box_24dp.xml b/app/src/main/res/drawable/ic_check_box_24dp.xml new file mode 100644 index 0000000000..9948171c21 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml b/app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml new file mode 100644 index 0000000000..cf8bfa24b5 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_box_set.xml b/app/src/main/res/drawable/ic_check_box_set.xml new file mode 100644 index 0000000000..c2d7e4c863 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_set.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_check_box_x_24dp.xml b/app/src/main/res/drawable/ic_check_box_x_24dp.xml new file mode 100644 index 0000000000..1b2c9be12b --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_x_24dp.xml @@ -0,0 +1,9 @@ + + +