Favourites categories screen

This commit is contained in:
Koitharu
2022-07-08 17:16:47 +03:00
parent 1381a7d957
commit c5de765e52
22 changed files with 299 additions and 248 deletions

View File

@@ -91,7 +91,7 @@
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:windowSoftInputMode="adjustResize" />
<activity
android:name="org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity"
android:name="org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity"
android:label="@string/favourites_categories"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity

View File

@@ -20,7 +20,7 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter
import org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener
import org.koitharu.kotatsu.explore.ui.model.ExploreItem
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.history.ui.HistoryActivity
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -88,7 +88,7 @@ class ExploreFragment : BaseFragment<FragmentExploreBinding>(),
R.id.button_local -> MangaListActivity.newIntent(v.context, MangaSource.LOCAL)
R.id.button_bookmarks -> BookmarksActivity.newIntent(v.context)
R.id.button_suggestions -> SuggestionsActivity.newIntent(v.context)
R.id.button_favourites -> CategoriesActivity.newIntent(v.context)
R.id.button_favourites -> FavouriteCategoriesActivity.newIntent(v.context)
R.id.button_random -> {
viewModel.openRandom()
return

View File

@@ -1,9 +1,9 @@
package org.koitharu.kotatsu.favourites.data
import java.util.*
import org.koitharu.kotatsu.core.db.entity.SortOrder
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.parsers.model.SortOrder
import java.util.*
fun FavouriteCategoryEntity.toFavouriteCategory(id: Long = categoryId.toLong()) = FavouriteCategory(
id = id,

View File

@@ -15,6 +15,17 @@ abstract class FavouriteCategoriesDao {
@Query("SELECT * FROM favourite_categories ORDER BY sort_key")
abstract fun observeAll(): Flow<List<FavouriteCategoryEntity>>
@MapInfo(valueColumn = "cover")
@Query(
"""
SELECT favourite_categories.*, manga.cover_url AS cover
FROM favourite_categories JOIN manga ON manga.manga_id IN
(SELECT manga_id FROM favourites WHERE favourites.category_id == favourite_categories.category_id)
ORDER BY favourite_categories.sort_key
"""
)
abstract fun observeAllWithDetails(): Flow<Map<FavouriteCategoryEntity, List<String>>>
@Query("SELECT * FROM favourite_categories WHERE category_id = :id")
abstract fun observe(id: Long): Flow<FavouriteCategoryEntity?>

View File

@@ -13,4 +13,31 @@ class FavouriteCategoryEntity(
@ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "order") val order: String,
@ColumnInfo(name = "track") val track: Boolean,
)
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FavouriteCategoryEntity
if (categoryId != other.categoryId) return false
if (createdAt != other.createdAt) return false
if (sortKey != other.sortKey) return false
if (title != other.title) return false
if (order != other.order) return false
if (track != other.track) return false
return true
}
override fun hashCode(): Int {
var result = categoryId
result = 31 * result + createdAt.hashCode()
result = 31 * result + sortKey
result = 31 * result + title.hashCode()
result = 31 * result + order.hashCode()
result = 31 * result + track.hashCode()
return result
}
}

View File

