Show current filter in list header

This commit is contained in:
Koitharu
2021-09-11 16:01:15 +03:00
parent c1b6cef362
commit 675e95da2b
10 changed files with 97 additions and 5 deletions

View File

@@ -22,12 +22,21 @@ class ChipsView @JvmOverloads constructor(
private var chipOnClickListener = OnClickListener { private var chipOnClickListener = OnClickListener {
onChipClickListener?.onChipClick(it as Chip, it.tag) onChipClickListener?.onChipClick(it as Chip, it.tag)
} }
private var chipOnCloseListener = OnClickListener {
onChipCloseClickListener?.onChipCloseClick(it as Chip, it.tag)
}
var onChipClickListener: OnChipClickListener? = null var onChipClickListener: OnChipClickListener? = null
set(value) { set(value) {
field = value field = value
val isChipClickable = value != null val isChipClickable = value != null
children.forEach { it.isClickable = isChipClickable } children.forEach { it.isClickable = isChipClickable }
} }
var onChipCloseClickListener: OnChipCloseClickListener? = null
set(value) {
field = value
val isCloseIconVisible = value != null
children.forEach { (it as? Chip)?.isCloseIconVisible = isCloseIconVisible }
}
override fun requestLayout() { override fun requestLayout() {
if (isLayoutSuppressedCompat) { if (isLayoutSuppressedCompat) {
@@ -69,7 +78,8 @@ class ChipsView @JvmOverloads constructor(
val drawable = ChipDrawable.createFromAttributes(context, null, 0, R.style.Widget_Kotatsu_Chip) val drawable = ChipDrawable.createFromAttributes(context, null, 0, R.style.Widget_Kotatsu_Chip)
chip.setChipDrawable(drawable) chip.setChipDrawable(drawable)
chip.setTextColor(ContextCompat.getColor(context, R.color.color_primary)) chip.setTextColor(ContextCompat.getColor(context, R.color.color_primary))
chip.isCloseIconVisible = false chip.isCloseIconVisible = onChipCloseClickListener != null
chip.setOnCloseIconClickListener(chipOnCloseListener)
chip.setEnsureMinTouchTargetSize(false) chip.setEnsureMinTouchTargetSize(false)
chip.setOnClickListener(chipOnClickListener) chip.setOnClickListener(chipOnClickListener)
addView(chip) addView(chip)
@@ -96,4 +106,9 @@ class ChipsView @JvmOverloads constructor(
fun onChipClick(chip: Chip, data: Any?) fun onChipClick(chip: Chip, data: Any?)
} }
fun interface OnChipCloseClickListener {
fun onChipCloseClick(chip: Chip, data: Any?)
}
} }

View File

@@ -71,7 +71,13 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
drawer = binding.root as? DrawerLayout drawer = binding.root as? DrawerLayout
drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
listAdapter = MangaListAdapter(get(), viewLifecycleOwner, this, ::resolveException) listAdapter = MangaListAdapter(
coil = get(),
lifecycleOwner = viewLifecycleOwner,
clickListener = this,
onRetryClick = ::resolveException,
onTagRemoveClick = viewModel::onRemoveFilterTag
)
paginationListener = PaginationScrollListener(4, this) paginationListener = PaginationScrollListener(4, this)
with(binding.recyclerView) { with(binding.recyclerView) {
setHasFixedSize(true) setHasFixedSize(true)
@@ -287,7 +293,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
final override fun getSectionTitle(position: Int): CharSequence? { final override fun getSectionTitle(position: Int): CharSequence? {
return when (binding.recyclerViewFilter.adapter?.getItemViewType(position)) { return when (binding.recyclerViewFilter.adapter?.getItemViewType(position)) {
FilterAdapter.VIEW_TYPE_SORT -> getString(R.string.sort_order) FilterAdapter.VIEW_TYPE_SORT -> getString(R.string.sort_order)
FilterAdapter.VIEW_TYPE_TAG -> getString(R.string.genre) FilterAdapter.VIEW_TYPE_TAG -> getString(R.string.genres)
else -> null else -> null
} }
} }

View File

@@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -36,6 +37,8 @@ abstract class MangaListViewModel(
} }
} }
open fun onRemoveFilterTag(tag: MangaTag) = Unit
abstract fun onRefresh() abstract fun onRefresh()
abstract fun onRetry() abstract fun onRetry()

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.list.ui.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.databinding.ItemCurrentFilterBinding
import org.koitharu.kotatsu.list.ui.model.CurrentFilterModel
import org.koitharu.kotatsu.list.ui.model.ListModel
fun currentFilterAD(
onTagRemoveClick: (MangaTag) -> Unit,
) = adapterDelegateViewBinding<CurrentFilterModel, ListModel, ItemCurrentFilterBinding>(
{ inflater, parent -> ItemCurrentFilterBinding.inflate(inflater, parent, false) }
) {
binding.chipsTags.onChipCloseClickListener = ChipsView.OnChipCloseClickListener { chip, data ->
onTagRemoveClick(data as? MangaTag ?: return@OnChipCloseClickListener)
}
bind {
binding.chipsTags.setChips(item.chips)
}
}

View File

@@ -6,6 +6,7 @@ import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.ui.DateTimeAgo import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaGridModel import org.koitharu.kotatsu.list.ui.model.MangaGridModel
@@ -17,7 +18,8 @@ class MangaListAdapter(
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Manga>, clickListener: OnListItemClickListener<Manga>,
onRetryClick: (Throwable) -> Unit onRetryClick: (Throwable) -> Unit,
onTagRemoveClick: (MangaTag) -> Unit,
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) { ) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
init { init {
@@ -38,6 +40,7 @@ class MangaListAdapter(
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(onRetryClick)) .addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(onRetryClick))
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD()) .addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD())
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD()) .addDelegate(ITEM_TYPE_HEADER, listHeaderAD())
.addDelegate(ITEM_TYPE_FILTER, currentFilterAD(onTagRemoveClick))
} }
fun setItems(list: List<ListModel>, commitCallback: Runnable) { fun setItems(list: List<ListModel>, commitCallback: Runnable) {
@@ -79,5 +82,6 @@ class MangaListAdapter(
const val ITEM_TYPE_ERROR_FOOTER = 7 const val ITEM_TYPE_ERROR_FOOTER = 7
const val ITEM_TYPE_EMPTY = 8 const val ITEM_TYPE_EMPTY = 8
const val ITEM_TYPE_HEADER = 9 const val ITEM_TYPE_HEADER = 9
const val ITEM_TYPE_FILTER = 10
} }
} }

