mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-17 15:19:15 +01:00
Filters with flexible adapter
This commit is contained in:
parent
7b9f5d0e9f
commit
90a99dde1f
@ -3,7 +3,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)
|
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 Select<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)
|
||||||
abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter<Int>(name, state) {
|
abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter<Int>(name, state) {
|
||||||
@ -17,9 +17,24 @@ sealed class Filter<T>(val name: String, var state: T) {
|
|||||||
const val STATE_EXCLUDE = 2
|
const val STATE_EXCLUDE = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
abstract class Group<V>(name: String, state: List<V>): Filter<List<V>>(name, state)
|
||||||
|
|
||||||
abstract class Sort<V>(name: String, val values: Array<V>, state: Selection? = null)
|
abstract class Sort(name: String, val values: Array<String>, state: Selection? = null)
|
||||||
: Filter<Sort.Selection?>(name, state) {
|
: Filter<Sort.Selection?>(name, state) {
|
||||||
data class Selection(val index: Int, val ascending: Boolean)
|
data class Selection(val index: Int, val ascending: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Filter<*>) return false
|
||||||
|
|
||||||
|
return name == other.name && state == other.state
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = name.hashCode()
|
||||||
|
result = 31 * result + (state?.hashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,14 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.data.source.model
|
package eu.kanade.tachiyomi.data.source.model
|
||||||
|
|
||||||
class FilterList(list: List<Filter<*>>) : List<Filter<*>> by list {
|
data class FilterList(val list: List<Filter<*>>) : List<Filter<*>> by list {
|
||||||
|
|
||||||
constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
|
constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
|
||||||
|
|
||||||
fun hasSameState(other: FilterList): Boolean {
|
|
||||||
if (size != other.size) return false
|
|
||||||
|
|
||||||
return (0..lastIndex)
|
|
||||||
.all { get(it).javaClass == other[it].javaClass && get(it).state == other[it].state }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -99,7 +99,7 @@ class Batoto : ParsedOnlineSource(), LoginSource {
|
|||||||
is TextField -> {
|
is TextField -> {
|
||||||
if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state)
|
if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state)
|
||||||
}
|
}
|
||||||
is ListField -> {
|
is SelectField -> {
|
||||||
val sel = filter.values[filter.state].value
|
val sel = filter.values[filter.state].value
|
||||||
if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel)
|
if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel)
|
||||||
}
|
}
|
||||||
@ -290,9 +290,9 @@ class Batoto : ParsedOnlineSource(), LoginSource {
|
|||||||
private class Status : Filter.TriState("Completed")
|
private class Status : Filter.TriState("Completed")
|
||||||
private class Genre(name: String, val id: Int) : Filter.TriState(name)
|
private class Genre(name: String, val id: Int) : 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 SelectField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.Select<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",
|
private class OrderBy() : Filter.Sort("Order by",
|
||||||
arrayOf("Title", "Author", "Artist", "Rating", "Views", "Last Update"),
|
arrayOf("Title", "Author", "Artist", "Rating", "Views", "Last Update"),
|
||||||
Filter.Sort.Selection(4, false))
|
Filter.Sort.Selection(4, false))
|
||||||
|
|
||||||
@ -302,14 +302,14 @@ class Batoto : ParsedOnlineSource(), LoginSource {
|
|||||||
// on https://bato.to/search
|
// on https://bato.to/search
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = FilterList(
|
||||||
TextField("Author", "artist_name"),
|
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"))),
|
SelectField("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", ""),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
OrderBy(),
|
OrderBy(),
|
||||||
Filter.Separator(),
|
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"))),
|
SelectField("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),
|
||||||
Genre("Action", 1),
|
Genre("Action", 1),
|
||||||
Genre("Adventure", 2),
|
Genre("Adventure", 2),
|
||||||
|
@ -58,7 +58,12 @@ class Mangafox : ParsedOnlineSource() {
|
|||||||
val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query)
|
val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query)
|
||||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||||
when (filter) {
|
when (filter) {
|
||||||
is Genre -> url.addQueryParameter(filter.id, filter.state.toString())
|
is Status -> url.addQueryParameter(filter.id, filter.state.toString())
|
||||||
|
is GenreList -> {
|
||||||
|
filter.state.forEach { genre ->
|
||||||
|
url.addQueryParameter(genre.id, genre.state.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
is TextField -> url.addQueryParameter(filter.key, filter.state)
|
is TextField -> url.addQueryParameter(filter.key, filter.state)
|
||||||
is Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString())
|
is Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString())
|
||||||
is OrderBy -> {
|
is OrderBy -> {
|
||||||
@ -161,24 +166,29 @@ class Mangafox : ParsedOnlineSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Status(val id: String = "is_completed") : 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 Type() : Filter.List<String>("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua"))
|
private class Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua"))
|
||||||
private class OrderBy() : Filter.Sort<String>("Order by",
|
private class OrderBy : Filter.Sort("Order by",
|
||||||
arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"),
|
arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"),
|
||||||
Filter.Sort.Selection(2, false))
|
Filter.Sort.Selection(2, false))
|
||||||
|
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
||||||
|
|
||||||
// $('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(
|
override fun getFilterList() = FilterList(
|
||||||
TextField("Author", "author"),
|
TextField("Author", "author"),
|
||||||
TextField("Artist", "artist"),
|
TextField("Artist", "artist"),
|
||||||
Type(),
|
Type(),
|
||||||
Genre("Completed", "is_completed"),
|
Status(),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
OrderBy(),
|
OrderBy(),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
Filter.Header("Genres"),
|
GenreList(getGenreList())
|
||||||
|
)
|
||||||
|
|
||||||
|
// $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n')
|
||||||
|
// on http://mangafox.me/search.php
|
||||||
|
private fun getGenreList() = listOf(
|
||||||
Genre("Action"),
|
Genre("Action"),
|
||||||
Genre("Adult"),
|
Genre("Adult"),
|
||||||
Genre("Adventure"),
|
Genre("Adventure"),
|
||||||
|
@ -162,15 +162,11 @@ class Mangahere : ParsedOnlineSource() {
|
|||||||
|
|
||||||
override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src")
|
override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src")
|
||||||
|
|
||||||
private data class ListValue(val name: String, val value: String) {
|
private class Status : Filter.TriState("Completed")
|
||||||
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 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 Type() : Filter.List<String>("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)"))
|
private class Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)"))
|
||||||
private class OrderBy() : Filter.Sort<String>("Order by",
|
private class OrderBy : Filter.Sort("Order by",
|
||||||
arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"),
|
arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"),
|
||||||
Filter.Sort.Selection(2, false))
|
Filter.Sort.Selection(2, false))
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class Mangasee : ParsedOnlineSource() {
|
|||||||
if (filter.state?.ascending != true)
|
if (filter.state?.ascending != true)
|
||||||
url.addQueryParameter("sortOrder", "descending")
|
url.addQueryParameter("sortOrder", "descending")
|
||||||
}
|
}
|
||||||
is ListField -> if (filter.state != 0) url.addQueryParameter(filter.key, filter.values[filter.state])
|
is SelectField -> 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.name else genres + "," + filter.name
|
Filter.TriState.STATE_INCLUDE -> genres = if (genres == null) filter.name else genres + "," + filter.name
|
||||||
@ -155,23 +155,19 @@ class Mangasee : ParsedOnlineSource() {
|
|||||||
|
|
||||||
override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src")
|
override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src")
|
||||||
|
|
||||||
private data class SortOption(val name: String, val keys: Array<String>, val values: Array<String>) {
|
private class Sort : Filter.Sort("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false))
|
||||||
override fun toString(): String = name
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Sort() : Filter.Sort<String>("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false))
|
|
||||||
private class Genre(name: String) : 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 SelectField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.Select<String>(name, values, state)
|
||||||
|
|
||||||
// [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n')
|
// [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n')
|
||||||
// http://mangasee.co/advanced-search/
|
// http://mangasee.co/advanced-search/
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = FilterList(
|
||||||
TextField("Years", "year"),
|
TextField("Years", "year"),
|
||||||
TextField("Author", "author"),
|
TextField("Author", "author"),
|
||||||
ListField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")),
|
SelectField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")),
|
||||||
ListField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")),
|
SelectField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")),
|
||||||
ListField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")),
|
SelectField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
Sort(),
|
Sort(),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
|
@ -164,7 +164,7 @@ class Readmangatoday : ParsedOnlineSource() {
|
|||||||
private class Status() : Filter.TriState("Completed")
|
private class Status() : Filter.TriState("Completed")
|
||||||
private class Genre(name: String, val id: Int) : Filter.TriState(name)
|
private class Genre(name: String, val id: Int) : 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 Type() : Filter.List<String>("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua"))
|
private class Type() : Filter.Select<String>("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')
|
// [...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
|
// http://www.readmanga.today/advanced-search
|
||||||
|
@ -237,10 +237,10 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
navView.onSearchClicked = {
|
navView.onSearchClicked = {
|
||||||
val allDefault = navView.adapter.items.hasSameState(presenter.source.getFilterList())
|
val allDefault = presenter.sourceFilters == presenter.source.getFilterList()
|
||||||
showProgressBar()
|
showProgressBar()
|
||||||
adapter.clear()
|
adapter.clear()
|
||||||
presenter.setSourceFilter(if (allDefault) FilterList() else navView.adapter.items)
|
presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters)
|
||||||
}
|
}
|
||||||
|
|
||||||
navView.onResetClicked = {
|
navView.onResetClicked = {
|
||||||
|
@ -1,29 +1,24 @@
|
|||||||
package eu.kanade.tachiyomi.ui.catalogue
|
package eu.kanade.tachiyomi.ui.catalogue
|
||||||
|
|
||||||
import android.content.Context
|
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.util.AttributeSet
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
|
import eu.davidea.flexibleadapter.items.ISectionable
|
||||||
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
|
||||||
import eu.kanade.tachiyomi.util.dpToPx
|
import eu.kanade.tachiyomi.ui.catalogue.filter.*
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
|
||||||
import eu.kanade.tachiyomi.util.inflate
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
|
|
||||||
import eu.kanade.tachiyomi.widget.SimpleNavigationView
|
import eu.kanade.tachiyomi.widget.SimpleNavigationView
|
||||||
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
|
|
||||||
import kotlinx.android.synthetic.main.catalogue_drawer_content.view.*
|
import kotlinx.android.synthetic.main.catalogue_drawer_content.view.*
|
||||||
|
|
||||||
|
|
||||||
class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
|
class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
|
||||||
: SimpleNavigationView(context, attrs) {
|
: SimpleNavigationView(context, attrs) {
|
||||||
|
|
||||||
val adapter = Adapter()
|
val adapter = FlexibleAdapter<IFlexible<*>>(null)
|
||||||
|
|
||||||
var onSearchClicked = {}
|
var onSearchClicked = {}
|
||||||
|
|
||||||
@ -32,170 +27,52 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs:
|
|||||||
init {
|
init {
|
||||||
recycler.adapter = adapter
|
recycler.adapter = adapter
|
||||||
val view = inflate(R.layout.catalogue_drawer_content)
|
val view = inflate(R.layout.catalogue_drawer_content)
|
||||||
(view as ViewGroup).addView(recycler)
|
((view as ViewGroup).getChildAt(1) as ViewGroup).addView(recycler)
|
||||||
addView(view)
|
addView(view)
|
||||||
|
|
||||||
search_btn.setOnClickListener { onSearchClicked() }
|
search_btn.setOnClickListener { onSearchClicked() }
|
||||||
reset_btn.setOnClickListener { onResetClicked() }
|
reset_btn.setOnClickListener { onResetClicked() }
|
||||||
|
|
||||||
|
adapter.setDisplayHeadersAtStartUp(true)
|
||||||
|
adapter.setStickyHeaders(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFilters(items: FilterList) {
|
fun setFilters(filters: FilterList) {
|
||||||
adapter.items = items
|
val items = filters.mapNotNull {
|
||||||
adapter.notifyDataSetChanged()
|
when (it) {
|
||||||
|
is Filter.Header -> HeaderItem(it)
|
||||||
|
is Filter.Separator -> SeparatorItem(it)
|
||||||
|
is Filter.CheckBox -> CheckboxItem(it)
|
||||||
|
is Filter.TriState -> TriStateItem(it)
|
||||||
|
is Filter.Text -> TextItem(it)
|
||||||
|
is Filter.Select<*> -> SelectItem(it)
|
||||||
|
is Filter.Group<*> -> {
|
||||||
|
val group = GroupItem(it)
|
||||||
|
val subItems = it.state.mapNotNull {
|
||||||
|
when (it) {
|
||||||
|
is Filter.CheckBox -> CheckboxSectionItem(it)
|
||||||
|
is Filter.TriState -> TriStateSectionItem(it)
|
||||||
|
is Filter.Text -> TextSectionItem(it)
|
||||||
|
is Filter.Select<*> -> SelectSectionItem(it)
|
||||||
|
else -> null
|
||||||
|
} as? ISectionable<*, *>
|
||||||
}
|
}
|
||||||
|
subItems.forEach { it.header = group }
|
||||||
inner class Adapter : RecyclerView.Adapter<Holder>() {
|
group.subItems = subItems
|
||||||
|
group
|
||||||
var items: FilterList = FilterList()
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return items.size
|
|
||||||
}
|
}
|
||||||
|
is Filter.Sort -> {
|
||||||
override fun getItemViewType(position: Int): Int {
|
val group = SortGroup(it)
|
||||||
return when (items[position]) {
|
val subItems = it.values.mapNotNull {
|
||||||
is Filter.Header -> VIEW_TYPE_HEADER
|
SortItem(it, group)
|
||||||
is Filter.Separator -> VIEW_TYPE_SEPARATOR
|
}
|
||||||
is Filter.CheckBox -> VIEW_TYPE_CHECKBOX
|
group.subItems = subItems
|
||||||
is Filter.TriState -> VIEW_TYPE_MULTISTATE
|
group
|
||||||
is Filter.List<*> -> VIEW_TYPE_LIST
|
}
|
||||||
is Filter.Text -> VIEW_TYPE_TEXT
|
else -> null
|
||||||
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
|
|
||||||
text.setPadding(4.dpToPx, 0, 0, 0)
|
|
||||||
text.compoundDrawablePadding = 20.dpToPx
|
|
||||||
}
|
|
||||||
VIEW_TYPE_LIST -> SpinnerHolder(parent)
|
|
||||||
VIEW_TYPE_TEXT -> EditTextHolder(parent)
|
|
||||||
VIEW_TYPE_SORT -> SortHolder(parent)
|
|
||||||
else -> throw Exception("Unknown view type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: Holder, position: Int) {
|
|
||||||
val filter = items[position]
|
|
||||||
when (filter) {
|
|
||||||
is Filter.Header -> {
|
|
||||||
val view = holder.itemView as TextView
|
|
||||||
view.visibility = if (filter.name.isEmpty()) View.GONE else View.VISIBLE
|
|
||||||
view.text = filter.name
|
|
||||||
}
|
|
||||||
is Filter.CheckBox -> {
|
|
||||||
val view = (holder as CheckboxHolder).check
|
|
||||||
view.text = filter.name
|
|
||||||
view.isChecked = filter.state
|
|
||||||
holder.itemView.setOnClickListener {
|
|
||||||
view.toggle()
|
|
||||||
filter.state = view.isChecked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Filter.TriState -> {
|
|
||||||
val view = (holder as MultiStateHolder).text
|
|
||||||
view.text = filter.name
|
|
||||||
|
|
||||||
fun getIcon() = VectorDrawableCompat.create(view.resources, when (filter.state) {
|
|
||||||
Filter.TriState.STATE_IGNORE -> R.drawable.ic_check_box_outline_blank_24dp
|
|
||||||
Filter.TriState.STATE_INCLUDE -> R.drawable.ic_check_box_24dp
|
|
||||||
Filter.TriState.STATE_EXCLUDE -> R.drawable.ic_check_box_x_24dp
|
|
||||||
else -> throw Exception("Unknown state")
|
|
||||||
}, null)?.apply {
|
|
||||||
val color = if (filter.state == Filter.TriState.STATE_INCLUDE)
|
|
||||||
R.attr.colorAccent
|
|
||||||
else
|
|
||||||
android.R.attr.textColorSecondary
|
|
||||||
|
|
||||||
setTint(view.context.getResourceColor(color))
|
|
||||||
}
|
|
||||||
|
|
||||||
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
|
|
||||||
holder.itemView.setOnClickListener {
|
|
||||||
filter.state = (filter.state + 1) % 3
|
|
||||||
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Filter.List<*> -> {
|
|
||||||
holder as SpinnerHolder
|
|
||||||
holder.text.text = filter.name + ": "
|
|
||||||
|
|
||||||
val spinner = holder.spinner
|
|
||||||
spinner.prompt = filter.name
|
|
||||||
spinner.adapter = ArrayAdapter<Any>(holder.itemView.context,
|
|
||||||
android.R.layout.simple_spinner_item, filter.values).apply {
|
|
||||||
setDropDownViewResource(R.layout.spinner_item)
|
|
||||||
}
|
|
||||||
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { position ->
|
|
||||||
filter.state = position
|
|
||||||
}
|
|
||||||
spinner.setSelection(filter.state)
|
|
||||||
}
|
|
||||||
is Filter.Text -> {
|
|
||||||
holder as EditTextHolder
|
|
||||||
holder.wrapper.visibility = if (filter.name.isEmpty()) View.GONE else View.VISIBLE
|
|
||||||
holder.wrapper.hint = filter.name
|
|
||||||
holder.edit.setText(filter.state)
|
|
||||||
holder.edit.addTextChangedListener(object : SimpleTextWatcher() {
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
|
||||||
filter.state = s.toString()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
adapter.updateDataSet(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
|
||||||
|
open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem<CheckboxItem.Holder>() {
|
||||||
|
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.navigation_view_checkbox
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
|
||||||
|
return Holder(inflater.inflate(layoutRes, parent, false), adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
val view = holder.check
|
||||||
|
view.text = filter.name
|
||||||
|
view.isChecked = filter.state
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
view.toggle()
|
||||||
|
filter.state = view.isChecked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other is CheckboxItem) {
|
||||||
|
return filter == other.filter
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
|
val check = itemView.findViewById(R.id.nav_view_item) as CheckBox
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
|
||||||
|
import eu.davidea.flexibleadapter.items.ISectionable
|
||||||
|
import eu.davidea.viewholders.ExpandableViewHolder
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.util.setVectorCompat
|
||||||
|
|
||||||
|
class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<GroupItem.Holder, ISectionable<*, *>>() {
|
||||||
|
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.navigation_view_group
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
|
||||||
|
return Holder(inflater.inflate(layoutRes, parent, false), adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
holder.title.text = filter.name
|
||||||
|
|
||||||
|
holder.icon.setVectorCompat(if (isExpanded)
|
||||||
|
R.drawable.ic_expand_more_white_24dp
|
||||||
|
else
|
||||||
|
R.drawable.ic_chevron_right_white_24dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other is GroupItem) {
|
||||||
|
return filter == other.filter
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter, true) {
|
||||||
|
|
||||||
|
val title = itemView.findViewById(R.id.title) as TextView
|
||||||
|
val icon = itemView.findViewById(R.id.expand_icon) as ImageView
|
||||||
|
|
||||||
|
override fun shouldNotifyParentOnClick(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.support.design.R
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
|
||||||
|
class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem<HeaderItem.Holder>() {
|
||||||
|
|
||||||
|
@SuppressLint("PrivateResource")
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.design_navigation_item_subheader
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
|
||||||
|
return Holder(inflater.inflate(layoutRes, parent, false), adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
val view = holder.itemView as TextView
|
||||||
|
view.visibility = if (filter.name.isEmpty()) View.GONE else View.VISIBLE
|
||||||
|
view.text = filter.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other is HeaderItem) {
|
||||||
|
return filter == other.filter
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, val adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter)
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import eu.davidea.flexibleadapter.items.ISectionable
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
|
||||||
|
class TriStateSectionItem(filter: Filter.TriState) : TriStateItem(filter), ISectionable<TriStateItem.Holder, GroupItem> {
|
||||||
|
|
||||||
|
private var head: GroupItem? = null
|
||||||
|
|
||||||
|
override fun getHeader(): GroupItem? = head
|
||||||
|
|
||||||
|
override fun setHeader(header: GroupItem?) {
|
||||||
|
head = header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextSectionItem(filter: Filter.Text) : TextItem(filter), ISectionable<TextItem.Holder, GroupItem> {
|
||||||
|
|
||||||
|
private var head: GroupItem? = null
|
||||||
|
|
||||||
|
override fun getHeader(): GroupItem? = head
|
||||||
|
|
||||||
|
override fun setHeader(header: GroupItem?) {
|
||||||
|
head = header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckboxSectionItem(filter: Filter.CheckBox) : CheckboxItem(filter), ISectionable<CheckboxItem.Holder, GroupItem> {
|
||||||
|
|
||||||
|
private var head: GroupItem? = null
|
||||||
|
|
||||||
|
override fun getHeader(): GroupItem? = head
|
||||||
|
|
||||||
|
override fun setHeader(header: GroupItem?) {
|
||||||
|
head = header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectSectionItem(filter: Filter.Select<*>) : SelectItem(filter), ISectionable<SelectItem.Holder, GroupItem> {
|
||||||
|
|
||||||
|
private var head: GroupItem? = null
|
||||||
|
|
||||||
|
override fun getHeader(): GroupItem? = head
|
||||||
|
|
||||||
|
override fun setHeader(header: GroupItem?) {
|
||||||
|
head = header
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
|
||||||
|
|
||||||
|
open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<SelectItem.Holder>() {
|
||||||
|
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.navigation_view_spinner
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
|
||||||
|
return Holder(inflater.inflate(layoutRes, parent, false), adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
holder.text.text = filter.name + ": "
|
||||||
|
|
||||||
|
val spinner = holder.spinner
|
||||||
|
spinner.prompt = filter.name
|
||||||
|
spinner.adapter = ArrayAdapter<Any>(holder.itemView.context,
|
||||||
|
android.R.layout.simple_spinner_item, filter.values).apply {
|
||||||
|
setDropDownViewResource(R.layout.spinner_item)
|
||||||
|
}
|
||||||
|
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { position ->
|
||||||
|
filter.state = position
|
||||||
|
}
|
||||||
|
spinner.setSelection(filter.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other is SelectItem) {
|
||||||
|
return filter == other.filter
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
|
val text = itemView.findViewById(R.id.nav_view_item_text) as TextView
|
||||||
|
val spinner = itemView.findViewById(R.id.nav_view_item) as Spinner
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.support.design.R
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
|
||||||
|
class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem<SeparatorItem.Holder>() {
|
||||||
|
|
||||||
|
@SuppressLint("PrivateResource")
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.design_navigation_item_separator
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
|
||||||
|
return Holder(inflater.inflate(layoutRes, parent, false), adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other is SeparatorItem) {
|
||||||
|
return filter == other.filter
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, val adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter)
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
|
||||||
|
import eu.davidea.flexibleadapter.items.ISectionable
|
||||||
|
import eu.davidea.viewholders.ExpandableViewHolder
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.util.setVectorCompat
|
||||||
|
|
||||||
|
class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGroup.Holder, ISectionable<*, *>>() {
|
||||||
|
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.navigation_view_sort
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
|
||||||
|
return Holder(inflater.inflate(layoutRes, parent, false), adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
holder.title.text = filter.name
|
||||||
|
|
||||||
|
holder.icon.setVectorCompat(if (isExpanded)
|
||||||
|
R.drawable.ic_expand_more_white_24dp
|
||||||
|
else
|
||||||
|
R.drawable.ic_chevron_right_white_24dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other is SortGroup) {
|
||||||
|
return filter == other.filter
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter, true) {
|
||||||
|
|
||||||
|
val title = itemView.findViewById(R.id.title) as TextView
|
||||||
|
val icon = itemView.findViewById(R.id.expand_icon) as ImageView
|
||||||
|
|
||||||
|
override fun shouldNotifyParentOnClick(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import android.support.graphics.drawable.VectorDrawableCompat
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CheckedTextView
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
|
|
||||||
|
class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem<SortItem.Holder, SortGroup>(group) {
|
||||||
|
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.navigation_view_sort_item
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
|
||||||
|
return Holder(inflater.inflate(layoutRes, parent, false), adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
val view = holder.text
|
||||||
|
view.text = name
|
||||||
|
val filter = group.filter
|
||||||
|
|
||||||
|
val i = filter.values.indexOf(name)
|
||||||
|
|
||||||
|
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(view.context, R.drawable.empty_drawable_32dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
val pre = filter.state?.index ?: i
|
||||||
|
if (pre != i) {
|
||||||
|
filter.state = Filter.Sort.Selection(i, false)
|
||||||
|
} else {
|
||||||
|
filter.state = Filter.Sort.Selection(i, filter.state?.ascending == false)
|
||||||
|
}
|
||||||
|
|
||||||
|
group.subItems.forEach { adapter.notifyItemChanged(adapter.getGlobalPositionOf(it)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other is SortItem) {
|
||||||
|
return name == other.name && group == other.group
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = name.hashCode()
|
||||||
|
result = 31 * result + group.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
|
val text = itemView.findViewById(R.id.nav_view_item) as CheckedTextView
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import android.support.design.widget.TextInputLayout
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.EditText
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
|
||||||
|
|
||||||
|
open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Holder>() {
|
||||||
|
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.navigation_view_text
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
|
||||||
|
return Holder(inflater.inflate(layoutRes, parent, false), adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
holder.wrapper.visibility = if (filter.name.isEmpty()) View.GONE else View.VISIBLE
|
||||||
|
holder.wrapper.hint = filter.name
|
||||||
|
holder.edit.setText(filter.state)
|
||||||
|
holder.edit.addTextChangedListener(object : SimpleTextWatcher() {
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
|
filter.state = s.toString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other is TextItem) {
|
||||||
|
return filter == other.filter
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
|
val wrapper = itemView.findViewById(R.id.nav_view_item_wrapper) as TextInputLayout
|
||||||
|
val edit = itemView.findViewById(R.id.nav_view_item) as EditText
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.catalogue.filter
|
||||||
|
|
||||||
|
import android.support.design.R
|
||||||
|
import android.support.graphics.drawable.VectorDrawableCompat
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CheckedTextView
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.util.dpToPx
|
||||||
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
|
import eu.kanade.tachiyomi.R as TR
|
||||||
|
|
||||||
|
open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriStateItem.Holder>() {
|
||||||
|
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return TR.layout.navigation_view_checkedtext
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup?): Holder {
|
||||||
|
return Holder(inflater.inflate(layoutRes, parent, false), adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
val view = holder.text
|
||||||
|
view.text = filter.name
|
||||||
|
|
||||||
|
fun getIcon() = VectorDrawableCompat.create(view.resources, when (filter.state) {
|
||||||
|
Filter.TriState.STATE_IGNORE -> TR.drawable.ic_check_box_outline_blank_24dp
|
||||||
|
Filter.TriState.STATE_INCLUDE -> TR.drawable.ic_check_box_24dp
|
||||||
|
Filter.TriState.STATE_EXCLUDE -> TR.drawable.ic_check_box_x_24dp
|
||||||
|
else -> throw Exception("Unknown state")
|
||||||
|
}, null)?.apply {
|
||||||
|
val color = if (filter.state == Filter.TriState.STATE_INCLUDE)
|
||||||
|
R.attr.colorAccent
|
||||||
|
else
|
||||||
|
android.R.attr.textColorSecondary
|
||||||
|
|
||||||
|
setTint(view.context.getResourceColor(color))
|
||||||
|
}
|
||||||
|
|
||||||
|
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
filter.state = (filter.state + 1) % 3
|
||||||
|
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other is TriStateItem) {
|
||||||
|
return filter == other.filter
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, val adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
|
val text = itemView.findViewById(TR.id.nav_view_item) as CheckedTextView
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Align with native checkbox
|
||||||
|
text.setPadding(4.dpToPx, 0, 0, 0)
|
||||||
|
text.compoundDrawablePadding = 20.dpToPx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,7 +17,6 @@ class CategoryAdapter(private val activity: CategoryActivity) :
|
|||||||
* Called when item is released.
|
* Called when item is released.
|
||||||
*/
|
*/
|
||||||
fun onItemReleased() {
|
fun onItemReleased() {
|
||||||
// Update database
|
|
||||||
activity.onItemReleased()
|
activity.onItemReleased()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_expand_more_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_expand_more_white_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
|
||||||
|
</vector>
|
@ -25,4 +25,8 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
30
app/src/main/res/layout/navigation_view_group.xml
Normal file
30
app/src/main/res/layout/navigation_view_group.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||||
|
android:background="?colorPrimary"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||||
|
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||||
|
android:elevation="2dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||||
|
android:textColor="@color/textColorPrimaryDark"
|
||||||
|
tools:text="Header"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/expand_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
30
app/src/main/res/layout/navigation_view_sort.xml
Normal file
30
app/src/main/res/layout/navigation_view_sort.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||||
|
android:background="?colorPrimary"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||||
|
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||||
|
android:elevation="2dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||||
|
android:textColor="@color/textColorPrimaryDark"
|
||||||
|
tools:text="Header"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/expand_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
21
app/src/main/res/layout/navigation_view_sort_item.xml
Normal file
21
app/src/main/res/layout/navigation_view_sort_item.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||||
|
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||||
|
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<CheckedTextView
|
||||||
|
android:id="@+id/nav_view_item"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawablePadding="@dimen/material_component_lists_icon_left_padding"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
Loading…
Reference in New Issue
Block a user