From 942d4fe5abfb246f1d839ab8b68a88fa909affbd Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 3 Jul 2023 13:57:51 +0300 Subject: [PATCH] Refactor ListModel --- .../ui/adapter/BookmarksGroupAdapter.kt | 34 +----- .../bookmarks/ui/model/BookmarksGroup.kt | 15 ++- .../core/ui/image/TrimTransformation.kt | 19 +++- .../kotatsu/core/ui/model/DateTimeAgo.kt | 2 +- .../kotatsu/details/ui/model/MangaBranch.kt | 14 +++ .../details/ui/scrobbling/ScrobblingInfoAD.kt | 3 +- .../ui/scrobbling/ScrollingInfoAdapter.kt | 21 +--- .../download/ui/list/DownloadItemModel.kt | 18 +++ .../download/ui/list/DownloadsAdapter.kt | 45 +------- .../download/ui/list/DownloadsViewModel.kt | 3 +- .../kotatsu/explore/ui/ExploreFragment.kt | 17 +-- .../kotatsu/explore/ui/ExploreViewModel.kt | 37 ++++--- .../explore/ui/adapter/ExploreAdapter.kt | 21 ++-- .../ui/adapter/ExploreAdapterDelegates.kt | 73 ++---------- .../explore/ui/adapter/ExploreDiffCallback.kt | 29 ----- .../ui/adapter/ExploreListEventListener.kt | 6 +- .../explore/ui/model/ExploreButtons.kt | 19 ++++ .../kotatsu/explore/ui/model/ExploreItem.kt | 104 ------------------ .../explore/ui/model/MangaSourceItem.kt | 30 +++++ .../explore/ui/model/RecommendationsItem.kt | 27 +++++ .../categories/adapter/CategoriesAdapter.kt | 40 +------ .../categories/adapter/CategoryListModel.kt | 4 + .../select/model/CategoriesHeaderItem.kt | 13 ++- .../select/model/MangaCategoryItem.kt | 36 +++++- .../kotatsu/filter/ui/FilterAdapter.kt | 17 +-- .../filter/ui/model/FilterHeaderModel.kt | 2 +- .../kotatsu/filter/ui/model/FilterItem.kt | 29 +++++ .../history/ui/HistoryListViewModel.kt | 3 +- .../kotatsu/list/ui/ListModelDiffCallback.kt | 22 ++++ .../kotatsu/list/ui/adapter/ListHeader2AD.kt | 36 ------ .../kotatsu/list/ui/adapter/ListHeaderAD.kt | 10 -- .../list/ui/adapter/MangaGridItemAD.kt | 2 + .../list/ui/adapter/MangaListAdapter.kt | 69 +----------- .../ui/adapter/MangaListDetailedItemAD.kt | 2 + .../list/ui/adapter/MangaListItemAD.kt | 2 + .../list/ui/adapter/RelatedDateItemAD.kt | 14 --- .../kotatsu/list/ui/model/EmptyHint.kt | 34 +++++- .../kotatsu/list/ui/model/EmptyState.kt | 12 +- .../kotatsu/list/ui/model/ErrorFooter.kt | 25 ++++- .../kotatsu/list/ui/model/ErrorState.kt | 27 ++++- .../kotatsu/list/ui/model/ListHeader.kt | 10 +- .../kotatsu/list/ui/model/ListModel.kt | 4 + .../kotatsu/list/ui/model/LoadingFooter.kt | 4 + .../kotatsu/list/ui/model/LoadingState.kt | 4 + .../kotatsu/list/ui/model/MangaGridModel.kt | 30 ++++- .../kotatsu/list/ui/model/MangaItemModel.kt | 28 +++-- .../list/ui/model/MangaListDetailedModel.kt | 33 +++++- .../kotatsu/list/ui/model/MangaListModel.kt | 31 +++++- .../reader/ui/thumbnails/PageThumbnail.kt | 4 + .../adapter/PageThumbnailAdapter.kt | 32 +----- .../common/domain/model/ScrobblerManga.kt | 4 + .../common/domain/model/ScrobblingInfo.kt | 4 + .../common/domain/model/ScrobblingStatus.kt | 11 +- .../common/ui/selector/model/ScrobblerHint.kt | 8 +- .../search/ui/multi/MultiSearchListModel.kt | 14 +++ .../settings/storage/DirectoryModel.kt | 13 +++ .../shelf/ui/adapter/MangaItemDiffCallback.kt | 30 ----- .../kotatsu/shelf/ui/adapter/ShelfAdapter.kt | 41 +------ .../kotatsu/shelf/ui/adapter/ShelfGroupAD.kt | 3 +- .../shelf/ui/config/ShelfSettingsAdapter.kt | 32 +----- .../config/ShelfSettingsAdapterDelegates.kt | 5 +- .../shelf/ui/config/ShelfSettingsItemModel.kt | 17 +++ .../shelf/ui/model/ShelfSectionModel.kt | 12 ++ .../kotatsu/tracker/ui/feed/FeedViewModel.kt | 3 +- .../tracker/ui/feed/adapter/FeedAdapter.kt | 37 +------ .../kotatsu/tracker/ui/feed/model/FeedItem.kt | 33 +++++- .../main/res/layout/item_recommendation.xml | 3 +- 67 files changed, 671 insertions(+), 715 deletions(-) delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreDiffCallback.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreButtons.kt delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/MangaSourceItem.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/RecommendationsItem.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/list/ui/ListModelDiffCallback.kt delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeader2AD.kt delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/RelatedDateItemAD.kt delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/MangaItemDiffCallback.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt index 31ab12fd7..b95e1f195 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt @@ -1,7 +1,6 @@ package org.koitharu.kotatsu.bookmarks.ui.adapter import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter @@ -9,15 +8,14 @@ import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.SectionedSelectionController +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener 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 org.koitharu.kotatsu.list.ui.model.LoadingFooter import org.koitharu.kotatsu.parsers.model.Manga -import kotlin.jvm.internal.Intrinsics class BookmarksGroupAdapter( coil: ImageLoader, @@ -26,7 +24,7 @@ class BookmarksGroupAdapter( listener: ListStateHolderListener, bookmarkClickListener: OnListItemClickListener, groupClickListener: OnListItemClickListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback) { init { val pool = RecyclerView.RecycledViewPool() @@ -46,32 +44,4 @@ class BookmarksGroupAdapter( .addDelegate(emptyStateListAD(coil, lifecycleOwner, listener)) .addDelegate(errorStateListAD(listener)) } - - private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return when { - oldItem is BookmarksGroup && newItem is BookmarksGroup -> { - oldItem.manga.id == newItem.manga.id - } - - oldItem is LoadingFooter && newItem is LoadingFooter -> { - oldItem.key == newItem.key - } - - else -> oldItem.javaClass == newItem.javaClass - } - } - - override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return Intrinsics.areEqual(oldItem, newItem) - } - - override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? { - return when { - oldItem is BookmarksGroup && newItem is BookmarksGroup -> Unit - else -> super.getChangePayload(oldItem, newItem) - } - } - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/model/BookmarksGroup.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/model/BookmarksGroup.kt index c9dc2e129..99f3afcdb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/model/BookmarksGroup.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/model/BookmarksGroup.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.bookmarks.ui.model import org.koitharu.kotatsu.bookmarks.domain.Bookmark +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.util.areItemsEquals @@ -10,6 +11,18 @@ class BookmarksGroup( val bookmarks: List, ) : ListModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is BookmarksGroup && other.manga.id == manga.id + } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is BookmarksGroup && previousState.bookmarks != bookmarks) { + ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED + } else { + super.getChangePayload(previousState) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -28,4 +41,4 @@ class BookmarksGroup( result = 31 * result + bookmarks.sumOf { it.imageUrl.hashCode() } return result } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TrimTransformation.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TrimTransformation.kt index bc22724ed..ad9e13009 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TrimTransformation.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TrimTransformation.kt @@ -15,7 +15,7 @@ class TrimTransformation( private val tolerance: Int = 20, ) : Transformation { - override val cacheKey: String = javaClass.name + override val cacheKey: String = "${javaClass.name}-$tolerance" override suspend fun transform(input: Bitmap, size: Size): Bitmap { var left = 0 @@ -98,14 +98,23 @@ class TrimTransformation( } } - override fun equals(other: Any?) = other is TrimTransformation - - override fun hashCode() = javaClass.hashCode() - private fun isColorTheSame(@ColorInt a: Int, @ColorInt b: Int): Boolean { return abs(a.red - b.red) <= tolerance && abs(a.green - b.green) <= tolerance && abs(a.blue - b.blue) <= tolerance && abs(a.alpha - b.alpha) <= tolerance } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TrimTransformation + + return tolerance == other.tolerance + } + + override fun hashCode(): Int { + return tolerance + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/DateTimeAgo.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/DateTimeAgo.kt index 8e468b5ad..e5f67cba5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/DateTimeAgo.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/DateTimeAgo.kt @@ -7,7 +7,7 @@ import org.koitharu.kotatsu.core.util.ext.format import org.koitharu.kotatsu.list.ui.model.ListModel import java.util.Date -sealed class DateTimeAgo : ListModel { +sealed class DateTimeAgo { abstract fun format(resources: Resources): String diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/MangaBranch.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/MangaBranch.kt index 8588187f6..3cd38cddc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/MangaBranch.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/MangaBranch.kt @@ -1,5 +1,7 @@ package org.koitharu.kotatsu.details.ui.model +import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel class MangaBranch( @@ -8,6 +10,18 @@ class MangaBranch( val isSelected: Boolean, ) : ListModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is MangaBranch && other.name == name + } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is MangaBranch && previousState.isSelected != isSelected) { + ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED + } else { + super.getChangePayload(previousState) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoAD.kt index 1447002d3..a7059e477 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoAD.kt @@ -9,13 +9,14 @@ import org.koitharu.kotatsu.core.util.ext.disposeImageRequest import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemScrobblingInfoBinding +import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo fun scrobblingInfoAD( lifecycleOwner: LifecycleOwner, coil: ImageLoader, fragmentManager: FragmentManager, -) = adapterDelegateViewBinding( +) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemScrobblingInfoBinding.inflate(layoutInflater, parent, false) }, ) { binding.root.setOnClickListener { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrollingInfoAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrollingInfoAdapter.kt index d4cfd55a2..b63a5f790 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrollingInfoAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrollingInfoAdapter.kt @@ -2,33 +2,18 @@ package org.koitharu.kotatsu.details.ui.scrobbling import androidx.fragment.app.FragmentManager import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.DiffUtil import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter -import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback +import org.koitharu.kotatsu.list.ui.model.ListModel class ScrollingInfoAdapter( lifecycleOwner: LifecycleOwner, coil: ImageLoader, fragmentManager: FragmentManager, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback) { init { delegatesManager.addDelegate(scrobblingInfoAD(lifecycleOwner, coil, fragmentManager)) } - - private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ScrobblingInfo, newItem: ScrobblingInfo): Boolean { - return oldItem.scrobbler == newItem.scrobbler - } - - override fun areContentsTheSame(oldItem: ScrobblingInfo, newItem: ScrobblingInfo): Boolean { - return oldItem == newItem - } - - override fun getChangePayload(oldItem: ScrobblingInfo, newItem: ScrobblingInfo): Any { - return Unit - } - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemModel.kt index f8d95a66c..2a4e5a613 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemModel.kt @@ -47,6 +47,24 @@ class DownloadItemModel( return timestamp.compareTo(other.timestamp) } + override fun areItemsTheSame(other: ListModel): Boolean { + return other is DownloadItemModel && other.id == id + } + + override fun getChangePayload(previousState: ListModel): Any? { + return when (previousState) { + is DownloadItemModel -> { + if (workState == previousState.workState) { + Unit + } else { + null + } + } + + else -> super.getChangePayload(previousState) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsAdapter.kt index 16fa29387..c08e21395 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsAdapter.kt @@ -1,62 +1,25 @@ package org.koitharu.kotatsu.download.ui.list import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.DiffUtil import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter -import org.koitharu.kotatsu.core.ui.model.DateTimeAgo +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD +import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD -import org.koitharu.kotatsu.list.ui.adapter.relatedDateItemAD import org.koitharu.kotatsu.list.ui.model.ListModel -import kotlin.jvm.internal.Intrinsics class DownloadsAdapter( lifecycleOwner: LifecycleOwner, coil: ImageLoader, listener: DownloadItemListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback) { init { delegatesManager.addDelegate(ITEM_TYPE_DOWNLOAD, downloadItemAD(lifecycleOwner, coil, listener)) .addDelegate(loadingStateAD()) .addDelegate(emptyStateListAD(coil, lifecycleOwner, null)) - .addDelegate(relatedDateItemAD()) - } - - private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel) = when { - - oldItem is DownloadItemModel && newItem is DownloadItemModel -> { - oldItem.id == newItem.id - } - - oldItem is DateTimeAgo && newItem is DateTimeAgo -> { - oldItem == newItem - } - - else -> oldItem.javaClass == newItem.javaClass - } - - override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return Intrinsics.areEqual(oldItem, newItem) - } - - override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? { - return when (newItem) { - is DownloadItemModel -> { - oldItem as DownloadItemModel - if (oldItem.workState == newItem.workState) { - Unit - } else { - null - } - } - - else -> super.getChangePayload(oldItem, newItem) - } - } + .addDelegate(listHeaderAD(null)) } companion object { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt index 24b3dc68f..42d266b65 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt @@ -26,6 +26,7 @@ import org.koitharu.kotatsu.core.util.ext.daysDiff import org.koitharu.kotatsu.download.domain.DownloadState import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.list.ui.model.EmptyState +import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.parsers.model.Manga @@ -183,7 +184,7 @@ class DownloadsViewModel @Inject constructor( for (item in this) { val date = timeAgo(item.timestamp) if (prevDate != date) { - destination += date + destination += ListHeader(date, 0, null) } prevDate = date destination += item diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index a2737790c..94fe7fe75 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -31,9 +31,10 @@ import org.koitharu.kotatsu.databinding.FragmentExploreBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter import org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener -import org.koitharu.kotatsu.explore.ui.model.ExploreItem +import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity import org.koitharu.kotatsu.history.ui.HistoryActivity +import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.search.ui.MangaListActivity @@ -46,7 +47,7 @@ class ExploreFragment : BaseFragment(), RecyclerViewOwner, ExploreListEventListener, - OnListItemClickListener { + OnListItemClickListener { @Inject lateinit var coil: ImageLoader @@ -96,7 +97,7 @@ class ExploreFragment : ) } - override fun onManageClick(view: View) { + override fun onListHeaderClick(item: ListHeader, view: View) { startActivity(SettingsActivity.newManageSourcesIntent(view.context)) } @@ -117,12 +118,12 @@ class ExploreFragment : startActivity(intent) } - override fun onItemClick(item: ExploreItem.Source, view: View) { + override fun onItemClick(item: MangaSourceItem, view: View) { val intent = MangaListActivity.newIntent(view.context, item.source) startActivity(intent) } - override fun onItemLongClick(item: ExploreItem.Source, view: View): Boolean { + override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean { val menu = PopupMenu(view.context, view) menu.inflate(R.menu.popup_source) menu.setOnMenuItemClickListener(SourceMenuListener(item)) @@ -132,7 +133,9 @@ class ExploreFragment : override fun onRetryClick(error: Throwable) = Unit - override fun onEmptyActionClick() = onManageClick(requireView()) + override fun onEmptyActionClick() { + startActivity(SettingsActivity.newManageSourcesIntent(context ?: return)) + } private fun onOpenManga(manga: Manga) { val intent = DetailsActivity.newIntent(context ?: return, manga) @@ -164,7 +167,7 @@ class ExploreFragment : } private inner class SourceMenuListener( - private val sourceItem: ExploreItem.Source, + private val sourceItem: MangaSourceItem, ) : PopupMenu.OnMenuItemClickListener { override fun onMenuItemClick(item: MenuItem): Boolean { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt index 128c2ef0a..504cbdf7e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt @@ -22,7 +22,13 @@ import org.koitharu.kotatsu.core.ui.util.ReversibleHandle import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.explore.domain.ExploreRepository -import org.koitharu.kotatsu.explore.ui.model.ExploreItem +import org.koitharu.kotatsu.explore.ui.model.ExploreButtons +import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem +import org.koitharu.kotatsu.explore.ui.model.RecommendationsItem +import org.koitharu.kotatsu.list.ui.model.EmptyHint +import org.koitharu.kotatsu.list.ui.model.ListHeader +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaState @@ -46,13 +52,13 @@ class ExploreViewModel @Inject constructor( val onActionDone = MutableEventFlow() val onShowSuggestionsTip = MutableEventFlow() - val content: StateFlow> = isLoading.flatMapLatest { loading -> + val content: StateFlow> = isLoading.flatMapLatest { loading -> if (loading) { - flowOf(listOf(ExploreItem.Loading)) + flowOf(getLoadingStateList()) } else { createContentFlow() } - }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(ExploreItem.Loading)) + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, getLoadingStateList()) init { launchJob(Dispatchers.Default) { @@ -98,13 +104,11 @@ class ExploreViewModel @Inject constructor( .map { settings.getMangaSources(includeHidden = false) } .combine(isGrid) { content, grid -> buildList(content, grid) } - private fun buildList(sources: List, isGrid: Boolean): List { - val result = ArrayList(sources.size + 3) - result += ExploreItem.Buttons( - isSuggestionsEnabled = settings.isSuggestionsEnabled, - ) - result += ExploreItem.Header(R.string.suggestions, isButtonVisible = false) - result += ExploreItem.Recommendation( + private fun buildList(sources: List, isGrid: Boolean): List { + val result = ArrayList(sources.size + 4) + result += ExploreButtons() + result += ListHeader(R.string.suggestions, 0, null) + result += RecommendationsItem( Manga( 0, "Test", @@ -123,11 +127,11 @@ class ExploreViewModel @Inject constructor( MangaSource.DESUME, ), ) // TODO - result += ExploreItem.Header(R.string.remote_sources, sources.isNotEmpty()) if (sources.isNotEmpty()) { - sources.mapTo(result) { ExploreItem.Source(it, isGrid) } + result += ListHeader(R.string.remote_sources, R.string.manage, null) + sources.mapTo(result) { MangaSourceItem(it, isGrid) } } else { - result += ExploreItem.EmptyHint( + result += EmptyHint( icon = R.drawable.ic_empty_common, textPrimary = R.string.no_manga_sources, textSecondary = R.string.no_manga_sources_text, @@ -136,4 +140,9 @@ class ExploreViewModel @Inject constructor( } return result } + + private fun getLoadingStateList() = listOf( + ExploreButtons(), + LoadingState, + ) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt index 7f928722e..66e5fc939 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt @@ -4,25 +4,29 @@ import androidx.lifecycle.LifecycleOwner import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.explore.ui.model.ExploreItem +import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback +import org.koitharu.kotatsu.list.ui.adapter.emptyHintAD +import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD +import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD +import org.koitharu.kotatsu.list.ui.model.ListModel class ExploreAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, listener: ExploreListEventListener, - clickListener: OnListItemClickListener, -) : AsyncListDifferDelegationAdapter(ExploreDiffCallback()) { + clickListener: OnListItemClickListener, +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback) { init { delegatesManager .addDelegate(ITEM_TYPE_BUTTONS, exploreButtonsAD(listener)) - .addDelegate(ITEM_TYPE_RECOMMENDATION_HEADER, exploreRecommendationHeaderAD()) .addDelegate(ITEM_TYPE_RECOMMENDATION, exploreRecommendationItemAD(coil, listener, lifecycleOwner)) - .addDelegate(ITEM_TYPE_HEADER, exploreSourcesHeaderAD(listener)) + .addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener)) .addDelegate(ITEM_TYPE_SOURCE_LIST, exploreSourceListItemAD(coil, clickListener, lifecycleOwner)) .addDelegate(ITEM_TYPE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner)) - .addDelegate(ITEM_TYPE_HINT, exploreEmptyHintListAD(listener)) - .addDelegate(ITEM_TYPE_LOADING, exploreLoadingAD()) + .addDelegate(ITEM_TYPE_HINT, emptyHintAD(coil, lifecycleOwner, listener)) + .addDelegate(ITEM_TYPE_LOADING, loadingStateAD()) } companion object { @@ -33,7 +37,6 @@ class ExploreAdapter( const val ITEM_TYPE_SOURCE_GRID = 3 const val ITEM_TYPE_HINT = 4 const val ITEM_TYPE_LOADING = 5 - const val ITEM_TYPE_RECOMMENDATION_HEADER = 6 - const val ITEM_TYPE_RECOMMENDATION = 7 + const val ITEM_TYPE_RECOMMENDATION = 6 } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt index 478e04350..966d5f94c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt @@ -1,10 +1,8 @@ package org.koitharu.kotatsu.explore.ui.adapter import android.view.View -import androidx.core.view.isVisible import androidx.lifecycle.LifecycleOwner import coil.ImageLoader -import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.parser.favicon.faviconUri @@ -14,20 +12,19 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.util.ext.disposeImageRequest import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.newImageRequest -import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.core.util.ext.source -import org.koitharu.kotatsu.databinding.ItemEmptyCardBinding import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceListBinding -import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding import org.koitharu.kotatsu.databinding.ItemRecommendationBinding -import org.koitharu.kotatsu.explore.ui.model.ExploreItem -import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener +import org.koitharu.kotatsu.explore.ui.model.ExploreButtons +import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem +import org.koitharu.kotatsu.explore.ui.model.RecommendationsItem +import org.koitharu.kotatsu.list.ui.model.ListModel fun exploreButtonsAD( clickListener: View.OnClickListener, -) = adapterDelegateViewBinding( +) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemExploreButtonsBinding.inflate(layoutInflater, parent, false) }, ) { @@ -43,21 +40,11 @@ fun exploreButtonsAD( //} } -fun exploreRecommendationHeaderAD() = adapterDelegateViewBinding( - { layoutInflater, parent -> ItemHeaderButtonBinding.inflate(layoutInflater, parent, false) } -) { - - bind { - binding.textViewTitle.setText(item.titleResId) - binding.buttonMore.isVisible = false - } -} - fun exploreRecommendationItemAD( coil: ImageLoader, clickListener: View.OnClickListener, lifecycleOwner: LifecycleOwner, -) = adapterDelegateViewBinding( +) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemRecommendationBinding.inflate(layoutInflater, parent, false) } ) { @@ -77,31 +64,13 @@ fun exploreRecommendationItemAD( } } -fun exploreSourcesHeaderAD( - listener: ExploreListEventListener, -) = adapterDelegateViewBinding( - { layoutInflater, parent -> ItemHeaderButtonBinding.inflate(layoutInflater, parent, false) }, -) { - - val listenerAdapter = View.OnClickListener { - listener.onManageClick(itemView) - } - - binding.buttonMore.setOnClickListener(listenerAdapter) - - bind { - binding.textViewTitle.setText(item.titleResId) - binding.buttonMore.isVisible = item.isButtonVisible - } -} - fun exploreSourceListItemAD( coil: ImageLoader, - listener: OnListItemClickListener, + listener: OnListItemClickListener, lifecycleOwner: LifecycleOwner, -) = adapterDelegateViewBinding( +) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemExploreSourceListBinding.inflate(layoutInflater, parent, false) }, - on = { item, _, _ -> item is ExploreItem.Source && !item.isGrid }, + on = { item, _, _ -> item is MangaSourceItem && !item.isGrid }, ) { val eventListener = AdapterDelegateClickListenerAdapter(this, listener) @@ -128,11 +97,11 @@ fun exploreSourceListItemAD( fun exploreSourceGridItemAD( coil: ImageLoader, - listener: OnListItemClickListener, + listener: OnListItemClickListener, lifecycleOwner: LifecycleOwner, -) = adapterDelegateViewBinding( +) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemExploreSourceGridBinding.inflate(layoutInflater, parent, false) }, - on = { item, _, _ -> item is ExploreItem.Source && item.isGrid }, + on = { item, _, _ -> item is MangaSourceItem && item.isGrid }, ) { val eventListener = AdapterDelegateClickListenerAdapter(this, listener) @@ -156,21 +125,3 @@ fun exploreSourceGridItemAD( binding.imageViewIcon.disposeImageRequest() } } - -fun exploreEmptyHintListAD( - listener: ListStateHolderListener, -) = adapterDelegateViewBinding( - { inflater, parent -> ItemEmptyCardBinding.inflate(inflater, parent, false) }, -) { - - binding.buttonRetry.setOnClickListener { listener.onEmptyActionClick() } - - bind { - binding.icon.setImageResource(item.icon) - binding.textPrimary.setText(item.textPrimary) - binding.textSecondary.setTextAndVisible(item.textSecondary) - binding.buttonRetry.setTextAndVisible(item.actionStringRes) - } -} - -fun exploreLoadingAD() = adapterDelegate(R.layout.item_loading_state) {} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreDiffCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreDiffCallback.kt deleted file mode 100644 index d8bf22bd9..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreDiffCallback.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.koitharu.kotatsu.explore.ui.adapter - -import androidx.recyclerview.widget.DiffUtil -import org.koitharu.kotatsu.explore.ui.model.ExploreItem - -class ExploreDiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ExploreItem, newItem: ExploreItem): Boolean { - return when { - oldItem.javaClass != newItem.javaClass -> false - oldItem is ExploreItem.Buttons && newItem is ExploreItem.Buttons -> true - oldItem is ExploreItem.Loading && newItem is ExploreItem.Loading -> true - oldItem is ExploreItem.EmptyHint && newItem is ExploreItem.EmptyHint -> true - oldItem is ExploreItem.Source && newItem is ExploreItem.Source -> { - oldItem.source == newItem.source && oldItem.isGrid == newItem.isGrid - } - - oldItem is ExploreItem.Header && newItem is ExploreItem.Header -> { - oldItem.titleResId == newItem.titleResId - } - - else -> false - } - } - - override fun areContentsTheSame(oldItem: ExploreItem, newItem: ExploreItem): Boolean { - return oldItem == newItem - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreListEventListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreListEventListener.kt index 4bd122086..2814e2709 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreListEventListener.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreListEventListener.kt @@ -1,9 +1,7 @@ package org.koitharu.kotatsu.explore.ui.adapter import android.view.View +import org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener -interface ExploreListEventListener : ListStateHolderListener, View.OnClickListener { - - fun onManageClick(view: View) -} \ No newline at end of file +interface ExploreListEventListener : ListStateHolderListener, View.OnClickListener, ListHeaderClickListener diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreButtons.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreButtons.kt new file mode 100644 index 000000000..90b224b65 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreButtons.kt @@ -0,0 +1,19 @@ +package org.koitharu.kotatsu.explore.ui.model + +import org.koitharu.kotatsu.list.ui.model.ListModel + +class ExploreButtons : ListModel { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is ExploreButtons + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return javaClass == other?.javaClass + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt deleted file mode 100644 index e8abfb0e5..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt +++ /dev/null @@ -1,104 +0,0 @@ -package org.koitharu.kotatsu.explore.ui.model - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import org.koitharu.kotatsu.list.ui.model.EmptyState -import org.koitharu.kotatsu.list.ui.model.ListModel -import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.model.MangaSource - -sealed interface ExploreItem : ListModel { - - class Buttons( - val isSuggestionsEnabled: Boolean - ) : ExploreItem { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Buttons - - return isSuggestionsEnabled == other.isSuggestionsEnabled - } - - override fun hashCode(): Int { - return isSuggestionsEnabled.hashCode() - } - } - - class Header( - @StringRes val titleResId: Int, - val isButtonVisible: Boolean, - ) : ExploreItem { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Header - - if (titleResId != other.titleResId) return false - return isButtonVisible == other.isButtonVisible - } - - override fun hashCode(): Int { - var result = titleResId - result = 31 * result + isButtonVisible.hashCode() - return result - } - } - - class Recommendation( - val manga: Manga - ) : ExploreItem { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Recommendation - - return manga == other.manga - } - - override fun hashCode(): Int { - return 31 * manga.hashCode() - } - - } - - class Source( - val source: MangaSource, - val isGrid: Boolean, - ) : ExploreItem { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Source - - if (source != other.source) return false - return isGrid == other.isGrid - } - - override fun hashCode(): Int { - var result = source.hashCode() - result = 31 * result + isGrid.hashCode() - return result - } - } - - class EmptyHint( - @DrawableRes icon: Int, - @StringRes textPrimary: Int, - @StringRes textSecondary: Int, - @StringRes actionStringRes: Int, - ) : EmptyState(icon, textPrimary, textSecondary, actionStringRes), ExploreItem - - object Loading : ExploreItem { - - override fun equals(other: Any?): Boolean = other === Loading - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/MangaSourceItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/MangaSourceItem.kt new file mode 100644 index 000000000..fa0c0e9e5 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/MangaSourceItem.kt @@ -0,0 +1,30 @@ +package org.koitharu.kotatsu.explore.ui.model + +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.parsers.model.MangaSource + +class MangaSourceItem( + val source: MangaSource, + val isGrid: Boolean, +) : ListModel { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is MangaSourceItem && other.source == source + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MangaSourceItem + + if (source != other.source) return false + return isGrid == other.isGrid + } + + override fun hashCode(): Int { + var result = source.hashCode() + result = 31 * result + isGrid.hashCode() + return result + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/RecommendationsItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/RecommendationsItem.kt new file mode 100644 index 000000000..c4586ef62 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/RecommendationsItem.kt @@ -0,0 +1,27 @@ +package org.koitharu.kotatsu.explore.ui.model + +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.parsers.model.Manga + +class RecommendationsItem( + val manga: Manga +) : ListModel { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is RecommendationsItem + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RecommendationsItem + + return manga == other.manga + } + + override fun hashCode(): Int { + return 31 * manga.hashCode() + } + +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt index 741faa08a..30a8e8fc5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt @@ -1,61 +1,25 @@ package org.koitharu.kotatsu.favourites.ui.categories.adapter import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.DiffUtil import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD import org.koitharu.kotatsu.list.ui.model.ListModel -import kotlin.jvm.internal.Intrinsics class CategoriesAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, onItemClickListener: FavouriteCategoriesListListener, listListener: ListStateHolderListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback) { init { delegatesManager.addDelegate(categoryAD(coil, lifecycleOwner, onItemClickListener)) .addDelegate(emptyStateListAD(coil, lifecycleOwner, listListener)) .addDelegate(loadingStateAD()) } - - private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return when { - oldItem is CategoryListModel && newItem is CategoryListModel -> { - oldItem.category.id == newItem.category.id - } - - else -> oldItem.javaClass == newItem.javaClass - } - } - - override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return Intrinsics.areEqual(oldItem, newItem) - } - - override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? { - return when { - oldItem is CategoryListModel && newItem is CategoryListModel -> { - if (oldItem.category == newItem.category && - oldItem.mangaCount == newItem.mangaCount && - oldItem.covers == newItem.covers && - oldItem.isReorderMode != newItem.isReorderMode - ) { - Unit - } else { - super.getChangePayload(oldItem, newItem) - } - } - - else -> super.getChangePayload(oldItem, newItem) - } - } - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryListModel.kt index f09c775d1..5995548d2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryListModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryListModel.kt @@ -11,6 +11,10 @@ class CategoryListModel( val isReorderMode: Boolean, ) : ListModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is CategoryListModel && other.category.id == category.id + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/model/CategoriesHeaderItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/model/CategoriesHeaderItem.kt index cde85e768..8f7f34bb0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/model/CategoriesHeaderItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/model/CategoriesHeaderItem.kt @@ -4,5 +4,16 @@ import org.koitharu.kotatsu.list.ui.model.ListModel class CategoriesHeaderItem : ListModel { - override fun equals(other: Any?): Boolean = other?.javaClass == CategoriesHeaderItem::class.java + override fun areItemsTheSame(other: ListModel): Boolean { + return other is CategoriesHeaderItem + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return javaClass == other?.javaClass + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/model/MangaCategoryItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/model/MangaCategoryItem.kt index 721176233..d75fbad8a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/model/MangaCategoryItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/model/MangaCategoryItem.kt @@ -1,9 +1,41 @@ package org.koitharu.kotatsu.favourites.ui.categories.select.model +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel -data class MangaCategoryItem( +class MangaCategoryItem( val id: Long, val name: String, val isChecked: Boolean, -) : ListModel +) : ListModel { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is MangaCategoryItem && other.id == id + } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is MangaCategoryItem && previousState.isChecked != isChecked) { + ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED + } else { + super.getChangePayload(previousState) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MangaCategoryItem + + if (id != other.id) return false + if (name != other.name) return false + return isChecked == other.isChecked + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + name.hashCode() + result = 31 * result + isChecked.hashCode() + return result + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterAdapter.kt index 74d7459fc..ae1a9b9ef 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterAdapter.kt @@ -5,7 +5,7 @@ import androidx.recyclerview.widget.AsyncListDiffer.ListListener import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller import org.koitharu.kotatsu.filter.ui.model.FilterItem -import org.koitharu.kotatsu.list.ui.adapter.listSimpleHeaderAD +import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD import org.koitharu.kotatsu.list.ui.model.ListModel @@ -16,12 +16,8 @@ class FilterAdapter( ) : AsyncListDifferDelegationAdapter(FilterDiffCallback()), FastScroller.SectionIndexer { init { - delegatesManager - .addDelegate(filterSortDelegate(listener)) - .addDelegate(filterTagDelegate(listener)) - .addDelegate(listSimpleHeaderAD()) - .addDelegate(loadingStateAD()) - .addDelegate(loadingFooterAD()) + delegatesManager.addDelegate(filterSortDelegate(listener)).addDelegate(filterTagDelegate(listener)) + .addDelegate(listHeaderAD(null)).addDelegate(loadingStateAD()).addDelegate(loadingFooterAD()) .addDelegate(filterErrorDelegate()) differ.addListListener(listListener) } @@ -36,11 +32,4 @@ class FilterAdapter( } return null } - - companion object { - - const val ITEM_TYPE_HEADER = 0 - const val ITEM_TYPE_SORT = 1 - const val ITEM_TYPE_TAG = 2 - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterHeaderModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterHeaderModel.kt index cf9dcd834..c981c9782 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterHeaderModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterHeaderModel.kt @@ -8,7 +8,7 @@ class FilterHeaderModel( val chips: Collection, val sortOrder: SortOrder?, val hasSelectedTags: Boolean, -) : ListModel { +) { val textSummary: String get() = chips.mapNotNull { if (it.isChecked) it.title else null }.joinToString() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterItem.kt index a2ad2cddb..e096ebc51 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterItem.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.filter.ui.model import androidx.annotation.StringRes +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.SortOrder @@ -12,6 +13,18 @@ sealed interface FilterItem : ListModel { val isSelected: Boolean, ) : FilterItem { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is Sort && other.order == order + } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is Sort && previousState.isSelected != isSelected) { + ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED + } else { + super.getChangePayload(previousState) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -34,6 +47,18 @@ sealed interface FilterItem : ListModel { val isChecked: Boolean, ) : FilterItem { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is Tag && other.tag == tag + } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is Tag && previousState.isChecked != isChecked) { + ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED + } else { + super.getChangePayload(previousState) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -55,6 +80,10 @@ sealed interface FilterItem : ListModel { @StringRes val textResId: Int, ) : FilterItem { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is Error && textResId == other.textResId + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt index 673fbc87d..818252d9f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt @@ -24,6 +24,7 @@ import org.koitharu.kotatsu.history.domain.model.MangaWithHistory import org.koitharu.kotatsu.list.domain.ListExtraProvider import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.model.EmptyState +import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.toErrorState @@ -108,7 +109,7 @@ class HistoryListViewModel @Inject constructor( if (grouped) { val date = timeAgo(history.updatedAt) if (prevDate != date) { - result += date + result += ListHeader(date, 0, null) } prevDate = date } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/ListModelDiffCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/ListModelDiffCallback.kt new file mode 100644 index 000000000..80ec73d4b --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/ListModelDiffCallback.kt @@ -0,0 +1,22 @@ +package org.koitharu.kotatsu.list.ui + +import androidx.recyclerview.widget.DiffUtil +import org.koitharu.kotatsu.list.ui.model.ListModel + +object ListModelDiffCallback : DiffUtil.ItemCallback() { + + val PAYLOAD_CHECKED_CHANGED = Any() + val PAYLOAD_NESTED_LIST_CHANGED = Any() + + override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { + return oldItem.areItemsTheSame(newItem) + } + + override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { + return oldItem == newItem + } + + override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? { + return newItem.getChangePayload(oldItem) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeader2AD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeader2AD.kt deleted file mode 100644 index 518983bd4..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeader2AD.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.koitharu.kotatsu.list.ui.adapter - -import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled -import org.koitharu.kotatsu.databinding.FragmentFilterHeaderBinding -import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel -import org.koitharu.kotatsu.list.ui.model.ListModel -import org.koitharu.kotatsu.parsers.model.MangaTag - -@Deprecated("") -fun listHeader2AD( - listener: MangaListListener, -) = adapterDelegateViewBinding( - { layoutInflater, parent -> FragmentFilterHeaderBinding.inflate(layoutInflater, parent, false) }, -) { - - var ignoreChecking = false - binding.chipsTags.setOnCheckedStateChangeListener { _, _ -> - if (!ignoreChecking) { - listener.onUpdateFilter(binding.chipsTags.getCheckedData(MangaTag::class.java)) - } - } - - bind { payloads -> - if (payloads.isNotEmpty()) { - if (context.isAnimationsEnabled) { - binding.scrollView.smoothScrollTo(0, 0) - } else { - binding.scrollView.scrollTo(0, 0) - } - } - ignoreChecking = true - binding.chipsTags.setChips(item.chips) // TODO use recyclerview - ignoreChecking = false - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt index 8076dc68b..51a58e2e0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt @@ -3,7 +3,6 @@ package org.koitharu.kotatsu.list.ui.adapter import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding -import org.koitharu.kotatsu.databinding.ItemHeaderSingleBinding import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel @@ -23,12 +22,3 @@ fun listHeaderAD( binding.buttonMore.setTextAndVisible(item.buttonTextRes) } } - -fun listSimpleHeaderAD() = adapterDelegateViewBinding( - { inflater, parent -> ItemHeaderSingleBinding.inflate(inflater, parent, false) }, -) { - - bind { - binding.textViewTitle.text = item.getText(context) - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt index e47650a25..0b75d440e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt @@ -6,6 +6,7 @@ import com.google.android.material.badge.BadgeDrawable import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver +import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.util.ext.disposeImageRequest import org.koitharu.kotatsu.core.util.ext.enqueueWith @@ -44,6 +45,7 @@ fun mangaGridItemAD( placeholder(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder) error(R.drawable.ic_error_placeholder) + transformations(TrimTransformation()) allowRgb565(true) source(item.source) enqueueWith(coil) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt index 09a4809d6..a28a6c390 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt @@ -1,25 +1,16 @@ package org.koitharu.kotatsu.list.ui.adapter import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.DiffUtil import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter -import org.koitharu.kotatsu.core.ui.model.DateTimeAgo -import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel -import org.koitharu.kotatsu.list.ui.model.ListHeader +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel -import org.koitharu.kotatsu.list.ui.model.LoadingFooter -import org.koitharu.kotatsu.list.ui.model.MangaGridModel -import org.koitharu.kotatsu.list.ui.model.MangaItemModel -import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel -import org.koitharu.kotatsu.list.ui.model.MangaListModel -import kotlin.jvm.internal.Intrinsics open class MangaListAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, listener: MangaListListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback) { init { delegatesManager @@ -28,64 +19,10 @@ open class MangaListAdapter( .addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, listener, null)) .addDelegate(ITEM_TYPE_LOADING_FOOTER, loadingFooterAD()) .addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD()) - .addDelegate(ITEM_TYPE_DATE, relatedDateItemAD()) .addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener)) .addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener)) .addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener)) .addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener)) - .addDelegate(ITEM_TYPE_HEADER_2, listHeader2AD(listener)) - } - - private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel) = when { - oldItem is MangaListModel && newItem is MangaListModel -> { - oldItem.id == newItem.id - } - - oldItem is MangaListDetailedModel && newItem is MangaListDetailedModel -> { - oldItem.id == newItem.id - } - - oldItem is MangaGridModel && newItem is MangaGridModel -> { - oldItem.id == newItem.id - } - - oldItem is DateTimeAgo && newItem is DateTimeAgo -> { - oldItem == newItem - } - - oldItem is ListHeader && newItem is ListHeader -> { - oldItem.textRes == newItem.textRes && - oldItem.text == newItem.text && - oldItem.dateTimeAgo == newItem.dateTimeAgo - } - - oldItem is LoadingFooter && newItem is LoadingFooter -> { - oldItem.key == newItem.key - } - - else -> oldItem.javaClass == newItem.javaClass - } - - override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return Intrinsics.areEqual(oldItem, newItem) - } - - override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? { - return when (newItem) { - is MangaItemModel -> { - oldItem as MangaItemModel - if (oldItem.progress != newItem.progress) { - PAYLOAD_PROGRESS - } else { - } - } - - is FilterHeaderModel -> Unit - else -> super.getChangePayload(oldItem, newItem) - } - } } companion object { @@ -95,12 +32,10 @@ open class MangaListAdapter( const val ITEM_TYPE_MANGA_GRID = 2 const val ITEM_TYPE_LOADING_FOOTER = 3 const val ITEM_TYPE_LOADING_STATE = 4 - const val ITEM_TYPE_DATE = 5 const val ITEM_TYPE_ERROR_STATE = 6 const val ITEM_TYPE_ERROR_FOOTER = 7 const val ITEM_TYPE_EMPTY = 8 const val ITEM_TYPE_HEADER = 9 - const val ITEM_TYPE_HEADER_2 = 10 val PAYLOAD_PROGRESS = Any() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt index 95396fca3..5707e8069 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt @@ -9,6 +9,7 @@ import com.google.android.material.chip.Chip import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver +import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.util.ext.disposeImageRequest import org.koitharu.kotatsu.core.util.ext.enqueueWith @@ -57,6 +58,7 @@ fun mangaListDetailedItemAD( placeholder(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder) error(R.drawable.ic_error_placeholder) + transformations(TrimTransformation()) allowRgb565(true) source(item.source) enqueueWith(coil) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt index 77f68418d..5d7f656dc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt @@ -5,6 +5,7 @@ import coil.ImageLoader import com.google.android.material.badge.BadgeDrawable import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.util.ext.disposeImageRequest import org.koitharu.kotatsu.core.util.ext.enqueueWith @@ -40,6 +41,7 @@ fun mangaListItemAD( fallback(R.drawable.ic_placeholder) error(R.drawable.ic_error_placeholder) allowRgb565(true) + transformations(TrimTransformation()) source(item.source) enqueueWith(coil) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/RelatedDateItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/RelatedDateItemAD.kt deleted file mode 100644 index 3a615aab5..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/RelatedDateItemAD.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.koitharu.kotatsu.list.ui.adapter - -import android.widget.TextView -import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.ui.model.DateTimeAgo -import org.koitharu.kotatsu.list.ui.model.ListModel - -fun relatedDateItemAD() = adapterDelegate(R.layout.item_header) { - - bind { - (itemView as TextView).text = item.format(context.resources) - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyHint.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyHint.kt index cc42528eb..ad10cba44 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyHint.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyHint.kt @@ -4,11 +4,35 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes class EmptyHint( - @DrawableRes icon: Int, - @StringRes textPrimary: Int, - @StringRes textSecondary: Int, - @StringRes actionStringRes: Int, -) : EmptyState(icon, textPrimary, textSecondary, actionStringRes) { + @DrawableRes val icon: Int, + @StringRes val textPrimary: Int, + @StringRes val textSecondary: Int, + @StringRes val actionStringRes: Int, +) : ListModel { fun toState() = EmptyState(icon, textPrimary, textSecondary, actionStringRes) + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is EmptyHint && textPrimary == other.textPrimary + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as EmptyHint + + if (icon != other.icon) return false + if (textPrimary != other.textPrimary) return false + if (textSecondary != other.textSecondary) return false + return actionStringRes == other.actionStringRes + } + + override fun hashCode(): Int { + var result = icon + result = 31 * result + textPrimary + result = 31 * result + textSecondary + result = 31 * result + actionStringRes + return result + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyState.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyState.kt index ddef7eac6..609a6ea46 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyState.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyState.kt @@ -3,7 +3,7 @@ package org.koitharu.kotatsu.list.ui.model import androidx.annotation.DrawableRes import androidx.annotation.StringRes -open class EmptyState( +class EmptyState( @DrawableRes val icon: Int, @StringRes val textPrimary: Int, @StringRes val textSecondary: Int, @@ -19,9 +19,7 @@ open class EmptyState( if (icon != other.icon) return false if (textPrimary != other.textPrimary) return false if (textSecondary != other.textSecondary) return false - if (actionStringRes != other.actionStringRes) return false - - return true + return actionStringRes == other.actionStringRes } override fun hashCode(): Int { @@ -31,4 +29,8 @@ open class EmptyState( result = 31 * result + actionStringRes return result } -} \ No newline at end of file + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is EmptyState + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorFooter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorFooter.kt index 7bcaf03d8..e29ac986f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorFooter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorFooter.kt @@ -2,7 +2,28 @@ package org.koitharu.kotatsu.list.ui.model import androidx.annotation.DrawableRes -data class ErrorFooter( +class ErrorFooter( val exception: Throwable, @DrawableRes val icon: Int -) : ListModel \ No newline at end of file +) : ListModel { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ErrorFooter + + if (exception != other.exception) return false + return icon == other.icon + } + + override fun hashCode(): Int { + var result = exception.hashCode() + result = 31 * result + icon + return result + } + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is ErrorFooter && exception == other.exception + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorState.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorState.kt index 43f566555..a53c8bdc2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorState.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorState.kt @@ -3,9 +3,32 @@ package org.koitharu.kotatsu.list.ui.model import androidx.annotation.DrawableRes import androidx.annotation.StringRes -data class ErrorState( +class ErrorState( val exception: Throwable, @DrawableRes val icon: Int, val canRetry: Boolean, @StringRes val buttonText: Int -) : ListModel \ No newline at end of file +) : ListModel { + + + override fun areItemsTheSame(other: ListModel) = other is ErrorState + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ErrorState + + if (exception != other.exception) return false + if (icon != other.icon) return false + if (canRetry != other.canRetry) return false + return buttonText == other.buttonText + } + + override fun hashCode(): Int { + var result = exception.hashCode() + result = 31 * result + icon + result = 31 * result + canRetry.hashCode() + result = 31 * result + buttonText + return result + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListHeader.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListHeader.kt index 1be8b5b74..c35baf93f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListHeader.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListHeader.kt @@ -5,9 +5,9 @@ import androidx.annotation.StringRes import org.koitharu.kotatsu.core.ui.model.DateTimeAgo class ListHeader private constructor( - val text: CharSequence?, - @StringRes val textRes: Int, - val dateTimeAgo: DateTimeAgo?, + private val text: CharSequence?, + @StringRes private val textRes: Int, + private val dateTimeAgo: DateTimeAgo?, @StringRes val buttonTextRes: Int, val payload: Any?, ) : ListModel { @@ -36,6 +36,10 @@ class ListHeader private constructor( else -> dateTimeAgo?.format(context.resources) } + override fun areItemsTheSame(other: ListModel): Boolean { + return other is ListHeader && text == other.text && dateTimeAgo == other.dateTimeAgo && textRes == other.textRes + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModel.kt index ba16c8278..1f92be769 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModel.kt @@ -3,4 +3,8 @@ package org.koitharu.kotatsu.list.ui.model interface ListModel { override fun equals(other: Any?): Boolean + + fun areItemsTheSame(other: ListModel): Boolean + + fun getChangePayload(previousState: ListModel): Any? = null } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingFooter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingFooter.kt index c7c336a7a..1a2f8382e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingFooter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingFooter.kt @@ -16,4 +16,8 @@ class LoadingFooter @JvmOverloads constructor( override fun hashCode(): Int { return key } + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is LoadingFooter && key == other.key + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingState.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingState.kt index ae0a3088e..36fa63418 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingState.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingState.kt @@ -3,4 +3,8 @@ package org.koitharu.kotatsu.list.ui.model object LoadingState : ListModel { override fun equals(other: Any?): Boolean = other === LoadingState + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is LoadingState + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt index 97a414879..42f09464b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt @@ -1,12 +1,38 @@ package org.koitharu.kotatsu.list.ui.model +import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter import org.koitharu.kotatsu.parsers.model.Manga -data class MangaGridModel( +class MangaGridModel( override val id: Long, override val title: String, override val coverUrl: String, override val manga: Manga, override val counter: Int, override val progress: Float, -) : MangaItemModel \ No newline at end of file +) : MangaItemModel() { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MangaGridModel + + if (id != other.id) return false + if (title != other.title) return false + if (coverUrl != other.coverUrl) return false + if (manga != other.manga) return false + if (counter != other.counter) return false + return progress == other.progress + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + title.hashCode() + result = 31 * result + coverUrl.hashCode() + result = 31 * result + manga.hashCode() + result = 31 * result + counter + result = 31 * result + progress.hashCode() + return result + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaItemModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaItemModel.kt index 2d4710a70..bdc002c5c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaItemModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaItemModel.kt @@ -1,17 +1,31 @@ package org.koitharu.kotatsu.list.ui.model +import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource -sealed interface MangaItemModel : ListModel { +sealed class MangaItemModel : ListModel { - val id: Long - val manga: Manga - val title: String - val coverUrl: String - val counter: Int - val progress: Float + abstract val id: Long + abstract val manga: Manga + abstract val title: String + abstract val coverUrl: String + abstract val counter: Int + abstract val progress: Float val source: MangaSource get() = manga.source + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is MangaItemModel && other.javaClass == javaClass && id == other.id + } + + override fun getChangePayload(previousState: ListModel): Any? { + return when { + previousState !is MangaItemModel -> super.getChangePayload(previousState) + progress != previousState.progress -> MangaListAdapter.PAYLOAD_PROGRESS + counter != previousState.counter -> Unit + else -> null + } + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListDetailedModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListDetailedModel.kt index 1d246ebb2..a8840c16c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListDetailedModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListDetailedModel.kt @@ -3,7 +3,7 @@ package org.koitharu.kotatsu.list.ui.model import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.parsers.model.Manga -data class MangaListDetailedModel( +class MangaListDetailedModel( override val id: Long, override val title: String, val subtitle: String?, @@ -12,4 +12,33 @@ data class MangaListDetailedModel( override val counter: Int, override val progress: Float, val tags: List, -) : MangaItemModel +) : MangaItemModel() { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MangaListDetailedModel + + if (id != other.id) return false + if (title != other.title) return false + if (subtitle != other.subtitle) return false + if (coverUrl != other.coverUrl) return false + if (manga != other.manga) return false + if (counter != other.counter) return false + if (progress != other.progress) return false + return tags == other.tags + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + title.hashCode() + result = 31 * result + (subtitle?.hashCode() ?: 0) + result = 31 * result + coverUrl.hashCode() + result = 31 * result + manga.hashCode() + result = 31 * result + counter + result = 31 * result + progress.hashCode() + result = 31 * result + tags.hashCode() + return result + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt index 71ac5743c..7566505b0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt @@ -2,7 +2,7 @@ package org.koitharu.kotatsu.list.ui.model import org.koitharu.kotatsu.parsers.model.Manga -data class MangaListModel( +class MangaListModel( override val id: Long, override val title: String, val subtitle: String, @@ -10,4 +10,31 @@ data class MangaListModel( override val manga: Manga, override val counter: Int, override val progress: Float, -) : MangaItemModel \ No newline at end of file +) : MangaItemModel() { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MangaListModel + + if (id != other.id) return false + if (title != other.title) return false + if (subtitle != other.subtitle) return false + if (coverUrl != other.coverUrl) return false + if (manga != other.manga) return false + if (counter != other.counter) return false + return progress == other.progress + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + title.hashCode() + result = 31 * result + subtitle.hashCode() + result = 31 * result + coverUrl.hashCode() + result = 31 * result + manga.hashCode() + result = 31 * result + counter + result = 31 * result + progress.hashCode() + return result + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnail.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnail.kt index f413b9615..304f59234 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnail.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnail.kt @@ -13,6 +13,10 @@ class PageThumbnail( val number get() = page.index + 1 + override fun areItemsTheSame(other: ListModel): Boolean { + return other is PageThumbnail && page == other.page + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt index 1b197fdb6..596ce6b75 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt @@ -2,23 +2,22 @@ package org.koitharu.kotatsu.reader.ui.thumbnails.adapter import android.content.Context import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.DiffUtil import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel -import org.koitharu.kotatsu.list.ui.model.LoadingFooter import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail class PageThumbnailAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, clickListener: OnListItemClickListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()), FastScroller.SectionIndexer { +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback), FastScroller.SectionIndexer { init { delegatesManager.addDelegate(ITEM_TYPE_THUMBNAIL, pageThumbnailAD(coil, lifecycleOwner, clickListener)) @@ -37,33 +36,6 @@ class PageThumbnailAdapter( return null } - private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return when { - oldItem is PageThumbnail && newItem is PageThumbnail -> { - oldItem.page == newItem.page - } - - oldItem is ListHeader && newItem is ListHeader -> { - oldItem.textRes == newItem.textRes && - oldItem.text == newItem.text && - oldItem.dateTimeAgo == newItem.dateTimeAgo - } - - oldItem is LoadingFooter && newItem is LoadingFooter -> { - oldItem.key == newItem.key - } - - else -> oldItem.javaClass == newItem.javaClass - } - } - - override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return oldItem == newItem - } - } - companion object { const val ITEM_TYPE_THUMBNAIL = 0 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblerManga.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblerManga.kt index b00da2c8d..9bb3af85f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblerManga.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblerManga.kt @@ -10,6 +10,10 @@ class ScrobblerManga( val url: String, ) : ListModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is ScrobblerManga && other.id == id + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingInfo.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingInfo.kt index 5dd798f33..75dc931d7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingInfo.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingInfo.kt @@ -16,6 +16,10 @@ class ScrobblingInfo( val externalUrl: String, ) : ListModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is ScrobblingInfo && other.scrobbler == scrobbler + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingStatus.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingStatus.kt index 70118d585..33ac263f2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingStatus.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingStatus.kt @@ -4,10 +4,9 @@ import org.koitharu.kotatsu.list.ui.model.ListModel enum class ScrobblingStatus : ListModel { - PLANNED, - READING, - RE_READING, - COMPLETED, - ON_HOLD, - DROPPED, + PLANNED, READING, RE_READING, COMPLETED, ON_HOLD, DROPPED; + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is ScrobblingStatus && other.ordinal == ordinal + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/model/ScrobblerHint.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/model/ScrobblerHint.kt index 83c122fa7..ec2342521 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/model/ScrobblerHint.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/model/ScrobblerHint.kt @@ -12,6 +12,10 @@ class ScrobblerHint( @StringRes val actionStringRes: Int, ) : ListModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is ScrobblerHint && other.textPrimary == textPrimary + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -22,9 +26,7 @@ class ScrobblerHint( if (textPrimary != other.textPrimary) return false if (textSecondary != other.textSecondary) return false if (error != other.error) return false - if (actionStringRes != other.actionStringRes) return false - - return true + return actionStringRes == other.actionStringRes } override fun hashCode(): Int { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchListModel.kt index 8f6ed856e..ac38bd770 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchListModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchListModel.kt @@ -1,5 +1,7 @@ package org.koitharu.kotatsu.search.ui.multi +import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.MangaItemModel import org.koitharu.kotatsu.parsers.model.MangaSource @@ -11,6 +13,18 @@ class MultiSearchListModel( val error: Throwable?, ) : ListModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is MultiSearchListModel && source == other.source + } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is MultiSearchListModel && previousState.list != list) { + ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED + } else { + super.getChangePayload(previousState) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/DirectoryModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/DirectoryModel.kt index b6226aa03..401ae7452 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/DirectoryModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/DirectoryModel.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.settings.storage import androidx.annotation.StringRes +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel import java.io.File @@ -12,6 +13,18 @@ class DirectoryModel( val isAvailable: Boolean, ) : ListModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is DirectoryModel && other.file == file && other.title == title && other.titleRes == titleRes + } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is DirectoryModel && previousState.isChecked != isChecked) { + ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED + } else { + super.getChangePayload(previousState) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/MangaItemDiffCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/MangaItemDiffCallback.kt deleted file mode 100644 index a934dbd52..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/MangaItemDiffCallback.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.koitharu.kotatsu.shelf.ui.adapter - -import androidx.recyclerview.widget.DiffUtil -import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter -import org.koitharu.kotatsu.list.ui.model.ListModel -import org.koitharu.kotatsu.list.ui.model.MangaItemModel -import kotlin.jvm.internal.Intrinsics - -class MangaItemDiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - oldItem as MangaItemModel - newItem as MangaItemModel - return oldItem.javaClass == newItem.javaClass && oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return Intrinsics.areEqual(oldItem, newItem) - } - - override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? { - oldItem as MangaItemModel - newItem as MangaItemModel - return when { - oldItem.progress != newItem.progress -> MangaListAdapter.PAYLOAD_PROGRESS - oldItem.counter != newItem.counter -> Unit - else -> super.getChangePayload(oldItem, newItem) - } - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/ShelfAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/ShelfAdapter.kt index 85baded5e..0b1680d7e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/ShelfAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/ShelfAdapter.kt @@ -10,6 +10,7 @@ import org.koitharu.kotatsu.core.ui.list.NestedScrollStateHandle import org.koitharu.kotatsu.core.ui.list.SectionedSelectionController import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller import org.koitharu.kotatsu.list.ui.ItemSizeResolver +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.adapter.emptyHintAD import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD @@ -27,12 +28,11 @@ class ShelfAdapter( sizeResolver: ItemSizeResolver, selectionController: SectionedSelectionController, nestedScrollStateHandle: NestedScrollStateHandle, -) : AsyncListDifferDelegationAdapter(DiffCallback()), FastScroller.SectionIndexer { +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback), FastScroller.SectionIndexer { init { val pool = RecyclerView.RecycledViewPool() - delegatesManager - .addDelegate( + delegatesManager.addDelegate( shelfGroupAD( sharedPool = pool, lifecycleOwner = lifecycleOwner, @@ -42,44 +42,13 @@ class ShelfAdapter( listener = listener, nestedScrollStateHandle = nestedScrollStateHandle, ), - ) - .addDelegate(loadingStateAD()) - .addDelegate(loadingFooterAD()) + ).addDelegate(loadingStateAD()).addDelegate(loadingFooterAD()) .addDelegate(emptyHintAD(coil, lifecycleOwner, listener)) - .addDelegate(emptyStateListAD(coil, lifecycleOwner, listener)) - .addDelegate(errorStateListAD(listener)) + .addDelegate(emptyStateListAD(coil, lifecycleOwner, listener)).addDelegate(errorStateListAD(listener)) } override fun getSectionText(context: Context, position: Int): CharSequence? { val item = items.getOrNull(position) as? ShelfSectionModel ?: return null return item.getTitle(context.resources) } - - private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return when { - oldItem is ShelfSectionModel && newItem is ShelfSectionModel -> { - oldItem.key == newItem.key - } - - oldItem is LoadingFooter && newItem is LoadingFooter -> { - oldItem.key == newItem.key - } - - else -> oldItem.javaClass == newItem.javaClass - } - } - - override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return Intrinsics.areEqual(oldItem, newItem) - } - - override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? { - return when { - oldItem is ShelfSectionModel && newItem is ShelfSectionModel -> Unit - else -> super.getChangePayload(oldItem, newItem) - } - } - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/ShelfGroupAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/ShelfGroupAD.kt index c0e561475..9e633798a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/ShelfGroupAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/adapter/ShelfGroupAD.kt @@ -16,6 +16,7 @@ import org.koitharu.kotatsu.core.util.ext.removeItemDecoration import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.databinding.ItemListGroupBinding import org.koitharu.kotatsu.list.ui.ItemSizeResolver +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga @@ -47,7 +48,7 @@ fun shelfGroupAD( } val adapter = AsyncListDifferDelegationAdapter( - MangaItemDiffCallback(), + ListModelDiffCallback, mangaGridItemAD(coil, lifecycleOwner, listenerAdapter, sizeResolver), ) adapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapter.kt index 19aaa81cf..3e8c4287d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapter.kt @@ -1,41 +1,15 @@ package org.koitharu.kotatsu.shelf.ui.config -import androidx.recyclerview.widget.DiffUtil import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback +import org.koitharu.kotatsu.list.ui.model.ListModel class ShelfSettingsAdapter( listener: ShelfSettingsListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback) { init { delegatesManager.addDelegate(shelfCategoryAD(listener)) .addDelegate(shelfSectionAD(listener)) } - - class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ShelfSettingsItemModel, newItem: ShelfSettingsItemModel): Boolean { - return when { - oldItem is ShelfSettingsItemModel.Section && newItem is ShelfSettingsItemModel.Section -> { - oldItem.section == newItem.section - } - - oldItem is ShelfSettingsItemModel.FavouriteCategory && newItem is ShelfSettingsItemModel.FavouriteCategory -> { - oldItem.id == newItem.id - } - - else -> false - } - } - - override fun areContentsTheSame(oldItem: ShelfSettingsItemModel, newItem: ShelfSettingsItemModel): Boolean { - return oldItem == newItem - } - - override fun getChangePayload(oldItem: ShelfSettingsItemModel, newItem: ShelfSettingsItemModel): Any? { - return if (oldItem.isChecked == newItem.isChecked) { - super.getChangePayload(oldItem, newItem) - } else Unit - } - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapterDelegates.kt index 736aa125f..4f0a6ef17 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapterDelegates.kt @@ -10,13 +10,14 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.util.ext.setChecked import org.koitharu.kotatsu.databinding.ItemCategoryCheckableMultipleBinding import org.koitharu.kotatsu.databinding.ItemShelfSectionDraggableBinding +import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.shelf.domain.model.ShelfSection @SuppressLint("ClickableViewAccessibility") fun shelfSectionAD( listener: ShelfSettingsListener, ) = - adapterDelegateViewBinding( + adapterDelegateViewBinding( { layoutInflater, parent -> ItemShelfSectionDraggableBinding.inflate(layoutInflater, parent, false) }, ) { @@ -50,7 +51,7 @@ fun shelfSectionAD( fun shelfCategoryAD( listener: ShelfSettingsListener, ) = - adapterDelegateViewBinding( + adapterDelegateViewBinding( { layoutInflater, parent -> ItemCategoryCheckableMultipleBinding.inflate(layoutInflater, parent, false) }, ) { itemView.setOnClickListener { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsItemModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsItemModel.kt index c45d6bee3..87606f97c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsItemModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsItemModel.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.shelf.ui.config +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.shelf.domain.model.ShelfSection @@ -12,6 +13,18 @@ sealed interface ShelfSettingsItemModel : ListModel { override val isChecked: Boolean, ) : ShelfSettingsItemModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is Section && section == other.section + } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is Section && previousState.isChecked != isChecked) { + ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED + } else { + super.getChangePayload(previousState) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -35,6 +48,10 @@ sealed interface ShelfSettingsItemModel : ListModel { override val isChecked: Boolean, ) : ShelfSettingsItemModel { + override fun areItemsTheSame(other: ListModel): Boolean { + return other is FavouriteCategory && other.id == id + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/model/ShelfSectionModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/model/ShelfSectionModel.kt index 168087d10..ff0cc7160 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/model/ShelfSectionModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/shelf/ui/model/ShelfSectionModel.kt @@ -19,6 +19,18 @@ sealed interface ShelfSectionModel : ListModel { override fun toString(): String + override fun areItemsTheSame(other: ListModel): Boolean { + return other is ShelfSectionModel && key == other.key + } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is ShelfSectionModel) { + Unit + } else { + null + } + } + class History( override val items: List, override val showAllButtonText: Int, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt index ce1c89b3a..f54e551ef 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt @@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.daysDiff import org.koitharu.kotatsu.list.ui.model.EmptyState +import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.tracker.domain.TrackingRepository @@ -75,7 +76,7 @@ class FeedViewModel @Inject constructor( for (item in this) { val date = timeAgo(item.createdAt) if (prevDate != date) { - destination += date + destination += ListHeader(date, 0, null) } prevDate = date destination += item.toFeedItem() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt index 063e17d8c..41aab371d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt @@ -2,38 +2,34 @@ package org.koitharu.kotatsu.tracker.ui.feed.adapter import android.content.Context import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.DiffUtil import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller import org.koitharu.kotatsu.core.ui.model.DateTimeAgo +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD import org.koitharu.kotatsu.list.ui.adapter.errorFooterAD import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD +import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD -import org.koitharu.kotatsu.list.ui.adapter.relatedDateItemAD import org.koitharu.kotatsu.list.ui.model.ListModel -import org.koitharu.kotatsu.list.ui.model.LoadingFooter -import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem -import kotlin.jvm.internal.Intrinsics class FeedAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, listener: MangaListListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()), FastScroller.SectionIndexer { +) : AsyncListDifferDelegationAdapter(ListModelDiffCallback), FastScroller.SectionIndexer { init { - delegatesManager - .addDelegate(ITEM_TYPE_FEED, feedItemAD(coil, lifecycleOwner, listener)) + delegatesManager.addDelegate(ITEM_TYPE_FEED, feedItemAD(coil, lifecycleOwner, listener)) .addDelegate(ITEM_TYPE_LOADING_FOOTER, loadingFooterAD()) .addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD()) .addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener)) .addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener)) + .addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener)) .addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener)) - .addDelegate(ITEM_TYPE_DATE_HEADER, relatedDateItemAD()) } override fun getSectionText(context: Context, position: Int): CharSequence? { @@ -47,29 +43,6 @@ class FeedAdapter( return null } - private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel) = when { - oldItem is FeedItem && newItem is FeedItem -> { - oldItem.id == newItem.id - } - - oldItem is DateTimeAgo && newItem is DateTimeAgo -> { - oldItem == newItem - } - - oldItem is LoadingFooter && newItem is LoadingFooter -> { - oldItem.key == newItem.key - } - - else -> oldItem.javaClass == newItem.javaClass - } - - override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { - return Intrinsics.areEqual(oldItem, newItem) - } - } - companion object { const val ITEM_TYPE_FEED = 0 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/model/FeedItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/model/FeedItem.kt index 90e97624d..dd7d20d6b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/model/FeedItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/model/FeedItem.kt @@ -3,11 +3,40 @@ package org.koitharu.kotatsu.tracker.ui.feed.model import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga -data class FeedItem( +class FeedItem( val id: Long, val imageUrl: String, val title: String, val manga: Manga, val count: Int, val isNew: Boolean, -) : ListModel +) : ListModel { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is FeedItem && other.id == id + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FeedItem + + if (id != other.id) return false + if (imageUrl != other.imageUrl) return false + if (title != other.title) return false + if (manga != other.manga) return false + if (count != other.count) return false + return isNew == other.isNew + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + imageUrl.hashCode() + result = 31 * result + title.hashCode() + result = 31 * result + manga.hashCode() + result = 31 * result + count + result = 31 * result + isNew.hashCode() + return result + } +} diff --git a/app/src/main/res/layout/item_recommendation.xml b/app/src/main/res/layout/item_recommendation.xml index be8db323e..dc76a680b 100644 --- a/app/src/main/res/layout/item_recommendation.xml +++ b/app/src/main/res/layout/item_recommendation.xml @@ -50,7 +50,8 @@ app:layout_constraintTop_toBottomOf="@+id/textView_title" tools:text="@tools:sample/lorem/random" /> -