Multiple selection in library

This commit is contained in:
Koitharu
2022-07-04 18:18:08 +03:00
parent b81aeaebd3
commit f42f244443
13 changed files with 361 additions and 102 deletions

View File

@@ -27,11 +27,14 @@ class ListSelectionController(
) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider {
private var actionMode: ActionMode? = null
private val stateEventObserver = StateEventObserver()
val count: Int
get() = decoration.checkedItemsCount
init {
registryOwner.lifecycle.addObserver(StateEventObserver())
}
fun snapshot(): Set<Long> {
return peekCheckedIds().toSet()
}
@@ -55,7 +58,6 @@ class ListSelectionController(
fun attachToRecyclerView(recyclerView: RecyclerView) {
recyclerView.addItemDecoration(decoration)
registryOwner.lifecycle.addObserver(stateEventObserver)
}
override fun saveState(): Bundle {

View File

@@ -0,0 +1,186 @@
package org.koitharu.kotatsu.base.ui.list
import android.app.Activity
import android.os.Bundle
import android.util.ArrayMap
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
import kotlinx.coroutines.Dispatchers
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
import kotlin.coroutines.EmptyCoroutineContext
private const val PROVIDER_NAME = "selection_decoration_sectioned"
class SectionedSelectionController<T : Any>(
private val activity: Activity,
private val registryOwner: SavedStateRegistryOwner,
private val callback: Callback<T>,
) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider {
private var actionMode: ActionMode? = null
private var pendingData: MutableMap<String, Collection<Long>>? = null
private val decorations = ArrayMap<T, AbstractSelectionItemDecoration>()
val count: Int
get() = decorations.values.sumOf { it.checkedItemsCount }
init {
registryOwner.lifecycle.addObserver(StateEventObserver())
}
fun snapshot(): Map<T, Set<Long>> {
return decorations.mapValues { it.value.checkedItemsIds.toSet() }
}
fun peekCheckedIds(): Map<T, Set<Long>> {
return decorations.mapValues { it.value.checkedItemsIds }
}
fun clear() {
decorations.values.forEach {
it.clearSelection()
}
notifySelectionChanged()
}
fun attachToRecyclerView(section: T, recyclerView: RecyclerView) {
val decoration = getDecoration(section)
val pendingIds = pendingData?.remove(section.toString())
if (!pendingIds.isNullOrEmpty()) {
decoration.checkAll(pendingIds)
startActionMode()
notifySelectionChanged()
}
recyclerView.addItemDecoration(decoration)
if (pendingData?.isEmpty() == true) {
pendingData = null
}
}
override fun saveState(): Bundle {
val bundle = Bundle(decorations.size)
for ((k, v) in decorations) {
bundle.putLongArray(k.toString(), v.checkedItemsIds.toLongArray())
}
return bundle
}
fun onItemClick(section: T, id: Long): Boolean {
val decoration = getDecoration(section)
if (isInSelectionMode()) {
decoration.toggleItemChecked(id)
if (isInSelectionMode()) {
actionMode?.invalidate()
} else {
actionMode?.finish()
}
notifySelectionChanged()
return true
}
return false
}
fun onItemLongClick(section: T, id: Long): Boolean {
val decoration = getDecoration(section)
startActionMode()
return actionMode?.also {
decoration.setItemIsChecked(id, true)
notifySelectionChanged()
} != null
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onCreateActionMode(mode, menu)
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onPrepareActionMode(mode, menu)
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return callback.onActionItemClicked(mode, item)
}
override fun onDestroyActionMode(mode: ActionMode) {
callback.onDestroyActionMode(mode)
clear()
actionMode = null
}
private fun startActionMode() {
if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
}
}
private fun isInSelectionMode(): Boolean {
return decorations.values.any { x -> x.checkedItemsCount > 0 }
}
private fun notifySelectionChanged() {
val count = this.count
callback.onSelectionChanged(count)
if (count == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
}
}
private fun restoreState(ids: MutableMap<String, Collection<Long>>) {
if (ids.isEmpty() || isInSelectionMode()) {
return
}
for ((k, v) in decorations) {
val items = ids.remove(k.toString())
if (!items.isNullOrEmpty()) {
v.checkAll(items)
}
}
pendingData = ids
if (isInSelectionMode()) {
startActionMode()
notifySelectionChanged()
}
}
private fun getDecoration(section: T): AbstractSelectionItemDecoration {
return decorations.getOrPut(section) {
callback.onCreateItemDecoration(section)
}
}
interface Callback<T> : ListSelectionController.Callback {
fun onCreateItemDecoration(section: T): AbstractSelectionItemDecoration
}
private inner class StateEventObserver : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_CREATE) {
val registry = registryOwner.savedStateRegistry
registry.registerSavedStateProvider(PROVIDER_NAME, this@SectionedSelectionController)
val state = registry.consumeRestoredStateForKey(PROVIDER_NAME)
if (state != null) {
Dispatchers.Main.dispatch(EmptyCoroutineContext) { // == Handler.post
if (source.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
restoreState(
state.keySet().associateWithTo(HashMap()) { state.getLongArray(it)?.toList().orEmpty() }
)
}
}
}
}
}
}
}

