Browse Source

Complete group filters

len 8 years ago
parent
commit
71ab6d38e4

+ 22 - 10
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt

@@ -93,8 +93,18 @@ class Batoto : ParsedOnlineSource(), LoginSource {
                 is Status -> if (!filter.isIgnored()) {
                     url.addQueryParameter("completed", if (filter.isExcluded()) "i" else "c")
                 }
-                is Genre -> if (!filter.isIgnored()) {
-                    genres += (if (filter.isExcluded()) ";e" else ";i") + filter.id
+                is GenreList -> {
+                    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 -> {
                     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 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("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"),
             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(
             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"))),
             Status(),
             Flag("Exclude mature", "mature", "m", ""),
-            Filter.Separator(),
             OrderBy(),
-            Filter.Separator(),
-            Filter.Header("Genres"),
+            GenreList(getGenreList())
+    )
+
+    // [...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"))),
             Genre("4-Koma", 40),
             Genre("Action", 1),

+ 10 - 6
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt

@@ -64,7 +64,7 @@ class Kissmanga : ParsedOnlineSource() {
                 when (filter) {
                     is Author -> add("authorArtist", 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) = ""
 
-    private class Status() : Filter.TriState("Completed")
-    private class Author() : Filter.Text("Author")
+    private class Status : Filter.TriState("Completed")
+    private class Author : Filter.Text("Author")
     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(
             Author(),
             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("Action"),
             Genre("Adult"),

+ 1 - 7
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt

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

+ 8 - 6
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt

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

+ 42 - 38
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt

@@ -50,8 +50,8 @@ class Mangasee : ParsedOnlineSource() {
     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
         val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder()
         if (!query.isEmpty()) url.addQueryParameter("keyword", query)
-        var genres: String? = null
-        var genresNo: String? = null
+        val genres = mutableListOf<String>()
+        val genresNo = mutableListOf<String>()
         for (filter in if (filters.isEmpty()) getFilterList() else filters) {
             when (filter) {
                 is Sort -> {
@@ -62,14 +62,16 @@ class Mangasee : ParsedOnlineSource() {
                 }
                 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
-                    Filter.TriState.STATE_EXCLUDE -> genresNo = if (genresNo == null) filter.name else genresNo + "," + filter.name
+                is GenreList -> filter.state.forEach { genre ->
+                    when (genre.state) {
+                        Filter.TriState.STATE_INCLUDE -> genres.add(genre.name)
+                        Filter.TriState.STATE_EXCLUDE -> genresNo.add(genre.name)
+                    }
                 }
             }
         }
-        if (genres != null) url.addQueryParameter("genre", genres)
-        if (genresNo != null) url.addQueryParameter("genreNo", genresNo)
+        if (genres.isNotEmpty()) url.addQueryParameter("genre", genres.joinToString(","))
+        if (genresNo.isNotEmpty()) url.addQueryParameter("genreNo", genresNo.joinToString(","))
 
         val (body, requestUrl) = convertQueryToPost(page, url.toString())
         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 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 Genre(name: String) : Filter.TriState(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 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(
             TextField("Years", "year"),
             TextField("Author", "author"),
             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(),
-            Filter.Header("Genres"),
+            GenreList(getGenreList())
+    )
+
+    // [...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("Adult"),
             Genre("Adventure"),
@@ -210,30 +240,4 @@ class Mangasee : ParsedOnlineSource() {
             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
-    }
-
 }

+ 14 - 8
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt

@@ -70,9 +70,11 @@ class Readmangatoday : ParsedOnlineSource() {
                 is TextField -> builder.add(filter.key, 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 Genre -> when (filter.state) {
-                    Filter.TriState.STATE_INCLUDE -> builder.add("include[]", filter.id.toString())
-                    Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", filter.id.toString())
+                is GenreList -> filter.state.forEach { genre ->
+                    when (genre.state) {
+                        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")
 
-    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 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(
             TextField("Author", "author-name"),
             TextField("Artist", "artist-name"),
             Type(),
             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("Adventure", 4),
             Genre("Comedy", 5),

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt

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

+ 6 - 44
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt

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

+ 47 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt

@@ -1,6 +1,8 @@
 package eu.kanade.tachiyomi.ui.catalogue
 
 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.database.DatabaseHelper
 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.Source
 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.SManga
 import eu.kanade.tachiyomi.data.source.online.LoginSource
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
+import eu.kanade.tachiyomi.ui.catalogue.filter.*
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
@@ -68,6 +72,12 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
      * Modifiable list of filters.
      */
     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.
@@ -362,4 +372,41 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
         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
+            }
+        }
+    }
+
 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/GroupItem.kt

@@ -44,7 +44,7 @@ class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<Grou
         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 icon = itemView.findViewById(R.id.expand_icon) as ImageView

+ 48 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SectionItems.kt

@@ -12,6 +12,18 @@ class TriStateSectionItem(filter: Filter.TriState) : TriStateItem(filter), ISect
     override fun setHeader(header: GroupItem?) {
         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> {
@@ -23,6 +35,18 @@ class TextSectionItem(filter: Filter.Text) : TextItem(filter), ISectionable<Text
     override fun setHeader(header: GroupItem?) {
         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> {
@@ -34,6 +58,18 @@ class CheckboxSectionItem(filter: Filter.CheckBox) : CheckboxItem(filter), ISect
     override fun setHeader(header: GroupItem?) {
         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> {
@@ -45,4 +81,16 @@ class SelectSectionItem(filter: Filter.Select<*>) : SelectItem(filter), ISection
     override fun setHeader(header: GroupItem?) {
         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()
+    }
 }

+ 4 - 15
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortGroup.kt

@@ -3,24 +3,22 @@ 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<*, *>>() {
 
+    // Use an id instead of the layout res to allow to reuse the layout.
     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 {
-        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?>?) {
@@ -44,14 +42,5 @@ class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGrou
         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
-        }
-    }
-
+    class Holder(view: View, adapter: FlexibleAdapter<*>) : GroupItem.Holder(view, adapter)
 }

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortItem.kt

@@ -15,12 +15,13 @@ import eu.kanade.tachiyomi.util.getResourceColor
 
 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 {
-        return R.layout.navigation_view_sort_item
+        return R.id.catalogue_filter_sort_item
     }
 
     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?>?) {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt

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

+ 0 - 30
app/src/main/res/layout/navigation_view_sort.xml

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

+ 0 - 21
app/src/main/res/layout/navigation_view_sort_item.xml

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

+ 1 - 1
app/src/main/res/layout/navigation_view_text.xml

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

+ 6 - 0
app/src/main/res/values/ids.xml

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