Complete group filters

This commit is contained in:
len 2017-01-15 17:04:31 +01:00
parent e76fb7a524
commit 71ab6d38e4
18 changed files with 217 additions and 193 deletions

View file

@ -93,8 +93,18 @@ class Batoto : ParsedOnlineSource(), LoginSource {
is Status -> if (!filter.isIgnored()) { is Status -> if (!filter.isIgnored()) {
url.addQueryParameter("completed", if (filter.isExcluded()) "i" else "c") url.addQueryParameter("completed", if (filter.isExcluded()) "i" else "c")
} }
is Genre -> if (!filter.isIgnored()) { is GenreList -> {
genres += (if (filter.isExcluded()) ";e" else ";i") + filter.id filter.state.forEach { filter ->
when (filter) {
is Genre -> if (!filter.isIgnored()) {
genres += (if (filter.isExcluded()) ";e" else ";i") + filter.id
}
is SelectField -> {
val sel = filter.values[filter.state].value
if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel)
}
}
}
} }
is TextField -> { is TextField -> {
if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state)
@ -292,23 +302,25 @@ class Batoto : ParsedOnlineSource(), LoginSource {
private class TextField(name: String, val key: String) : Filter.Text(name) private class TextField(name: String, val key: String) : Filter.Text(name)
private class SelectField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.Select<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("Order by", private class GenreList(genres: List<Filter<*>>) : Filter.Group<Filter<*>>("Genres", genres)
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))
// [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => {
// const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})`
// }).join(',\n')
// on https://bato.to/search
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "artist_name"), TextField("Author", "artist_name"),
SelectField("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(),
OrderBy(), OrderBy(),
Filter.Separator(), GenreList(getGenreList())
Filter.Header("Genres"), )
// [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => {
// const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})`
// }).join(',\n')
// on https://bato.to/search
private fun getGenreList() = listOf(
SelectField("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),

View file

@ -64,7 +64,7 @@ class Kissmanga : ParsedOnlineSource() {
when (filter) { when (filter) {
is Author -> add("authorArtist", filter.state) is Author -> add("authorArtist", filter.state)
is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state]) is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state])
is Genre -> add("genres", filter.state.toString()) is GenreList -> filter.state.forEach { genre -> add("genres", genre.state.toString()) }
} }
} }
} }
@ -134,16 +134,20 @@ class Kissmanga : ParsedOnlineSource() {
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
private class Status() : Filter.TriState("Completed") private class Status : Filter.TriState("Completed")
private class Author() : Filter.Text("Author") private class Author : Filter.Text("Author")
private class Genre(name: String) : Filter.TriState(name) private class Genre(name: String) : Filter.TriState(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
// $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n')
// on http://kissmanga.com/AdvanceSearch
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Author(), Author(),
Status(), Status(),
Filter.Header("Genres"), GenreList(getGenreList())
)
// $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n')
// on http://kissmanga.com/AdvanceSearch
private fun getGenreList() = listOf(
Genre("4-Koma"), Genre("4-Koma"),
Genre("Action"), Genre("Action"),
Genre("Adult"), Genre("Adult"),

View file

@ -59,11 +59,7 @@ class Mangafox : ParsedOnlineSource() {
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) { when (filter) {
is Status -> url.addQueryParameter(filter.id, filter.state.toString()) is Status -> url.addQueryParameter(filter.id, filter.state.toString())
is GenreList -> { is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) }
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 -> {
@ -180,9 +176,7 @@ class Mangafox : ParsedOnlineSource() {
TextField("Artist", "artist"), TextField("Artist", "artist"),
Type(), Type(),
Status(), Status(),
Filter.Separator(),
OrderBy(), OrderBy(),
Filter.Separator(),
GenreList(getGenreList()) GenreList(getGenreList())
) )

View file

@ -61,7 +61,7 @@ class Mangahere : ParsedOnlineSource() {
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) { when (filter) {
is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state]) is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state])
is Genre -> url.addQueryParameter(filter.id, filter.state.toString()) is 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("direction", arrayOf("", "rl", "lr")[filter.state]) is Type -> url.addQueryParameter("direction", arrayOf("", "rl", "lr")[filter.state])
is OrderBy -> { is OrderBy -> {
@ -169,18 +169,20 @@ class Mangahere : ParsedOnlineSource() {
private class OrderBy : Filter.Sort("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)
// [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n')
// http://www.mangahere.co/advsearch.htm
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "author"), TextField("Author", "author"),
TextField("Artist", "artist"), TextField("Artist", "artist"),
Type(), Type(),
Status(), Status(),
Filter.Separator(),
OrderBy(), OrderBy(),
Filter.Separator(), GenreList(getGenreList())
Filter.Header("Genres"), )
// [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n')
// http://www.mangahere.co/advsearch.htm
private fun getGenreList() = listOf(
Genre("Action"), Genre("Action"),
Genre("Adventure"), Genre("Adventure"),
Genre("Comedy"), Genre("Comedy"),

View file

@ -50,8 +50,8 @@ class Mangasee : ParsedOnlineSource() {
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder() val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder()
if (!query.isEmpty()) url.addQueryParameter("keyword", query) if (!query.isEmpty()) url.addQueryParameter("keyword", query)
var genres: String? = null val genres = mutableListOf<String>()
var genresNo: String? = null val genresNo = mutableListOf<String>()
for (filter in if (filters.isEmpty()) getFilterList() else filters) { for (filter in if (filters.isEmpty()) getFilterList() else filters) {
when (filter) { when (filter) {
is Sort -> { is Sort -> {
@ -62,14 +62,16 @@ class Mangasee : ParsedOnlineSource() {
} }
is SelectField -> 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 GenreList -> filter.state.forEach { genre ->
Filter.TriState.STATE_INCLUDE -> genres = if (genres == null) filter.name else genres + "," + filter.name when (genre.state) {
Filter.TriState.STATE_EXCLUDE -> genresNo = if (genresNo == null) filter.name else genresNo + "," + filter.name Filter.TriState.STATE_INCLUDE -> genres.add(genre.name)
Filter.TriState.STATE_EXCLUDE -> genresNo.add(genre.name)
}
} }
} }
} }
if (genres != null) url.addQueryParameter("genre", genres) if (genres.isNotEmpty()) url.addQueryParameter("genre", genres.joinToString(","))
if (genresNo != null) url.addQueryParameter("genreNo", genresNo) if (genresNo.isNotEmpty()) url.addQueryParameter("genreNo", genresNo.joinToString(","))
val (body, requestUrl) = convertQueryToPost(page, url.toString()) val (body, requestUrl) = convertQueryToPost(page, url.toString())
return POST(requestUrl, headers, body.build()) return POST(requestUrl, headers, body.build())
@ -155,23 +157,51 @@ 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")
override fun latestUpdatesNextPageSelector() = "button.requestMore"
override fun latestUpdatesSelector(): String = "a.latestSeries"
override fun latestUpdatesRequest(page: Int): Request {
val url = "http://mangaseeonline.net/home/latest.request.php"
val (body, requestUrl) = convertQueryToPost(page, url)
return POST(requestUrl, headers, body.build())
}
override fun latestUpdatesFromElement(element: Element): SManga {
val manga = SManga.create()
element.select("a.latestSeries").first().let {
val chapterUrl = it.attr("href")
val indexOfMangaUrl = chapterUrl.indexOf("-chapter-")
val indexOfLastPath = chapterUrl.lastIndexOf("/")
val mangaUrl = chapterUrl.substring(indexOfLastPath, indexOfMangaUrl)
val defaultText = it.select("p.clamp2").text()
val m = recentUpdatesPattern.matcher(defaultText)
val title = if (m.matches()) m.group(1) else defaultText
manga.setUrlWithoutDomain("/manga" + mangaUrl)
manga.title = title
}
return manga
}
private class Sort : Filter.Sort("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 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 SelectField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.Select<String>(name, values, state) private class SelectField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.Select<String>(name, values, state)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
// [...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( override fun getFilterList() = FilterList(
TextField("Years", "year"), TextField("Years", "year"),
TextField("Author", "author"), TextField("Author", "author"),
SelectField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")), 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("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")),
SelectField("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(),
Sort(), Sort(),
Filter.Separator(), GenreList(getGenreList())
Filter.Header("Genres"), )
// [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n')
// http://mangasee.co/advanced-search/
private fun getGenreList() = listOf(
Genre("Action"), Genre("Action"),
Genre("Adult"), Genre("Adult"),
Genre("Adventure"), Genre("Adventure"),
@ -210,30 +240,4 @@ class Mangasee : ParsedOnlineSource() {
Genre("Yuri") Genre("Yuri")
) )
override fun latestUpdatesNextPageSelector() = "button.requestMore"
override fun latestUpdatesSelector(): String = "a.latestSeries"
override fun latestUpdatesRequest(page: Int): Request {
val url = "http://mangaseeonline.net/home/latest.request.php"
val (body, requestUrl) = convertQueryToPost(page, url)
return POST(requestUrl, headers, body.build())
}
override fun latestUpdatesFromElement(element: Element): SManga {
val manga = SManga.create()
element.select("a.latestSeries").first().let {
val chapterUrl = it.attr("href")
val indexOfMangaUrl = chapterUrl.indexOf("-chapter-")
val indexOfLastPath = chapterUrl.lastIndexOf("/")
val mangaUrl = chapterUrl.substring(indexOfLastPath, indexOfMangaUrl)
val defaultText = it.select("p.clamp2").text()
val m = recentUpdatesPattern.matcher(defaultText)
val title = if (m.matches()) m.group(1) else defaultText
manga.setUrlWithoutDomain("/manga" + mangaUrl)
manga.title = title
}
return manga
}
} }

View file

@ -70,9 +70,11 @@ class Readmangatoday : ParsedOnlineSource() {
is TextField -> builder.add(filter.key, filter.state) is TextField -> builder.add(filter.key, filter.state)
is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state]) is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state])
is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state]) is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state])
is Genre -> when (filter.state) { is GenreList -> filter.state.forEach { genre ->
Filter.TriState.STATE_INCLUDE -> builder.add("include[]", filter.id.toString()) when (genre.state) {
Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", filter.id.toString()) Filter.TriState.STATE_INCLUDE -> builder.add("include[]", genre.id.toString())
Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", genre.id.toString())
}
} }
} }
} }
@ -161,19 +163,23 @@ class Readmangatoday : ParsedOnlineSource() {
override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src") override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src")
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.Select<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"))
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
// [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n')
// http://www.readmanga.today/advanced-search
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "author-name"), TextField("Author", "author-name"),
TextField("Artist", "artist-name"), TextField("Artist", "artist-name"),
Type(), Type(),
Status(), Status(),
Filter.Header("Genres"), GenreList(getGenreList())
)
// [...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
private fun getGenreList() = listOf(
Genre("Action", 2), Genre("Action", 2),
Genre("Adventure", 4), Genre("Adventure", 4),
Genre("Comedy", 5), Genre("Comedy", 5),

View file

@ -208,7 +208,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
showProgressBar() showProgressBar()
adapter.clear() adapter.clear()
presenter.setActiveSource(source) presenter.setActiveSource(source)
navView?.setFilters(presenter.sourceFilters) navView?.setFilters(presenter.filterItems)
activity.invalidateOptionsMenu() activity.invalidateOptionsMenu()
} }
} }
@ -229,7 +229,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
this.navView = navView this.navView = navView
activity.drawer.addView(navView) activity.drawer.addView(navView)
activity.drawer.addDrawerListener(drawerListener) activity.drawer.addDrawerListener(drawerListener)
navView.setFilters(presenter.sourceFilters) navView.setFilters(presenter.filterItems)
navView.post { navView.post {
if (isAdded && !activity.drawer.isDrawerOpen(navView)) if (isAdded && !activity.drawer.isDrawerOpen(navView))
@ -247,7 +247,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
presenter.appliedFilters = FilterList() presenter.appliedFilters = FilterList()
val newFilters = presenter.source.getFilterList() val newFilters = presenter.source.getFilterList()
presenter.sourceFilters = newFilters presenter.sourceFilters = newFilters
navView.setFilters(newFilters) navView.setFilters(presenter.filterItems)
} }
showProgressBar() showProgressBar()

