Context menus

This commit is contained in:
Koitharu
2024-09-24 16:35:44 +03:00
parent 5a2a31d1c8
commit 063527b240
35 changed files with 320 additions and 190 deletions

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.bookmarks.ui
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -129,7 +130,11 @@ class AllBookmarksFragment :
} }
override fun onItemLongClick(item: Bookmark, view: View): Boolean { override fun onItemLongClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemLongClick(item.pageId) ?: false return selectionController?.onItemLongClick(view, item.pageId) ?: false
}
override fun onItemContextClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.pageId) ?: false
} }
override fun onRetryClick(error: Throwable) = Unit override fun onRetryClick(error: Throwable) = Unit
@@ -148,23 +153,23 @@ class AllBookmarksFragment :
override fun onCreateActionMode( override fun onCreateActionMode(
controller: ListSelectionController, controller: ListSelectionController,
mode: ActionMode, menuInflater: MenuInflater,
menu: Menu, menu: Menu,
): Boolean { ): Boolean {
mode.menuInflater.inflate(R.menu.mode_bookmarks, menu) menuInflater.inflate(R.menu.mode_bookmarks, menu)
return true return true
} }
override fun onActionItemClicked( override fun onActionItemClicked(
controller: ListSelectionController, controller: ListSelectionController,
mode: ActionMode, mode: ActionMode?,
item: MenuItem, item: MenuItem,
): Boolean { ): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_remove -> { R.id.action_remove -> {
val ids = selectionController?.snapshot() ?: return false val ids = selectionController?.snapshot() ?: return false
viewModel.removeBookmarks(ids) viewModel.removeBookmarks(ids)
mode.finish() mode?.finish()
true true
} }

View File

@@ -22,10 +22,7 @@ fun bookmarkLargeAD(
) = adapterDelegateViewBinding<Bookmark, ListModel, ItemBookmarkLargeBinding>( ) = adapterDelegateViewBinding<Bookmark, ListModel, ItemBookmarkLargeBinding>(
{ inflater, parent -> ItemBookmarkLargeBinding.inflate(inflater, parent, false) }, { inflater, parent -> ItemBookmarkLargeBinding.inflate(inflater, parent, false) },
) { ) {
val listener = AdapterDelegateClickListenerAdapter(this, clickListener) AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
binding.root.setOnClickListener(listener)
binding.root.setOnLongClickListener(listener)
bind { bind {
binding.imageViewThumb.newImageRequest(lifecycleOwner, item.imageLoadData)?.run { binding.imageViewThumb.newImageRequest(lifecycleOwner, item.imageLoadData)?.run {

View File

@@ -21,10 +21,7 @@ fun bookmarkListAD(
) = adapterDelegateViewBinding<Bookmark, Bookmark, ItemBookmarkBinding>( ) = adapterDelegateViewBinding<Bookmark, Bookmark, ItemBookmarkBinding>(
{ inflater, parent -> ItemBookmarkBinding.inflate(inflater, parent, false) }, { inflater, parent -> ItemBookmarkBinding.inflate(inflater, parent, false) },
) { ) {
val listener = AdapterDelegateClickListenerAdapter(this, clickListener) AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
binding.root.setOnClickListener(listener)
binding.root.setOnLongClickListener(listener)
bind { bind {
binding.imageViewThumb.newImageRequest(lifecycleOwner, item.imageLoadData)?.run { binding.imageViewThumb.newImageRequest(lifecycleOwner, item.imageLoadData)?.run {

View File

@@ -0,0 +1,8 @@
package org.koitharu.kotatsu.core.ui
import android.view.View
fun interface OnContextClickListenerCompat {
fun onContextClick(v: View): Boolean
}

View File

@@ -3,18 +3,46 @@ package org.koitharu.kotatsu.core.ui.list
import android.view.View import android.view.View
import android.view.View.OnClickListener import android.view.View.OnClickListener
import android.view.View.OnLongClickListener import android.view.View.OnLongClickListener
import androidx.core.util.Function
import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
class AdapterDelegateClickListenerAdapter<I>( class AdapterDelegateClickListenerAdapter<I, O>(
private val adapterDelegate: AdapterDelegateViewBindingViewHolder<out I, *>, private val adapterDelegate: AdapterDelegateViewBindingViewHolder<out I, *>,
private val clickListener: OnListItemClickListener<I>, private val clickListener: OnListItemClickListener<O>,
) : OnClickListener, OnLongClickListener { private val itemMapper: Function<I, O>,
) : OnClickListener, OnLongClickListener, OnContextClickListenerCompat {
override fun onClick(v: View) { override fun onClick(v: View) {
clickListener.onItemClick(adapterDelegate.item, v) clickListener.onItemClick(mappedItem(), v)
} }
override fun onLongClick(v: View): Boolean { override fun onLongClick(v: View): Boolean {
return clickListener.onItemLongClick(adapterDelegate.item, v) return clickListener.onItemLongClick(mappedItem(), v)
}
override fun onContextClick(v: View): Boolean {
return clickListener.onItemContextClick(mappedItem(), v)
}
private fun mappedItem(): O = itemMapper.apply(adapterDelegate.item)
fun attach(itemView: View) {
itemView.setOnClickListener(this)
itemView.setOnLongClickListener(this)
itemView.setOnContextClickListenerCompat(this)
}
companion object {
operator fun <T> invoke(
adapterDelegate: AdapterDelegateViewBindingViewHolder<out T, *>,
clickListener: OnListItemClickListener<T>
): AdapterDelegateClickListenerAdapter<T, T> = AdapterDelegateClickListenerAdapter(
adapterDelegate = adapterDelegate,
clickListener = clickListener,
itemMapper = { x -> x },
)
} }
} }

View File

@@ -2,10 +2,14 @@ package org.koitharu.kotatsu.core.ui.list
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu
import androidx.collection.LongSet import androidx.collection.LongSet
import androidx.collection.longSetOf
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@@ -29,18 +33,21 @@ class ListSelectionController(
) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider { ) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider {
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private var focusedItemId: LongSet? = null
var useActionMode: Boolean = true
val count: Int val count: Int
get() = decoration.checkedItemsCount get() = if (focusedItemId != null) 1 else decoration.checkedItemsCount
init { init {
registryOwner.lifecycle.addObserver(StateEventObserver()) registryOwner.lifecycle.addObserver(StateEventObserver())
} }
fun snapshot(): Set<Long> = peekCheckedIds().toSet() fun snapshot(): Set<Long> = (focusedItemId ?: peekCheckedIds()).toSet()
fun peekCheckedIds(): LongSet { fun peekCheckedIds(): LongSet {
return decoration.checkedItemsIds return focusedItemId ?: decoration.checkedItemsIds
} }
fun clear() { fun clear() {
@@ -52,6 +59,7 @@ class ListSelectionController(
if (ids.isEmpty()) { if (ids.isEmpty()) {
return return
} }
startActionMode()
decoration.checkAll(ids) decoration.checkAll(ids)
notifySelectionChanged() notifySelectionChanged()
} }
@@ -80,15 +88,42 @@ class ListSelectionController(
return false return false
} }
fun onItemLongClick(id: Long): Boolean { fun onItemLongClick(view: View, id: Long): Boolean {
return startActionMode()?.also { return if (useActionMode) {
decoration.setItemIsChecked(id, true) startSelection(id)
notifySelectionChanged() } else {
} != null onItemContextClick(view, id)
}
} }
fun onItemContextClick(view: View, id: Long): Boolean {
focusedItemId = longSetOf(id)
val menu = PopupMenu(view.context, view)
callback.onCreateActionMode(this, menu.menuInflater, menu.menu)
callback.onPrepareActionMode(this, null, menu.menu)
menu.setForceShowIcon(true)
if (menu.menu.hasVisibleItems()) {
menu.setOnMenuItemClickListener { menuItem ->
callback.onActionItemClicked(this, null, menuItem)
}
menu.setOnDismissListener {
focusedItemId = null
}
menu.show()
return true
} else {
focusedItemId = null
return false
}
}
fun startSelection(id: Long): Boolean = startActionMode()?.also {
decoration.setItemIsChecked(id, true)
notifySelectionChanged()
} != null
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onCreateActionMode(this, mode, menu) return callback.onCreateActionMode(this, mode.menuInflater, menu)
} }
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
@@ -106,6 +141,7 @@ class ListSelectionController(
} }
private fun startActionMode(): ActionMode? { private fun startActionMode(): ActionMode? {
focusedItemId = null
return actionMode ?: appCompatDelegate.startSupportActionMode(this).also { return actionMode ?: appCompatDelegate.startSupportActionMode(this).also {
actionMode = it actionMode = it
} }
@@ -134,14 +170,14 @@ class ListSelectionController(
fun onSelectionChanged(controller: ListSelectionController, count: Int) fun onSelectionChanged(controller: ListSelectionController, count: Int)
fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean fun onCreateActionMode(controller: ListSelectionController, menuInflater: MenuInflater, 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() mode?.title = controller.count.toString()
return true return true
} }
fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean
fun onDestroyActionMode(controller: ListSelectionController, mode: ActionMode) = Unit fun onDestroyActionMode(controller: ListSelectionController, mode: ActionMode) = Unit
} }

View File

@@ -6,5 +6,7 @@ fun interface OnListItemClickListener<I> {
fun onItemClick(item: I, view: View) fun onItemClick(item: I, view: View)
fun onItemLongClick(item: I, view: View) = false fun onItemLongClick(item: I, view: View): Boolean = false
fun onItemContextClick(item: I, view: View): Boolean = onItemLongClick(item, view)
} }

View File

@@ -4,11 +4,15 @@ import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
class PopupMenuMediator( class PopupMenuMediator(
private val provider: MenuProvider, private val provider: MenuProvider,
) : View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { ) : View.OnLongClickListener, OnContextClickListenerCompat, PopupMenu.OnMenuItemClickListener,
PopupMenu.OnDismissListener {
override fun onContextClick(v: View): Boolean = onLongClick(v)
override fun onLongClick(v: View): Boolean { override fun onLongClick(v: View): Boolean {
val menu = PopupMenu(v.context, v) val menu = PopupMenu(v.context, v)

View File

@@ -21,6 +21,7 @@ import com.google.android.material.progressindicator.BaseProgressIndicator
import com.google.android.material.slider.RangeSlider import com.google.android.material.slider.RangeSlider
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
import kotlin.math.roundToInt import kotlin.math.roundToInt
fun View.hasGlobalPoint(x: Int, y: Int): Boolean { fun View.hasGlobalPoint(x: Int, y: Int): Boolean {
@@ -153,9 +154,9 @@ fun BaseProgressIndicator<*>.showOrHide(value: Boolean) {
} }
} }
fun View.setOnContextClickListenerCompat(listener: View.OnLongClickListener) { fun View.setOnContextClickListenerCompat(listener: OnContextClickListenerCompat) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setOnContextClickListener(listener::onLongClick) setOnContextClickListener(listener::onContextClick)
} }
} }

View File

@@ -54,6 +54,7 @@ import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
import org.koitharu.kotatsu.core.ui.image.ChipIconTarget import org.koitharu.kotatsu.core.ui.image.ChipIconTarget
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
@@ -114,7 +115,8 @@ class DetailsActivity :
BaseActivity<ActivityDetailsBinding>(), BaseActivity<ActivityDetailsBinding>(),
View.OnClickListener, View.OnClickListener,
View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, View.OnLayoutChangeListener, View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, View.OnLayoutChangeListener,
ViewTreeObserver.OnDrawListener, ChipsView.OnChipClickListener, OnListItemClickListener<Bookmark> { ViewTreeObserver.OnDrawListener, ChipsView.OnChipClickListener, OnListItemClickListener<Bookmark>,
OnContextClickListenerCompat {
@Inject @Inject
lateinit var shortcutManager: AppShortcutManager lateinit var shortcutManager: AppShortcutManager
@@ -291,6 +293,8 @@ class DetailsActivity :
startActivity(MangaListActivity.newIntent(this, tag.source, MangaListFilter(tags = setOf(tag)))) startActivity(MangaListActivity.newIntent(this, tag.source, MangaListFilter(tags = setOf(tag))))
} }
override fun onContextClick(v: View): Boolean = onLongClick(v)
override fun onLongClick(v: View): Boolean = when (v.id) { override fun onLongClick(v: View): Boolean = when (v.id) {
R.id.button_read -> { R.id.button_read -> {
val menu = PopupMenu(v.context, v) val menu = PopupMenu(v.context, v)

View File

@@ -18,9 +18,7 @@ fun chapterGridItemAD(
on = { item, _, _ -> item is ChapterListItem && item.isGrid }, on = { item, _, _ -> item is ChapterListItem && item.isGrid },
) { ) {
val eventListener = AdapterDelegateClickListenerAdapter(this, clickListener) AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
bind { payloads -> bind { payloads ->
if (payloads.isEmpty()) { if (payloads.isEmpty()) {

View File

@@ -22,9 +22,7 @@ fun chapterListItemAD(
on = { item, _, _ -> item is ChapterListItem && !item.isGrid }, on = { item, _, _ -> item is ChapterListItem && !item.isGrid },
) { ) {
val eventListener = AdapterDelegateClickListenerAdapter(this, clickListener) AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
bind { bind {
binding.textViewTitle.text = item.chapter.name binding.textViewTitle.text = item.chapter.name

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.details.ui.pager.bookmarks
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -133,7 +134,11 @@ class BookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(),
} }
override fun onItemLongClick(item: Bookmark, view: View): Boolean { override fun onItemLongClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemLongClick(item.pageId) ?: false return selectionController?.onItemLongClick(view, item.pageId) ?: false
}
override fun onItemContextClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.pageId) ?: false
} }
override fun onSelectionChanged(controller: ListSelectionController, count: Int) { override fun onSelectionChanged(controller: ListSelectionController, count: Int) {
@@ -142,23 +147,23 @@ class BookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(),
override fun onCreateActionMode( override fun onCreateActionMode(
controller: ListSelectionController, controller: ListSelectionController,
mode: ActionMode, menuInflater: MenuInflater,
menu: Menu, menu: Menu,
): Boolean { ): Boolean {
mode.menuInflater.inflate(R.menu.mode_bookmarks, menu) menuInflater.inflate(R.menu.mode_bookmarks, menu)
return true return true
} }
override fun onActionItemClicked( override fun onActionItemClicked(
controller: ListSelectionController, controller: ListSelectionController,
mode: ActionMode, mode: ActionMode?,
item: MenuItem, item: MenuItem,
): Boolean { ): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_remove -> { R.id.action_remove -> {
val ids = selectionController?.snapshot() ?: return false val ids = selectionController?.snapshot() ?: return false
viewModel.removeBookmarks(ids) viewModel.removeBookmarks(ids)
mode.finish() mode?.finish()
true true
} }

View File

@@ -116,7 +116,11 @@ class ChaptersFragment :
} }
override fun onItemLongClick(item: ChapterListItem, view: View): Boolean { override fun onItemLongClick(item: ChapterListItem, view: View): Boolean {
return selectionController?.onItemLongClick(item.chapter.id) ?: false return selectionController?.onItemLongClick(view, item.chapter.id) ?: false
}
override fun onItemContextClick(item: ChapterListItem, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.chapter.id) ?: false
} }
override fun onWindowInsetsChanged(insets: Insets) = Unit override fun onWindowInsetsChanged(insets: Insets) = Unit
@@ -149,7 +153,7 @@ class ChaptersFragment :
items?.indexOfFirst(predicate) ?: -1 items?.indexOfFirst(predicate) ?: -1
} }
if (position >= 0) { if (position >= 0) {
selectionController?.onItemLongClick(chapterId) selectionController?.startSelection(chapterId)
val lm = (viewBinding?.recyclerViewChapters?.layoutManager as? LinearLayoutManager) val lm = (viewBinding?.recyclerViewChapters?.layoutManager as? LinearLayoutManager)
if (lm != null) { if (lm != null) {
val offset = resources.getDimensionPixelOffset(R.dimen.chapter_list_item_height) val offset = resources.getDimensionPixelOffset(R.dimen.chapter_list_item_height)

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.details.ui.pager.chapters package org.koitharu.kotatsu.details.ui.pager.chapters
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@@ -19,12 +20,16 @@ class ChaptersSelectionCallback(
recyclerView: RecyclerView, recyclerView: RecyclerView,
) : BaseListSelectionCallback(recyclerView) { ) : BaseListSelectionCallback(recyclerView) {
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_chapters, menu) controller: ListSelectionController,
menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_chapters, menu)
return true return true
} }
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {
val selectedIds = controller.peekCheckedIds() val selectedIds = controller.peekCheckedIds()
val allItems = viewModel.chapters.value val allItems = viewModel.chapters.value
val items = allItems.withIndex().filter { it.value.chapter.id in selectedIds } val items = allItems.withIndex().filter { it.value.chapter.id in selectedIds }
@@ -38,7 +43,7 @@ class ChaptersSelectionCallback(
menu.findItem(R.id.action_delete).isVisible = canDelete menu.findItem(R.id.action_delete).isVisible = canDelete
menu.findItem(R.id.action_select_all).isVisible = items.size < allItems.size menu.findItem(R.id.action_select_all).isVisible = items.size < allItems.size
menu.findItem(R.id.action_mark_current).isVisible = items.size == 1 menu.findItem(R.id.action_mark_current).isVisible = items.size == 1
mode.title = items.size.toString() mode?.title = items.size.toString()
var hasGap = false var hasGap = false
for (i in 0 until items.size - 1) { for (i in 0 until items.size - 1) {
if (items[i].index + 1 != items[i + 1].index) { if (items[i].index + 1 != items[i + 1].index) {
@@ -50,11 +55,11 @@ class ChaptersSelectionCallback(
return true return true
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_save -> { R.id.action_save -> {
viewModel.download(controller.snapshot()) viewModel.download(controller.snapshot())
mode.finish() mode?.finish()
true true
} }
@@ -73,7 +78,7 @@ class ChaptersSelectionCallback(
).show() ).show()
} }
} }
mode.finish() mode?.finish()
true true
} }
@@ -112,7 +117,7 @@ class ChaptersSelectionCallback(
} else { } else {
return false return false
} }
mode.finish() mode?.finish()
true true
} }

View File

@@ -32,9 +32,7 @@ fun pageThumbnailAD(
height = (gridWidth / 13f * 18f).toInt(), height = (gridWidth / 13f * 18f).toInt(),
) )
val clickListenerAdapter = AdapterDelegateClickListenerAdapter(this, clickListener) AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
binding.root.setOnClickListener(clickListenerAdapter)
binding.root.setOnLongClickListener(clickListenerAdapter)
bind { bind {
val data: Any = item.page.preview?.takeUnless { it.isEmpty() } ?: item.page.toMangaPage() val data: Any = item.page.preview?.takeUnless { it.isEmpty() } ?: item.page.toMangaPage()

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.details.ui.related package org.koitharu.kotatsu.details.ui.related
import android.view.Menu import android.view.Menu
import androidx.appcompat.view.ActionMode import android.view.MenuInflater
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@@ -16,9 +16,13 @@ class RelatedListFragment : MangaListFragment() {
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_remote, menu) controller: ListSelectionController,
return super.onCreateActionMode(controller, mode, menu) menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_remote, menu)
return super.onCreateActionMode(controller, menuInflater, menu)
} }
} }

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.download.ui.list
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
@@ -87,7 +88,11 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
} }
override fun onItemLongClick(item: DownloadItemModel, view: View): Boolean { override fun onItemLongClick(item: DownloadItemModel, view: View): Boolean {
return selectionController.onItemLongClick(item.id.mostSignificantBits) return selectionController.onItemLongClick(view, item.id.mostSignificantBits)
}
override fun onItemContextClick(item: DownloadItemModel, view: View): Boolean {
return selectionController.onItemContextClick(view, item.id.mostSignificantBits)
} }
override fun onExpandClick(item: DownloadItemModel) { override fun onExpandClick(item: DownloadItemModel) {
@@ -120,34 +125,38 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
viewBinding.recyclerView.invalidateItemDecorations() viewBinding.recyclerView.invalidateItemDecorations()
} }
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_downloads, menu) controller: ListSelectionController,
menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_downloads, menu)
return true return true
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_resume -> { R.id.action_resume -> {
viewModel.resume(controller.snapshot()) viewModel.resume(controller.snapshot())
mode.finish() mode?.finish()
true true
} }
R.id.action_pause -> { R.id.action_pause -> {
viewModel.pause(controller.snapshot()) viewModel.pause(controller.snapshot())
mode.finish() mode?.finish()
true true
} }
R.id.action_cancel -> { R.id.action_cancel -> {
viewModel.cancel(controller.snapshot()) viewModel.cancel(controller.snapshot())
mode.finish() mode?.finish()
true true
} }
R.id.action_remove -> { R.id.action_remove -> {
viewModel.remove(controller.snapshot()) viewModel.remove(controller.snapshot())
mode.finish() mode?.finish()
true true
} }
@@ -160,7 +169,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
} }
} }
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {
val snapshot = viewModel.snapshot(controller.peekCheckedIds()) val snapshot = viewModel.snapshot(controller.peekCheckedIds())
var canPause = true var canPause = true
var canResume = true var canResume = true

View File

@@ -7,6 +7,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -147,7 +148,11 @@ class ExploreFragment :
} }
override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean { override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean {
return sourceSelectionController?.onItemLongClick(item.id) ?: false return sourceSelectionController?.onItemLongClick(view, item.id) ?: false
}
override fun onItemContextClick(item: MangaSourceItem, view: View): Boolean {
return sourceSelectionController?.onItemContextClick(view, item.id) ?: false
} }
override fun onRetryClick(error: Throwable) = Unit override fun onRetryClick(error: Throwable) = Unit
@@ -160,12 +165,16 @@ class ExploreFragment :
viewBinding?.recyclerView?.invalidateItemDecorations() viewBinding?.recyclerView?.invalidateItemDecorations()
} }
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_source, menu) controller: ListSelectionController,
menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_source, menu)
return true return true
} }
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {
val selectedSources = viewModel.sourcesSnapshot(controller.peekCheckedIds()) val selectedSources = viewModel.sourcesSnapshot(controller.peekCheckedIds())
val isSingleSelection = selectedSources.size == 1 val isSingleSelection = selectedSources.size == 1
menu.findItem(R.id.action_settings).isVisible = isSingleSelection menu.findItem(R.id.action_settings).isVisible = isSingleSelection
@@ -177,7 +186,7 @@ class ExploreFragment :
return super.onPrepareActionMode(controller, mode, menu) return super.onPrepareActionMode(controller, mode, menu)
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
val selectedSources = viewModel.sourcesSnapshot(controller.peekCheckedIds()) val selectedSources = viewModel.sourcesSnapshot(controller.peekCheckedIds())
if (selectedSources.isEmpty()) { if (selectedSources.isEmpty()) {
return false return false
@@ -186,35 +195,35 @@ class ExploreFragment :
R.id.action_settings -> { R.id.action_settings -> {
val source = selectedSources.singleOrNull() ?: return false val source = selectedSources.singleOrNull() ?: return false
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), source)) startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), source))
mode.finish() mode?.finish()
} }
R.id.action_disable -> { R.id.action_disable -> {
viewModel.disableSources(selectedSources) viewModel.disableSources(selectedSources)
mode.finish() mode?.finish()
} }
R.id.action_delete -> { R.id.action_delete -> {
selectedSources.forEach { selectedSources.forEach {
(it.mangaSource as? ExternalMangaSource)?.let { uninstallExternalSource(it) } (it.mangaSource as? ExternalMangaSource)?.let { uninstallExternalSource(it) }
} }
mode.finish() mode?.finish()
} }
R.id.action_shortcut -> { R.id.action_shortcut -> {
val source = selectedSources.singleOrNull() ?: return false val source = selectedSources.singleOrNull() ?: return false
viewModel.requestPinShortcut(source) viewModel.requestPinShortcut(source)
mode.finish() mode?.finish()
} }
R.id.action_pin -> { R.id.action_pin -> {
viewModel.setSourcesPinned(selectedSources, isPinned = true) viewModel.setSourcesPinned(selectedSources, isPinned = true)
mode.finish() mode?.finish()
} }
R.id.action_unpin -> { R.id.action_unpin -> {
viewModel.setSourcesPinned(selectedSources, isPinned = false) viewModel.setSourcesPinned(selectedSources, isPinned = false)
mode.finish() mode?.finish()
} }
else -> return false else -> return false

