Add Sort filter [Catalogs] (#633)

* Add Sort filter

* remove old views

* onClick default descending

* update remaining catalogs
This commit is contained in:
paronos 2017-01-12 15:37:38 +01:00 committed by inorichi
parent f717c57648
commit a03dceff7d
6 changed files with 108 additions and 37 deletions

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.source.model
sealed class Filter<T>(val name: String, var state: T) { sealed class Filter<T>(val name: String, var state: T) {
open class Header(name: String) : Filter<Any>(name, 0) open class Header(name: String) : Filter<Any>(name, 0)
open class Separator(name: String = "") : Filter<Any>(name, 0)
abstract class List<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state) abstract class List<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state)
abstract class Text(name: String, state: String = "") : Filter<String>(name, state) abstract class Text(name: String, state: String = "") : Filter<String>(name, state)
abstract class CheckBox(name: String, state: Boolean = false) : Filter<Boolean>(name, state) abstract class CheckBox(name: String, state: Boolean = false) : Filter<Boolean>(name, state)
@ -9,10 +10,16 @@ sealed class Filter<T>(val name: String, var state: T) {
fun isIgnored() = state == STATE_IGNORE fun isIgnored() = state == STATE_IGNORE
fun isIncluded() = state == STATE_INCLUDE fun isIncluded() = state == STATE_INCLUDE
fun isExcluded() = state == STATE_EXCLUDE fun isExcluded() = state == STATE_EXCLUDE
companion object { companion object {
const val STATE_IGNORE = 0 const val STATE_IGNORE = 0
const val STATE_INCLUDE = 1 const val STATE_INCLUDE = 1
const val STATE_EXCLUDE = 2 const val STATE_EXCLUDE = 2
} }
} }
abstract class Sort<V>(name: String, val values: Array<V>, state: Selection? = null)
: Filter<Sort.Selection?>(name, state) {
data class Selection(val index: Int, val ascending: Boolean)
}
} }

View File

@ -107,6 +107,10 @@ class Batoto : ParsedOnlineSource(), LoginSource {
val sel = if (filter.state) filter.valTrue else filter.valFalse val sel = if (filter.state) filter.valTrue else filter.valFalse
if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) 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) 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 TextField(name: String, val key: String) : Filter.Text(name)
private class ListField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.List<ListValue>(name, values, state) private class ListField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.List<ListValue>(name, values, state)
private class Flag(name: String, val key: String, val valTrue: String, val valFalse: String) : Filter.CheckBox(name) private class Flag(name: String, val key: String, val valTrue: String, val valFalse: String) : Filter.CheckBox(name)
private class OrderBy() : Filter.Sort<String>("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) => { // [...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})` // 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"))), 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(), Status(),
Flag("Exclude mature", "mature", "m", ""), 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), Filter.Separator(),
Flag("Ascending order", "order", "asc", "desc"), OrderBy(),
Filter.Separator(),
Filter.Header("Genres"), Filter.Header("Genres"),
ListField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))), ListField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))),
Genre("4-Koma", 40), Genre("4-Koma", 40),

View File

@ -24,7 +24,7 @@ class Mangafox : ParsedOnlineSource() {
override val supportsLatest = true override val supportsLatest = true
override fun popularMangaSelector() = "div#mangalist > ul.list > li" override fun popularMangaSelector() = "div#mangalist > ul.list > li"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
val pageStr = if (page != 1) "$page.htm" else "" val pageStr = if (page != 1) "$page.htm" else ""
return GET("$baseUrl/directory/$pageStr", headers) return GET("$baseUrl/directory/$pageStr", headers)
@ -60,8 +60,11 @@ class Mangafox : ParsedOnlineSource() {
when (filter) { when (filter) {
is Genre -> url.addQueryParameter(filter.id, filter.state.toString()) is Genre -> url.addQueryParameter(filter.id, filter.state.toString())
is TextField -> url.addQueryParameter(filter.key, filter.state) is TextField -> url.addQueryParameter(filter.key, filter.state)
is ListField -> url.addQueryParameter(filter.key, filter.values[filter.state].value) is Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString())
is Order -> url.addQueryParameter("order", if (filter.state) "az" else "za") 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()) 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 Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name)
private class TextField(name: String, val key: String) : Filter.Text(name) private class TextField(name: String, val key: String) : Filter.Text(name)
private class ListField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.List<ListValue>(name, values, state) private class Type() : Filter.List<String>("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua"))
private class Order() : Filter.CheckBox("Ascending order") private class OrderBy() : Filter.Sort<String>("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') // $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n')
// on http://mangafox.me/search.php // on http://mangafox.me/search.php
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "author"), TextField("Author", "author"),
TextField("Artist", "artist"), 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"), 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), Filter.Separator(),
Order(), OrderBy(),
Filter.Separator(),
Filter.Header("Genres"), Filter.Header("Genres"),
Genre("Action"), Genre("Action"),
Genre("Adult"), Genre("Adult"),

View File