View File

@@ -15,6 +15,8 @@ sealed class DateTimeAgo : ListModel {
override fun format(resources: Resources): String {
return resources.getString(R.string.just_now)
}
override fun toString() = "just_now"
}
class MinutesAgo(val minutes: Int) : DateTimeAgo() {
@@ -31,6 +33,8 @@ sealed class DateTimeAgo : ListModel {
}
override fun hashCode(): Int = minutes
override fun toString() = "minutes_ago_$minutes"
}
class HoursAgo(val hours: Int) : DateTimeAgo() {
@@ -46,18 +50,24 @@ sealed class DateTimeAgo : ListModel {
}
override fun hashCode(): Int = hours
override fun toString() = "hours_ago_$hours"
}
object Today : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getString(R.string.today)
}
override fun toString() = "today"
}
object Yesterday : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getString(R.string.yesterday)
}
override fun toString() = "yesterday"
}
class DaysAgo(val days: Int) : DateTimeAgo() {
@@ -73,6 +83,8 @@ sealed class DateTimeAgo : ListModel {
}
override fun hashCode(): Int = days
override fun toString() = "days_ago_$days"
}
class Absolute(private val date: Date) : DateTimeAgo() {
@@ -97,11 +109,15 @@ sealed class DateTimeAgo : ListModel {
override fun hashCode(): Int {
return day
}
override fun toString() = "abs_$day"
}
object LongAgo : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getString(R.string.long_ago)
}
override fun toString() = "long_ago"
}
}

View File

