Refactor filters
This commit is contained in:
@@ -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?
|
||||
)
|
||||
@@ -22,19 +22,17 @@ import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.ItemTypeDividerDecoration
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.SectionItemDecoration
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.databinding.FragmentListBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
|
||||
import org.koitharu.kotatsu.list.ui.filter.FilterAdapter
|
||||
import org.koitharu.kotatsu.list.ui.filter.OnFilterChangedListener
|
||||
import org.koitharu.kotatsu.list.ui.filter.FilterAdapter2
|
||||
import org.koitharu.kotatsu.list.ui.filter.FilterItem
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
import org.koitharu.kotatsu.main.ui.MainActivity
|
||||
@@ -42,10 +40,11 @@ import org.koitharu.kotatsu.utils.RecycledViewPoolHolder
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
|
||||
abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
PaginationScrollListener.Callback, OnListItemClickListener<Manga>, OnFilterChangedListener,
|
||||
SectionItemDecoration.Callback, SwipeRefreshLayout.OnRefreshListener {
|
||||
PaginationScrollListener.Callback, OnListItemClickListener<Manga>,
|
||||
SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
private var listAdapter: MangaListAdapter? = null
|
||||
private var filterAdapter: FilterAdapter2? = null
|
||||
private var paginationListener: PaginationScrollListener? = null
|
||||
private val spanResolver = MangaListSpanResolver()
|
||||
private val spanSizeLookup = SpanSizeLookup()
|
||||
@@ -78,6 +77,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
onRetryClick = ::resolveException,
|
||||
onTagRemoveClick = viewModel::onRemoveFilterTag
|
||||
)
|
||||
filterAdapter = FilterAdapter2(viewModel)
|
||||
paginationListener = PaginationScrollListener(4, this)
|
||||
with(binding.recyclerView) {
|
||||
setHasFixedSize(true)
|
||||
@@ -94,8 +94,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
}
|
||||
with(binding.recyclerViewFilter) {
|
||||
setHasFixedSize(true)
|
||||
addItemDecoration(ItemTypeDividerDecoration(view.context))
|
||||
addItemDecoration(SectionItemDecoration(false, this@MangaListFragment))
|
||||
adapter = filterAdapter
|
||||
}
|
||||
|
||||
(parentFragment as? RecycledViewPoolHolder)?.let {
|
||||
@@ -113,6 +112,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
override fun onDestroyView() {
|
||||
drawer = null
|
||||
listAdapter = null
|
||||
filterAdapter = null
|
||||
paginationListener = null
|
||||
spanSizeLookup.invalidateCache()
|
||||
super.onDestroyView()
|
||||
@@ -203,28 +203,21 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
}
|
||||
}
|
||||
|
||||
protected fun onInitFilter(config: MangaFilterConfig) {
|
||||
binding.recyclerViewFilter.adapter = FilterAdapter(
|
||||
sortOrders = config.sortOrders,
|
||||
tags = config.tags,
|
||||
state = config.currentFilter,
|
||||
listener = this
|
||||
)
|
||||
protected fun onInitFilter(filter: List<FilterItem>) {
|
||||
filterAdapter?.items = filter
|
||||
drawer?.setDrawerLockMode(
|
||||
if (config.sortOrders.isEmpty() && config.tags.isEmpty()) {
|
||||
if (filter.isEmpty()) {
|
||||
DrawerLayout.LOCK_MODE_LOCKED_CLOSED
|
||||
} else {
|
||||
DrawerLayout.LOCK_MODE_UNLOCKED
|
||||
}
|
||||
) ?: binding.dividerFilter?.let {
|
||||
it.isGone = config.sortOrders.isEmpty() && config.tags.isEmpty()
|
||||
it.isGone = filter.isEmpty()
|
||||
binding.recyclerViewFilter.isVisible = it.isVisible
|
||||
}
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onFilterChanged(filter: MangaFilter) = Unit
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top
|
||||
binding.recyclerViewFilter.updatePadding(
|
||||
@@ -284,20 +277,6 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
}
|
||||
}
|
||||
|
||||
final override fun isSection(position: Int): Boolean {
|
||||
return position == 0 || binding.recyclerViewFilter.adapter?.run {
|
||||
getItemViewType(position) != getItemViewType(position - 1)
|
||||
} ?: false
|
||||
}
|
||||
|
||||
final override fun getSectionTitle(position: Int): CharSequence? {
|
||||
return when (binding.recyclerViewFilter.adapter?.getItemViewType(position)) {
|
||||
FilterAdapter.VIEW_TYPE_SORT -> getString(R.string.sort_order)
|
||||
FilterAdapter.VIEW_TYPE_TAG -> getString(R.string.genres)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) = Unit
|
||||
|
||||
protected open fun onPopupMenuItemSelected(item: MenuItem, data: Manga) = false
|
||||
|
||||
@@ -1,23 +1,32 @@
|
||||
package org.koitharu.kotatsu.list.ui
|
||||
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.list.domain.AvailableFilters
|
||||
import org.koitharu.kotatsu.list.ui.filter.FilterItem
|
||||
import org.koitharu.kotatsu.list.ui.filter.OnFilterChangedListener
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
|
||||
abstract class MangaListViewModel(
|
||||
private val settings: AppSettings
|
||||
) : BaseViewModel() {
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel(), OnFilterChangedListener {
|
||||
|
||||
abstract val content: LiveData<List<ListModel>>
|
||||
val filter = MutableLiveData<MangaFilterConfig>()
|
||||
val filter = MutableLiveData<List<FilterItem>>()
|
||||
val listMode = MutableLiveData<ListMode>()
|
||||
val gridScale = settings.observe()
|
||||
.filter { it == AppSettings.KEY_GRID_SIZE }
|
||||
@@ -37,7 +46,62 @@ abstract class MangaListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
open fun onRemoveFilterTag(tag: MangaTag) = Unit
|
||||
protected var currentFilter: MangaFilter = MangaFilter(null, emptySet())
|
||||
private set(value) {
|
||||
field = value
|
||||
onFilterChanged()
|
||||
}
|
||||
protected var availableFilters: AvailableFilters? = null
|
||||
private var filterJob: Job? = null
|
||||
|
||||
final override fun onSortItemClick(item: FilterItem.Sort) {
|
||||
currentFilter = currentFilter.copy(sortOrder = item.order)
|
||||
}
|
||||
|
||||
final override fun onTagItemClick(item: FilterItem.Tag) {
|
||||
val tags = if (item.isChecked) {
|
||||
currentFilter.tags - item.tag
|
||||
} else {
|
||||
currentFilter.tags + item.tag
|
||||
}
|
||||
currentFilter = currentFilter.copy(tags = tags)
|
||||
}
|
||||
|
||||
fun onRemoveFilterTag(tag: MangaTag) {
|
||||
val tags = currentFilter.tags
|
||||
if (tag !in tags) {
|
||||
return
|
||||
}
|
||||
currentFilter = currentFilter.copy(tags = tags - tag)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
open fun onFilterChanged() {
|
||||
val previousJob = filterJob
|
||||
filterJob = launchJob(Dispatchers.Default) {
|
||||
previousJob?.cancelAndJoin()
|
||||
filter.postValue(
|
||||
availableFilters?.run {
|
||||
val list = ArrayList<FilterItem>(size + 2)
|
||||
if (sortOrders.isNotEmpty()) {
|
||||
val selectedSort = currentFilter.sortOrder ?: sortOrders.first()
|
||||
list += FilterItem.Header(R.string.sort_order)
|
||||
sortOrders.sortedBy { it.ordinal }.mapTo(list) {
|
||||
FilterItem.Sort(it, isSelected = it == selectedSort)
|
||||
}
|
||||
}
|
||||
if (tags.isNotEmpty()) {
|
||||
list += FilterItem.Header(R.string.genres)
|
||||
tags.sortedBy { it.title }.mapTo(list) {
|
||||
FilterItem.Tag(it, isChecked = it in currentFilter.tags)
|
||||
}
|
||||
}
|
||||
ensureActive()
|
||||
list
|
||||
}.orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onRefresh()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import android.view.MenuItem
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity
|
||||
@@ -29,10 +28,6 @@ class RemoteListFragment : MangaListFragment() {
|
||||
return source.title
|
||||
}
|
||||
|
||||
override fun onFilterChanged(filter: MangaFilter) {
|
||||
viewModel.applyFilter(filter)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.opt_list_remote, menu)
|
||||
|
||||
@@ -9,12 +9,10 @@ import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.list.ui.MangaFilterConfig
|
||||
import org.koitharu.kotatsu.list.domain.AvailableFilters
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
@@ -27,7 +25,6 @@ class RemoteListViewModel(
|
||||
private val mangaList = MutableStateFlow<List<Manga>?>(null)
|
||||
private val hasNextPage = MutableStateFlow(false)
|
||||
private val listError = MutableStateFlow<Throwable?>(null)
|
||||
private var appliedFilter: MangaFilter? = null
|
||||
private var loadingJob: Job? = null
|
||||
private val headerModel = ListHeader((repository as RemoteMangaRepository).title, 0)
|
||||
|
||||
@@ -68,16 +65,6 @@ class RemoteListViewModel(
|
||||
loadList(append = !mangaList.value.isNullOrEmpty())
|
||||
}
|
||||
|
||||
override fun onRemoveFilterTag(tag: MangaTag) {
|
||||
val filter = appliedFilter ?: return
|
||||
if (tag !in filter.tags) {
|
||||
return
|
||||
}
|
||||
applyFilter(
|
||||
filter.copy(tags = filter.tags - tag)
|
||||
)
|
||||
}
|
||||
|
||||
fun loadNextPage() {
|
||||
if (hasNextPage.value && listError.value == null) {
|
||||
loadList(append = true)
|
||||
@@ -93,8 +80,8 @@ class RemoteListViewModel(
|
||||
listError.value = null
|
||||
val list = repository.getList2(
|
||||
offset = if (append) mangaList.value?.size ?: 0 else 0,
|
||||
sortOrder = appliedFilter?.sortOrder,
|
||||
tags = appliedFilter?.tags,
|
||||
sortOrder = currentFilter.sortOrder,
|
||||
tags = currentFilter.tags,
|
||||
)
|
||||
if (!append) {
|
||||
mangaList.value = list
|
||||
@@ -111,26 +98,29 @@ class RemoteListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun applyFilter(newFilter: MangaFilter) {
|
||||
appliedFilter = newFilter
|
||||
override fun onFilterChanged() {
|
||||
super.onFilterChanged()
|
||||
mangaList.value = null
|
||||
hasNextPage.value = false
|
||||
loadList(false)
|
||||
filter.value?.run {
|
||||
filter.value = copy(currentFilter = newFilter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFilterModel() = appliedFilter?.run {
|
||||
CurrentFilterModel(tags.map { ChipsView.ChipModel(0, it.title, it) })
|
||||
private fun createFilterModel(): CurrentFilterModel? {
|
||||
val tags = currentFilter.tags
|
||||
return if (tags.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
CurrentFilterModel(tags.map { ChipsView.ChipModel(0, it.title, it) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFilter() {
|
||||
launchJob(Dispatchers.Default) {
|
||||
try {
|
||||
val sorts = repository.sortOrders.sortedBy { it.ordinal }
|
||||
val tags = repository.getTags().sortedBy { it.title }
|
||||
filter.postValue(MangaFilterConfig(sorts, tags, appliedFilter))
|
||||
val sorts = repository.sortOrders
|
||||
val tags = repository.getTags()
|
||||
availableFilters = AvailableFilters(sorts, tags)
|
||||
onFilterChanged()
|
||||
} catch (e: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
|
||||
Reference in New Issue
Block a user