View File

@@ -20,7 +20,6 @@ import org.koitharu.kotatsu.core.util.ext.drawableStart
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.recyclerView import org.koitharu.kotatsu.core.util.ext.recyclerView
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.setProgressIcon import org.koitharu.kotatsu.core.util.ext.setProgressIcon
import org.koitharu.kotatsu.core.util.ext.source import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.core.util.ext.textAndVisible
@@ -117,13 +116,9 @@ fun exploreSourceListItemAD(
on = { item, _, _ -> item is MangaSourceItem && !item.isGrid }, on = { item, _, _ -> item is MangaSourceItem && !item.isGrid },
) { ) {
val eventListener = AdapterDelegateClickListenerAdapter(this, listener) AdapterDelegateClickListenerAdapter(this, listener).attach(itemView)
val iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small) val iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small)
binding.root.setOnClickListener(eventListener)
binding.root.setOnLongClickListener(eventListener)
binding.root.setOnContextClickListenerCompat(eventListener)
bind { bind {
binding.textViewTitle.text = item.source.getTitle(context) binding.textViewTitle.text = item.source.getTitle(context)
binding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null binding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null
@@ -154,13 +149,9 @@ fun exploreSourceGridItemAD(
on = { item, _, _ -> item is MangaSourceItem && item.isGrid }, on = { item, _, _ -> item is MangaSourceItem && item.isGrid },
) { ) {
val eventListener = AdapterDelegateClickListenerAdapter(this, listener) AdapterDelegateClickListenerAdapter(this, listener).attach(itemView)
val iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small) val iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small)
binding.root.setOnClickListener(eventListener)
binding.root.setOnLongClickListener(eventListener)
binding.root.setOnContextClickListenerCompat(eventListener)
bind { bind {
binding.textViewTitle.text = item.source.getTitle(context) binding.textViewTitle.text = item.source.getTitle(context)
binding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null binding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.favourites.ui.categories package org.koitharu.kotatsu.favourites.ui.categories
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@@ -17,12 +18,16 @@ class CategoriesSelectionCallback(
recyclerView.invalidateItemDecorations() recyclerView.invalidateItemDecorations()
} }
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_category, menu) controller: ListSelectionController,
menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_category, menu)
return true return true
} }
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {
val categories = viewModel.getCategories(controller.peekCheckedIds()) val categories = viewModel.getCategories(controller.peekCheckedIds())
var canShow = categories.isNotEmpty() var canShow = categories.isNotEmpty()
var canHide = canShow var canHide = canShow
@@ -35,11 +40,11 @@ class CategoriesSelectionCallback(
} }
menu.findItem(R.id.action_show)?.isVisible = canShow menu.findItem(R.id.action_show)?.isVisible = canShow
menu.findItem(R.id.action_hide)?.isVisible = canHide menu.findItem(R.id.action_hide)?.isVisible = canHide
mode.title = controller.count.toString() mode?.title = controller.count.toString()
return true return true
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
/*R.id.action_view -> { /*R.id.action_view -> {
val id = controller.peekCheckedIds().singleOrNull() ?: return false val id = controller.peekCheckedIds().singleOrNull() ?: return false
@@ -53,13 +58,13 @@ class CategoriesSelectionCallback(
R.id.action_show -> { R.id.action_show -> {
viewModel.setIsVisible(controller.snapshot(), true) viewModel.setIsVisible(controller.snapshot(), true)
mode.finish() mode?.finish()
true true
} }
R.id.action_hide -> { R.id.action_hide -> {
viewModel.setIsVisible(controller.snapshot(), false) viewModel.setIsVisible(controller.snapshot(), false)
mode.finish() mode?.finish()
true true
} }
@@ -72,7 +77,7 @@ class CategoriesSelectionCallback(
} }
} }
private fun confirmDeleteCategories(ids: Set<Long>, mode: ActionMode) { private fun confirmDeleteCategories(ids: Set<Long>, mode: ActionMode?) {
buildAlertDialog(recyclerView.context, isCentered = true) { buildAlertDialog(recyclerView.context, isCentered = true) {
setMessage(R.string.categories_delete_confirm) setMessage(R.string.categories_delete_confirm)
setTitle(R.string.remove_category) setTitle(R.string.remove_category)
@@ -80,7 +85,7 @@ class CategoriesSelectionCallback(
setNegativeButton(android.R.string.cancel, null) setNegativeButton(android.R.string.cancel, null)
setPositiveButton(R.string.remove) { _, _ -> setPositiveButton(R.string.remove) { _, _ ->
viewModel.deleteCategories(ids) viewModel.deleteCategories(ids)
mode.finish() mode?.finish()
} }
}.show() }.show()
} }

View File

@@ -98,7 +98,11 @@ class FavouriteCategoriesActivity :
} }
override fun onItemLongClick(item: FavouriteCategory?, view: View): Boolean { override fun onItemLongClick(item: FavouriteCategory?, view: View): Boolean {
return item != null && selectionController.onItemLongClick(item.id) return item != null && selectionController.onItemLongClick(view, item.id)
}
override fun onItemContextClick(item: FavouriteCategory?, view: View): Boolean {
return item != null && selectionController.onItemContextClick(view, item.id)
} }
override fun onSupportActionModeStarted(mode: ActionMode) { override fun onSupportActionModeStarted(mode: ActionMode) {

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.favourites.ui.list
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
@@ -52,27 +53,32 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
return true return true
} }
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_favourites, menu) controller: ListSelectionController,
return super.onCreateActionMode(controller, mode, menu) menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_favourites, menu)
return super.onCreateActionMode(controller, menuInflater, menu)
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_remove -> { R.id.action_remove -> {
viewModel.removeFromFavourites(selectedItemsIds) viewModel.removeFromFavourites(selectedItemsIds)
mode.finish() mode?.finish()
true true
} }
R.id.action_mark_current -> { R.id.action_mark_current -> {
val itemsSnapshot = selectedItems
MaterialAlertDialogBuilder(context ?: return false) MaterialAlertDialogBuilder(context ?: return false)
.setTitle(item.title) .setTitle(item.title)
.setMessage(R.string.mark_as_completed_prompt) .setMessage(R.string.mark_as_completed_prompt)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
viewModel.markAsRead(selectedItems) viewModel.markAsRead(itemsSnapshot)
mode.finish() mode?.finish()
}.show() }.show()
true true
} }

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.history.ui
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
@@ -34,28 +35,33 @@ class HistoryListFragment : MangaListFragment() {
override fun onEmptyActionClick() = viewModel.clearFilter() override fun onEmptyActionClick() = viewModel.clearFilter()
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_history, menu) controller: ListSelectionController,
return super.onCreateActionMode(controller, mode, menu) menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_history, menu)
return super.onCreateActionMode(controller, menuInflater, menu)
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_remove -> { R.id.action_remove -> {
viewModel.removeFromHistory(selectedItemsIds) viewModel.removeFromHistory(selectedItemsIds)
mode.finish() mode?.finish()
true true
} }
R.id.action_mark_current -> { R.id.action_mark_current -> {
val itemsSnapshot = selectedItems
buildAlertDialog(context ?: return false, isCentered = true) { buildAlertDialog(context ?: return false, isCentered = true) {
setTitle(item.title) setTitle(item.title)
setIcon(item.icon) setIcon(item.icon)
setMessage(R.string.mark_as_completed_prompt) setMessage(R.string.mark_as_completed_prompt)
setNegativeButton(android.R.string.cancel, null) setNegativeButton(android.R.string.cancel, null)
setPositiveButton(android.R.string.ok) { _, _ -> setPositiveButton(android.R.string.ok) { _, _ ->
viewModel.markAsRead(selectedItems) viewModel.markAsRead(itemsSnapshot)
mode.finish() mode?.finish()
} }
}.show() }.show()
true true

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.list.ui
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -11,7 +12,6 @@ import androidx.annotation.CallSuper
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.collection.ArraySet import androidx.collection.ArraySet
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isNotEmpty
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@@ -153,7 +153,11 @@ abstract class MangaListFragment :
} }
override fun onItemLongClick(item: Manga, view: View): Boolean { override fun onItemLongClick(item: Manga, view: View): Boolean {
return selectionController?.onItemLongClick(item.id) ?: false return selectionController?.onItemLongClick(view, item.id) ?: false
}
override fun onItemContextClick(item: Manga, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.id) ?: false
} }
override fun onReadClick(manga: Manga, view: View) { override fun onReadClick(manga: Manga, view: View) {
@@ -280,18 +284,22 @@ abstract class MangaListFragment :
} }
@CallSuper @CallSuper
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {
val hasNoLocal = selectedItems.none { it.isLocal } val hasNoLocal = selectedItems.none { it.isLocal }
menu.findItem(R.id.action_save)?.isVisible = hasNoLocal menu.findItem(R.id.action_save)?.isVisible = hasNoLocal
menu.findItem(R.id.action_fix)?.isVisible = hasNoLocal menu.findItem(R.id.action_fix)?.isVisible = hasNoLocal
return super.onPrepareActionMode(controller, mode, menu) return super.onPrepareActionMode(controller, mode, menu)
} }
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
return menu.isNotEmpty() controller: ListSelectionController,
menuInflater: MenuInflater,
menu: Menu
): Boolean {
return menu.hasVisibleItems()
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_select_all -> { R.id.action_select_all -> {
val ids = listAdapter?.items?.mapNotNull { val ids = listAdapter?.items?.mapNotNull {
@@ -303,31 +311,32 @@ abstract class MangaListFragment :
R.id.action_share -> { R.id.action_share -> {
ShareHelper(requireContext()).shareMangaLinks(selectedItems) ShareHelper(requireContext()).shareMangaLinks(selectedItems)
mode.finish() mode?.finish()
true true
} }
R.id.action_favourite -> { R.id.action_favourite -> {
FavoriteSheet.show(getChildFragmentManager(), selectedItems) FavoriteSheet.show(getChildFragmentManager(), selectedItems)
mode.finish() mode?.finish()
true true
} }
R.id.action_save -> { R.id.action_save -> {
viewModel.download(selectedItems) viewModel.download(selectedItems)
mode.finish() mode?.finish()
true true
} }
R.id.action_fix -> { R.id.action_fix -> {
val itemsSnapshot = selectedItemsIds
buildAlertDialog(context ?: return false, isCentered = true) { buildAlertDialog(context ?: return false, isCentered = true) {
setTitle(item.title) setTitle(item.title)
setIcon(item.icon) setIcon(item.icon)
setMessage(R.string.manga_fix_prompt) setMessage(R.string.manga_fix_prompt)
setNegativeButton(android.R.string.cancel, null) setNegativeButton(android.R.string.cancel, null)
setPositiveButton(R.string.fix) { _, _ -> setPositiveButton(R.string.fix) { _, _ ->
AutoFixService.start(context, selectedItemsIds) AutoFixService.start(context, itemsSnapshot)
mode.finish() mode?.finish()
} }
}.show() }.show()
true true

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.list.ui.adapter package org.koitharu.kotatsu.list.ui.adapter
import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
@@ -8,11 +7,11 @@ import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.source import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED
@@ -31,13 +30,7 @@ fun mangaGridItemAD(
) { ) {
var badge: BadgeDrawable? = null var badge: BadgeDrawable? = null
val eventListener = object : View.OnClickListener, View.OnLongClickListener { AdapterDelegateClickListenerAdapter(this, clickListener, MangaGridModel::manga).attach(itemView)
override fun onClick(v: View) = clickListener.onItemClick(item.manga, v)
override fun onLongClick(v: View): Boolean = clickListener.onItemLongClick(item.manga, v)
}
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
itemView.setOnContextClickListenerCompat(eventListener)
sizeResolver.attachToView(lifecycleOwner, itemView, binding.textViewTitle, binding.progressView) sizeResolver.attachToView(lifecycleOwner, itemView, binding.textViewTitle, binding.progressView)
bind { payloads -> bind { payloads ->

View File

@@ -1,16 +1,15 @@
package org.koitharu.kotatsu.list.ui.adapter package org.koitharu.kotatsu.list.ui.adapter
import android.view.View
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import com.google.android.material.badge.BadgeDrawable import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.source import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
@@ -27,14 +26,7 @@ fun mangaListDetailedItemAD(
) { ) {
var badge: BadgeDrawable? = null var badge: BadgeDrawable? = null
val listenerAdapter = object : View.OnClickListener, View.OnLongClickListener { AdapterDelegateClickListenerAdapter(this, clickListener, MangaDetailedListModel::manga).attach(itemView)
override fun onClick(v: View) = clickListener.onItemClick(item.manga, v)
override fun onLongClick(v: View): Boolean = clickListener.onItemLongClick(item.manga, v)
}
itemView.setOnClickListener(listenerAdapter)
itemView.setOnLongClickListener(listenerAdapter)
itemView.setOnContextClickListenerCompat(listenerAdapter)
bind { payloads -> bind { payloads ->
binding.textViewTitle.text = item.title binding.textViewTitle.text = item.title

View File

@@ -1,16 +1,15 @@
package org.koitharu.kotatsu.list.ui.adapter package org.koitharu.kotatsu.list.ui.adapter
import android.view.View
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import com.google.android.material.badge.BadgeDrawable import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.source import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemMangaListBinding import org.koitharu.kotatsu.databinding.ItemMangaListBinding
@@ -27,13 +26,7 @@ fun mangaListItemAD(
) { ) {
var badge: BadgeDrawable? = null var badge: BadgeDrawable? = null
val eventListener = object : View.OnClickListener, View.OnLongClickListener { AdapterDelegateClickListenerAdapter(this, clickListener, MangaCompactListModel::manga).attach(itemView)
override fun onClick(v: View) = clickListener.onItemClick(item.manga, v)
override fun onLongClick(v: View): Boolean = clickListener.onItemLongClick(item.manga, v)
}
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
itemView.setOnContextClickListenerCompat(eventListener)
bind { bind {
binding.textViewTitle.text = item.title binding.textViewTitle.text = item.title

View File

@@ -4,6 +4,7 @@ import android.Manifest
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -88,16 +89,16 @@ class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner {
override fun onCreateActionMode( override fun onCreateActionMode(
controller: ListSelectionController, controller: ListSelectionController,
mode: ActionMode, menuInflater: MenuInflater,
menu: Menu, menu: Menu,
): Boolean { ): Boolean {
mode.menuInflater.inflate(R.menu.mode_local, menu) menuInflater.inflate(R.menu.mode_local, menu)
return super.onCreateActionMode(controller, mode, menu) return super.onCreateActionMode(controller, menuInflater, menu)
} }
override fun onActionItemClicked( override fun onActionItemClicked(
controller: ListSelectionController, controller: ListSelectionController,
mode: ActionMode, mode: ActionMode?,
item: MenuItem, item: MenuItem,
): Boolean { ): Boolean {
return when (item.itemId) { return when (item.itemId) {
@@ -109,7 +110,7 @@ class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner {
R.id.action_share -> { R.id.action_share -> {
val files = selectedItems.map { it.url.toUri().toFile() } val files = selectedItems.map { it.url.toUri().toFile() }
ShareHelper(requireContext()).shareCbz(files) ShareHelper(requireContext()).shareCbz(files)
mode.finish() mode?.finish()
true true
} }
@@ -117,13 +118,13 @@ class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner {
} }
} }
private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) { private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode?) {
MaterialAlertDialogBuilder(context ?: return) MaterialAlertDialogBuilder(context ?: return)
.setTitle(R.string.delete_manga) .setTitle(R.string.delete_manga)
.setMessage(getString(R.string.text_delete_local_manga_batch)) .setMessage(getString(R.string.text_delete_local_manga_batch))
.setPositiveButton(R.string.delete) { _, _ -> .setPositiveButton(R.string.delete) { _, _ ->
viewModel.delete(ids) viewModel.delete(ids)
mode.finish() mode?.finish()
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.show() .show()

View File

@@ -5,7 +5,6 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.view.ActionMode
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@@ -56,9 +55,13 @@ class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
viewModel.loadNextPage() viewModel.loadNextPage()
} }
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_remote, menu) controller: ListSelectionController,
return super.onCreateActionMode(controller, mode, menu) menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_remote, menu)
return super.onCreateActionMode(controller, menuInflater, menu)
} }
override fun onFilterClick(view: View?) { override fun onFilterClick(view: View?) {

View File

@@ -20,8 +20,7 @@ fun scrobblingMangaAD(
{ layoutInflater, parent -> ItemScrobblingMangaBinding.inflate(layoutInflater, parent, false) }, { layoutInflater, parent -> ItemScrobblingMangaBinding.inflate(layoutInflater, parent, false) },
) { ) {
val clickListenerAdapter = AdapterDelegateClickListenerAdapter(this, clickListener) AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
itemView.setOnClickListener(clickListenerAdapter)
bind { bind {
binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run { binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run {

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
@@ -118,7 +119,11 @@ class SearchActivity :
} }
override fun onItemLongClick(item: Manga, view: View): Boolean { override fun onItemLongClick(item: Manga, view: View): Boolean {
return selectionController.onItemLongClick(item.id) return selectionController.onItemLongClick(view, item.id)
}
override fun onItemContextClick(item: Manga, view: View): Boolean {
return selectionController.onItemContextClick(view, item.id)
} }
override fun onReadClick(manga: Manga, view: View) { override fun onReadClick(manga: Manga, view: View) {
@@ -155,28 +160,32 @@ class SearchActivity :
viewBinding.recyclerView.invalidateNestedItemDecorations() viewBinding.recyclerView.invalidateNestedItemDecorations()
} }
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_remote, menu) controller: ListSelectionController,
menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_remote, menu)
return true return true
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_share -> { R.id.action_share -> {
ShareHelper(this).shareMangaLinks(collectSelectedItems()) ShareHelper(this).shareMangaLinks(collectSelectedItems())
mode.finish() mode?.finish()
true true
} }
R.id.action_favourite -> { R.id.action_favourite -> {
FavoriteSheet.show(supportFragmentManager, collectSelectedItems()) FavoriteSheet.show(supportFragmentManager, collectSelectedItems())
mode.finish() mode?.finish()
true true
} }
R.id.action_save -> { R.id.action_save -> {
viewModel.download(collectSelectedItems()) viewModel.download(collectSelectedItems())
mode.finish() mode?.finish()
true true
} }

View File

@@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.view.ActionMode
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@@ -29,11 +28,11 @@ class SuggestionsFragment : MangaListFragment() {
override fun onCreateActionMode( override fun onCreateActionMode(
controller: ListSelectionController, controller: ListSelectionController,
mode: ActionMode, menuInflater: MenuInflater,
menu: Menu, menu: Menu,
): Boolean { ): Boolean {
mode.menuInflater.inflate(R.menu.mode_remote, menu) menuInflater.inflate(R.menu.mode_remote, menu)
return super.onCreateActionMode(controller, mode, menu) return super.onCreateActionMode(controller, menuInflater, menu)
} }
private inner class SuggestionMenuProvider : MenuProvider { private inner class SuggestionMenuProvider : MenuProvider {
@@ -71,10 +70,11 @@ class SuggestionsFragment : MangaListFragment() {
companion object { companion object {
@Deprecated( @Deprecated(
"", ReplaceWith( "",
ReplaceWith(
"SuggestionsFragment()", "SuggestionsFragment()",
"org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment" "org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment",
) ),
) )
fun newInstance() = SuggestionsFragment() fun newInstance() = SuggestionsFragment()
} }

View File

@@ -62,6 +62,8 @@ class FeedFragment :
onItemClick(item.manga, v) onItemClick(item.manga, v)
} }
with(binding.recyclerView) { with(binding.recyclerView) {
val paddingVertical = resources.getDimensionPixelSize(R.dimen.list_spacing_normal)
setPadding(0, paddingVertical, 0, paddingVertical)
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
adapter = feedAdapter adapter = feedAdapter
setHasFixedSize(true) setHasFixedSize(true)

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.tracker.ui.updates package org.koitharu.kotatsu.tracker.ui.updates
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
@@ -17,16 +18,20 @@ class UpdatesFragment : MangaListFragment() {
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(
mode.menuInflater.inflate(R.menu.mode_updates, menu) controller: ListSelectionController,
return super.onCreateActionMode(controller, mode, menu) menuInflater: MenuInflater,
menu: Menu
): Boolean {
menuInflater.inflate(R.menu.mode_updates, menu)
return super.onCreateActionMode(controller, menuInflater, menu)
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_remove -> { R.id.action_remove -> {
viewModel.remove(controller.snapshot()) viewModel.remove(controller.snapshot())
mode.finish() mode?.finish()
true true
} }