@ -63,8 +63,11 @@ class Mangahere : ParsedOnlineSource() {
is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state]) is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state])
is Genre -> url.addQueryParameter(filter.id, filter.state.toString()) is Genre -> url.addQueryParameter(filter.id, filter.state.toString())
is TextField -> url.addQueryParameter(filter.key, filter.state) is TextField -> url.addQueryParameter(filter.key, filter.state)
is ListField -> url.addQueryParameter(filter.key, filter.values[filter.state].value) is Type -> url.addQueryParameter("direction", arrayOf("", "rl", "lr")[filter.state])
is Order -> url.addQueryParameter("order", if (filter.state) "az" else "za") 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()) url.addQueryParameter("page", page.toString())
@ -166,18 +169,21 @@ class Mangahere : ParsedOnlineSource() {
private class Status() : Filter.TriState("Completed") private class Status() : Filter.TriState("Completed")
private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(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 TextField(name: String, val key: String) : Filter.Text(name)
private class ListField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.List<ListValue>(name, values, state) private class Type() : Filter.List<String>("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)"))
private class Order() : Filter.CheckBox("Ascending order") private class OrderBy() : Filter.Sort<String>("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') // [...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 // http://www.mangahere.co/advsearch.htm
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "author"), TextField("Author", "author"),
TextField("Artist", "artist"), 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(), 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), Filter.Separator(),
Order(), OrderBy(),
Filter.Separator(),
Filter.Header("Genres"), Filter.Header("Genres"),
Genre("Action"), Genre("Action"),
Genre("Adventure"), Genre("Adventure"),

View File

@ -30,7 +30,7 @@ class Mangasee : ParsedOnlineSource() {
override fun popularMangaSelector() = "div.requested > div.row" override fun popularMangaSelector() = "div.requested > div.row"
override fun popularMangaRequest(page: Int): Request { 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()) return POST(requestUrl, headers, body.build())
} }
@ -54,14 +54,17 @@ class Mangasee : ParsedOnlineSource() {
var genresNo: String? = null var genresNo: String? = null
for (filter in if (filters.isEmpty()) getFilterList() else filters) { for (filter in if (filters.isEmpty()) getFilterList() else filters) {
when (filter) { when (filter) {
is Sort -> filter.values[filter.state].keys.forEachIndexed { i, s -> is Sort -> {
url.addQueryParameter(s, filter.values[filter.state].values[i]) 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 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 TextField -> if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state)
is Genre -> when (filter.state) { is Genre -> when (filter.state) {
Filter.TriState.STATE_INCLUDE -> genres = if (genres == null) filter.id else genres + "," + 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.id else genresNo + "," + filter.id 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 override fun toString(): String = name
} }
private class Sort(name: String, values: Array<SortOption>, state: Int = 0) : Filter.List<SortOption>(name, values, state) private class Sort() : Filter.Sort<String>("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false))
private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) private class Genre(name: String) : Filter.TriState(name)
private class TextField(name: String, val key: String) : Filter.Text(name) private class TextField(name: String, val key: String) : Filter.Text(name)
private class ListField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.List<String>(name, values, state) private class ListField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.List<String>(name, values, state)
@ -166,16 +169,12 @@ class Mangasee : ParsedOnlineSource() {
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Years", "year"), TextField("Years", "year"),
TextField("Author", "author"), 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("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")),
ListField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")), 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")), ListField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")),
Filter.Separator(),
Sort(),
Filter.Separator(),
Filter.Header("Genres"), Filter.Header("Genres"),
Genre("Action"), Genre("Action"),
Genre("Adult"), Genre("Adult"),

View File

@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.ui.catalogue
import android.content.Context import android.content.Context
import android.support.graphics.drawable.VectorDrawableCompat import android.support.graphics.drawable.VectorDrawableCompat
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.*
import android.widget.TextView
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.data.source.model.Filter
import eu.kanade.tachiyomi.data.source.model.FilterList 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 { override fun getItemViewType(position: Int): Int {
return when (items[position]) { return when (items[position]) {
is Filter.Header -> VIEW_TYPE_HEADER is Filter.Header -> VIEW_TYPE_HEADER
is Filter.Separator -> VIEW_TYPE_SEPARATOR
is Filter.CheckBox -> VIEW_TYPE_CHECKBOX is Filter.CheckBox -> VIEW_TYPE_CHECKBOX
is Filter.TriState -> VIEW_TYPE_MULTISTATE is Filter.TriState -> VIEW_TYPE_MULTISTATE
is Filter.List<*> -> VIEW_TYPE_LIST is Filter.List<*> -> VIEW_TYPE_LIST
is Filter.Text -> VIEW_TYPE_TEXT is Filter.Text -> VIEW_TYPE_TEXT
is Filter.Sort<*> -> VIEW_TYPE_SORT
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
return when (viewType) { return when (viewType) {
VIEW_TYPE_HEADER -> HeaderHolder(parent) VIEW_TYPE_HEADER -> HeaderHolder(parent)
VIEW_TYPE_SEPARATOR -> SeparatorHolder(parent)
VIEW_TYPE_CHECKBOX -> CheckboxHolder(parent, null) VIEW_TYPE_CHECKBOX -> CheckboxHolder(parent, null)
VIEW_TYPE_MULTISTATE -> MultiStateHolder(parent, null).apply { VIEW_TYPE_MULTISTATE -> MultiStateHolder(parent, null).apply {
// Adjust view with checkbox // Adjust view with checkbox
@ -73,6 +76,7 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs:
} }
VIEW_TYPE_LIST -> SpinnerHolder(parent) VIEW_TYPE_LIST -> SpinnerHolder(parent)
VIEW_TYPE_TEXT -> EditTextHolder(parent) VIEW_TYPE_TEXT -> EditTextHolder(parent)
VIEW_TYPE_SORT -> SortHolder(parent)
else -> throw Exception("Unknown view type") 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<MultiStateHolder>(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
}
}
}
} }