Filters with flexible adapter

This commit is contained in:
len 2017-01-14 22:38:31 +01:00
parent 7b9f5d0e9f
commit 90a99dde1f
26 changed files with 752 additions and 209 deletions

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.source.model
sealed class Filter<T>(val name: String, var state: T) {
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 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 CheckBox(name: String, state: Boolean = false) : Filter<Boolean>(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
}
}
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) {
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
}
}

View File

@ -1,14 +1,7 @@
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())
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 }
}
}

View File

@ -99,7 +99,7 @@ class Batoto : ParsedOnlineSource(), LoginSource {
is TextField -> {
if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state)
}
is ListField -> {
is SelectField -> {
val sel = filter.values[filter.state].value
if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel)
}
@ -290,9 +290,9 @@ class Batoto : ParsedOnlineSource(), LoginSource {
private class Status : Filter.TriState("Completed")
private class Genre(name: String, val id: Int) : Filter.TriState(name)
private class TextField(name: String, val key: String) : Filter.Text(name)
private class ListField(name: String, val key: String, values: Array<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 OrderBy() : Filter.Sort<String>("Order by",
private class OrderBy() : Filter.Sort("Order by",
arrayOf("Title", "Author", "Artist", "Rating", "Views", "Last Update"),
Filter.Sort.Selection(4, false))
@ -302,14 +302,14 @@ class Batoto : ParsedOnlineSource(), LoginSource {
// on https://bato.to/search
override fun getFilterList() = FilterList(
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(),
Flag("Exclude mature", "mature", "m", ""),
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"))),
SelectField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))),
Genre("4-Koma", 40),
Genre("Action", 1),
Genre("Adventure", 2),

View File

@ -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)
(if (filters.isEmpty()) getFilterList() else filters).forEach { 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 Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString())
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 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 OrderBy() : Filter.Sort<String>("Order by",
private class Type : Filter.Select<String>("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))
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(
TextField("Author", "author"),
TextField("Artist", "artist"),
Type(),
Genre("Completed", "is_completed"),
Status(),
Filter.Separator(),
OrderBy(),
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("Adult"),
Genre("Adventure"),

View File

@ -162,15 +162,11 @@ class Mangahere : ParsedOnlineSource() {
override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src")
private data class ListValue(val name: String, val value: String) {
override fun toString(): String = name
}
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 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 OrderBy() : Filter.Sort<String>("Order by",
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("Order by",
arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"),
Filter.Sort.Selection(2, false))

View File

@ -60,7 +60,7 @@ class Mangasee : ParsedOnlineSource() {
if (filter.state?.ascending != true)
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 Genre -> when (filter.state) {
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")
private data class SortOption(val name: String, val keys: Array<String>, val values: Array<String>) {
override fun toString(): String = name
}
private class Sort() : Filter.Sort<String>("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false))
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<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')
// http://mangasee.co/advanced-search/
override fun getFilterList() = FilterList(
TextField("Years", "year"),
TextField("Author", "author"),
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")),
SelectField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")),
SelectField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")),
SelectField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")),
Filter.Separator(),
Sort(),
Filter.Separator(),

View File

@ -164,7 +164,7 @@ class Readmangatoday : ParsedOnlineSource() {
private class Status() : Filter.TriState("Completed")
private class Genre(name: String, val id: Int) : Filter.TriState(name)
private class TextField(name: String, val key: String) : Filter.Text(name)
private class Type() : Filter.List<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')
// http://www.readmanga.today/advanced-search

View File

@ -237,10 +237,10 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
}
navView.onSearchClicked = {
val allDefault = navView.adapter.items.hasSameState(presenter.source.getFilterList())
val allDefault = presenter.sourceFilters == presenter.source.getFilterList()
showProgressBar()
adapter.clear()
presenter.setSourceFilter(if (allDefault) FilterList() else navView.adapter.items)
presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters)
}
navView.onResetClicked = {

View File

@ -1,29 +1,24 @@
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.*
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.data.source.model.Filter
import eu.kanade.tachiyomi.data.source.model.FilterList
import eu.kanade.tachiyomi.util.dpToPx
import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.ui.catalogue.filter.*
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
import eu.kanade.tachiyomi.widget.SimpleNavigationView
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
import kotlinx.android.synthetic.main.catalogue_drawer_content.view.*
class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
: SimpleNavigationView(context, attrs) {
val adapter = Adapter()
val adapter = FlexibleAdapter<IFlexible<*>>(null)
var onSearchClicked = {}
@ -32,170 +27,52 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs:
init {
recycler.adapter = adapter
val view = inflate(R.layout.catalogue_drawer_content)
(view as ViewGroup).addView(recycler)
((view as ViewGroup).getChildAt(1) as ViewGroup).addView(recycler)
addView(view)
search_btn.setOnClickListener { onSearchClicked() }
reset_btn.setOnClickListener { onResetClicked() }
adapter.setDisplayHeadersAtStartUp(true)
adapter.setStickyHeaders(true)
}
fun setFilters(items: FilterList) {
adapter.items = items
adapter.notifyDataSetChanged()
fun setFilters(filters: FilterList) {
val items = filters.mapNotNull {
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<*, *>
}
inner class Adapter : RecyclerView.Adapter<Holder>() {
var items: FilterList = FilterList()
override fun getItemCount(): Int {
return items.size
}
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
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)
subItems.forEach { it.header = group }
group.subItems = subItems
group
}
is Filter.Sort -> {
val group = SortGroup(it)
val subItems = it.values.mapNotNull {
SortItem(it, group)
}
group.subItems = subItems
group
}
else -> null
}
}
}
}
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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -17,7 +17,6 @@ class CategoryAdapter(private val activity: CategoryActivity) :
* Called when item is released.
*/
fun onItemReleased() {
// Update database
activity.onItemReleased()
}

View 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="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>

View 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>

View File

@ -25,4 +25,8 @@
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View 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>

View 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>

View 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>