From a03dceff7d3eccf047bdc4f9bb8c449fccd9a32c Mon Sep 17 00:00:00 2001 From: paronos Date: Thu, 12 Jan 2017 15:37:38 +0100 Subject: [PATCH] Add Sort filter [Catalogs] (#633) * Add Sort filter * remove old views * onClick default descending * update remaining catalogs --- .../tachiyomi/data/source/model/Filter.kt | 7 +++ .../data/source/online/english/Batoto.kt | 12 ++++- .../data/source/online/english/Mangafox.kt | 26 ++++----- .../data/source/online/english/Mangahere.kt | 20 ++++--- .../data/source/online/english/Mangasee.kt | 27 +++++----- .../ui/catalogue/CatalogueNavigationView.kt | 53 ++++++++++++++++++- 6 files changed, 108 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Filter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Filter.kt index b1b9c6d67b..c78074d985 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Filter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Filter.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.source.model sealed class Filter(val name: String, var state: T) { open class Header(name: String) : Filter(name, 0) + open class Separator(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) @@ -9,10 +10,16 @@ sealed class Filter(val name: String, var state: T) { fun isIgnored() = state == STATE_IGNORE fun isIncluded() = state == STATE_INCLUDE fun isExcluded() = state == STATE_EXCLUDE + companion object { const val STATE_IGNORE = 0 const val STATE_INCLUDE = 1 const val STATE_EXCLUDE = 2 } } + + abstract class Sort(name: String, val values: Array, state: Selection? = null) + : Filter(name, state) { + data class Selection(val index: Int, val ascending: Boolean) + } } \ No newline at end of file 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 6f47727525..8a433bb6fb 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 @@ -107,6 +107,10 @@ class Batoto : ParsedOnlineSource(), LoginSource { val sel = if (filter.state) filter.valTrue else filter.valFalse if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) } + is OrderBy -> { + url.addQueryParameter("order_cond", arrayOf("title", "author", "artist", "rating", "views", "update")[filter.state!!.index]) + url.addQueryParameter("order", if (filter.state?.ascending == true) "asc" else "desc") + } } } if (!genres.isEmpty()) url.addQueryParameter("genres", genres) @@ -288,6 +292,9 @@ class Batoto : ParsedOnlineSource(), LoginSource { 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) + private class OrderBy() : Filter.Sort("Order by", + arrayOf("Title", "Author", "Artist", "Rating", "Views", "Last Update"), + Filter.Sort.Selection(4, false)) // [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => { // const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})` @@ -298,8 +305,9 @@ class Batoto : ParsedOnlineSource(), LoginSource { 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", ""), - 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.Separator(), + OrderBy(), + Filter.Separator(), 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), 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 7f2060eb15..6f3719c20b 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 @@ -24,7 +24,7 @@ class Mangafox : ParsedOnlineSource() { override val supportsLatest = true override fun popularMangaSelector() = "div#mangalist > ul.list > li" - + override fun popularMangaRequest(page: Int): Request { val pageStr = if (page != 1) "$page.htm" else "" return GET("$baseUrl/directory/$pageStr", headers) @@ -60,8 +60,11 @@ class Mangafox : ParsedOnlineSource() { 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") + is Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString()) + is OrderBy -> { + url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index]) + url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za") + } } } url.addQueryParameter("page", page.toString()) @@ -158,24 +161,23 @@ class Mangafox : ParsedOnlineSource() { } } - 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") + private class Type() : Filter.List("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) + private class OrderBy() : Filter.Sort("Order by", + arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"), + Filter.Sort.Selection(2, false)) // $('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() = FilterList( TextField("Author", "author"), TextField("Artist", "artist"), - ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga", "1"), ListValue("Korean Manhwa", "2"), ListValue("Chinese Manhua", "3"))), + Type(), Genre("Completed", "is_completed"), - 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.Separator(), + OrderBy(), + Filter.Separator(), Filter.Header("Genres"), Genre("Action"), Genre("Adult"), 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 4280a9a3fb..b0974b25c3 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 @@ -63,8 +63,11 @@ class Mangahere : ParsedOnlineSource() { 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") + is Type -> url.addQueryParameter("direction", arrayOf("", "rl", "lr")[filter.state]) + is OrderBy -> { + url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index]) + url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za") + } } } url.addQueryParameter("page", page.toString()) @@ -166,18 +169,21 @@ class Mangahere : ParsedOnlineSource() { 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") + private class Type() : Filter.List("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)")) + private class OrderBy() : Filter.Sort("Order by", + arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"), + Filter.Sort.Selection(2, false)) // [...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() = FilterList( 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"))), + Type(), Status(), - 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.Separator(), + OrderBy(), + Filter.Separator(), Filter.Header("Genres"), Genre("Action"), Genre("Adventure"), 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 c95c258fc9..e7422104f3 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 : ParsedOnlineSource() { override fun popularMangaSelector() = "div.requested > div.row" override fun popularMangaRequest(page: Int): Request { - val (body, requestUrl) = convertQueryToPost(page, "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending&todo=1") + val (body, requestUrl) = convertQueryToPost(page, "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending") return POST(requestUrl, headers, body.build()) } @@ -54,14 +54,17 @@ class Mangasee : ParsedOnlineSource() { var genresNo: String? = null for (filter in if (filters.isEmpty()) getFilterList() else filters) { when (filter) { - is Sort -> filter.values[filter.state].keys.forEachIndexed { i, s -> - url.addQueryParameter(s, filter.values[filter.state].values[i]) + is Sort -> { + if (filter.state?.index != 0) + url.addQueryParameter("sortBy", if (filter.state?.index == 1) "dateUpdated" else "popularity") + if (filter.state?.ascending != true) + url.addQueryParameter("sortOrder", "descending") } 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 + Filter.TriState.STATE_INCLUDE -> genres = if (genres == null) filter.name else genres + "," + filter.name + Filter.TriState.STATE_EXCLUDE -> genresNo = if (genresNo == null) filter.name else genresNo + "," + filter.name } } } @@ -156,8 +159,8 @@ class Mangasee : ParsedOnlineSource() { 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 Sort() : Filter.Sort("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false)) + private class Genre(name: String) : 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) @@ -166,16 +169,12 @@ class Mangasee : ParsedOnlineSource() { override fun getFilterList() = FilterList( 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.Separator(), + Sort(), + Filter.Separator(), Filter.Header("Genres"), Genre("Action"), Genre("Adult"), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt index 3c9d20abd1..702fdada16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt @@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.ui.catalogue import android.content.Context import android.support.graphics.drawable.VectorDrawableCompat +import android.support.v4.content.ContextCompat import android.support.v7.widget.RecyclerView import android.util.AttributeSet import android.view.View import android.view.ViewGroup -import android.widget.ArrayAdapter -import android.widget.TextView +import android.widget.* import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.data.source.model.FilterList @@ -55,16 +55,19 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: override fun getItemViewType(position: Int): Int { return when (items[position]) { is Filter.Header -> VIEW_TYPE_HEADER + is Filter.Separator -> VIEW_TYPE_SEPARATOR is Filter.CheckBox -> VIEW_TYPE_CHECKBOX is Filter.TriState -> VIEW_TYPE_MULTISTATE is Filter.List<*> -> VIEW_TYPE_LIST is Filter.Text -> VIEW_TYPE_TEXT + is Filter.Sort<*> -> VIEW_TYPE_SORT } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { return when (viewType) { VIEW_TYPE_HEADER -> HeaderHolder(parent) + VIEW_TYPE_SEPARATOR -> SeparatorHolder(parent) VIEW_TYPE_CHECKBOX -> CheckboxHolder(parent, null) VIEW_TYPE_MULTISTATE -> MultiStateHolder(parent, null).apply { // Adjust view with checkbox @@ -73,6 +76,7 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: } VIEW_TYPE_LIST -> SpinnerHolder(parent) VIEW_TYPE_TEXT -> EditTextHolder(parent) + VIEW_TYPE_SORT -> SortHolder(parent) else -> throw Exception("Unknown view type") } } @@ -144,9 +148,54 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: } }) } + is Filter.Sort<*> -> { + val view = (holder as SortHolder).sortView + view.removeAllViews() + if (!filter.name.isEmpty()) { + val header = HeaderHolder(view) + (header.itemView as TextView).text = filter.name + view.addView(header.itemView) + } + val holders = Array(filter.values.size, { MultiStateHolder(view, null) }) + for ((i, rb) in holders.withIndex()) { + rb.text.text = filter.values[i].toString() + + fun getIcon() = when (filter.state) { + Filter.Sort.Selection(i, false) -> VectorDrawableCompat.create(view.resources, R.drawable.ic_keyboard_arrow_down_black_32dp, null) + ?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) } + Filter.Sort.Selection(i, true) -> VectorDrawableCompat.create(view.resources, R.drawable.ic_keyboard_arrow_up_black_32dp, null) + ?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) } + else -> ContextCompat.getDrawable(context, R.drawable.empty_drawable_32dp) + } + + rb.text.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null) + rb.itemView.setOnClickListener { + val pre = filter.state?.index ?: i + if (pre != i) { + holders[pre].text.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null) + filter.state = Filter.Sort.Selection(i, false) + } else { + filter.state = Filter.Sort.Selection(i, filter.state?.ascending == false) + } + rb.text.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null) + } + + view.addView(rb.itemView) + } + } } } } + val VIEW_TYPE_SORT = 0 + + private class SortHolder(parent: ViewGroup, val sortView: SortView = SortView(parent)) : Holder(sortView) { + class SortView(parent: ViewGroup) : LinearLayout(parent.context) { + init { + orientation = LinearLayout.VERTICAL + } + } + } + } \ No newline at end of file