Refactor filters
parent
68e9588f24
commit
f18c182a6a
@ -0,0 +1,30 @@
|
|||||||
|
package org.koitharu.kotatsu.list.domain
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaTag
|
||||||
|
import org.koitharu.kotatsu.core.model.SortOrder
|
||||||
|
|
||||||
|
class AvailableFilters(
|
||||||
|
val sortOrders: Set<SortOrder>,
|
||||||
|
val tags: Set<MangaTag>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val size: Int
|
||||||
|
get() = sortOrders.size + tags.size
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
other as AvailableFilters
|
||||||
|
if (sortOrders != other.sortOrders) return false
|
||||||
|
if (tags != other.tags) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = sortOrders.hashCode()
|
||||||
|
result = 31 * result + tags.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEmpty(): Boolean = sortOrders.isEmpty() && tags.isEmpty()
|
||||||
|
}
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.list.ui
|
|
||||||
|
|
||||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
|
||||||
import org.koitharu.kotatsu.core.model.MangaTag
|
|
||||||
import org.koitharu.kotatsu.core.model.SortOrder
|
|
||||||
|
|
||||||
data class MangaFilterConfig(
|
|
||||||
val sortOrders: List<SortOrder>,
|
|
||||||
val tags: List<MangaTag>,
|
|
||||||
val currentFilter: MangaFilter?
|
|
||||||
)
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.list.ui.filter
|
|
||||||
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
|
|
||||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
|
||||||
import org.koitharu.kotatsu.core.model.MangaTag
|
|
||||||
import org.koitharu.kotatsu.core.model.SortOrder
|
|
||||||
|
|
||||||
class FilterAdapter(
|
|
||||||
private val sortOrders: List<SortOrder> = emptyList(),
|
|
||||||
private val tags: List<MangaTag> = emptyList(),
|
|
||||||
state: MangaFilter?,
|
|
||||||
private val listener: OnFilterChangedListener
|
|
||||||
) : RecyclerView.Adapter<BaseViewHolder<*, Boolean, *>>() {
|
|
||||||
|
|
||||||
private var currentState = state ?: MangaFilter(sortOrders.firstOrNull(), emptySet())
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
|
|
||||||
VIEW_TYPE_SORT -> FilterSortHolder(parent).apply {
|
|
||||||
itemView.setOnClickListener {
|
|
||||||
setCheckedSort(requireData())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VIEW_TYPE_TAG -> FilterTagHolder(parent).apply {
|
|
||||||
itemView.setOnClickListener {
|
|
||||||
setCheckedTag(boundData ?: return@setOnClickListener, !isChecked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw IllegalArgumentException("Unknown viewType $viewType")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = sortOrders.size + tags.size
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: BaseViewHolder<*, Boolean, *>, position: Int) {
|
|
||||||
when (holder) {
|
|
||||||
is FilterSortHolder -> {
|
|
||||||
val item = sortOrders[position]
|
|
||||||
holder.bind(item, item == currentState.sortOrder)
|
|
||||||
}
|
|
||||||
is FilterTagHolder -> {
|
|
||||||
val item = tags[position - sortOrders.size]
|
|
||||||
holder.bind(item, item in currentState.tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) = when (position) {
|
|
||||||
in sortOrders.indices -> VIEW_TYPE_SORT
|
|
||||||
else -> VIEW_TYPE_TAG
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setCheckedTag(tag: MangaTag, isChecked: Boolean) {
|
|
||||||
currentState = if (tag in currentState.tags) {
|
|
||||||
if (!isChecked) {
|
|
||||||
currentState.copy(tags = currentState.tags - tag)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isChecked) {
|
|
||||||
currentState.copy(tags = currentState.tags + tag)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val index = tags.indexOf(tag)
|
|
||||||
if (index in tags.indices) {
|
|
||||||
notifyItemChanged(sortOrders.size + index)
|
|
||||||
}
|
|
||||||
listener.onFilterChanged(currentState)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setCheckedSort(sort: SortOrder) {
|
|
||||||
if (sort != currentState.sortOrder) {
|
|
||||||
val oldItemPos = sortOrders.indexOf(currentState.sortOrder)
|
|
||||||
val newItemPos = sortOrders.indexOf(sort)
|
|
||||||
currentState = currentState.copy(sortOrder = sort)
|
|
||||||
if (oldItemPos in sortOrders.indices) {
|
|
||||||
notifyItemChanged(oldItemPos)
|
|
||||||
}
|
|
||||||
if (newItemPos in sortOrders.indices) {
|
|
||||||
notifyItemChanged(newItemPos)
|
|
||||||
}
|
|
||||||
listener.onFilterChanged(currentState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
const val VIEW_TYPE_SORT = 0
|
|
||||||
const val VIEW_TYPE_TAG = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package org.koitharu.kotatsu.list.ui.filter
|
||||||
|
|
||||||
|
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||||
|
|
||||||
|
class FilterAdapter2(
|
||||||
|
listener: OnFilterChangedListener,
|
||||||
|
) : AsyncListDifferDelegationAdapter<FilterItem>(
|
||||||
|
FilterDiffCallback(),
|
||||||
|
filterSortDelegate(listener),
|
||||||
|
filterTagDelegate(listener),
|
||||||
|
filterHeaderDelegate(),
|
||||||
|
)
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package org.koitharu.kotatsu.list.ui.filter
|
||||||
|
|
||||||
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
|
import org.koitharu.kotatsu.databinding.ItemCheckableMultipleBinding
|
||||||
|
import org.koitharu.kotatsu.databinding.ItemCheckableSingleBinding
|
||||||
|
import org.koitharu.kotatsu.databinding.ItemFilterHeaderBinding
|
||||||
|
|
||||||
|
fun filterSortDelegate(
|
||||||
|
listener: OnFilterChangedListener,
|
||||||
|
) = adapterDelegateViewBinding<FilterItem.Sort, FilterItem, ItemCheckableSingleBinding>(
|
||||||
|
{ layoutInflater, parent -> ItemCheckableSingleBinding.inflate(layoutInflater, parent, false) }
|
||||||
|
) {
|
||||||
|
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
listener.onSortItemClick(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind {
|
||||||
|
binding.root.setText(item.order.titleRes)
|
||||||
|
binding.root.isChecked = item.isSelected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filterTagDelegate(
|
||||||
|
listener: OnFilterChangedListener,
|
||||||
|
) = adapterDelegateViewBinding<FilterItem.Tag, FilterItem, ItemCheckableMultipleBinding>(
|
||||||
|
{ layoutInflater, parent -> ItemCheckableMultipleBinding.inflate(layoutInflater, parent, false) }
|
||||||
|
) {
|
||||||
|
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
listener.onTagItemClick(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind {
|
||||||
|
binding.root.text = item.tag.title
|
||||||
|
binding.root.isChecked = item.isChecked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filterHeaderDelegate() = adapterDelegateViewBinding<FilterItem.Header, FilterItem, ItemFilterHeaderBinding>(
|
||||||
|
{ layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) }
|
||||||
|
) {
|
||||||
|
|
||||||
|
bind {
|
||||||
|
binding.root.setText(item.titleResId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package org.koitharu.kotatsu.list.ui.filter
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
|
||||||
|
class FilterDiffCallback : DiffUtil.ItemCallback<FilterItem>() {
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItem: FilterItem, newItem: FilterItem): Boolean {
|
||||||
|
return when {
|
||||||
|
oldItem.javaClass != newItem.javaClass -> false
|
||||||
|
oldItem is FilterItem.Header && newItem is FilterItem.Header -> {
|
||||||
|
oldItem.titleResId == newItem.titleResId
|
||||||
|
}
|
||||||
|
oldItem is FilterItem.Tag && newItem is FilterItem.Tag -> {
|
||||||
|
oldItem.tag == newItem.tag
|
||||||
|
}
|
||||||
|
oldItem is FilterItem.Sort && newItem is FilterItem.Sort -> {
|
||||||
|
oldItem.order == newItem.order
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: FilterItem, newItem: FilterItem): Boolean {
|
||||||
|
return when {
|
||||||
|
oldItem is FilterItem.Header && newItem is FilterItem.Header -> true
|
||||||
|
oldItem is FilterItem.Tag && newItem is FilterItem.Tag -> {
|
||||||
|
oldItem.isChecked == newItem.isChecked
|
||||||
|
}
|
||||||
|
oldItem is FilterItem.Sort && newItem is FilterItem.Sort -> {
|
||||||
|
oldItem.isSelected == newItem.isSelected
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChangePayload(oldItem: FilterItem, newItem: FilterItem): Any? {
|
||||||
|
val isCheckedChanged = when {
|
||||||
|
oldItem is FilterItem.Tag && newItem is FilterItem.Tag -> {
|
||||||
|
oldItem.isChecked != newItem.isChecked
|
||||||
|
}
|
||||||
|
oldItem is FilterItem.Sort && newItem is FilterItem.Sort -> {
|
||||||
|
oldItem.isSelected != newItem.isSelected
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
return if (isCheckedChanged) Unit else super.getChangePayload(oldItem, newItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package org.koitharu.kotatsu.list.ui.filter
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaTag
|
||||||
|
import org.koitharu.kotatsu.core.model.SortOrder
|
||||||
|
|
||||||
|
sealed interface FilterItem {
|
||||||
|
|
||||||
|
class Header(
|
||||||
|
@StringRes val titleResId: Int,
|
||||||
|
) : FilterItem
|
||||||
|
|
||||||
|
class Sort(
|
||||||
|
val order: SortOrder,
|
||||||
|
val isSelected: Boolean,
|
||||||
|
) : FilterItem
|
||||||
|
|
||||||
|
class Tag(
|
||||||
|
val tag: MangaTag,
|
||||||
|
val isChecked: Boolean,
|
||||||
|
) : FilterItem
|
||||||
|
}
|
||||||
@ -1,18 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.list.ui.filter
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
|
|
||||||
import org.koitharu.kotatsu.core.model.SortOrder
|
|
||||||
import org.koitharu.kotatsu.databinding.ItemCheckableSingleBinding
|
|
||||||
|
|
||||||
class FilterSortHolder(parent: ViewGroup) :
|
|
||||||
BaseViewHolder<SortOrder, Boolean, ItemCheckableSingleBinding>(
|
|
||||||
ItemCheckableSingleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun onBind(data: SortOrder, extra: Boolean) {
|
|
||||||
binding.root.setText(data.titleRes)
|
|
||||||
binding.root.isChecked = extra
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.list.ui.filter
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
|
|
||||||
import org.koitharu.kotatsu.core.model.MangaTag
|
|
||||||
import org.koitharu.kotatsu.databinding.ItemCheckableMultipleBinding
|
|
||||||
|
|
||||||
class FilterTagHolder(parent: ViewGroup) :
|
|
||||||
BaseViewHolder<MangaTag, Boolean, ItemCheckableMultipleBinding>(
|
|
||||||
ItemCheckableMultipleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
|
||||||
) {
|
|
||||||
|
|
||||||
val isChecked: Boolean
|
|
||||||
get() = binding.root.isChecked
|
|
||||||
|
|
||||||
override fun onBind(data: MangaTag, extra: Boolean) {
|
|
||||||
binding.root.text = data.title
|
|
||||||
binding.root.isChecked = extra
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
package org.koitharu.kotatsu.list.ui.filter
|
package org.koitharu.kotatsu.list.ui.filter
|
||||||
|
|
||||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
interface OnFilterChangedListener {
|
||||||
|
|
||||||
fun interface OnFilterChangedListener {
|
fun onSortItemClick(item: FilterItem.Sort)
|
||||||
|
|
||||||
fun onFilterChanged(filter: MangaFilter)
|
fun onTagItemClick(item: FilterItem.Tag)
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue