Fix favourites categories

This commit is contained in:
Koitharu
2021-01-06 07:57:20 +02:00
parent e674e0f36f
commit d1e17c8ec2
7 changed files with 60 additions and 31 deletions

View File

@@ -4,6 +4,7 @@ import androidx.collection.ArraySet
import androidx.room.withTransaction import androidx.room.withTransaction
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
@@ -61,13 +62,13 @@ class FavouritesRepository(private val db: MangaDatabase) {
fun observeCategories(): Flow<List<FavouriteCategory>> { fun observeCategories(): Flow<List<FavouriteCategory>> {
return db.favouriteCategoriesDao.observeAll().mapItems { return db.favouriteCategoriesDao.observeAll().mapItems {
it.toFavouriteCategory() it.toFavouriteCategory()
} }.distinctUntilChanged()
} }
fun observeCategories(mangaId: Long): Flow<List<FavouriteCategory>> { fun observeCategories(mangaId: Long): Flow<List<FavouriteCategory>> {
return db.favouritesDao.observe(mangaId).map { entity -> return db.favouritesDao.observe(mangaId).map { entity ->
entity?.categories?.map { it.toFavouriteCategory() }.orEmpty() entity?.categories?.map { it.toFavouriteCategory() }.orEmpty()
} }.distinctUntilChanged()
} }
fun observeCategoriesIds(mangaId: Long): Flow<List<Long>> { fun observeCategoriesIds(mangaId: Long): Flow<List<Long>> {

View File

@@ -26,6 +26,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
private val editDelegate by lazy(LazyThreadSafetyMode.NONE) { private val editDelegate by lazy(LazyThreadSafetyMode.NONE) {
CategoriesEditDelegate(requireContext(), this) CategoriesEditDelegate(requireContext(), this)
} }
private var pagerAdapter: FavouritesPagerAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -40,13 +41,22 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val adapter = FavouritesPagerAdapter(this, this) val adapter = FavouritesPagerAdapter(this, this)
viewModel.categories.value?.let {
adapter.replaceData(wrapCategories(it))
}
binding.pager.adapter = adapter binding.pager.adapter = adapter
pagerAdapter = adapter
TabLayoutMediator(binding.tabs, binding.pager, adapter).attach() TabLayoutMediator(binding.tabs, binding.pager, adapter).attach()
viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged) viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, ::onError)
} }
override fun onDestroyView() {
pagerAdapter = null
super.onDestroyView()
}
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
binding.tabs.updatePadding( binding.tabs.updatePadding(
left = insets.left, left = insets.left,
@@ -55,10 +65,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
} }
private fun onCategoriesChanged(categories: List<FavouriteCategory>) { private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
val data = ArrayList<FavouriteCategory>(categories.size + 1) pagerAdapter?.replaceData(wrapCategories(categories))
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date())
data += categories
(binding.pager.adapter as? FavouritesPagerAdapter)?.replaceData(data)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@@ -109,6 +116,13 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
viewModel.createCategory(name) viewModel.createCategory(name)
} }
private fun wrapCategories(categories: List<FavouriteCategory>): List<FavouriteCategory> {
val data = ArrayList<FavouriteCategory>(categories.size + 1)
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date())
data += categories
return data
}
companion object { companion object {
fun newInstance() = FavouritesContainerFragment() fun newInstance() = FavouritesContainerFragment()

View File

@@ -2,44 +2,55 @@ package org.koitharu.kotatsu.favourites.ui
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.base.ui.list.AdapterUpdater
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
import org.koitharu.kotatsu.utils.ext.replaceWith
class FavouritesPagerAdapter( class FavouritesPagerAdapter(
fragment: Fragment, fragment: Fragment,
private val longClickListener: FavouritesTabLongClickListener private val longClickListener: FavouritesTabLongClickListener
) : FragmentStateAdapter(fragment), ) : FragmentStateAdapter(fragment.childFragmentManager, fragment.viewLifecycleOwner.lifecycle),
TabLayoutMediator.TabConfigurationStrategy, View.OnLongClickListener { TabLayoutMediator.TabConfigurationStrategy, View.OnLongClickListener {
private val dataSet = ArrayList<FavouriteCategory>() private val differ = AsyncListDiffer(this, DiffCallback())
override fun getItemCount() = dataSet.size override fun getItemCount() = differ.currentList.size
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
val item = dataSet[position] val item = differ.currentList[position]
return FavouritesListFragment.newInstance(item.id) return FavouritesListFragment.newInstance(item.id)
} }
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) { override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val item = dataSet[position] val item = differ.currentList[position]
tab.text = item.title tab.text = item.title
tab.view.tag = item tab.view.tag = item
tab.view.setOnLongClickListener(this) tab.view.setOnLongClickListener(this)
} }
fun replaceData(data: List<FavouriteCategory>) { fun replaceData(data: List<FavouriteCategory>) {
val updater = AdapterUpdater(dataSet, data, FavouriteCategory::id) differ.submitList(data)
dataSet.replaceWith(data)
updater(this)
} }
override fun onLongClick(v: View): Boolean { override fun onLongClick(v: View): Boolean {
val item = v.tag as? FavouriteCategory ?: return false val item = v.tag as? FavouriteCategory ?: return false
return longClickListener.onTabLongClick(v, item) return longClickListener.onTabLongClick(v, item)
} }
private class DiffCallback : DiffUtil.ItemCallback<FavouriteCategory>() {
override fun areItemsTheSame(
oldItem: FavouriteCategory,
newItem: FavouriteCategory
): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(
oldItem: FavouriteCategory,
newItem: FavouriteCategory
): Boolean = oldItem.id == newItem.id && oldItem.title == newItem.title
}
} }