@@ -5,10 +5,7 @@ import kotlinx.coroutines.flow.*
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.*
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.favourites.data.FavouriteManga
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
import org.koitharu.kotatsu.favourites.data.*
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
@@ -55,6 +52,13 @@ class FavouritesRepository(
}.distinctUntilChanged()
}
fun observeCategoriesWithDetails(): Flow<Map<FavouriteCategory, List<String>>> {
return db.favouriteCategoriesDao.observeAllWithDetails()
.map {
it.mapKeys { (k, _) -> k.toFavouriteCategory() }
}
}
fun observeCategory(id: Long): Flow<FavouriteCategory?> {
return db.favouriteCategoriesDao.observe(id)
.map { it?.toFavouriteCategory() }

View File

@@ -49,14 +49,14 @@ class FavouritesContainerFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = FavouritesPagerAdapter(this, this)
viewModel.visibleCategories.value?.let(::onCategoriesChanged)
viewModel.allCategories.value?.let(::onCategoriesChanged)
binding.pager.adapter = adapter
pagerAdapter = adapter
TabLayoutMediator(binding.tabs, binding.pager, adapter).attach()
actionModeDelegate.addListener(this, viewLifecycleOwner)
addMenuProvider(FavouritesContainerMenuProvider(view.context))
viewModel.visibleCategories.observe(viewLifecycleOwner, ::onCategoriesChanged)
viewModel.allCategories.observe(viewLifecycleOwner, ::onCategoriesChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError)
}
@@ -103,10 +103,10 @@ class FavouritesContainerFragment :
}
override fun onTabLongClick(tabView: View, item: CategoryListModel): Boolean {
when (item) {
/*when (item) {
is CategoryListModel.All -> showAllCategoriesMenu(tabView)
is CategoryListModel.CategoryItem -> showCategoryMenu(tabView, item.category)
}
}*/
return true
}
@@ -133,7 +133,12 @@ class FavouritesContainerFragment :
menu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(category)
R.id.action_edit -> startActivity(FavouritesCategoryEditActivity.newIntent(tabView.context, category.id))
R.id.action_edit -> startActivity(
FavouritesCategoryEditActivity.newIntent(
tabView.context,
category.id
)
)
else -> return@setOnMenuItemClickListener false
}
true

View File

@@ -6,7 +6,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
class FavouritesContainerMenuProvider(
private val context: Context,
@@ -19,7 +19,7 @@ class FavouritesContainerMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_categories -> {
context.startActivity(CategoriesActivity.newIntent(context))
context.startActivity(FavouriteCategoriesActivity.newIntent(context))
true
}
else -> false

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.favourites.ui
import android.annotation.SuppressLint
import android.view.View
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.AsyncListDiffer
@@ -7,7 +8,6 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
@@ -24,24 +24,21 @@ class FavouritesPagerAdapter(
override fun createFragment(position: Int): Fragment {
val item = differ.currentList[position]
return FavouritesListFragment.newInstance(item.id)
return FavouritesListFragment.newInstance(item.category.id)
}
override fun getItemId(position: Int): Long {
return differ.currentList[position].id
return differ.currentList[position].category.id
}
override fun containsItem(itemId: Long): Boolean {
return differ.currentList.any { it.id == itemId }
return differ.currentList.any { it.category.id == itemId }
}
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val item = differ.currentList[position]
tab.text = when (item) {
is CategoryListModel.All -> tab.view.context.getString(R.string.all_favourites)
is CategoryListModel.CategoryItem -> item.category.title
}
tab.view.tag = item.id
tab.text = item.category.title
tab.view.tag = item.category.id
tab.view.setOnLongClickListener(this)
}
@@ -51,7 +48,7 @@ class FavouritesPagerAdapter(
override fun onLongClick(v: View): Boolean {
val itemId = v.tag as? Long ?: return false
val item = differ.currentList.find { x -> x.id == itemId } ?: return false
val item = differ.currentList.find { x -> x.category.id == itemId } ?: return false
return longClickListener.onTabLongClick(v, item)
}
@@ -60,14 +57,11 @@ class FavouritesPagerAdapter(
override fun areItemsTheSame(
oldItem: CategoryListModel,
newItem: CategoryListModel
): Boolean = when {
oldItem is CategoryListModel.All && newItem is CategoryListModel.All -> true
oldItem is CategoryListModel.CategoryItem && newItem is CategoryListModel.CategoryItem -> {
oldItem.category.id == newItem.category.id
}
else -> false
): Boolean {
return oldItem.category.id == newItem.category.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(
oldItem: CategoryListModel,
newItem: CategoryListModel

View File

@@ -1,6 +0,0 @@
package org.koitharu.kotatsu.favourites.ui.categories
interface AllCategoriesToggleListener {
fun onAllCategoriesToggle(isVisible: Boolean)
}

View File

@@ -1,49 +1,49 @@
package org.koitharu.kotatsu.favourites.ui.categories
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.favourites.ui.categories.adapter.allCategoriesAD
import org.koitharu.kotatsu.favourites.ui.categories.adapter.categoryAD
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import kotlin.jvm.internal.Intrinsics
class CategoriesAdapter(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
onItemClickListener: OnListItemClickListener<FavouriteCategory>,
allCategoriesToggleListener: AllCategoriesToggleListener,
) : AsyncListDifferDelegationAdapter<CategoryListModel>(DiffCallback()) {
listListener: ListStateHolderListener,
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
init {
delegatesManager.addDelegate(categoryAD(onItemClickListener))
.addDelegate(allCategoriesAD(allCategoriesToggleListener))
setHasStableIds(true)
delegatesManager.addDelegate(categoryAD(coil, lifecycleOwner, onItemClickListener))
.addDelegate(emptyStateListAD(listListener))
.addDelegate(loadingStateAD())
}
override fun getItemId(position: Int): Long {
return items[position].id
}
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
private class DiffCallback : DiffUtil.ItemCallback<CategoryListModel>() {
override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
return when {
oldItem is CategoryListModel && newItem is CategoryListModel -> {
oldItem.category.id == newItem.category.id
}
else -> oldItem.javaClass == newItem.javaClass
}
}
override fun areItemsTheSame(
oldItem: CategoryListModel,
newItem: CategoryListModel,
): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
return Intrinsics.areEqual(oldItem, newItem)
}
override fun areContentsTheSame(
oldItem: CategoryListModel,
newItem: CategoryListModel,
): Boolean = oldItem == newItem
override fun getChangePayload(
oldItem: CategoryListModel,
newItem: CategoryListModel,
): Any? = when {
oldItem is CategoryListModel.All && newItem is CategoryListModel.All -> Unit
oldItem is CategoryListModel.CategoryItem &&
newItem is CategoryListModel.CategoryItem &&
oldItem.category.title != newItem.category.title -> null
else -> Unit
override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? {
return super.getChangePayload(oldItem, newItem)
}
}
}

View File

@@ -1,36 +1,38 @@
package org.koitharu.kotatsu.favourites.ui.categories
import android.app.ActivityOptions
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.measureHeight
class CategoriesActivity :
class FavouriteCategoriesActivity :
BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<FavouriteCategory>,
View.OnClickListener,
CategoriesEditDelegate.CategoriesEditCallback,
AllCategoriesToggleListener {
ListStateHolderListener {
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
@@ -42,7 +44,7 @@ class CategoriesActivity :
super.onCreate(savedInstanceState)
setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
adapter = CategoriesAdapter(this, this)
adapter = CategoriesAdapter(get(), this, this, this)
editDelegate = CategoriesEditDelegate(this, this)
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.adapter = adapter
@@ -50,7 +52,7 @@ class CategoriesActivity :
reorderHelper = ItemTouchHelper(ReorderHelperCallback())
reorderHelper.attachToRecyclerView(binding.recyclerView)
viewModel.allCategories.observe(this, ::onCategoriesChanged)
viewModel.detalizedCategories.observe(this, ::onCategoriesChanged)
viewModel.onError.observe(this, ::onError)
}
@@ -61,7 +63,7 @@ class CategoriesActivity :
}
override fun onItemClick(item: FavouriteCategory, view: View) {
val menu = PopupMenu(view.context, view)
/*val menu = PopupMenu(view.context, view)
menu.inflate(R.menu.popup_category)
menu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
@@ -70,7 +72,11 @@ class CategoriesActivity :
}
true
}
menu.show()
menu.show()*/
val intent = FavouritesActivity.newIntent(this, item)
val options =
ActivityOptions.makeScaleUpAnimation(view, view.width / 2, view.height / 2, view.width, view.height)
startActivity(intent, options.toBundle())
}
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
@@ -79,9 +85,9 @@ class CategoriesActivity :
return true
}
override fun onAllCategoriesToggle(isVisible: Boolean) {
viewModel.setAllCategoriesVisible(isVisible)
}
override fun onRetryClick(error: Throwable) = Unit
override fun onEmptyActionClick() = Unit
override fun onWindowInsetsChanged(insets: Insets) {
binding.fabAdd.updateLayoutParams<ViewGroup.MarginLayoutParams> {
@@ -96,9 +102,8 @@ class CategoriesActivity :
)
}
private fun onCategoriesChanged(categories: List<CategoryListModel>) {
private fun onCategoriesChanged(categories: List<ListModel>) {
adapter.items = categories
binding.textViewHolder.isVisible = categories.isEmpty()
}
private fun onError(e: Throwable) {
@@ -152,6 +157,6 @@ class CategoriesActivity :
SortOrder.RATING,
)
fun newIntent(context: Context) = Intent(context, CategoriesActivity::class.java)
fun newIntent(context: Context) = Intent(context, FavouriteCategoriesActivity::class.java)
}
}

View File

@@ -3,14 +3,15 @@ package org.koitharu.kotatsu.favourites.ui.categories
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.mapItems
import org.koitharu.kotatsu.utils.ext.requireValue
import java.util.*
class FavouritesCategoriesViewModel(
@@ -20,19 +21,25 @@ class FavouritesCategoriesViewModel(
private var reorderJob: Job? = null
val allCategories = combine(
repository.observeCategories(),
observeAllCategoriesVisible(),
) { list, showAll ->
mapCategories(list, showAll, true)
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
val visibleCategories = combine(
repository.observeCategories(),
observeAllCategoriesVisible(),
) { list, showAll ->
mapCategories(list, showAll, showAll && list.isNotEmpty())
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
val allCategories = repository.observeCategories()
.mapItems {
CategoryListModel(
mangaCount = 0,
covers = listOf(),
category = it,
)
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
val detalizedCategories = repository.observeCategoriesWithDetails()
.map {
it.map { (category, covers) ->
CategoryListModel(
mangaCount = covers.size,
covers = covers.take(3),
category = category,
)
}
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
fun deleteCategory(id: Long) {
launchJob {
@@ -48,30 +55,13 @@ class FavouritesCategoriesViewModel(
val prevJob = reorderJob
reorderJob = launchJob(Dispatchers.Default) {
prevJob?.join()
val items = allCategories.value ?: error("This should not happen")
val ids = items.mapTo(ArrayList(items.size)) { it.id }
val items = detalizedCategories.requireValue()
val ids = items.mapNotNullTo(ArrayList(items.size)) {
(it as? CategoryListModel)?.category?.id
}
Collections.swap(ids, oldPos, newPos)
ids.remove(0L)
repository.reorderCategories(ids)
}
}
private fun mapCategories(
categories: List<FavouriteCategory>,
isAllCategoriesVisible: Boolean,
withAllCategoriesItem: Boolean,
): List<CategoryListModel> {
val result = ArrayList<CategoryListModel>(categories.size + 1)
if (withAllCategoriesItem) {
result.add(CategoryListModel.All(isAllCategoriesVisible))
}
categories.mapTo(result) {
CategoryListModel.CategoryItem(it)
}
return result
}
private fun observeAllCategoriesVisible() = settings.observeAsFlow(AppSettings.KEY_ALL_FAVOURITES_VISIBLE) {
isAllFavouritesVisible
}
}

View File

@@ -1,20 +0,0 @@
package org.koitharu.kotatsu.favourites.ui.categories.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.databinding.ItemCategoriesAllBinding
import org.koitharu.kotatsu.favourites.ui.categories.AllCategoriesToggleListener
fun allCategoriesAD(
allCategoriesToggleListener: AllCategoriesToggleListener,
) = adapterDelegateViewBinding<CategoryListModel.All, CategoryListModel, ItemCategoriesAllBinding>(
{ inflater, parent -> ItemCategoriesAllBinding.inflate(inflater, parent, false) }
) {
binding.imageViewToggle.setOnClickListener {
allCategoriesToggleListener.onAllCategoriesToggle(!item.isVisible)
}
bind {
binding.imageViewToggle.isChecked = item.isVisible
}
}

View File

@@ -1,30 +1,65 @@
package org.koitharu.kotatsu.favourites.ui.categories.adapter
import android.view.MotionEvent
import android.view.View
import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.request.Disposable
import coil.size.Scale
import coil.util.CoilUtils
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ItemCategoryBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest
fun categoryAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<FavouriteCategory>
) = adapterDelegateViewBinding<CategoryListModel.CategoryItem, CategoryListModel, ItemCategoryBinding>(
) = adapterDelegateViewBinding<CategoryListModel, ListModel, ItemCategoryBinding>(
{ inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) }
) {
binding.imageViewMore.setOnClickListener {
clickListener.onItemClick(item.category, it)
val eventListener = object : OnClickListener, OnLongClickListener {
override fun onClick(v: View) = clickListener.onItemClick(item.category, v)
override fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, v)
}
@Suppress("ClickableViewAccessibility")
binding.imageViewHandle.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
clickListener.onItemLongClick(item.category, itemView)
} else {
false
val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3)
val imageRequests = arrayOfNulls<Disposable?>(coverViews.size)
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
bind {
imageRequests.forEach { it?.dispose() }
binding.textViewTitle.text = item.category.title
binding.textViewSubtitle.text = context.resources.getQuantityString(
R.plurals.items,
item.mangaCount,
item.mangaCount,
)
repeat(coverViews.size) { i ->
imageRequests[i] = coverViews[i].newImageRequest(item.covers.getOrNull(i))
.placeholder(R.drawable.ic_placeholder)
.fallback(null)
.error(R.drawable.ic_placeholder)
.scale(Scale.FILL)
.allowRgb565(true)
.lifecycle(lifecycleOwner)
.enqueueWith(coil)
}
}
bind {
binding.textViewTitle.text = item.category.title
onViewRecycled {
repeat(coverViews.size) { i ->
imageRequests[i]?.dispose()
imageRequests[i] = null
CoilUtils.dispose(coverViews[i])
coverViews[i].setImageDrawable(null)
}
}
}

View File

@@ -3,59 +3,33 @@ package org.koitharu.kotatsu.favourites.ui.categories.adapter
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.list.ui.model.ListModel
sealed interface CategoryListModel : ListModel {
class CategoryListModel(
val mangaCount: Int,
val covers: List<String>,
val category: FavouriteCategory,
) : ListModel {
val id: Long
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
class All(
val isVisible: Boolean,
) : CategoryListModel {
other as CategoryListModel
override val id: Long = 0L
if (mangaCount != other.mangaCount) return false
if (covers != other.covers) return false
if (category.id != other.category.id) return false
if (category.title != other.category.title) return false
if (category.order != other.category.order) return false
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as All
if (isVisible != other.isVisible) return false
return true
}
override fun hashCode(): Int {
return isVisible.hashCode()
}
return true
}
class CategoryItem(
val category: FavouriteCategory,
) : CategoryListModel {
override val id: Long
get() = category.id
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CategoryItem
if (category.id != other.category.id) return false
if (category.title != other.category.title) return false
if (category.order != other.category.order) return false
if (category.isTrackingEnabled != other.category.isTrackingEnabled) return false
return true
}
override fun hashCode(): Int {
var result = category.id.hashCode()
result = 31 * result + category.title.hashCode()
result = 31 * result + category.order.hashCode()
result = 31 * result + category.isTrackingEnabled.hashCode()
return result
}
override fun hashCode(): Int {
var result = mangaCount
result = 31 * result + covers.hashCode()
result = 31 * result + category.id.hashCode()
result = 31 * result + category.title.hashCode()
result = 31 * result + category.order.hashCode()
return result
}
}

View File

@@ -18,7 +18,7 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.ui.titleRes
import org.koitharu.kotatsu.databinding.ActivityCategoryEditBinding
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
@@ -84,7 +84,7 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
}
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedSortOrder = CategoriesActivity.SORT_ORDERS.getOrNull(position)
selectedSortOrder = FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(position)
}
private fun onCategoryChanged(category: FavouriteCategory?) {
@@ -114,7 +114,7 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
}
private fun initSortSpinner() {
val entries = CategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val entries = FavouriteCategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, entries)
binding.editSort.setAdapter(adapter)
binding.editSort.onItemClickListener = this
@@ -122,9 +122,9 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
private fun getSelectedSortOrder(): SortOrder {
selectedSortOrder?.let { return it }
val entries = CategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val entries = FavouriteCategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val index = entries.indexOf(binding.editSort.text.toString())
return CategoriesActivity.SORT_ORDERS.getOrNull(index) ?: SortOrder.NEWEST
return FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(index) ?: SortOrder.NEWEST
}
companion object {

View File

@@ -7,7 +7,7 @@ import androidx.core.view.MenuProvider
import androidx.core.view.iterator
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.titleRes
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
class FavouritesListMenuProvider(
private val viewModel: FavouritesListViewModel,
@@ -16,7 +16,7 @@ class FavouritesListMenuProvider(
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_favourites_list, menu)
menu.findItem(R.id.action_order)?.subMenu?.let { submenu ->
for ((i, item) in CategoriesActivity.SORT_ORDERS.withIndex()) {
for ((i, item) in FavouriteCategoriesActivity.SORT_ORDERS.withIndex()) {
val menuItem = submenu.add(R.id.group_order, Menu.NONE, i, item.titleRes)
menuItem.isCheckable = true
}
@@ -28,7 +28,7 @@ class FavouritesListMenuProvider(
menu.findItem(R.id.action_order)?.subMenu?.let { submenu ->
val selectedOrder = viewModel.sortOrder.value
for (item in submenu) {
val order = CategoriesActivity.SORT_ORDERS.getOrNull(item.order)
val order = FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(item.order)
item.isChecked = order == selectedOrder
}
}
@@ -38,7 +38,7 @@ class FavouritesListMenuProvider(
return when {
menuItem.itemId == R.id.action_order -> false
menuItem.groupId == R.id.group_order -> {
val order = CategoriesActivity.SORT_ORDERS.getOrNull(menuItem.order) ?: return false
val order = FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(menuItem.order) ?: return false
viewModel.setSortOrder(order)
true
}

View File

@@ -8,7 +8,7 @@ import androidx.core.view.MenuProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.RememberSelectionDialogListener
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.utils.ext.startOfDay
import java.util.*
import java.util.concurrent.TimeUnit
@@ -30,7 +30,7 @@ class LibraryMenuProvider(
true
}
R.id.action_categories -> {
context.startActivity(CategoriesActivity.newIntent(context))
context.startActivity(FavouriteCategoriesActivity.newIntent(context))
true
}
else -> false

View File

@@ -23,7 +23,7 @@ import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
@@ -103,7 +103,7 @@ class TrackerSettingsFragment :
}
}
AppSettings.KEY_TRACK_CATEGORIES -> {
startActivity(CategoriesActivity.newIntent(preference.context))
startActivity(FavouriteCategoriesActivity.newIntent(preference.context))
true
}
KEY_IGNORE_DOZE -> {

View File

@@ -51,18 +51,6 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<TextView
android:id="@+id/textView_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:gravity="center"
android:text="@string/text_categories_holder"
android:textAppearance="?attr/textAppearanceBody2"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab_add"
android:layout_width="wrap_content"

View File

@@ -1,41 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?android:windowBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="Overdraw">
android:layout_height="wrap_content"
android:background="@drawable/list_selector"
android:paddingVertical="12dp"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd">
<ImageView
android:id="@+id/imageView_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="?listPreferredItemPaddingStart"
android:scaleType="center"
android:src="@drawable/ic_reorder_handle" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover3"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_marginStart="24dp"
android:layout_marginBottom="12dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,13:18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover2"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_marginStart="12dp"
android:alpha="50"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,13:18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover1"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_marginTop="12dp"
android:alpha="120"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,13:18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" />
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:layout_marginStart="@dimen/margin_normal"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyLarge"
app:layout_constraintBottom_toTopOf="@id/textView_subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@tools:sample/lorem[1]" />
<ImageView
android:id="@+id/imageView_more"
android:layout_width="wrap_content"
<TextView
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:padding="?listPreferredItemPaddingEnd"
android:scaleType="center"
app:srcCompat="@drawable/abc_ic_menu_overflow_material" />
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="4dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
app:layout_constraintTop_toBottomOf="@id/textView_title"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@tools:sample/lorem[1]" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>