View File

@@ -0,0 +1,7 @@
package org.koitharu.kotatsu.list.ui.model
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
data class CurrentFilterModel(
val chips: Collection<ChipsView.ChipModel>,
) : ListModel

View File

@@ -7,8 +7,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R 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.Manga
import org.koitharu.kotatsu.core.model.MangaFilter 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.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -40,8 +42,9 @@ class RemoteListViewModel(
list == null -> listOf(LoadingState) list == null -> listOf(LoadingState)
list.isEmpty() -> listOf(EmptyState(R.drawable.ic_book_cross, R.string.nothing_found, R.string._empty)) list.isEmpty() -> listOf(EmptyState(R.drawable.ic_book_cross, R.string.nothing_found, R.string._empty))
else -> { else -> {
val result = ArrayList<ListModel>(list.size + 2) val result = ArrayList<ListModel>(list.size + 3)
result += headerModel result += headerModel
createFilterModel()?.let { result.add(it) }
list.toUi(result, mode) list.toUi(result, mode)
when { when {
error != null -> result += error.toErrorFooter() error != null -> result += error.toErrorFooter()
@@ -65,6 +68,16 @@ class RemoteListViewModel(
loadList(append = !mangaList.value.isNullOrEmpty()) 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() { fun loadNextPage() {
if (hasNextPage.value && listError.value == null) { if (hasNextPage.value && listError.value == null) {
loadList(append = true) loadList(append = true)
@@ -108,6 +121,10 @@ class RemoteListViewModel(
} }
} }
private fun createFilterModel() = appliedFilter?.run {
CurrentFilterModel(tags.map { ChipsView.ChipModel(0, it.title, it) })
}
private fun loadFilter() { private fun loadFilter() {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
try { try {

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
android:id="@+id/chips_tags"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:closeIconEnabled="true"
app:singleLine="true" />
</HorizontalScrollView>

View File

@@ -236,4 +236,5 @@
<string name="auth_complete">Авторизация выполнена</string> <string name="auth_complete">Авторизация выполнена</string>
<string name="auth_not_supported_by">Авторизация в %s не поддерживается</string> <string name="auth_not_supported_by">Авторизация в %s не поддерживается</string>
<string name="text_clear_cookies_prompt">Вы выйдете из всех источников, в которых Вы авторизованы</string> <string name="text_clear_cookies_prompt">Вы выйдете из всех источников, в которых Вы авторизованы</string>
<string name="genres">Жанры</string>
</resources> </resources>

View File

@@ -239,4 +239,5 @@
<string name="auth_complete">Authorization complete</string> <string name="auth_complete">Authorization complete</string>
<string name="auth_not_supported_by">Authorization on %s is not supported</string> <string name="auth_not_supported_by">Authorization on %s is not supported</string>
<string name="text_clear_cookies_prompt">You will be logged out from all sources that you are authorized in</string> <string name="text_clear_cookies_prompt">You will be logged out from all sources that you are authorized in</string>
<string name="genres">Genres</string>
</resources> </resources>