Migrate favourite categories to AdapterDelegates
This commit is contained in:
@@ -21,7 +21,7 @@ class ChaptersSelectionDecoration(context: Context) : RecyclerView.ItemDecoratio
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
|
||||
init {
|
||||
paint.color = context.getThemeColor(android.R.attr.colorControlActivated)
|
||||
paint.color = context.getThemeColor(com.google.android.material.R.attr.colorSurface)
|
||||
paint.style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.favourites.data
|
||||
|
||||
import androidx.room.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
|
||||
@Dao
|
||||
abstract class FavouriteCategoriesDao {
|
||||
@@ -8,6 +10,9 @@ abstract class FavouriteCategoriesDao {
|
||||
@Query("SELECT * FROM favourite_categories ORDER BY sort_key")
|
||||
abstract suspend fun findAll(): List<FavouriteCategoryEntity>
|
||||
|
||||
@Query("SELECT * FROM favourite_categories ORDER BY sort_key")
|
||||
abstract fun observeAll(): Flow<List<FavouriteCategoryEntity>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
abstract suspend fun insert(category: FavouriteCategoryEntity): Long
|
||||
|
||||
|
||||
@@ -58,6 +58,12 @@ class FavouritesRepository(private val db: MangaDatabase) {
|
||||
return entities?.map { it.toFavouriteCategory() }.orEmpty()
|
||||
}
|
||||
|
||||
fun observeCategories(): Flow<List<FavouriteCategory>> {
|
||||
return db.favouriteCategoriesDao.observeAll().mapItems {
|
||||
it.toFavouriteCategory()
|
||||
}
|
||||
}
|
||||
|
||||
fun observeCategories(mangaId: Long): Flow<List<FavouriteCategory>> {
|
||||
return db.favouritesDao.observe(mangaId).map { entity ->
|
||||
entity?.categories?.map { it.toFavouriteCategory() }.orEmpty()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.favourites.domain
|
||||
|
||||
@Deprecated("Use flow")
|
||||
fun interface OnFavouritesChangeListener {
|
||||
|
||||
fun onFavouritesChanged(mangaId: Long)
|
||||
|
||||
@@ -12,8 +12,6 @@ import org.koin.android.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.domain.OnFavouritesChangeListener
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
|
||||
@@ -22,8 +20,7 @@ import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
|
||||
OnFavouritesChangeListener, FavouritesTabLongClickListener,
|
||||
CategoriesEditDelegate.CategoriesEditCallback {
|
||||
FavouritesTabLongClickListener, CategoriesEditDelegate.CategoriesEditCallback {
|
||||
|
||||
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
|
||||
|
||||
@@ -41,18 +38,12 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
|
||||
val adapter = FavouritesPagerAdapter(this, this)
|
||||
pager.adapter = adapter
|
||||
TabLayoutMediator(tabs, pager, adapter).attach()
|
||||
FavouritesRepository.subscribe(this)
|
||||
|
||||
viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged)
|
||||
viewModel.onError.observe(viewLifecycleOwner, ::onError)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
FavouritesRepository.unsubscribe(this)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
fun onCategoriesChanged(categories: List<FavouriteCategory>) {
|
||||
private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
|
||||
val data = ArrayList<FavouriteCategory>(categories.size + 1)
|
||||
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date())
|
||||
data += categories
|
||||
@@ -75,19 +66,13 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
|
||||
}
|
||||
|
||||
override fun getTitle(): CharSequence? {
|
||||
return getString(R.string.favourites)
|
||||
return context?.getString(R.string.favourites)
|
||||
}
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
Snackbar.make(pager, e.message ?: return, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onFavouritesChanged(mangaId: Long) = Unit
|
||||
|
||||
override fun onCategoriesChanged() {
|
||||
viewModel.loadAllCategories()
|
||||
}
|
||||
|
||||
override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean {
|
||||
val menuRes = if (category.id == 0L) R.menu.popup_category_empty else R.menu.popup_category
|
||||
tabView.showPopupMenu(menuRes) {
|
||||
|
||||
@@ -15,12 +15,12 @@ import kotlinx.android.synthetic.main.activity_categories.*
|
||||
import org.koin.android.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.showPopupMenu
|
||||
|
||||
class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<FavouriteCategory>,
|
||||
class CategoriesActivity : BaseActivity(), OnListItemClickListener<FavouriteCategory>,
|
||||
View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback {
|
||||
|
||||
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
|
||||
@@ -52,7 +52,7 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(item: FavouriteCategory, position: Int, view: View) {
|
||||
override fun onItemClick(item: FavouriteCategory, view: View) {
|
||||
view.showPopupMenu(R.menu.popup_category) {
|
||||
when (it.itemId) {
|
||||
R.id.action_remove -> editDelegate.deleteCategory(item)
|
||||
@@ -62,15 +62,15 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: FavouriteCategory, position: Int, view: View): Boolean {
|
||||
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
|
||||
reorderHelper.startDrag(
|
||||
recyclerView.findViewHolderForAdapterPosition(position) ?: return false
|
||||
recyclerView.findContainingViewHolder(view) ?: return false
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
|
||||
adapter.replaceData(categories)
|
||||
adapter.items = categories
|
||||
textView_holder.isVisible = categories.isEmpty()
|
||||
}
|
||||
|
||||
@@ -102,8 +102,7 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
|
||||
): Boolean {
|
||||
val oldPos = viewHolder.bindingAdapterPosition
|
||||
val newPos = target.bindingAdapterPosition
|
||||
adapter.moveItem(oldPos, newPos)
|
||||
viewModel.storeCategoriesOrder(adapter.items.map { it.id })
|
||||
viewModel.reorderCategories(oldPos, newPos)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +1,27 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.item_category.*
|
||||
import org.koitharu.kotatsu.base.ui.list.BaseRecyclerAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
|
||||
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class CategoriesAdapter(private val onItemClickListener: OnRecyclerItemClickListener<FavouriteCategory>) :
|
||||
BaseRecyclerAdapter<FavouriteCategory, Unit>() {
|
||||
class CategoriesAdapter(
|
||||
onItemClickListener: OnListItemClickListener<FavouriteCategory>
|
||||
) : AsyncListDifferDelegationAdapter<FavouriteCategory>(DiffCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup) = CategoryHolder(parent)
|
||||
|
||||
override fun onGetItemId(item: FavouriteCategory) = item.id
|
||||
|
||||
override fun getExtra(item: FavouriteCategory, position: Int) = Unit
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onViewAttachedToWindow(holder: BaseViewHolder<FavouriteCategory, Unit>) {
|
||||
holder.imageView_more.setOnClickListener { v ->
|
||||
onItemClickListener.onItemClick(holder.requireData(), holder.bindingAdapterPosition, v)
|
||||
}
|
||||
holder.imageView_handle.setOnTouchListener { v, event ->
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
onItemClickListener.onItemLongClick(
|
||||
holder.requireData(),
|
||||
holder.bindingAdapterPosition,
|
||||
v
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
init {
|
||||
delegatesManager.addDelegate(categoryAD(onItemClickListener))
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: BaseViewHolder<FavouriteCategory, Unit>) {
|
||||
holder.imageView_more.setOnClickListener(null)
|
||||
holder.imageView_handle.setOnTouchListener(null)
|
||||
}
|
||||
private class DiffCallback : DiffUtil.ItemCallback<FavouriteCategory>() {
|
||||
|
||||
fun moveItem(oldPos: Int, newPos: Int) {
|
||||
val item = dataSet.removeAt(oldPos)
|
||||
dataSet.add(newPos, item)
|
||||
notifyItemMoved(oldPos, newPos)
|
||||
override fun areItemsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean {
|
||||
return Intrinsics.areEqual(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import android.view.MotionEvent
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_category.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
|
||||
fun categoryAD(
|
||||
clickListener: OnListItemClickListener<FavouriteCategory>
|
||||
) = adapterDelegateLayoutContainer<FavouriteCategory, FavouriteCategory>(R.layout.item_category) {
|
||||
|
||||
imageView_more.setOnClickListener {
|
||||
clickListener.onItemClick(item, it)
|
||||
}
|
||||
@Suppress("ClickableViewAccessibility")
|
||||
imageView_handle.setOnTouchListener { v, event ->
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
clickListener.onItemLongClick(item, v)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
bind {
|
||||
textView_title.text = item.title
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.item_category.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
|
||||
class CategoryHolder(parent: ViewGroup) :
|
||||
BaseViewHolder<FavouriteCategory, Unit>(parent, R.layout.item_category) {
|
||||
|
||||
override fun onBind(data: FavouriteCategory, extra: Unit) {
|
||||
textView_title.text = data.title
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
@@ -13,53 +19,47 @@ class FavouritesCategoriesViewModel(
|
||||
) : BaseViewModel() {
|
||||
|
||||
private var reorderJob: Job? = null
|
||||
private var mangaSubscription: Job? = null
|
||||
|
||||
val categories = MutableLiveData<List<FavouriteCategory>>()
|
||||
val mangaCategories = MutableLiveData<Set<Int>>()
|
||||
val categories = repository.observeCategories()
|
||||
.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
val mangaCategories = MutableLiveData<Set<Long>>(emptySet())
|
||||
|
||||
init {
|
||||
loadAllCategories()
|
||||
}
|
||||
|
||||
fun loadAllCategories() {
|
||||
launchJob {
|
||||
categories.value = repository.getAllCategories()
|
||||
}
|
||||
}
|
||||
|
||||
fun loadMangaCategories(manga: Manga) {
|
||||
launchJob {
|
||||
val categories = repository.getCategories(manga.id)
|
||||
mangaCategories.value = categories.mapToSet { it.id.toInt() }
|
||||
}
|
||||
fun observeMangaCategories(mangaId: Long) {
|
||||
mangaSubscription?.cancel()
|
||||
mangaSubscription = repository.observeCategories(mangaId)
|
||||
.map { list -> list.mapToSet { it.id } }
|
||||
.onEach { mangaCategories.postValue(it) }
|
||||
.launchIn(viewModelScope + Dispatchers.Default)
|
||||
}
|
||||
|
||||
fun createCategory(name: String) {
|
||||
launchJob {
|
||||
repository.addCategory(name)
|
||||
categories.value = repository.getAllCategories()
|
||||
}
|
||||
}
|
||||
|
||||
fun renameCategory(id: Long, name: String) {
|
||||
launchJob {
|
||||
repository.renameCategory(id, name)
|
||||
categories.value = repository.getAllCategories()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteCategory(id: Long) {
|
||||
launchJob {
|
||||
repository.removeCategory(id)
|
||||
categories.value = repository.getAllCategories()
|
||||
}
|
||||
}
|
||||
|
||||
fun storeCategoriesOrder(orderedIds: List<Long>) {
|
||||
fun reorderCategories(oldPos: Int, newPos: Int) {
|
||||
val prevJob = reorderJob
|
||||
reorderJob = launchJob {
|
||||
prevJob?.join()
|
||||
repository.reorderCategories(orderedIds)
|
||||
val items = categories.value ?: error("This should not happen")
|
||||
val ids = items.mapTo(ArrayList(items.size)) { it.id }
|
||||
val item = ids.removeAt(oldPos)
|
||||
ids.add(newPos, item)
|
||||
repository.reorderCategories(ids)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.select
|
||||
|
||||
import android.util.SparseBooleanArray
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Checkable
|
||||
import androidx.core.util.set
|
||||
import androidx.collection.ArraySet
|
||||
import org.koitharu.kotatsu.base.ui.list.BaseRecyclerAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
@@ -11,18 +10,15 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
class CategoriesSelectAdapter(private val listener: OnCategoryCheckListener) :
|
||||
BaseRecyclerAdapter<FavouriteCategory, Boolean>() {
|
||||
|
||||
private val checkedIds = SparseBooleanArray()
|
||||
private val checkedIds = ArraySet<Long>()
|
||||
|
||||
fun setCheckedIds(ids: Iterable<Int>) {
|
||||
fun setCheckedIds(ids: Iterable<Long>) {
|
||||
checkedIds.clear()
|
||||
ids.forEach {
|
||||
checkedIds[it] = true
|
||||
}
|
||||
checkedIds.addAll(ids)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getExtra(item: FavouriteCategory, position: Int) =
|
||||
checkedIds.get(item.id.toInt(), false)
|
||||
override fun getExtra(item: FavouriteCategory, position: Int) = item.id in checkedIds
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup) =
|
||||
CategoryCheckableHolder(
|
||||
|
||||
@@ -36,7 +36,7 @@ class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categ
|
||||
createCategory()
|
||||
}
|
||||
manga?.let {
|
||||
viewModel.loadMangaCategories(it)
|
||||
viewModel.observeMangaCategories(it.id)
|
||||
}
|
||||
|
||||
viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged)
|
||||
@@ -53,7 +53,7 @@ class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categ
|
||||
adapter?.replaceData(categories)
|
||||
}
|
||||
|
||||
private fun onCheckedCategoriesChanged(checkedIds: Set<Int>) {
|
||||
private fun onCheckedCategoriesChanged(checkedIds: Set<Long>) {
|
||||
adapter?.setCheckedIds(checkedIds)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,6 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScroll
|
||||
)
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.addOnScrollListener(PaginationScrollListener(4, this))
|
||||
if (savedInstanceState == null) {
|
||||
onScrolledToEnd()
|
||||
}
|
||||
|
||||
viewModel.content.observe(viewLifecycleOwner, this::onListChanged)
|
||||
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
|
||||
|
||||
@@ -9,7 +9,6 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||
import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress
|
||||
@@ -28,9 +27,7 @@ class FeedViewModel(
|
||||
|
||||
val isEmptyState = MutableLiveData(false)
|
||||
val content = combine(
|
||||
logList.drop(1).onEach {
|
||||
isEmptyState.postValue(it.isEmpty())
|
||||
}.mapItems {
|
||||
logList.drop(1).mapItems {
|
||||
it.toFeedItem(context.resources)
|
||||
},
|
||||
hasNextPage
|
||||
@@ -53,6 +50,8 @@ class FeedViewModel(
|
||||
logList.value = list
|
||||
} else if (list.isNotEmpty()) {
|
||||
logList.value += list
|
||||
} else {
|
||||
isEmptyState.value = true
|
||||
}
|
||||
hasNextPage.value = list.isNotEmpty()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user