Migrate source filter sheet to Compose (#9135)

This commit is contained in:
arkon
2023-02-23 22:32:40 -05:00
committed by GitHub
parent 36ae388332
commit 92132c59f5
36 changed files with 459 additions and 1305 deletions
@@ -8,13 +8,11 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.paging.compose.collectAsLazyPagingItems
@@ -45,7 +43,6 @@ data class SourceSearchScreen(
@Composable
override fun Content() {
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
val navigator = LocalNavigator.currentOrThrow
val scope = rememberCoroutineScope()
@@ -123,9 +120,5 @@ data class SourceSearchScreen(
}
else -> {}
}
LaunchedEffect(state.filters) {
screenModel.initFilterSheet(context)
}
}
}
@@ -28,7 +28,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
@@ -93,7 +92,6 @@ data class BrowseSourceScreen(
}
val scope = rememberCoroutineScope()
val context = LocalContext.current
val haptic = LocalHapticFeedback.current
val uriHandler = LocalUriHandler.current
val snackbarHostState = remember { SnackbarHostState() }
@@ -231,7 +229,21 @@ data class BrowseSourceScreen(
val onDismissRequest = { screenModel.setDialog(null) }
when (val dialog = state.dialog) {
is BrowseSourceScreenModel.Dialog.Migrate -> {}
is BrowseSourceScreenModel.Dialog.Filter -> {
SourceFilterDialog(
onDismissRequest = onDismissRequest,
filters = state.filters,
onReset = {
screenModel.resetFilters()
},
onFilter = {
screenModel.search(filters = state.filters)
},
onUpdate = {
screenModel.setFilters(it)
},
)
}
is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> {
DuplicateMangaDialog(
onDismissRequest = onDismissRequest,
@@ -259,13 +271,10 @@ data class BrowseSourceScreen(
},
)
}
is BrowseSourceScreenModel.Dialog.Migrate -> {}
else -> {}
}
LaunchedEffect(state.filters) {
screenModel.initFilterSheet(context)
}
LaunchedEffect(Unit) {
queryEvent.receiveAsFlow()
.collectLatest {
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
import android.content.Context
import android.content.res.Configuration
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.runtime.Immutable
@@ -14,7 +13,6 @@ import androidx.paging.filter
import androidx.paging.map
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.core.prefs.asState
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
@@ -33,19 +31,6 @@ import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxItem
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxSectionItem
import eu.kanade.tachiyomi.ui.browse.source.filter.GroupItem
import eu.kanade.tachiyomi.ui.browse.source.filter.HeaderItem
import eu.kanade.tachiyomi.ui.browse.source.filter.SelectItem
import eu.kanade.tachiyomi.ui.browse.source.filter.SelectSectionItem
import eu.kanade.tachiyomi.ui.browse.source.filter.SeparatorItem
import eu.kanade.tachiyomi.ui.browse.source.filter.SortGroup
import eu.kanade.tachiyomi.ui.browse.source.filter.SortItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TextItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TextSectionItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
import eu.kanade.tachiyomi.util.removeCovers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -125,11 +110,6 @@ class BrowseSourceScreenModel(
}
}
/**
* Sheet containing filter items.
*/
private var filterSheet: SourceFilterSheet? = null
/**
* Flow of Pager flow tied to [State.listing]
*/
@@ -175,6 +155,16 @@ class BrowseSourceScreenModel(
mutableState.update { it.copy(listing = listing) }
}
fun setFilters(filters: FilterList) {
if (source !is CatalogueSource) return
mutableState.update {
it.copy(
filters = filters,
)
}
}
fun search(query: String? = null, filters: FilterList? = null) {
if (source !is CatalogueSource) return
@@ -350,7 +340,7 @@ class BrowseSourceScreenModel(
return getDuplicateLibraryManga.await(manga.title)
}
fun moveMangaToCategories(manga: Manga, vararg categories: Category) {
private fun moveMangaToCategories(manga: Manga, vararg categories: Category) {
moveMangaToCategories(manga, categories.filter { it.id != 0L }.map { it.id })
}
@@ -364,7 +354,7 @@ class BrowseSourceScreenModel(
}
fun openFilterSheet() {
filterSheet?.show()
setDialog(Dialog.Filter)
}
fun setDialog(dialog: Dialog?) {
@@ -375,23 +365,6 @@ class BrowseSourceScreenModel(
mutableState.update { it.copy(toolbarQuery = query) }
}
fun initFilterSheet(context: Context) {
if (state.value.filters.isEmpty()) {
return
}
filterSheet = SourceFilterSheet(
context = context,
onFilterClicked = { search(filters = state.value.filters) },
onResetClicked = {
resetFilters()
filterSheet?.setFilters(state.value.filterItems)
},
)
filterSheet?.setFilters(state.value.filterItems)
}
sealed class Listing(open val query: String?, open val filters: FilterList) {
object Popular : Listing(query = GetRemoteManga.QUERY_POPULAR, filters = FilterList())
object Latest : Listing(query = GetRemoteManga.QUERY_LATEST, filters = FilterList())
@@ -409,6 +382,7 @@ class BrowseSourceScreenModel(
}
sealed class Dialog {
object Filter : Dialog()
data class RemoveManga(val manga: Manga) : Dialog()
data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog()
data class ChangeMangaCategory(
@@ -425,43 +399,6 @@ class BrowseSourceScreenModel(
val toolbarQuery: String? = null,
val dialog: Dialog? = null,
) {
val filterItems get() = filters.toItems()
val isUserQuery get() = listing is Listing.Search && !listing.query.isNullOrEmpty()
}
}
private fun FilterList.toItems(): List<IFlexible<*>> {
return mapNotNull { filter ->
when (filter) {
is SourceModelFilter.Header -> HeaderItem(filter)
is SourceModelFilter.Separator -> SeparatorItem(filter)
is SourceModelFilter.CheckBox -> CheckboxItem(filter)
is SourceModelFilter.TriState -> TriStateItem(filter)
is SourceModelFilter.Text -> TextItem(filter)
is SourceModelFilter.Select<*> -> SelectItem(filter)
is SourceModelFilter.Group<*> -> {
val group = GroupItem(filter)
val subItems = filter.state.mapNotNull {
when (it) {
is SourceModelFilter.CheckBox -> CheckboxSectionItem(it)
is SourceModelFilter.TriState -> TriStateSectionItem(it)
is SourceModelFilter.Text -> TextSectionItem(it)
is SourceModelFilter.Select<*> -> SelectSectionItem(it)
else -> null
}
}
subItems.forEach { it.header = group }
group.subItems = subItems
group
}
is SourceModelFilter.Sort -> {
val group = SortGroup(filter)
val subItems = filter.values.map {
SortItem(it, group)
}
group.subItems = subItems
group
}
}
}
}
@@ -0,0 +1,165 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.components.SelectItem
import eu.kanade.presentation.components.TriStateItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.widget.TriState
import eu.kanade.tachiyomi.widget.toTriStateFilter
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.CollapsibleBox
import tachiyomi.presentation.core.components.HeadingItem
import tachiyomi.presentation.core.components.LazyColumn
import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TextItem
import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.components.material.Divider
@Composable
fun SourceFilterDialog(
onDismissRequest: () -> Unit,
filters: FilterList,
onReset: () -> Unit,
onFilter: () -> Unit,
onUpdate: (FilterList) -> Unit,
) {
val updateFilters = { onUpdate(filters) }
AdaptiveSheet(
onDismissRequest = onDismissRequest,
) { contentPadding ->
LazyColumn(
contentPadding = contentPadding,
) {
stickyHeader {
Row(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.padding(8.dp),
) {
TextButton(onClick = onReset) {
Text(
text = stringResource(R.string.action_reset),
style = LocalTextStyle.current.copy(
color = MaterialTheme.colorScheme.primary,
),
)
}
Spacer(modifier = Modifier.weight(1f))
Button(onClick = onFilter) {
Text(stringResource(R.string.action_filter))
}
}
Divider()
}
items(filters) {
FilterItem(it, updateFilters)
}
}
}
}
@Composable
private fun FilterItem(filter: Filter<*>, onUpdate: () -> Unit) {
when (filter) {
is Filter.Header -> {
HeadingItem(filter.name)
}
is Filter.Separator -> {
Divider()
}
is Filter.CheckBox -> {
CheckboxItem(
label = filter.name,
checked = filter.state,
) {
filter.state = !filter.state
onUpdate()
}
}
is Filter.TriState -> {
TriStateItem(
label = filter.name,
state = filter.state.toTriStateFilter(),
) {
filter.state = TriState.valueOf(filter.state).next().value
onUpdate()
}
}
is Filter.Text -> {
TextItem(
label = filter.name,
value = filter.state,
) {
filter.state = it
onUpdate()
}
}
is Filter.Select<*> -> {
SelectItem(
label = filter.name,
options = filter.values,
selectedIndex = filter.state,
) {
filter.state = it
onUpdate()
}
}
is Filter.Sort -> {
CollapsibleBox(
heading = filter.name,
) {
Column {
filter.values.mapIndexed { index, item ->
SortItem(
label = item,
sortDescending = filter.state?.ascending?.not()
?.takeIf { index == filter.state?.index },
) {
val ascending = if (index == filter.state?.index) {
!filter.state!!.ascending
} else {
filter.state!!.ascending
}
filter.state = Filter.Sort.Selection(
index = index,
ascending = ascending,
)
onUpdate()
}
}
}
}
}
is Filter.Group<*> -> {
CollapsibleBox(
heading = filter.name,
) {
Column {
filter.state
.filterIsInstance<Filter<*>>()
.map { FilterItem(filter = it, onUpdate = onUpdate) }
}
}
}
}
}
@@ -1,63 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
import eu.kanade.tachiyomi.widget.SimpleNavigationView
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
class SourceFilterSheet(
context: Context,
private val onFilterClicked: () -> Unit,
private val onResetClicked: () -> Unit,
) : BaseBottomSheetDialog(context) {
private var filterNavView: FilterNavigationView = FilterNavigationView(context)
override fun createView(inflater: LayoutInflater): View {
filterNavView.onFilterClicked = {
onFilterClicked()
this.dismiss()
}
filterNavView.onResetClicked = onResetClicked
return filterNavView
}
fun setFilters(items: List<IFlexible<*>>) {
filterNavView.adapter.updateDataSet(items)
}
class FilterNavigationView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) :
SimpleNavigationView(context, attrs) {
var onFilterClicked = {}
var onResetClicked = {}
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
.setDisplayHeadersAtStartUp(true)
private val binding = SourceFilterSheetBinding.inflate(
LayoutInflater.from(context),
null,
false,
)
init {
recycler.adapter = adapter
recycler.setHasFixedSize(true)
(binding.root.getChildAt(1) as ViewGroup).addView(recycler)
addView(binding.root)
binding.filterBtn.setOnClickListener { onFilterClicked() }
binding.resetBtn.setOnClickListener { onResetClicked() }
}
}
}
@@ -1,46 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import android.view.View
import android.widget.CheckBox
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.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(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, 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 (javaClass != other?.javaClass) return false
return filter == (other as CheckboxItem).filter
}
override fun hashCode(): Int {
return filter.hashCode()
}
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
val check: CheckBox = itemView.findViewById(R.id.nav_view_item)
}
}
@@ -1,66 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.items.ISectionable
import eu.davidea.viewholders.ExpandableViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.view.setVectorCompat
class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<GroupItem.Holder, ISectionable<*, *>>() {
init {
isExpanded = false
}
override fun getLayoutRes(): Int {
return R.layout.navigation_view_group
}
override fun getItemViewType(): Int {
return 101
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
holder.title.text = filter.name
holder.icon.setVectorCompat(
if (isExpanded) {
R.drawable.ic_expand_less_24dp
} else {
R.drawable.ic_expand_more_24dp
},
)
holder.itemView.setOnClickListener(holder)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return filter == (other as GroupItem).filter
}
override fun hashCode(): Int {
return filter.hashCode()
}
open class Holder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter, true) {
val title: TextView = itemView.findViewById(R.id.title)
val icon: ImageView = itemView.findViewById(R.id.expand_icon)
override fun shouldNotifyParentOnClick(): Boolean {
return true
}
}
}
@@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import android.annotation.SuppressLint
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.R
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.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(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
val view = holder.itemView as TextView
view.text = filter.name
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return filter == (other as HeaderItem).filter
}
override fun hashCode(): Int {
return filter.hashCode()
}
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter)
}
@@ -1,100 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import eu.davidea.flexibleadapter.items.ISectionable
import eu.kanade.tachiyomi.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
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TriStateSectionItem
if (head != other.head) return false
return filter == other.filter
}
override fun hashCode(): Int {
return filter.hashCode() + (head?.hashCode() ?: 0)
}
}
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
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TextSectionItem
if (head != other.head) return false
return filter == other.filter
}
override fun hashCode(): Int {
return filter.hashCode() + (head?.hashCode() ?: 0)
}
}
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
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CheckboxSectionItem
if (head != other.head) return false
return filter == other.filter
}
override fun hashCode(): Int {
return filter.hashCode() + (head?.hashCode() ?: 0)
}
}
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
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SelectSectionItem
if (head != other.head) return false
return filter == other.filter
}
override fun hashCode(): Int {
return filter.hashCode() + (head?.hashCode() ?: 0)
}
}
@@ -1,59 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.widget.listener.IgnoreFirstSpinnerListener
open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<SelectItem.Holder>() {
override fun getLayoutRes(): Int {
return R.layout.navigation_view_spinner
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, 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.common_spinner_item)
}
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { pos ->
filter.state = pos
}
spinner.setSelection(filter.state)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return filter == (other as SelectItem).filter
}
override fun hashCode(): Int {
return filter.hashCode()
}
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
val text: TextView = itemView.findViewById(R.id.nav_view_item_text)
val spinner: Spinner = itemView.findViewById(R.id.nav_view_item)
}
}
@@ -1,38 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import android.annotation.SuppressLint
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.R
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.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(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return filter == (other as SeparatorItem).filter
}
override fun hashCode(): Int {
return filter.hashCode()
}
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter)
}
@@ -1,56 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.items.ISectionable
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.view.setVectorCompat
class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGroup.Holder, ISectionable<*, *>>() {
init {
isExpanded = false
}
override fun getLayoutRes(): Int {
return R.layout.navigation_view_group
}
override fun getItemViewType(): Int {
return 100
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
holder.title.text = filter.name
holder.icon.setVectorCompat(
if (isExpanded) {
R.drawable.ic_expand_less_24dp
} else {
R.drawable.ic_expand_more_24dp
},
)
holder.itemView.setOnClickListener(holder)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return filter == (other as SortGroup).filter
}
override fun hashCode(): Int {
return filter.hashCode()
}
class Holder(view: View, adapter: FlexibleAdapter<*>) : GroupItem.Holder(view, adapter)
}
@@ -1,75 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import android.view.View
import android.widget.CheckedTextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.system.getResourceColor
class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem<SortItem.Holder, SortGroup>(group) {
override fun getLayoutRes(): Int {
return R.layout.navigation_view_checkedtext
}
override fun getItemViewType(): Int {
return 102
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, 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) ->
AppCompatResources.getDrawable(view.context, R.drawable.ic_arrow_down_white_32dp)
?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) }
Filter.Sort.Selection(i, true) ->
AppCompatResources.getDrawable(view.context, R.drawable.ic_arrow_up_white_32dp)
?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) }
else -> AppCompatResources.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 (javaClass != other?.javaClass) return false
other as SortItem
return name == other.name && group == other.group
}
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: CheckedTextView = itemView.findViewById(R.id.nav_view_item)
}
}
@@ -1,47 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import android.view.View
import android.widget.EditText
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputLayout
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter
open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Holder>() {
override fun getLayoutRes(): Int {
return R.layout.navigation_view_text
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
holder.wrapper.hint = filter.name
holder.edit.setText(filter.state)
holder.edit.doOnTextChanged { text, _, _, _ ->
filter.state = text.toString()
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return filter == (other as TextItem).filter
}
override fun hashCode(): Int {
return filter.hashCode()
}
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
val wrapper: TextInputLayout = itemView.findViewById(R.id.nav_view_item_wrapper)
val edit: EditText = itemView.findViewById(R.id.nav_view_item)
}
}
@@ -1,80 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.filter
import android.view.View
import android.widget.CheckedTextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.R
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.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 getItemViewType(): Int {
return 103
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
val view = holder.text
view.text = filter.name
fun getIcon() = AppCompatResources.getDrawable(
view.context,
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")
},
)?.apply {
val color = if (filter.state == Filter.TriState.STATE_IGNORE) {
view.context.getResourceColor(R.attr.colorOnBackground, 0.38f)
} else {
view.context.getResourceColor(R.attr.colorPrimary)
}
setTint(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 (javaClass != other?.javaClass) return false
return filter == (other as TriStateItem).filter
}
override fun hashCode(): Int {
return filter.hashCode()
}
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
val text: CheckedTextView = itemView.findViewById(TR.id.nav_view_item)
init {
// Align with native checkbox
text.updatePadding(left = 4.dpToPx)
text.compoundDrawablePadding = 20.dpToPx
}
}
}