Favourites categories screen

pull/163/head
Koitharu 4 years ago
parent 1381a7d957
commit c5de765e52
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

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

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

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

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

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

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

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

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

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

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

@ -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<CategoryListModel>() {
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
override fun areItemsTheSame(
oldItem: CategoryListModel,
newItem: CategoryListModel,
): Boolean = oldItem.id == newItem.id
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 areContentsTheSame(
oldItem: CategoryListModel,
newItem: CategoryListModel,
): Boolean = oldItem == newItem
override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
return Intrinsics.areEqual(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)
}
}
}

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

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

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

@ -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)
}
@Suppress("ClickableViewAccessibility")
binding.imageViewHandle.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
clickListener.onItemLongClick(item.category, itemView)
} else {
false
}
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)
}
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)
}
}
onViewRecycled {
repeat(coverViews.size) { i ->
imageRequests[i]?.dispose()
imageRequests[i] = null
CoilUtils.dispose(coverViews[i])
coverViews[i].setImageDrawable(null)
}
}
}

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

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

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

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

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

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

@ -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>
Loading…
Cancel
Save