@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.library.ui
import android.os.Bundle
import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
@@ -12,30 +11,31 @@ import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
import org.koitharu.kotatsu.databinding.FragmentLibraryBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
import org.koitharu.kotatsu.history.ui.HistoryActivity
import org.koitharu.kotatsu.library.ui.adapter.LibraryAdapter
import org.koitharu.kotatsu.library.ui.model.LibraryGroupModel
import org.koitharu.kotatsu.library.ui.adapter.LibraryListEventListener
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.flattenTo
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.findViewsByType
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), MangaListListener, ActionMode.Callback {
class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), LibraryListEventListener,
SectionedSelectionController.Callback<LibrarySectionModel> {
private val viewModel by viewModel<LibraryViewModel>()
private var adapter: LibraryAdapter? = null
private var selectionDecoration: MangaSelectionDecoration? = null
private var actionMode: ActionMode? = null
private var selectionController: SectionedSelectionController<LibrarySectionModel>? = null
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): FragmentLibraryBinding {
return FragmentLibraryBinding.inflate(inflater, container, false)
@@ -44,19 +44,17 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), MangaListListene
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val sizeResolver = ItemSizeResolver(resources, get())
val itemCLickListener = object : OnListItemClickListener<LibraryGroupModel> {
override fun onItemClick(item: LibraryGroupModel, view: View) {
onGroupClick(item, view)
}
}
selectionDecoration = MangaSelectionDecoration(view.context)
selectionController = SectionedSelectionController(
activity = requireActivity(),
registryOwner = this,
callback = this,
)
adapter = LibraryAdapter(
lifecycleOwner = viewLifecycleOwner,
coil = get(),
listener = this,
itemClickListener = itemCLickListener,
sizeResolver = sizeResolver,
selectionDecoration = checkNotNull(selectionDecoration),
selectionController = checkNotNull(selectionController),
)
binding.recyclerView.adapter = adapter
binding.recyclerView.setHasFixedSize(true)
@@ -68,42 +66,30 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), MangaListListene
override fun onDestroyView() {
super.onDestroyView()
adapter = null
selectionDecoration = null
actionMode = null
selectionController = null
}
override fun onItemClick(item: Manga, view: View) {
if (selectionDecoration?.checkedItemsCount != 0) {
selectionDecoration?.toggleItemChecked(item.id)
if (selectionDecoration?.checkedItemsCount == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
invalidateItemDecorations()
}
return
override fun onItemClick(item: Manga, section: LibrarySectionModel, view: View) {
if (selectionController?.onItemClick(section, item.id) != true) {
val intent = DetailsActivity.newIntent(view.context, item)
startActivity(intent)
}
}
override fun onItemLongClick(item: Manga, section: LibrarySectionModel, view: View): Boolean {
return selectionController?.onItemLongClick(section, item.id) ?: false
}
override fun onSectionClick(section: LibrarySectionModel, view: View) {
val intent = when (section) {
is LibrarySectionModel.History -> HistoryActivity.newIntent(view.context)
is LibrarySectionModel.Favourites -> TODO()
}
val intent = DetailsActivity.newIntent(view.context, item)
startActivity(intent)
}
override fun onItemLongClick(item: Manga, view: View): Boolean {
if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
}
return actionMode?.also {
selectionDecoration?.setItemIsChecked(item.id, true)
invalidateItemDecorations()
it.invalidate()
} != null
}
override fun onRetryClick(error: Throwable) = Unit
override fun onTagRemoveClick(tag: MangaTag) = Unit
override fun onFilterClick() = Unit
override fun onEmptyActionClick() = Unit
override fun onWindowInsetsChanged(insets: Insets) {
@@ -121,7 +107,7 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), MangaListListene
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.title = selectionDecoration?.checkedItemsCount?.toString()
mode.title = selectionController?.count?.toString()
return true
}
@@ -147,26 +133,28 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), MangaListListene
}
}
override fun onDestroyActionMode(mode: ActionMode) {
selectionDecoration?.clearSelection()
override fun onSelectionChanged(count: Int) {
invalidateItemDecorations()
actionMode = null
}
private fun onGroupClick(item: LibraryGroupModel, view: View) {
val intent = when (item) {
is LibraryGroupModel.History -> HistoryActivity.newIntent(view.context)
is LibraryGroupModel.Favourites -> TODO()
override fun onCreateItemDecoration(section: LibrarySectionModel): AbstractSelectionItemDecoration {
return MangaSelectionDecoration(requireContext())
}
private fun collectSelectedItemsMap(): Map<LibrarySectionModel, Set<Manga>> {
val snapshot = selectionController?.snapshot()
if (snapshot.isNullOrEmpty()) {
return emptyMap()
}
startActivity(intent)
return snapshot.mapValues { (_, ids) -> viewModel.getManga(ids) }
}
private fun collectSelectedItems(): Set<Manga> {
val ids = selectionDecoration?.checkedItemsIds
if (ids.isNullOrEmpty()) {
val snapshot = selectionController?.snapshot()
if (snapshot.isNullOrEmpty()) {
return emptySet()
}
return emptySet()//viewModel.getItems(ids)
return viewModel.getManga(snapshot.values.flattenTo(HashSet()))
}
private fun invalidateItemDecorations() {

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.library.ui
import androidx.collection.ArraySet
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
@@ -16,7 +17,7 @@ import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.library.ui.model.LibraryGroupModel
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.parsers.model.Manga
@@ -57,6 +58,25 @@ class LibraryViewModel(
}
}
fun getManga(ids: Set<Long>): Set<Manga> {
val snapshot = content.value ?: return emptySet()
val result = ArraySet<Manga>(ids.size)
for (section in snapshot) {
if (section !is LibrarySectionModel) {
continue
}
for (item in section.items) {
if (item.id in ids) {
result.add(item.manga)
if (result.size == ids.size) {
return result
}
}
}
}
return result
}
private suspend fun mapList(
history: List<MangaWithHistory>,
favourites: Map<FavouriteCategory, List<Manga>>,
@@ -66,12 +86,12 @@ class LibraryViewModel(
result += mapHistory(history)
}
for ((category, list) in favourites) {
result += LibraryGroupModel.Favourites(list.toUi(ListMode.GRID, this), category, R.string.show_all)
result += LibrarySectionModel.Favourites(list.toUi(ListMode.GRID, this), category, R.string.show_all)
}
return result
}
private suspend fun mapHistory(list: List<MangaWithHistory>): List<LibraryGroupModel.History> {
private suspend fun mapHistory(list: List<MangaWithHistory>): List<LibrarySectionModel.History> {
val showPercent = settings.isReadingIndicatorsEnabled
val groups = ArrayList<DateTimeAgo>()
val map = HashMap<DateTimeAgo, ArrayList<MangaItemModel>>()
@@ -84,12 +104,12 @@ class LibraryViewModel(
}
map.getOrPut(date) { ArrayList() }.add(manga.toGridModel(counter, percent))
}
val result = ArrayList<LibraryGroupModel.History>(HISTORY_MAX_SEGMENTS)
val result = ArrayList<LibrarySectionModel.History>(HISTORY_MAX_SEGMENTS)
repeat(minOf(HISTORY_MAX_SEGMENTS - 1, groups.size - 1)) { i ->
val key = groups[i]
val values = map.remove(key)
if (!values.isNullOrEmpty()) {
result.add(LibraryGroupModel.History(values, key, 0))
result.add(LibrarySectionModel.History(values, key, 0))
}
}
val values = map.values.flatten()
@@ -99,7 +119,7 @@ class LibraryViewModel(
} else {
map.keys.singleOrNull() ?: DateTimeAgo.LongAgo
}
result.add(LibraryGroupModel.History(values, key, R.string.show_all))
result.add(LibrarySectionModel.History(values, key, R.string.show_all))
}
return result
}

View File

@@ -5,21 +5,22 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.library.ui.model.LibraryGroupModel
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.*
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import kotlin.jvm.internal.Intrinsics
class LibraryAdapter(
lifecycleOwner: LifecycleOwner,
coil: ImageLoader,
listener: MangaListListener,
listener: LibraryListEventListener,
sizeResolver: ItemSizeResolver,
selectionDecoration: MangaSelectionDecoration,
itemClickListener: OnListItemClickListener<LibraryGroupModel>,
selectionController: SectionedSelectionController<LibrarySectionModel>,
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
init {
@@ -31,9 +32,8 @@ class LibraryAdapter(
lifecycleOwner = lifecycleOwner,
coil = coil,
sizeResolver = sizeResolver,
selectionDecoration = selectionDecoration,
selectionController = selectionController,
listener = listener,
itemClickListener = itemClickListener,
)
)
.addDelegate(loadingStateAD())
@@ -46,7 +46,7 @@ class LibraryAdapter(
override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
return when {
oldItem is LibraryGroupModel && newItem is LibraryGroupModel -> {
oldItem is LibrarySectionModel && newItem is LibrarySectionModel -> {
oldItem.key == newItem.key
}
else -> oldItem.javaClass == newItem.javaClass
@@ -59,7 +59,7 @@ class LibraryAdapter(
override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? {
return when {
oldItem is LibraryGroupModel && newItem is LibraryGroupModel -> Unit
oldItem is LibrarySectionModel && newItem is LibrarySectionModel -> Unit
else -> super.getChangePayload(oldItem, newItem)
}
}

View File

@@ -1,21 +1,22 @@
package org.koitharu.kotatsu.library.ui.adapter
import android.view.View
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.databinding.ItemListGroupBinding
import org.koitharu.kotatsu.library.ui.model.LibraryGroupModel
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.clearItemDecorations
import org.koitharu.kotatsu.utils.ext.setTextAndVisible
fun libraryGroupAD(
@@ -23,26 +24,42 @@ fun libraryGroupAD(
lifecycleOwner: LifecycleOwner,
coil: ImageLoader,
sizeResolver: ItemSizeResolver,
selectionDecoration: MangaSelectionDecoration,
listener: OnListItemClickListener<Manga>,
itemClickListener: OnListItemClickListener<LibraryGroupModel>,
) = adapterDelegateViewBinding<LibraryGroupModel, ListModel, ItemListGroupBinding>(
selectionController: SectionedSelectionController<LibrarySectionModel>,
listener: LibraryListEventListener,
) = adapterDelegateViewBinding<LibrarySectionModel, ListModel, ItemListGroupBinding>(
{ layoutInflater, parent -> ItemListGroupBinding.inflate(layoutInflater, parent, false) }
) {
binding.recyclerView.setRecycledViewPool(sharedPool)
val adapter = AsyncListDifferDelegationAdapter<ListModel>(
MangaItemDiffCallback(),
mangaGridItemAD(coil, lifecycleOwner, listener, sizeResolver)
)
binding.recyclerView.addItemDecoration(selectionDecoration)
binding.recyclerView.adapter = adapter
val spacing = context.resources.getDimensionPixelOffset(R.dimen.grid_spacing)
binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
val eventListener = AdapterDelegateClickListenerAdapter(this, itemClickListener)
binding.buttonMore.setOnClickListener(eventListener)
val listenerAdapter = object : OnListItemClickListener<Manga>, View.OnClickListener {
override fun onItemClick(item: Manga, view: View) {
listener.onItemClick(item, this@adapterDelegateViewBinding.item, view)
}
bind {
override fun onItemLongClick(item: Manga, view: View): Boolean {
return listener.onItemLongClick(item, this@adapterDelegateViewBinding.item, view)
}
override fun onClick(v: View?) {
listener.onSectionClick(item, itemView)
}
}
val adapter = AsyncListDifferDelegationAdapter(
MangaItemDiffCallback(),
mangaGridItemAD(coil, lifecycleOwner, listenerAdapter, sizeResolver)
)
binding.recyclerView.setRecycledViewPool(sharedPool)
binding.recyclerView.adapter = adapter
val spacingDecoration = SpacingItemDecoration(context.resources.getDimensionPixelOffset(R.dimen.grid_spacing))
binding.recyclerView.addItemDecoration(spacingDecoration)
binding.buttonMore.setOnClickListener(listenerAdapter)
bind { payloads ->
if (payloads.isEmpty()) {
binding.recyclerView.clearItemDecorations()
binding.recyclerView.addItemDecoration(spacingDecoration)
selectionController.attachToRecyclerView(item, binding.recyclerView)
}
binding.textViewTitle.text = item.getTitle(context.resources)
binding.buttonMore.setTextAndVisible(item.showAllButtonText)
adapter.items = item.items

View File

@@ -0,0 +1,15 @@
package org.koitharu.kotatsu.library.ui.adapter
import android.view.View
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.parsers.model.Manga
interface LibraryListEventListener : ListStateHolderListener {
fun onItemClick(item: Manga, section: LibrarySectionModel, view: View)
fun onItemLongClick(item: Manga, section: LibrarySectionModel, view: View): Boolean
fun onSectionClick(section: LibrarySectionModel, view: View)
}

View File

@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
sealed class LibraryGroupModel(
sealed class LibrarySectionModel(
val items: List<MangaItemModel>,
@StringRes val showAllButtonText: Int,
) : ListModel {
@@ -20,7 +20,7 @@ sealed class LibraryGroupModel(
items: List<MangaItemModel>,
val timeAgo: DateTimeAgo?,
showAllButtonText: Int,
) : LibraryGroupModel(items, showAllButtonText) {
) : LibrarySectionModel(items, showAllButtonText) {
override val key: Any
get() = timeAgo?.javaClass ?: this::class.java
@@ -48,13 +48,17 @@ sealed class LibraryGroupModel(
result = 31 * result + showAllButtonText.hashCode()
return result
}
override fun toString(): String {
return "hist_$timeAgo"
}
}
class Favourites(
items: List<MangaItemModel>,
val category: FavouriteCategory,
showAllButtonText: Int,
) : LibraryGroupModel(items, showAllButtonText) {
) : LibrarySectionModel(items, showAllButtonText) {
override val key: Any
get() = category.id
@@ -82,5 +86,9 @@ sealed class LibraryGroupModel(
result = 31 * result + showAllButtonText.hashCode()
return result
}
override fun toString(): String {
return "fav_${category.id}"
}
}
}

View File

@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.utils.ext.setTextAndVisible
fun emptyStateListAD(
listener: MangaListListener,
listener: ListStateHolderListener,
) = adapterDelegateViewBinding<EmptyState, ListModel, ItemEmptyStateBinding>(
{ inflater, parent -> ItemEmptyStateBinding.inflate(inflater, parent, false) }
) {

View File

@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
fun errorStateListAD(
listener: MangaListListener,
listener: ListStateHolderListener,
) = adapterDelegateViewBinding<ErrorState, ListModel, ItemErrorStateBinding>(
{ inflater, parent -> ItemErrorStateBinding.inflate(inflater, parent, false) }
) {

View File

@@ -0,0 +1,8 @@
package org.koitharu.kotatsu.list.ui.adapter
interface ListStateHolderListener {
fun onRetryClick(error: Throwable)
fun onEmptyActionClick()
}

View File

@@ -4,10 +4,9 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
interface MangaListListener : OnListItemClickListener<Manga> {
interface MangaListListener : OnListItemClickListener<Manga>, ListStateHolderListener {
fun onRetryClick(error: Throwable)
fun onTagRemoveClick(tag: MangaTag)
fun onFilterClick()
fun onEmptyActionClick()
}