ActionMode selection in manga lists

This commit is contained in:
Koitharu
2022-04-06 17:21:09 +03:00
parent f2175b40c0
commit 76a9a0d1ab
52 changed files with 900 additions and 335 deletions

View File

@@ -9,6 +9,9 @@ import android.view.MenuItem
import android.view.View
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.view.ActionMode
import androidx.core.net.toFile
import androidx.core.net.toUri
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import org.koin.androidx.viewmodel.ext.android.viewModel
@@ -16,8 +19,7 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.progress.Progress
class LocalListFragment : MangaListFragment(), ActivityResultCallback<List<@JvmSuppressWildcards Uri>> {
@@ -46,7 +48,7 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback<List<@JvmS
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.onMangaRemoved.observe(viewLifecycleOwner, ::onItemRemoved)
viewModel.onMangaRemoved.observe(viewLifecycleOwner) { onItemRemoved() }
viewModel.importProgress.observe(viewLifecycleOwner, ::onImportProgressChanged)
}
@@ -97,35 +99,41 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback<List<@JvmS
viewModel.importFiles(result)
}
override fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) {
super.onCreatePopupMenu(inflater, menu, data)
inflater.inflate(R.menu.popup_local, menu)
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.mode_local, menu)
return super.onCreateActionMode(mode, menu)
}
override fun onPopupMenuItemSelected(item: MenuItem, data: Manga): Boolean {
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_delete -> {
MaterialAlertDialogBuilder(context ?: return false)
.setTitle(R.string.delete_manga)
.setMessage(getString(R.string.text_delete_local_manga, data.title))
.setPositiveButton(R.string.delete) { _, _ ->
viewModel.delete(data)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
R.id.action_remove -> {
showDeletionConfirm(selectedItemsIds, mode)
true
}
else -> super.onPopupMenuItemSelected(item, data)
R.id.action_share -> {
val files = selectedItems.map { it.url.toUri().toFile() }
ShareHelper(requireContext()).shareCbz(files)
mode.finish()
true
}
else -> super.onActionItemClicked(mode, item)
}
}
private fun onItemRemoved(item: Manga) {
Snackbar.make(
binding.recyclerView, getString(
R.string._s_deleted_from_local_storage,
item.title.ellipsize(16)
), Snackbar.LENGTH_SHORT
).show()
private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) {
MaterialAlertDialogBuilder(context ?: return)
.setTitle(R.string.delete_manga)
.setMessage(getString(R.string.text_delete_local_manga_batch))
.setPositiveButton(R.string.delete) { _, _ ->
viewModel.delete(ids)
mode.finish()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun onItemRemoved() {
Snackbar.make(binding.recyclerView, R.string.removal_completed, Snackbar.LENGTH_SHORT).show()
}
private fun onImportProgressChanged(progress: Progress?) {

View File

@@ -3,10 +3,12 @@ package org.koitharu.kotatsu.local.ui
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.ShortcutsRepository
@@ -19,7 +21,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.progress.Progress
import java.io.IOException
class LocalListViewModel(
private val repository: LocalMangaRepository,
@@ -28,7 +29,7 @@ class LocalListViewModel(
private val shortcutsRepository: ShortcutsRepository,
) : MangaListViewModel(settings) {
val onMangaRemoved = SingleLiveEvent<Manga>()
val onMangaRemoved = SingleLiveEvent<Unit>()
val importProgress = MutableLiveData<Progress?>(null)
private val listError = MutableStateFlow<Throwable?>(null)
private val mangaList = MutableStateFlow<List<Manga>?>(null)
@@ -87,18 +88,23 @@ class LocalListViewModel(
}
}
fun delete(manga: Manga) {
launchJob {
fun delete(ids: Set<Long>) {
launchLoadingJob {
withContext(Dispatchers.Default) {
val original = repository.getRemoteManga(manga)
repository.delete(manga) || throw IOException("Unable to delete file")
runCatching {
historyRepository.deleteOrSwap(manga, original)
val itemsToRemove = checkNotNull(mangaList.value).filter { it.id in ids }
for (manga in itemsToRemove) {
val original = repository.getRemoteManga(manga)
repository.delete(manga) || throw IOException("Unable to delete file")
runCatching {
historyRepository.deleteOrSwap(manga, original)
}
mangaList.update { list ->
list?.filterNot { it.id == manga.id }
}
}
mangaList.value = mangaList.value?.filterNot { it.id == manga.id }
}
shortcutsRepository.updateShortcuts()
onMangaRemoved.call(manga)
onMangaRemoved.call(Unit)
}
}