View File

@@ -93,8 +93,6 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
} }
private fun onCategoriesChanged(categories: List<FavouriteCategory>) { private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
// TODO check if not moved
adapter.items = categories adapter.items = categories
binding.textViewHolder.isVisible = categories.isEmpty() binding.textViewHolder.isVisible = categories.isEmpty()
} }
@@ -124,14 +122,22 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
recyclerView: RecyclerView, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder target: RecyclerView.ViewHolder
): Boolean { ): Boolean = true
val oldPos = viewHolder.bindingAdapterPosition
val newPos = target.bindingAdapterPosition
viewModel.reorderCategories(oldPos, newPos)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
override fun onMoved(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
fromPos: Int,
target: RecyclerView.ViewHolder,
toPos: Int,
x: Int,
y: Int
) {
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
viewModel.reorderCategories(fromPos, toPos)
}
} }
companion object { companion object {

View File

@@ -4,7 +4,6 @@ import androidx.recyclerview.widget.DiffUtil
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import kotlin.jvm.internal.Intrinsics
class CategoriesAdapter( class CategoriesAdapter(
onItemClickListener: OnListItemClickListener<FavouriteCategory> onItemClickListener: OnListItemClickListener<FavouriteCategory>
@@ -26,7 +25,7 @@ class CategoriesAdapter(
} }
override fun areContentsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean { override fun areContentsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean {
return Intrinsics.areEqual(oldItem, newItem) return oldItem.id == newItem.id && oldItem.title == newItem.title
} }
} }
} }

View File

@@ -6,6 +6,8 @@ import kotlinx.coroutines.Job
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import java.util.*
import kotlin.collections.ArrayList
class FavouritesCategoriesViewModel( class FavouritesCategoriesViewModel(
private val repository: FavouritesRepository private val repository: FavouritesRepository
@@ -40,8 +42,7 @@ class FavouritesCategoriesViewModel(
prevJob?.join() prevJob?.join()
val items = categories.value ?: error("This should not happen") val items = categories.value ?: error("This should not happen")
val ids = items.mapTo(ArrayList(items.size)) { it.id } val ids = items.mapTo(ArrayList(items.size)) { it.id }
val item = ids.removeAt(oldPos) Collections.swap(ids, oldPos, newPos)
ids.add(newPos, item)
repository.reorderCategories(ids) repository.reorderCategories(ids)
} }
} }

View File

@@ -14,7 +14,6 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.onFirst
class FavouritesListViewModel( class FavouritesListViewModel(
private val categoryId: Long, private val categoryId: Long,
@@ -38,8 +37,6 @@ class FavouritesListViewModel(
) )
else -> list.toUi(mode) else -> list.toUi(mode)
} }
}.onFirst {
isLoading.postValue(false)
}.catch { }.catch {
emit(listOf(it.toErrorState(canRetry = false))) emit(listOf(it.toErrorState(canRetry = false)))
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState)) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))