Remove from library action
This commit is contained in:
@@ -12,9 +12,9 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.savedstate.SavedStateRegistry
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
private const val KEY_SELECTION = "selection"
|
||||
private const val PROVIDER_NAME = "selection_decoration"
|
||||
@@ -159,7 +159,7 @@ class ListSelectionController(
|
||||
override fun onActionItemClicked(
|
||||
controller: ListSelectionController,
|
||||
mode: ActionMode,
|
||||
item: MenuItem
|
||||
item: MenuItem,
|
||||
): Boolean = onActionItemClicked(mode, item)
|
||||
|
||||
override fun onDestroyActionMode(controller: ListSelectionController, mode: ActionMode) {
|
||||
@@ -173,7 +173,10 @@ class ListSelectionController(
|
||||
|
||||
fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean
|
||||
|
||||
fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean
|
||||
fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.title = controller.count.toString()
|
||||
return true
|
||||
}
|
||||
|
||||
fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean
|
||||
|
||||
@@ -197,4 +200,4 @@ class ListSelectionController(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.savedstate.SavedStateRegistry
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
private const val PROVIDER_NAME = "selection_decoration_sectioned"
|
||||
|
||||
class SectionedSelectionController<T : Any>(
|
||||
private val activity: Activity,
|
||||
private val registryOwner: SavedStateRegistryOwner,
|
||||
private val owner: SavedStateRegistryOwner,
|
||||
private val callback: Callback<T>,
|
||||
) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider {
|
||||
|
||||
@@ -34,7 +34,7 @@ class SectionedSelectionController<T : Any>(
|
||||
get() = decorations.values.sumOf { it.checkedItemsCount }
|
||||
|
||||
init {
|
||||
registryOwner.lifecycle.addObserver(StateEventObserver())
|
||||
owner.lifecycle.addObserver(StateEventObserver())
|
||||
}
|
||||
|
||||
fun snapshot(): Map<T, Set<Long>> {
|
||||
@@ -117,19 +117,19 @@ class SectionedSelectionController<T : Any>(
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
return callback.onCreateActionMode(mode, menu)
|
||||
return callback.onCreateActionMode(this, mode, menu)
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
return callback.onPrepareActionMode(mode, menu)
|
||||
return callback.onPrepareActionMode(this, mode, menu)
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
return callback.onActionItemClicked(mode, item)
|
||||
return callback.onActionItemClicked(this, mode, item)
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode) {
|
||||
callback.onDestroyActionMode(mode)
|
||||
callback.onDestroyActionMode(this, mode)
|
||||
clear()
|
||||
actionMode = null
|
||||
}
|
||||
@@ -146,7 +146,7 @@ class SectionedSelectionController<T : Any>(
|
||||
|
||||
private fun notifySelectionChanged() {
|
||||
val count = this.count
|
||||
callback.onSelectionChanged(count)
|
||||
callback.onSelectionChanged(this, count)
|
||||
if (count == 0) {
|
||||
actionMode?.finish()
|
||||
} else {
|
||||
@@ -173,27 +173,48 @@ class SectionedSelectionController<T : Any>(
|
||||
|
||||
private fun getDecoration(section: T): AbstractSelectionItemDecoration {
|
||||
return decorations.getOrPut(section) {
|
||||
callback.onCreateItemDecoration(section)
|
||||
callback.onCreateItemDecoration(this, section)
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback<T> : ListSelectionController.Callback {
|
||||
interface Callback<T : Any> {
|
||||
|
||||
fun onCreateItemDecoration(section: T): AbstractSelectionItemDecoration
|
||||
fun onSelectionChanged(controller: SectionedSelectionController<T>, count: Int)
|
||||
|
||||
fun onCreateActionMode(controller: SectionedSelectionController<T>, mode: ActionMode, menu: Menu): Boolean
|
||||
|
||||
fun onPrepareActionMode(controller: SectionedSelectionController<T>, mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.title = controller.count.toString()
|
||||
return true
|
||||
}
|
||||
|
||||
fun onDestroyActionMode(controller: SectionedSelectionController<T>, mode: ActionMode) = Unit
|
||||
|
||||
fun onActionItemClicked(
|
||||
controller: SectionedSelectionController<T>,
|
||||
mode: ActionMode,
|
||||
item: MenuItem,
|
||||
): Boolean
|
||||
|
||||
fun onCreateItemDecoration(
|
||||
controller: SectionedSelectionController<T>,
|
||||
section: T,
|
||||
): AbstractSelectionItemDecoration
|
||||
}
|
||||
|
||||
private inner class StateEventObserver : LifecycleEventObserver {
|
||||
|
||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
if (event == Lifecycle.Event.ON_CREATE) {
|
||||
val registry = registryOwner.savedStateRegistry
|
||||
val registry = owner.savedStateRegistry
|
||||
registry.registerSavedStateProvider(PROVIDER_NAME, this@SectionedSelectionController)
|
||||
val state = registry.consumeRestoredStateForKey(PROVIDER_NAME)
|
||||
if (state != null) {
|
||||
Dispatchers.Main.dispatch(EmptyCoroutineContext) { // == Handler.post
|
||||
if (source.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
|
||||
restoreState(
|
||||
state.keySet().associateWithTo(HashMap()) { state.getLongArray(it)?.toList().orEmpty() }
|
||||
state.keySet()
|
||||
.associateWithTo(HashMap()) { state.getLongArray(it)?.toList().orEmpty() },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -201,4 +222,4 @@ class SectionedSelectionController<T : Any>(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,11 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations
|
||||
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
|
||||
|
||||
class BookmarksFragment : BaseFragment<FragmentListSimpleBinding>(), ListStateHolderListener,
|
||||
OnListItemClickListener<Bookmark>, SectionedSelectionController.Callback<Manga> {
|
||||
class BookmarksFragment :
|
||||
BaseFragment<FragmentListSimpleBinding>(),
|
||||
ListStateHolderListener,
|
||||
OnListItemClickListener<Bookmark>,
|
||||
SectionedSelectionController.Callback<Manga> {
|
||||
|
||||
private val viewModel by viewModel<BookmarksViewModel>()
|
||||
private var adapter: BookmarksGroupAdapter? = null
|
||||
@@ -45,7 +48,7 @@ class BookmarksFragment : BaseFragment<FragmentListSimpleBinding>(), ListStateHo
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
selectionController = SectionedSelectionController(
|
||||
activity = requireActivity(),
|
||||
registryOwner = this,
|
||||
owner = this,
|
||||
callback = this,
|
||||
)
|
||||
adapter = BookmarksGroupAdapter(
|
||||
@@ -87,21 +90,24 @@ class BookmarksFragment : BaseFragment<FragmentListSimpleBinding>(), ListStateHo
|
||||
|
||||
override fun onEmptyActionClick() = Unit
|
||||
|
||||
override fun onSelectionChanged(count: Int) {
|
||||
override fun onSelectionChanged(controller: SectionedSelectionController<Manga>, count: Int) {
|
||||
binding.recyclerView.invalidateNestedItemDecorations()
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
override fun onCreateActionMode(
|
||||
controller: SectionedSelectionController<Manga>,
|
||||
mode: ActionMode,
|
||||
menu: Menu,
|
||||
): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_bookmarks, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.title = selectionController?.count?.toString()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
override fun onActionItemClicked(
|
||||
controller: SectionedSelectionController<Manga>,
|
||||
mode: ActionMode,
|
||||
item: MenuItem,
|
||||
): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_remove -> {
|
||||
val ids = selectionController?.snapshot() ?: return false
|
||||
@@ -113,9 +119,10 @@ class BookmarksFragment : BaseFragment<FragmentListSimpleBinding>(), ListStateHo
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateItemDecoration(section: Manga): AbstractSelectionItemDecoration {
|
||||
return BookmarksSelectionDecoration(requireContext())
|
||||
}
|
||||
override fun onCreateItemDecoration(
|
||||
controller: SectionedSelectionController<Manga>,
|
||||
section: Manga,
|
||||
): AbstractSelectionItemDecoration = BookmarksSelectionDecoration(requireContext())
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.root.updatePadding(
|
||||
@@ -135,7 +142,7 @@ class BookmarksFragment : BaseFragment<FragmentListSimpleBinding>(), ListStateHo
|
||||
Snackbar.make(
|
||||
binding.recyclerView,
|
||||
e.getDisplayMessage(resources),
|
||||
Snackbar.LENGTH_SHORT
|
||||
Snackbar.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
|
||||
@@ -174,4 +181,4 @@ class BookmarksFragment : BaseFragment<FragmentListSimpleBinding>(), ListStateHo
|
||||
|
||||
fun newInstance() = BookmarksFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,15 +106,32 @@ abstract class FavouritesDao {
|
||||
|
||||
/** DELETE **/
|
||||
|
||||
suspend fun delete(mangaId: Long) = setDeletedAt(mangaId, System.currentTimeMillis())
|
||||
suspend fun delete(mangaId: Long) = setDeletedAt(
|
||||
mangaId = mangaId,
|
||||
deletedAt = System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
suspend fun delete(mangaId: Long, categoryId: Long) = setDeletedAt(mangaId, categoryId, System.currentTimeMillis())
|
||||
suspend fun delete(mangaId: Long, categoryId: Long) = setDeletedAt(
|
||||
categoryId = categoryId,
|
||||
mangaId = mangaId,
|
||||
deletedAt = System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
suspend fun deleteAll(categoryId: Long) = setDeletedAtAll(categoryId, System.currentTimeMillis())
|
||||
suspend fun deleteAll(categoryId: Long) = setDeletedAtAll(
|
||||
categoryId = categoryId,
|
||||
deletedAt = System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
suspend fun recover(mangaId: Long) = setDeletedAt(mangaId, 0L)
|
||||
suspend fun recover(mangaId: Long) = setDeletedAt(
|
||||
mangaId = mangaId,
|
||||
deletedAt = 0L,
|
||||
)
|
||||
|
||||
suspend fun recover(mangaId: Long, categoryId: Long) = setDeletedAt(categoryId, mangaId, 0L)
|
||||
suspend fun recover(categoryId: Long, mangaId: Long) = setDeletedAt(
|
||||
categoryId = categoryId,
|
||||
mangaId = mangaId,
|
||||
deletedAt = 0L,
|
||||
)
|
||||
|
||||
@Query("DELETE FROM favourites WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime")
|
||||
abstract suspend fun gc(maxDeletionTime: Long)
|
||||
@@ -139,7 +156,7 @@ abstract class FavouritesDao {
|
||||
protected abstract suspend fun setDeletedAt(mangaId: Long, deletedAt: Long)
|
||||
|
||||
@Query("UPDATE favourites SET deleted_at = :deletedAt WHERE manga_id = :mangaId AND category_id = :categoryId")
|
||||
abstract suspend fun setDeletedAt(mangaId: Long, categoryId: Long, deletedAt: Long)
|
||||
abstract suspend fun setDeletedAt(categoryId: Long, mangaId: Long, deletedAt: Long)
|
||||
|
||||
@Query("UPDATE favourites SET deleted_at = :deletedAt WHERE category_id = :categoryId AND deleted_at = 0")
|
||||
protected abstract suspend fun setDeletedAtAll(categoryId: Long, deletedAt: Long)
|
||||
|
||||
@@ -11,6 +11,6 @@ val libraryModule
|
||||
|
||||
factory { LibraryRepository(get()) }
|
||||
|
||||
viewModel { LibraryViewModel(get(), get(), get(), get()) }
|
||||
viewModel { LibraryViewModel(get(), get(), get(), get(), get()) }
|
||||
viewModel { LibraryCategoriesConfigViewModel(get()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package org.koitharu.kotatsu.library.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -12,32 +13,24 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.reverseAsync
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.databinding.FragmentLibraryBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
|
||||
import org.koitharu.kotatsu.history.ui.HistoryActivity
|
||||
import org.koitharu.kotatsu.library.ui.adapter.LibraryAdapter
|
||||
import org.koitharu.kotatsu.library.ui.adapter.LibraryListEventListener
|
||||
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
|
||||
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
|
||||
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.main.ui.BottomNavOwner
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.flattenTo
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations
|
||||
|
||||
class LibraryFragment :
|
||||
BaseFragment<FragmentLibraryBinding>(),
|
||||
LibraryListEventListener,
|
||||
SectionedSelectionController.Callback<LibrarySectionModel> {
|
||||
LibraryListEventListener {
|
||||
|
||||
private val viewModel by viewModel<LibraryViewModel>()
|
||||
private var adapter: LibraryAdapter? = null
|
||||
@@ -52,8 +45,8 @@ class LibraryFragment :
|
||||
val sizeResolver = ItemSizeResolver(resources, get())
|
||||
selectionController = SectionedSelectionController(
|
||||
activity = requireActivity(),
|
||||
registryOwner = this,
|
||||
callback = this,
|
||||
owner = this,
|
||||
callback = LibrarySelectionCallback(binding.recyclerView, childFragmentManager, viewModel),
|
||||
)
|
||||
adapter = LibraryAdapter(
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
@@ -111,62 +104,6 @@ class LibraryFragment :
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_library, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.title = selectionController?.count?.toString()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
val ctx = context ?: return false
|
||||
return when (item.itemId) {
|
||||
R.id.action_share -> {
|
||||
ShareHelper(ctx).shareMangaLinks(collectSelectedItems())
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.action_favourite -> {
|
||||
FavouriteCategoriesBottomSheet.show(childFragmentManager, collectSelectedItems())
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.action_save -> {
|
||||
DownloadService.confirmAndStart(ctx, collectSelectedItems())
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSelectionChanged(count: Int) {
|
||||
binding.recyclerView.invalidateNestedItemDecorations()
|
||||
}
|
||||
|
||||
override fun onCreateItemDecoration(section: LibrarySectionModel): AbstractSelectionItemDecoration {
|
||||
return MangaSelectionDecoration(requireContext())
|
||||
}
|
||||
|
||||
private fun collectSelectedItemsMap(): Map<LibrarySectionModel, Set<Manga>> {
|
||||
val snapshot = selectionController?.snapshot()
|
||||
if (snapshot.isNullOrEmpty()) {
|
||||
return emptyMap()
|
||||
}
|
||||
return snapshot.mapValues { (_, ids) -> viewModel.getManga(ids) }
|
||||
}
|
||||
|
||||
private fun collectSelectedItems(): Set<Manga> {
|
||||
val snapshot = selectionController?.snapshot()
|
||||
if (snapshot.isNullOrEmpty()) {
|
||||
return emptySet()
|
||||
}
|
||||
return viewModel.getManga(snapshot.values.flattenTo(HashSet()))
|
||||
}
|
||||
|
||||
private fun onListChanged(list: List<ListModel>) {
|
||||
adapter?.items = list
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.koitharu.kotatsu.library.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
|
||||
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
|
||||
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.flattenTo
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations
|
||||
|
||||
class LibrarySelectionCallback(
|
||||
private val recyclerView: RecyclerView,
|
||||
private val fragmentManager: FragmentManager,
|
||||
private val viewModel: LibraryViewModel,
|
||||
) : SectionedSelectionController.Callback<LibrarySectionModel> {
|
||||
|
||||
private val context: Context
|
||||
get() = recyclerView.context
|
||||
|
||||
override fun onCreateActionMode(
|
||||
controller: SectionedSelectionController<LibrarySectionModel>,
|
||||
mode: ActionMode,
|
||||
menu: Menu,
|
||||
): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_library, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(
|
||||
controller: SectionedSelectionController<LibrarySectionModel>,
|
||||
mode: ActionMode,
|
||||
menu: Menu,
|
||||
): Boolean {
|
||||
menu.findItem(R.id.action_remove).isVisible =
|
||||
controller.peekCheckedIds().count { (_, v) -> v.isNotEmpty() } == 1
|
||||
return super.onPrepareActionMode(controller, mode, menu)
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(
|
||||
controller: SectionedSelectionController<LibrarySectionModel>,
|
||||
mode: ActionMode,
|
||||
item: MenuItem,
|
||||
): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_share -> {
|
||||
ShareHelper(context).shareMangaLinks(collectSelectedItems(controller))
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.action_favourite -> {
|
||||
FavouriteCategoriesBottomSheet.show(fragmentManager, collectSelectedItems(controller))
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.action_save -> {
|
||||
DownloadService.confirmAndStart(context, collectSelectedItems(controller))
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.action_remove -> {
|
||||
val (group, ids) = controller.snapshot().entries.singleOrNull { it.value.isNotEmpty() } ?: return false
|
||||
when (group) {
|
||||
is LibrarySectionModel.Favourites -> viewModel.removeFromFavourites(group.category, ids)
|
||||
is LibrarySectionModel.History -> viewModel.removeFromHistory(ids)
|
||||
}
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSelectionChanged(controller: SectionedSelectionController<LibrarySectionModel>, count: Int) {
|
||||
recyclerView.invalidateNestedItemDecorations()
|
||||
}
|
||||
|
||||
override fun onCreateItemDecoration(
|
||||
controller: SectionedSelectionController<LibrarySectionModel>,
|
||||
section: LibrarySectionModel,
|
||||
): AbstractSelectionItemDecoration = MangaSelectionDecoration(context)
|
||||
|
||||
private fun collectSelectedItemsMap(
|
||||
controller: SectionedSelectionController<LibrarySectionModel>,
|
||||
): Map<LibrarySectionModel, Set<Manga>> {
|
||||
val snapshot = controller.peekCheckedIds()
|
||||
if (snapshot.isEmpty()) {
|
||||
return emptyMap()
|
||||
}
|
||||
return snapshot.mapValues { (_, ids) -> viewModel.getManga(ids) }
|
||||
}
|
||||
|
||||
private fun collectSelectedItems(
|
||||
controller: SectionedSelectionController<LibrarySectionModel>,
|
||||
): Set<Manga> {
|
||||
val snapshot = controller.peekCheckedIds()
|
||||
if (snapshot.isEmpty()) {
|
||||
return emptySet()
|
||||
}
|
||||
return viewModel.getManga(snapshot.values.flattenTo(HashSet()))
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.domain.MangaWithHistory
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
@@ -32,6 +33,7 @@ private const val HISTORY_MAX_SEGMENTS = 2
|
||||
class LibraryViewModel(
|
||||
repository: LibraryRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel(), ListExtraProvider {
|
||||
@@ -59,6 +61,16 @@ class LibraryViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun removeFromFavourites(category: FavouriteCategory, ids: Set<Long>) {
|
||||
if (ids.isEmpty()) {
|
||||
return
|
||||
}
|
||||
launchJob(Dispatchers.Default) {
|
||||
val handle = favouritesRepository.removeFromCategory(category.id, ids)
|
||||
onActionDone.postCall(ReversibleAction(R.string.removed_from_favourites, handle))
|
||||
}
|
||||
}
|
||||
|
||||
fun removeFromHistory(ids: Set<Long>) {
|
||||
if (ids.isEmpty()) {
|
||||
return
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.databinding.ItemListGroupBinding
|
||||
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
|
||||
@@ -16,7 +17,7 @@ import org.koitharu.kotatsu.list.ui.ItemSizeResolver
|
||||
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.clearItemDecorations
|
||||
import org.koitharu.kotatsu.utils.ext.removeItemDecoration
|
||||
import org.koitharu.kotatsu.utils.ext.setTextAndVisible
|
||||
|
||||
fun libraryGroupAD(
|
||||
@@ -55,8 +56,7 @@ fun libraryGroupAD(
|
||||
|
||||
bind { payloads ->
|
||||
if (payloads.isEmpty()) {
|
||||
binding.recyclerView.clearItemDecorations()
|
||||
binding.recyclerView.addItemDecoration(spacingDecoration)
|
||||
binding.recyclerView.removeItemDecoration(AbstractSelectionItemDecoration::class.java)
|
||||
selectionController.attachToRecyclerView(item, binding.recyclerView)
|
||||
}
|
||||
binding.textViewTitle.text = item.getTitle(context.resources)
|
||||
@@ -66,5 +66,6 @@ fun libraryGroupAD(
|
||||
|
||||
onViewRecycled {
|
||||
adapter.items = emptyList()
|
||||
binding.recyclerView.removeItemDecoration(AbstractSelectionItemDecoration::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder
|
||||
@@ -34,6 +35,15 @@ fun RecyclerView.clearItemDecorations() {
|
||||
suppressLayout(false)
|
||||
}
|
||||
|
||||
fun RecyclerView.removeItemDecoration(cls: Class<out ItemDecoration>) {
|
||||
repeat(itemDecorationCount) { i ->
|
||||
if (cls.isInstance(getItemDecorationAt(i))) {
|
||||
removeItemDecorationAt(i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var RecyclerView.firstVisibleItemPosition: Int
|
||||
get() = (layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition()
|
||||
?: RecyclerView.NO_POSITION
|
||||
@@ -69,13 +79,15 @@ fun View.measureWidth(): Int {
|
||||
}
|
||||
|
||||
inline fun ViewPager2.doOnPageChanged(crossinline callback: (Int) -> Unit) {
|
||||
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
registerOnPageChangeCallback(
|
||||
object : ViewPager2.OnPageChangeCallback() {
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
callback(position)
|
||||
}
|
||||
})
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
callback(position)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val ViewPager2.recyclerView: RecyclerView?
|
||||
@@ -157,4 +169,4 @@ val View.parents: Sequence<ViewParent>
|
||||
yield(p)
|
||||
p = p.parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user