View file

@ -5,11 +5,7 @@ import android.util.AttributeSet
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible 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.FilterList
import eu.kanade.tachiyomi.ui.catalogue.filter.*
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.widget.SimpleNavigationView import eu.kanade.tachiyomi.widget.SimpleNavigationView
import kotlinx.android.synthetic.main.catalogue_drawer_content.view.* import kotlinx.android.synthetic.main.catalogue_drawer_content.view.*
@ -18,7 +14,9 @@ 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 = FlexibleAdapter<IFlexible<*>>(null) val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
.setDisplayHeadersAtStartUp(true)
.setStickyHeaders(true)
var onSearchClicked = {} var onSearchClicked = {}
@ -26,53 +24,17 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs:
init { init {
recycler.adapter = adapter recycler.adapter = adapter
recycler.setHasFixedSize(true)
val view = inflate(R.layout.catalogue_drawer_content) val view = inflate(R.layout.catalogue_drawer_content)
((view as ViewGroup).getChildAt(1) 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(filters: FilterList) { fun setFilters(items: List<IFlexible<*>>) {
val items = filters.mapNotNull { adapter.updateDataSet(items.toMutableList())
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 }
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
}
}
adapter.updateDataSet(items)
} }
} }

View file

@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import android.os.Bundle import android.os.Bundle
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.items.ISectionable
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -9,10 +11,12 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.source.CatalogueSource import eu.kanade.tachiyomi.data.source.CatalogueSource
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.Source
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.SourceManager
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.data.source.model.SManga import eu.kanade.tachiyomi.data.source.model.SManga
import eu.kanade.tachiyomi.data.source.online.LoginSource import eu.kanade.tachiyomi.data.source.online.LoginSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.catalogue.filter.*
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -68,6 +72,12 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
* Modifiable list of filters. * Modifiable list of filters.
*/ */
var sourceFilters = FilterList() var sourceFilters = FilterList()
set(value) {
field = value
filterItems = value.toItems()
}
var filterItems: List<IFlexible<*>> = emptyList()
/** /**
* List of filters used by the [Pager]. If empty alongside [query], the popular query is used. * List of filters used by the [Pager]. If empty alongside [query], the popular query is used.
@ -362,4 +372,41 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
return CataloguePager(source, query, filters) return CataloguePager(source, query, filters)
} }
private fun FilterList.toItems(): List<IFlexible<*>> {
return 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<*, *>
}
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
}
}
}
} }

View file

@ -44,7 +44,7 @@ class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<Grou
return filter.hashCode() return filter.hashCode()
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter, true) { open class Holder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter, true) {
val title = itemView.findViewById(R.id.title) as TextView val title = itemView.findViewById(R.id.title) as TextView
val icon = itemView.findViewById(R.id.expand_icon) as ImageView val icon = itemView.findViewById(R.id.expand_icon) as ImageView

View file

@ -12,6 +12,18 @@ class TriStateSectionItem(filter: Filter.TriState) : TriStateItem(filter), ISect
override fun setHeader(header: GroupItem?) { override fun setHeader(header: GroupItem?) {
head = header head = header
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is TriStateSectionItem) {
return filter == other.filter
}
return false
}
override fun hashCode(): Int {
return filter.hashCode()
}
} }
class TextSectionItem(filter: Filter.Text) : TextItem(filter), ISectionable<TextItem.Holder, GroupItem> { class TextSectionItem(filter: Filter.Text) : TextItem(filter), ISectionable<TextItem.Holder, GroupItem> {
@ -23,6 +35,18 @@ class TextSectionItem(filter: Filter.Text) : TextItem(filter), ISectionable<Text
override fun setHeader(header: GroupItem?) { override fun setHeader(header: GroupItem?) {
head = header head = header
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is TextSectionItem) {
return filter == other.filter
}
return false
}
override fun hashCode(): Int {
return filter.hashCode()
}
} }
class CheckboxSectionItem(filter: Filter.CheckBox) : CheckboxItem(filter), ISectionable<CheckboxItem.Holder, GroupItem> { class CheckboxSectionItem(filter: Filter.CheckBox) : CheckboxItem(filter), ISectionable<CheckboxItem.Holder, GroupItem> {
@ -34,6 +58,18 @@ class CheckboxSectionItem(filter: Filter.CheckBox) : CheckboxItem(filter), ISect
override fun setHeader(header: GroupItem?) { override fun setHeader(header: GroupItem?) {
head = header head = header
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is CheckboxSectionItem) {
return filter == other.filter
}
return false
}
override fun hashCode(): Int {
return filter.hashCode()
}
} }
class SelectSectionItem(filter: Filter.Select<*>) : SelectItem(filter), ISectionable<SelectItem.Holder, GroupItem> { class SelectSectionItem(filter: Filter.Select<*>) : SelectItem(filter), ISectionable<SelectItem.Holder, GroupItem> {
@ -45,4 +81,16 @@ class SelectSectionItem(filter: Filter.Select<*>) : SelectItem(filter), ISection
override fun setHeader(header: GroupItem?) { override fun setHeader(header: GroupItem?) {
head = header head = header
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is SelectSectionItem) {
return filter == other.filter
}
return false
}
override fun hashCode(): Int {
return filter.hashCode()
}
} }

View file

@ -3,24 +3,22 @@ package eu.kanade.tachiyomi.ui.catalogue.filter
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
import eu.davidea.flexibleadapter.items.ISectionable import eu.davidea.flexibleadapter.items.ISectionable
import eu.davidea.viewholders.ExpandableViewHolder
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.util.setVectorCompat import eu.kanade.tachiyomi.util.setVectorCompat
class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGroup.Holder, ISectionable<*, *>>() { class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGroup.Holder, ISectionable<*, *>>() {
// Use an id instead of the layout res to allow to reuse the layout.
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return R.layout.navigation_view_sort return R.id.catalogue_filter_sort_group
} }
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder { override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
return Holder(inflater.inflate(layoutRes, parent, false), adapter) return Holder(inflater.inflate(R.layout.navigation_view_group, parent, false), adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {
@ -44,14 +42,5 @@ class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGrou
return filter.hashCode() return filter.hashCode()
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter, true) { class Holder(view: View, adapter: FlexibleAdapter<*>) : GroupItem.Holder(view, adapter)
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

@ -15,12 +15,13 @@ import eu.kanade.tachiyomi.util.getResourceColor
class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem<SortItem.Holder, SortGroup>(group) { class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem<SortItem.Holder, SortGroup>(group) {
// Use an id instead of the layout res to allow to reuse the layout.
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return R.layout.navigation_view_sort_item return R.id.catalogue_filter_sort_item
} }
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder { override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): Holder {
return Holder(inflater.inflate(layoutRes, parent, false), adapter) return Holder(inflater.inflate(R.layout.navigation_view_checkedtext, parent, false), adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List<Any?>?) {

View file

@ -92,7 +92,7 @@ class CategoryActivity :
*/ */
fun setCategories(categories: List<CategoryItem>) { fun setCategories(categories: List<CategoryItem>) {
actionMode?.finish() actionMode?.finish()
adapter.updateDataSet(categories) adapter.updateDataSet(categories.toMutableList())
val selected = categories.filter { it.isSelected } val selected = categories.filter { it.isSelected }
if (selected.isNotEmpty()) { if (selected.isNotEmpty()) {
selected.forEach { onItemLongClick(categories.indexOf(it)) } selected.forEach { onItemLongClick(categories.indexOf(it)) }

View file

@ -1,30 +0,0 @@
<?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

@ -1,21 +0,0 @@
<?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>

View file

@ -15,7 +15,7 @@
android:layout_weight="1" android:layout_weight="1"
android:gravity="center_vertical|start"> android:gravity="center_vertical|start">
<EditText <android.support.design.widget.TextInputEditText
android:id="@+id/nav_view_item" android:id="@+id/nav_view_item"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="catalogue_filter_sort_group" type="id"/>
<item name="catalogue_filter_sort_item" type="id"/>
</resources>