Option to hide 'All categories' tab from favourites
This commit is contained in:
@@ -5,6 +5,7 @@ import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.Parcelable.Creator
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.Checkable
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
@@ -61,6 +62,12 @@ class CheckableImageView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleOnClickListener : OnClickListener {
|
||||
override fun onClick(view: View) {
|
||||
(view as? Checkable)?.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
fun interface OnCheckedChangeListener {
|
||||
|
||||
fun onCheckedChanged(view: CheckableImageView, isChecked: Boolean)
|
||||
|
||||
@@ -11,10 +11,6 @@ import androidx.collection.arraySetOf
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
@@ -24,6 +20,10 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.ext.getEnumValue
|
||||
import org.koitharu.kotatsu.utils.ext.putEnumValue
|
||||
import org.koitharu.kotatsu.utils.ext.toUriOrNull
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class AppSettings(context: Context) {
|
||||
|
||||
@@ -67,6 +67,10 @@ class AppSettings(context: Context) {
|
||||
get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true)
|
||||
set(value) = prefs.edit { putBoolean(KEY_TRAFFIC_WARNING, value) }
|
||||
|
||||
var isAllFavouritesVisible: Boolean
|
||||
get() = prefs.getBoolean(KEY_ALL_FAVOURITES_VISIBLE, true)
|
||||
set(value) = prefs.edit { putBoolean(KEY_ALL_FAVOURITES_VISIBLE, value) }
|
||||
|
||||
val isUpdateCheckingEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_APP_UPDATE_AUTO, true)
|
||||
|
||||
@@ -278,6 +282,7 @@ class AppSettings(context: Context) {
|
||||
const val KEY_SEARCH_SINGLE_SOURCE = "search_single_source"
|
||||
const val KEY_DOWNLOADS_PARALLELISM = "downloads_parallelism"
|
||||
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
|
||||
const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible"
|
||||
|
||||
// About
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
|
||||
@@ -15,7 +15,7 @@ val favouritesModule
|
||||
viewModel { categoryId ->
|
||||
FavouritesListViewModel(categoryId.get(), get(), get(), get())
|
||||
}
|
||||
viewModel { FavouritesCategoriesViewModel(get()) }
|
||||
viewModel { FavouritesCategoriesViewModel(get(), get()) }
|
||||
viewModel { manga ->
|
||||
MangaCategoriesViewModel(manga.get(), get())
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class FavouritesRepository(private val db: MangaDatabase) {
|
||||
createdAt = System.currentTimeMillis(),
|
||||
sortKey = db.favouriteCategoriesDao.getNextSortKey(),
|
||||
categoryId = 0,
|
||||
order = SortOrder.UPDATED.name,
|
||||
order = SortOrder.NEWEST.name,
|
||||
)
|
||||
val id = db.favouriteCategoriesDao.insert(entity)
|
||||
return entity.toFavouriteCategory(id)
|
||||
|
||||
@@ -21,12 +21,11 @@ import org.koitharu.kotatsu.databinding.FragmentFavouritesBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.measureHeight
|
||||
import org.koitharu.kotatsu.utils.ext.resolveDp
|
||||
import java.util.*
|
||||
|
||||
class FavouritesContainerFragment :
|
||||
BaseFragment<FragmentFavouritesBinding>(),
|
||||
@@ -53,15 +52,15 @@ class FavouritesContainerFragment :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val adapter = FavouritesPagerAdapter(this, this)
|
||||
viewModel.categories.value?.let {
|
||||
adapter.replaceData(wrapCategories(it))
|
||||
viewModel.visibleCategories.value?.let {
|
||||
adapter.replaceData(it)
|
||||
}
|
||||
binding.pager.adapter = adapter
|
||||
pagerAdapter = adapter
|
||||
TabLayoutMediator(binding.tabs, binding.pager, adapter).attach()
|
||||
actionModeDelegate.addListener(this, viewLifecycleOwner)
|
||||
|
||||
viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged)
|
||||
viewModel.visibleCategories.observe(viewLifecycleOwner, ::onCategoriesChanged)
|
||||
viewModel.onError.observe(viewLifecycleOwner, ::onError)
|
||||
}
|
||||
|
||||
@@ -86,7 +85,8 @@ class FavouritesContainerFragment :
|
||||
top = headerHeight - insets.top
|
||||
)
|
||||
binding.pager.updatePadding(
|
||||
top = -headerHeight + resources.resolveDp(8) // 8 dp is needed so that the top of the list is not attached to tabs (visible when ActionMode is active)
|
||||
// 8 dp is needed so that the top of the list is not attached to tabs (visible when ActionMode is active)
|
||||
top = -headerHeight + resources.resolveDp(8)
|
||||
)
|
||||
binding.tabs.apply {
|
||||
updatePadding(
|
||||
@@ -99,8 +99,8 @@ class FavouritesContainerFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
|
||||
pagerAdapter?.replaceData(wrapCategories(categories))
|
||||
private fun onCategoriesChanged(categories: List<CategoryListModel>) {
|
||||
pagerAdapter?.replaceData(categories)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
@@ -122,26 +122,11 @@ class FavouritesContainerFragment :
|
||||
Snackbar.make(binding.pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean {
|
||||
val menuRes = if (category.id == 0L) R.menu.popup_category_empty else R.menu.popup_category
|
||||
val menu = PopupMenu(tabView.context, tabView)
|
||||
menu.inflate(menuRes)
|
||||
createOrderSubmenu(menu.menu, category)
|
||||
menu.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_remove -> editDelegate.deleteCategory(category)
|
||||
R.id.action_rename -> editDelegate.renameCategory(category)
|
||||
R.id.action_create -> editDelegate.createCategory()
|
||||
R.id.action_order -> return@setOnMenuItemClickListener false
|
||||
else -> {
|
||||
val order = CategoriesActivity.SORT_ORDERS.getOrNull(it.order)
|
||||
?: return@setOnMenuItemClickListener false
|
||||
viewModel.setCategoryOrder(category.id, order)
|
||||
}
|
||||
}
|
||||
true
|
||||
override fun onTabLongClick(tabView: View, item: CategoryListModel): Boolean {
|
||||
when (item) {
|
||||
is CategoryListModel.All -> showAllCategoriesMenu(tabView)
|
||||
is CategoryListModel.CategoryItem -> showCategoryMenu(tabView, item.category)
|
||||
}
|
||||
menu.show()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -157,13 +142,6 @@ class FavouritesContainerFragment :
|
||||
viewModel.createCategory(name)
|
||||
}
|
||||
|
||||
private fun wrapCategories(categories: List<FavouriteCategory>): List<FavouriteCategory> {
|
||||
val data = ArrayList<FavouriteCategory>(categories.size + 1)
|
||||
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, SortOrder.NEWEST, Date())
|
||||
data += categories
|
||||
return data
|
||||
}
|
||||
|
||||
private fun createOrderSubmenu(menu: Menu, category: FavouriteCategory) {
|
||||
val submenu = menu.findItem(R.id.action_order)?.subMenu ?: return
|
||||
for ((i, item) in CategoriesActivity.SORT_ORDERS.withIndex()) {
|
||||
@@ -181,6 +159,40 @@ class FavouritesContainerFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCategoryMenu(tabView: View, category: FavouriteCategory) {
|
||||
val menu = PopupMenu(tabView.context, tabView)
|
||||
menu.inflate(R.menu.popup_category)
|
||||
createOrderSubmenu(menu.menu, category)
|
||||
menu.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_remove -> editDelegate.deleteCategory(category)
|
||||
R.id.action_rename -> editDelegate.renameCategory(category)
|
||||
R.id.action_create -> editDelegate.createCategory()
|
||||
R.id.action_order -> return@setOnMenuItemClickListener false
|
||||
else -> {
|
||||
val order = CategoriesActivity.SORT_ORDERS.getOrNull(it.order)
|
||||
?: return@setOnMenuItemClickListener false
|
||||
viewModel.setCategoryOrder(category.id, order)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
|
||||
private fun showAllCategoriesMenu(tabView: View) {
|
||||
val menu = PopupMenu(tabView.context, tabView)
|
||||
menu.inflate(R.menu.popup_category_all)
|
||||
menu.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_create -> editDelegate.createCategory()
|
||||
R.id.action_hide -> viewModel.setAllCategoriesVisible(false)
|
||||
}
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = FavouritesContainerFragment()
|
||||
|
||||
@@ -7,14 +7,16 @@ 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.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
|
||||
|
||||
class FavouritesPagerAdapter(
|
||||
fragment: Fragment,
|
||||
private val longClickListener: FavouritesTabLongClickListener
|
||||
) : FragmentStateAdapter(fragment.childFragmentManager, fragment.viewLifecycleOwner.lifecycle),
|
||||
TabLayoutMediator.TabConfigurationStrategy, View.OnLongClickListener {
|
||||
TabLayoutMediator.TabConfigurationStrategy,
|
||||
View.OnLongClickListener {
|
||||
|
||||
private val differ = AsyncListDiffer(this, DiffCallback())
|
||||
|
||||
@@ -35,12 +37,15 @@ class FavouritesPagerAdapter(
|
||||
|
||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||
val item = differ.currentList[position]
|
||||
tab.text = item.title
|
||||
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.view.setOnLongClickListener(this)
|
||||
}
|
||||
|
||||
fun replaceData(data: List<FavouriteCategory>) {
|
||||
fun replaceData(data: List<CategoryListModel>) {
|
||||
differ.submitList(data)
|
||||
}
|
||||
|
||||
@@ -50,16 +55,22 @@ class FavouritesPagerAdapter(
|
||||
return longClickListener.onTabLongClick(v, item)
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<FavouriteCategory>() {
|
||||
private class DiffCallback : DiffUtil.ItemCallback<CategoryListModel>() {
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: FavouriteCategory,
|
||||
newItem: FavouriteCategory
|
||||
): Boolean = oldItem.id == newItem.id
|
||||
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
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: FavouriteCategory,
|
||||
newItem: FavouriteCategory
|
||||
): Boolean = oldItem.id == newItem.id && oldItem.title == newItem.title
|
||||
oldItem: CategoryListModel,
|
||||
newItem: CategoryListModel
|
||||
): Boolean = oldItem == newItem
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.koitharu.kotatsu.favourites.ui
|
||||
|
||||
import android.view.View
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||
|
||||
fun interface FavouritesTabLongClickListener {
|
||||
|
||||
fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean
|
||||
fun onTabLongClick(tabView: View, item: CategoryListModel): Boolean
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
interface AllCategoriesToggleListener {
|
||||
|
||||
fun onAllCategoriesToggle(isVisible: Boolean)
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.ui.titleRes
|
||||
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.measureHeight
|
||||
@@ -29,7 +30,7 @@ class CategoriesActivity :
|
||||
BaseActivity<ActivityCategoriesBinding>(),
|
||||
OnListItemClickListener<FavouriteCategory>,
|
||||
View.OnClickListener,
|
||||
CategoriesEditDelegate.CategoriesEditCallback {
|
||||
CategoriesEditDelegate.CategoriesEditCallback, AllCategoriesToggleListener {
|
||||
|
||||
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
|
||||
|
||||
@@ -41,7 +42,7 @@ class CategoriesActivity :
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
adapter = CategoriesAdapter(this)
|
||||
adapter = CategoriesAdapter(this, this)
|
||||
editDelegate = CategoriesEditDelegate(this, this)
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.adapter = adapter
|
||||
@@ -49,7 +50,7 @@ class CategoriesActivity :
|
||||
reorderHelper = ItemTouchHelper(ReorderHelperCallback())
|
||||
reorderHelper.attachToRecyclerView(binding.recyclerView)
|
||||
|
||||
viewModel.categories.observe(this, ::onCategoriesChanged)
|
||||
viewModel.allCategories.observe(this, ::onCategoriesChanged)
|
||||
viewModel.onError.observe(this, ::onError)
|
||||
}
|
||||
|
||||
@@ -84,6 +85,10 @@ class CategoriesActivity :
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onAllCategoriesToggle(isVisible: Boolean) {
|
||||
viewModel.setAllCategoriesVisible(isVisible)
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.fabAdd.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
rightMargin = topMargin + insets.right
|
||||
@@ -97,7 +102,7 @@ class CategoriesActivity :
|
||||
)
|
||||
}
|
||||
|
||||
private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
|
||||
private fun onCategoriesChanged(categories: List<CategoryListModel>) {
|
||||
adapter.items = categories
|
||||
binding.textViewHolder.isVisible = categories.isEmpty()
|
||||
}
|
||||
@@ -138,13 +143,19 @@ class CategoriesActivity :
|
||||
ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0
|
||||
) {
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder,
|
||||
): Boolean = true
|
||||
): Boolean = viewHolder.itemViewType == target.itemViewType
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||
override fun canDropOver(
|
||||
recyclerView: RecyclerView,
|
||||
current: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder,
|
||||
): Boolean = current.itemViewType == target.itemViewType
|
||||
|
||||
override fun onMoved(
|
||||
recyclerView: RecyclerView,
|
||||
@@ -158,6 +169,8 @@ class CategoriesActivity :
|
||||
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
|
||||
viewModel.reorderCategories(fromPos, toPos)
|
||||
}
|
||||
|
||||
override fun isLongPressDragEnabled(): Boolean = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -4,13 +4,18 @@ 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 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
|
||||
|
||||
class CategoriesAdapter(
|
||||
onItemClickListener: OnListItemClickListener<FavouriteCategory>,
|
||||
) : AsyncListDifferDelegationAdapter<FavouriteCategory>(DiffCallback()) {
|
||||
allCategoriesToggleListener: AllCategoriesToggleListener,
|
||||
) : AsyncListDifferDelegationAdapter<CategoryListModel>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
delegatesManager.addDelegate(categoryAD(onItemClickListener))
|
||||
.addDelegate(allCategoriesAD(allCategoriesToggleListener))
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
@@ -18,28 +23,23 @@ class CategoriesAdapter(
|
||||
return items[position].id
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<FavouriteCategory>() {
|
||||
private class DiffCallback : DiffUtil.ItemCallback<CategoryListModel>() {
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: FavouriteCategory,
|
||||
newItem: FavouriteCategory,
|
||||
): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
oldItem: CategoryListModel,
|
||||
newItem: CategoryListModel,
|
||||
): Boolean = oldItem.id == newItem.id
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: FavouriteCategory,
|
||||
newItem: FavouriteCategory,
|
||||
): Boolean {
|
||||
return oldItem.id == newItem.id && oldItem.title == newItem.title
|
||||
&& oldItem.order == newItem.order
|
||||
}
|
||||
oldItem: CategoryListModel,
|
||||
newItem: CategoryListModel,
|
||||
): Boolean = oldItem == newItem
|
||||
|
||||
override fun getChangePayload(
|
||||
oldItem: FavouriteCategory,
|
||||
newItem: FavouriteCategory,
|
||||
oldItem: CategoryListModel,
|
||||
newItem: CategoryListModel,
|
||||
): Any? = when {
|
||||
oldItem.title == newItem.title && oldItem.order != newItem.order -> newItem.order
|
||||
oldItem is CategoryListModel.All && newItem is CategoryListModel.All -> Unit
|
||||
else -> super.getChangePayload(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,36 @@ package org.koitharu.kotatsu.favourites.ui.categories
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
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.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import java.util.*
|
||||
|
||||
class FavouritesCategoriesViewModel(
|
||||
private val repository: FavouritesRepository
|
||||
private val repository: FavouritesRepository,
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private var reorderJob: Job? = null
|
||||
|
||||
val categories = repository.observeCategories()
|
||||
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
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)
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
fun createCategory(name: String) {
|
||||
launchJob {
|
||||
@@ -42,14 +58,40 @@ class FavouritesCategoriesViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun setAllCategoriesVisible(isVisible: Boolean) {
|
||||
settings.isAllFavouritesVisible = isVisible
|
||||
}
|
||||
|
||||
fun reorderCategories(oldPos: Int, newPos: Int) {
|
||||
val prevJob = reorderJob
|
||||
reorderJob = launchJob(Dispatchers.Default) {
|
||||
prevJob?.join()
|
||||
val items = categories.value ?: error("This should not happen")
|
||||
val items = allCategories.value ?: error("This should not happen")
|
||||
val ids = items.mapTo(ArrayList(items.size)) { it.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.observe()
|
||||
.filter { it == AppSettings.KEY_ALL_FAVOURITES_VISIBLE }
|
||||
.map { settings.isAllFavouritesVisible }
|
||||
.onStart { emit(settings.isAllFavouritesVisible) }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.adapter
|
||||
|
||||
import android.view.MotionEvent
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
@@ -8,23 +8,23 @@ import org.koitharu.kotatsu.databinding.ItemCategoryBinding
|
||||
|
||||
fun categoryAD(
|
||||
clickListener: OnListItemClickListener<FavouriteCategory>
|
||||
) = adapterDelegateViewBinding<FavouriteCategory, FavouriteCategory, ItemCategoryBinding>(
|
||||
) = adapterDelegateViewBinding<CategoryListModel.CategoryItem, CategoryListModel, ItemCategoryBinding>(
|
||||
{ inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) }
|
||||
) {
|
||||
|
||||
binding.imageViewMore.setOnClickListener {
|
||||
clickListener.onItemClick(item, it)
|
||||
clickListener.onItemClick(item.category, it)
|
||||
}
|
||||
@Suppress("ClickableViewAccessibility")
|
||||
binding.imageViewHandle.setOnTouchListener { v, event ->
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
clickListener.onItemLongClick(item, itemView)
|
||||
clickListener.onItemLongClick(item.category, itemView)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
bind {
|
||||
binding.textViewTitle.text = item.title
|
||||
binding.textViewTitle.text = item.category.title
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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 {
|
||||
|
||||
val id: Long
|
||||
|
||||
class All(
|
||||
val isVisible: Boolean,
|
||||
) : CategoryListModel {
|
||||
|
||||
override val id: Long = 0L
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = category.id.hashCode()
|
||||
result = 31 * result + category.title.hashCode()
|
||||
result = 31 * result + category.order.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,10 +141,7 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
recyclerView: RecyclerView,
|
||||
current: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder,
|
||||
): Boolean = current.itemViewType == target.itemViewType && viewModel.canReorder(
|
||||
current.bindingAdapterPosition,
|
||||
target.bindingAdapterPosition,
|
||||
)
|
||||
): Boolean = current.itemViewType == target.itemViewType
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||
|
||||
|
||||
12
app/src/main/res/drawable/ic_hidden.xml
Normal file
12
app/src/main/res/drawable/ic_hidden.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M2,5.27L3.28,4L20,20.72L18.73,22L15.65,18.92C14.5,19.3 13.28,19.5 12,19.5C7,19.5 2.73,16.39 1,12C1.69,10.24 2.79,8.69 4.19,7.46L2,5.27M12,9A3,3 0 0,1 15,12C15,12.35 14.94,12.69 14.83,13L11,9.17C11.31,9.06 11.65,9 12,9M12,4.5C17,4.5 21.27,7.61 23,12C22.18,14.08 20.79,15.88 19,17.19L17.58,15.76C18.94,14.82 20.06,13.54 20.82,12C19.17,8.64 15.76,6.5 12,6.5C10.91,6.5 9.84,6.68 8.84,7L7.3,5.47C8.74,4.85 10.33,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C12.69,17.5 13.37,17.43 14,17.29L11.72,15C10.29,14.85 9.15,13.71 9,12.28L5.6,8.87C4.61,9.72 3.78,10.78 3.18,12Z" />
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_shown.xml
Normal file
12
app/src/main/res/drawable/ic_shown.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9M12,4.5C17,4.5 21.27,7.61 23,12C21.27,16.39 17,19.5 12,19.5C7,19.5 2.73,16.39 1,12C2.73,7.61 7,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C15.76,17.5 19.17,15.36 20.82,12C19.17,8.64 15.76,6.5 12,6.5C8.24,6.5 4.83,8.64 3.18,12Z" />
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_shown_hidden.xml
Normal file
5
app/src/main/res/drawable/ic_shown_hidden.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_shown" android:state_checked="true" />
|
||||
<item android:drawable="@drawable/ic_hidden" android:state_checked="false" />
|
||||
</selector>
|
||||
33
app/src/main/res/layout/item_categories_all.xml
Normal file
33
app/src/main/res/layout/item_categories_all.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
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"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
tools:ignore="Overdraw">
|
||||
|
||||
<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:singleLine="true"
|
||||
android:text="@string/all_favourites"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.CheckableImageView
|
||||
android:id="@+id/imageView_toggle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="?listPreferredItemPaddingEnd"
|
||||
android:scaleType="center"
|
||||
app:srcCompat="@drawable/ic_shown_hidden" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -2,6 +2,10 @@
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_hide"
|
||||
android:title="@string/hide" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_create"
|
||||
android:title="@string/create_category" />
|
||||
@@ -279,4 +279,5 @@
|
||||
<string name="download_slowdown_summary">Helps avoid blocking your IP address</string>
|
||||
<string name="local_manga_processing">Saved manga processing</string>
|
||||
<string name="chapters_will_removed_background">Chapters will be removed in the background. It can take some time</string>
|
||||
<string name="hide">Hide</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user