A filter is a class extending one of the controls below, all nested in EntryFilter. Katari changes its
state as the user picks, and passes the whole list to getSearchContent.
abstractclass Select<V>( name: String, values: Array<V>, state: Int = 0 ) -
One choice from
values. The state is the index of the choice. abstractclass Sort( name: String, values: Array<String>, state: Selection? = null ) -
A sort order and its direction. The state is a
Sort.Selection(index, ascending), or null for the site's default. abstractclass Text( name: String, state: String = "" ) - A text field. The state is the text.
abstractclass CheckBox( name: String, state: Boolean = false ) -
An on/off switch. The state is
trueorfalse. abstractclass TriState( name: String, state: Int = 0 ) -
A choice to include, exclude or ignore, such as one genre. The state is
STATE_IGNORE(0),STATE_INCLUDE(1) orSTATE_EXCLUDE(2), also read withisIgnored(),isIncluded()andisExcluded(). abstractclass Group<V>( name: String, state: List<V> ) - A titled, collapsible set of other filters. The state is the list of filters inside it.
openclass Header( name: String ) - A heading between filters. It has no state.
openclass Separator( name: String = "" ) - A line between filters. It has no state.
Three more are covered below: EntryDateFilter for dates, and Autocomplete and
PagedGroup for options the site provides and
long lists. EntryFilter and
EntryFilterList are in eu.kanade.tachiyomi.source.entry; the metadata, dates and validation on this page
are in eu.kanade.tachiyomi.source.entry.filter.
Here are Example Comics' filters. The metadata in each is explained in the next sections:
class SortFilter :
EntryFilter.Select<String>("Sort by", arrayOf("Popular", "Newest", "A–Z")),
EntryFilterMetadataProvider {
override val filterMetadata = EntryFilterMetadata(
id = "sort",
role = EntryFilterRole.ORDERING,
optionIds = listOf("popular", "newest", "title"),
)
}
class StatusFilter :
EntryFilter.Select<String>("Status", arrayOf("Any", "Ongoing", "Completed")),
EntryFilterMetadataProvider {
override val filterMetadata = EntryFilterMetadata(
id = "status",
optionIds = listOf("any", "ongoing", "completed"),
neutralOptionId = "any",
)
}
class Genre(name: String, val slug: String) : EntryFilter.TriState(name), EntryFilterMetadataProvider {
override val filterMetadata = EntryFilterMetadata(id = "genre-$slug")
}
class GenreFilter(genres: List<Genre>) : EntryFilter.Group<Genre>("Genres", genres), EntryFilterMetadataProvider {
override val filterMetadata = EntryFilterMetadata(id = "genres")
}
fun exampleComicsFilters() = EntryFilterList(
SortFilter(),
StatusFilter(),
GenreFilter(listOf(Genre("Action", "action"), Genre("Romance", "romance"))),
)
getFilterList returns a new list each time. Katari works on its own copies of the filters and sets the user's values
on yours only while it calls getSearchContent:
override fun getFilterList() = exampleComicsFilters()
In getSearchContent, read each filter's state and turn it into the site's parameters. The option IDs from the metadata
make good parameter values:
private val EntryFilter.Select<*>.selectedOption: String
get() = (this as EntryFilterMetadataProvider).filterMetadata.optionIds[state]
fun EntryFilterList.applyTo(url: HttpUrl.Builder) {
forEach { filter ->
when (filter) {
is SortFilter -> url.addQueryParameter("sort", filter.selectedOption)
is StatusFilter -> if (filter.selectedOption != "any") url.addQueryParameter("status", filter.selectedOption)
is GenreFilter -> filter.state.forEach { genre ->
when (genre.state) {
EntryFilter.TriState.STATE_INCLUDE -> url.addQueryParameter("genre", genre.slug)
EntryFilter.TriState.STATE_EXCLUDE -> url.addQueryParameter("exclude", genre.slug)
}
}
else -> Unit
}
}
}
An exception thrown by a filter while Katari draws, counts or checks it affects only that filter: Katari carries on without its summary and checks, and says so under it.