Multiple selection in library

pull/163/head
Koitharu 4 years ago
parent b81aeaebd3
commit f42f244443
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -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 {

@ -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() }
)
}
}
}
}
}
}
}

@ -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"
}
}

@ -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,41 +66,29 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), MangaListListene
override fun onDestroyView() {
super.onDestroyView()
adapter = null
selectionDecoration = null
actionMode = 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
}
val intent = DetailsActivity.newIntent(view.context, item)
startActivity(intent)
selectionController = null
}
override fun onItemLongClick(item: Manga, view: View): Boolean {
if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
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)
}
return actionMode?.also {
selectionDecoration?.setItemIsChecked(item.id, true)
invalidateItemDecorations()
it.invalidate()
} != null
}
override fun onRetryClick(error: Throwable) = Unit
override fun onItemLongClick(item: Manga, section: LibrarySectionModel, view: View): Boolean {
return selectionController?.onItemLongClick(section, item.id) ?: false
}
override fun onTagRemoveClick(tag: MangaTag) = Unit
override fun onSectionClick(section: LibrarySectionModel, view: View) {
val intent = when (section) {
is LibrarySectionModel.History -> HistoryActivity.newIntent(view.context)
is LibrarySectionModel.Favourites -> TODO()
}
startActivity(intent)
}
override fun onFilterClick() = Unit
override fun onRetryClick(error: Throwable) = Unit
override fun onEmptyActionClick() = Unit
@ -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() {

@ -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
}

@ -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)
}
}

@ -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>(
val listenerAdapter = object : OnListItemClickListener<Manga>, View.OnClickListener {
override fun onItemClick(item: Manga, view: View) {
listener.onItemClick(item, this@adapterDelegateViewBinding.item, view)
}
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, listener, sizeResolver)
mangaGridItemAD(coil, lifecycleOwner, listenerAdapter, sizeResolver)
)
binding.recyclerView.addItemDecoration(selectionDecoration)
binding.recyclerView.setRecycledViewPool(sharedPool)
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 spacingDecoration = SpacingItemDecoration(context.resources.getDimensionPixelOffset(R.dimen.grid_spacing))
binding.recyclerView.addItemDecoration(spacingDecoration)
binding.buttonMore.setOnClickListener(listenerAdapter)
bind {
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

@ -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)
}

@ -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}"
}
}
}

@ -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) }
) {

@ -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) }
) {

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

@ -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()
}
Loading…
Cancel
Save