Migrate favourite categories to AdapterDelegates

This commit is contained in:
Koitharu
2020-11-28 08:30:49 +02:00
parent fa02cfd7e8
commit 53e36d23b1
14 changed files with 102 additions and 122 deletions

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.favourites.domain
@Deprecated("Use flow")
fun interface OnFavouritesChangeListener {
fun onFavouritesChanged(mangaId: Long)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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