From 7908eb1441864a0f07100c2a7b2171fda80478b0 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Tue, 4 Jul 2023 13:15:23 +0300 Subject: [PATCH] Favourites container fixes --- .../kotatsu/core/ui/BaseListAdapter.kt | 11 +++++- .../ui/container/FavouriteTabModel.kt | 29 +++++++++++++++ .../container/FavouritesContainerAdapter.kt | 37 ++++++++++++------- .../container/FavouritesContainerViewModel.kt | 8 ++++ .../kotatsu/list/ui/MangaListFragment.kt | 11 ++---- .../layout/fragment_favourites_container.xml | 28 +++++--------- 6 files changed, 83 insertions(+), 41 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabModel.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseListAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseListAdapter.kt index ef3bbced1..12ae09f87 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseListAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseListAdapter.kt @@ -1,8 +1,11 @@ package org.koitharu.kotatsu.core.ui +import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncListDiffer.ListListener import com.hannesdorfmann.adapterdelegates4.AdapterDelegate import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor import kotlinx.coroutines.flow.FlowCollector import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable import org.koitharu.kotatsu.list.ui.ListModelDiffCallback @@ -11,8 +14,12 @@ import kotlin.coroutines.suspendCoroutine open class BaseListAdapter( vararg delegates: AdapterDelegate>, -) : AsyncListDifferDelegationAdapter(ListModelDiffCallback, *delegates), - FlowCollector> { +) : AsyncListDifferDelegationAdapter( + AsyncDifferConfig.Builder(ListModelDiffCallback) + .setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor()) + .build(), + *delegates, +), FlowCollector> { override suspend fun emit(value: List) = suspendCoroutine { cont -> setItems(value, ContinuationResumeRunnable(cont)) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabModel.kt new file mode 100644 index 000000000..1a9b16276 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabModel.kt @@ -0,0 +1,29 @@ +package org.koitharu.kotatsu.favourites.ui.container + +import org.koitharu.kotatsu.list.ui.model.ListModel + +class FavouriteTabModel( + val id: Long, + val title: String, +) : ListModel { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is FavouriteTabModel && other.id == id + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FavouriteTabModel + + if (id != other.id) return false + return title == other.title + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + title.hashCode() + return result + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter.kt index 44ef66c0e..3265db964 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter.kt @@ -1,25 +1,31 @@ package org.koitharu.kotatsu.favourites.ui.container import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.AdapterListUpdateCallback +import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.DiffUtil import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor import kotlinx.coroutines.flow.FlowCollector -import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment import kotlin.coroutines.suspendCoroutine -class FavouritesContainerAdapter(fragment: Fragment) : FragmentStateAdapter( - fragment.childFragmentManager, - fragment.viewLifecycleOwner.lifecycle, -), +class FavouritesContainerAdapter(fragment: Fragment) : + FragmentStateAdapter(fragment.childFragmentManager, fragment.viewLifecycleOwner.lifecycle), TabConfigurationStrategy, - FlowCollector> { + FlowCollector> { - private val differ = AsyncListDiffer(this, DiffCallback()) + private val differ = AsyncListDiffer( + AdapterListUpdateCallback(this), + AsyncDifferConfig.Builder(DiffCallback()) + .setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor()) + .build(), + ) override fun getItemCount(): Int = differ.currentList.size @@ -27,8 +33,13 @@ class FavouritesContainerAdapter(fragment: Fragment) : FragmentStateAdapter( return differ.currentList[position].id } + override fun containsItem(itemId: Long): Boolean { + return differ.currentList.any { x -> x.id == itemId } + } + override fun createFragment(position: Int): Fragment { - return FavouritesListFragment.newInstance(getItemId(position)) + val item = differ.currentList[position] + return FavouritesListFragment.newInstance(item.id) } override fun onConfigureTab(tab: TabLayout.Tab, position: Int) { @@ -37,18 +48,18 @@ class FavouritesContainerAdapter(fragment: Fragment) : FragmentStateAdapter( tab.tag = item } - override suspend fun emit(value: List) = suspendCoroutine { cont -> + override suspend fun emit(value: List) = suspendCoroutine { cont -> differ.submitList(value, ContinuationResumeRunnable(cont)) } - private class DiffCallback : DiffUtil.ItemCallback() { + private class DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean { + override fun areItemsTheSame(oldItem: FavouriteTabModel, newItem: FavouriteTabModel): Boolean { return oldItem.id == newItem.id } - override fun areContentsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean { - return oldItem.title == newItem.title + override fun areContentsTheSame(oldItem: FavouriteTabModel, newItem: FavouriteTabModel): Boolean { + return oldItem == newItem } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerViewModel.kt index 4e4a17940..1b87b8139 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerViewModel.kt @@ -1,7 +1,13 @@ package org.koitharu.kotatsu.favourites.ui.container +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.plus import org.koitharu.kotatsu.core.ui.BaseViewModel +import org.koitharu.kotatsu.core.util.ext.mapItems import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import javax.inject.Inject @@ -11,4 +17,6 @@ class FavouritesContainerViewModel @Inject constructor( ) : BaseViewModel() { val categories = favouritesRepository.observeCategories() + .mapItems { FavouriteTabModel(it.id, it.title) } + .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList()) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index 021f4d83a..7ea25cb6d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -76,9 +76,6 @@ abstract class MangaListFragment : private var selectionController: ListSelectionController? = null private var spanResolver: MangaListSpanResolver? = null private val spanSizeLookup = SpanSizeLookup() - private val listCommitCallback = Runnable { - spanSizeLookup.invalidateCache() - } open val isSwipeRefreshEnabled = true protected abstract val viewModel: MangaListViewModel @@ -166,8 +163,9 @@ abstract class MangaListFragment : viewModel.onRefresh() } - private fun onListChanged(list: List) { - listAdapter?.setItems(list, listCommitCallback) + private suspend fun onListChanged(list: List) { + listAdapter?.emit(list) + spanSizeLookup.invalidateCache() } private fun resolveException(e: Throwable) { @@ -341,8 +339,7 @@ abstract class MangaListFragment : } override fun getSpanSize(position: Int): Int { - val total = - (requireViewBinding().recyclerView.layoutManager as? GridLayoutManager)?.spanCount ?: return 1 + val total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1 return when (listAdapter?.getItemViewType(position)) { ITEM_TYPE_MANGA_GRID -> 1 else -> total diff --git a/app/src/main/res/layout/fragment_favourites_container.xml b/app/src/main/res/layout/fragment_favourites_container.xml index 09498da3f..8d1ff7c9c 100644 --- a/app/src/main/res/layout/fragment_favourites_container.xml +++ b/app/src/main/res/layout/fragment_favourites_container.xml @@ -1,31 +1,21 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> - - - - - + app:tabGravity="start" + app:tabMode="scrollable" /> + android:layout_height="match_parent" /> - +