From 53e36d23b17123d47b4bc00aad921124415df77e Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 28 Nov 2020 08:30:49 +0200 Subject: [PATCH] Migrate favourite categories to AdapterDelegates --- .../ui/adapter/ChaptersSelectionDecoration.kt | 2 +- .../favourites/data/FavouriteCategoriesDao.kt | 5 ++ .../favourites/domain/FavouritesRepository.kt | 6 ++ .../domain/OnFavouritesChangeListener.kt | 1 + .../ui/FavouritesContainerFragment.kt | 21 +------ .../ui/categories/CategoriesActivity.kt | 15 +++-- .../ui/categories/CategoriesAdapter.kt | 56 ++++++------------- .../favourites/ui/categories/CategoryAD.kt | 29 ++++++++++ .../ui/categories/CategoryHolder.kt | 15 ----- .../FavouritesCategoriesViewModel.kt | 46 +++++++-------- .../select/CategoriesSelectAdapter.kt | 14 ++--- .../select/FavouriteCategoriesDialog.kt | 4 +- .../kotatsu/tracker/ui/FeedFragment.kt | 3 - .../kotatsu/tracker/ui/FeedViewModel.kt | 7 +-- 14 files changed, 102 insertions(+), 122 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryAD.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryHolder.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt index f51ab3216..943ee5391 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt @@ -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 } diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt index ab40fabbf..e56cf6999 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt @@ -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 + @Query("SELECT * FROM favourite_categories ORDER BY sort_key") + abstract fun observeAll(): Flow> + @Insert(onConflict = OnConflictStrategy.ABORT) abstract suspend fun insert(category: FavouriteCategoryEntity): Long diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index 37a6f7e3b..6008c793c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -58,6 +58,12 @@ class FavouritesRepository(private val db: MangaDatabase) { return entities?.map { it.toFavouriteCategory() }.orEmpty() } + fun observeCategories(): Flow> { + return db.favouriteCategoriesDao.observeAll().mapItems { + it.toFavouriteCategory() + } + } + fun observeCategories(mangaId: Long): Flow> { return db.favouritesDao.observe(mangaId).map { entity -> entity?.categories?.map { it.toFavouriteCategory() }.orEmpty() diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/OnFavouritesChangeListener.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/OnFavouritesChangeListener.kt index f7204f86a..1a9c7b2c1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/OnFavouritesChangeListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/OnFavouritesChangeListener.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.favourites.domain +@Deprecated("Use flow") fun interface OnFavouritesChangeListener { fun onFavouritesChanged(mangaId: Long) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt index 984d61e46..7f9ace391 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt @@ -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() @@ -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) { + private fun onCategoriesChanged(categories: List) { val data = ArrayList(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) { diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesActivity.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesActivity.kt index abfd229e1..d722348a8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesActivity.kt @@ -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, +class CategoriesActivity : BaseActivity(), OnListItemClickListener, View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback { private val viewModel by viewModel() @@ -52,7 +52,7 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener editDelegate.deleteCategory(item) @@ -62,15 +62,15 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener) { - adapter.replaceData(categories) + adapter.items = categories textView_holder.isVisible = categories.isEmpty() } @@ -102,8 +102,7 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener) : - BaseRecyclerAdapter() { +class CategoriesAdapter( + onItemClickListener: OnListItemClickListener +) : AsyncListDifferDelegationAdapter(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) { - 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) { - holder.imageView_more.setOnClickListener(null) - holder.imageView_handle.setOnTouchListener(null) - } + private class DiffCallback : DiffUtil.ItemCallback() { - 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) + } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryAD.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryAD.kt new file mode 100644 index 000000000..8e4884508 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryAD.kt @@ -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 +) = adapterDelegateLayoutContainer(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryHolder.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryHolder.kt deleted file mode 100644 index 46899ba42..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryHolder.kt +++ /dev/null @@ -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(parent, R.layout.item_category) { - - override fun onBind(data: FavouriteCategory, extra: Unit) { - textView_title.text = data.title - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt index 5f7de98a0..c13747aeb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt @@ -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>() - val mangaCategories = MutableLiveData>() + val categories = repository.observeCategories() + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + val mangaCategories = MutableLiveData>(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) { + 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) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/CategoriesSelectAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/CategoriesSelectAdapter.kt index 59db2ad2e..bb93fa74f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/CategoriesSelectAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/CategoriesSelectAdapter.kt @@ -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() { - private val checkedIds = SparseBooleanArray() + private val checkedIds = ArraySet() - fun setCheckedIds(ids: Iterable) { + fun setCheckedIds(ids: Iterable) { 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( diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteCategoriesDialog.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteCategoriesDialog.kt index e90c37b3d..26de7258a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteCategoriesDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteCategoriesDialog.kt @@ -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) { + private fun onCheckedCategoriesChanged(checkedIds: Set) { adapter?.setCheckedIds(checkedIds) } diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt index fa26dad6f..13b416f64 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt @@ -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) diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt index 9bc062111..85f4bbc91 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt @@ -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() }