Move to recyclerview-selection, part 1/2
This commit is contained in:
1 parent
bf3ee4cce2
commit
38c90f35f2
7 files changed
+146
-66
No files matched your search
@@ -75,8 +75,6 @@ import com.hippo.ehviewer.ui.legacy.FabLayout
|
||||
import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener
|
||||
import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener
|
||||
import com.hippo.ehviewer.ui.legacy.HandlerDrawable
|
||||
import com.hippo.ehviewer.ui.legacy.SelectableGalleryInfoRecyclerView
|
||||
import com.hippo.ehviewer.ui.legacy.SelectableGalleryInfoRecyclerView.CustomChoiceListener
|
||||
import com.hippo.ehviewer.ui.legacy.ViewTransition
|
||||
import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper
|
||||
import com.hippo.ehviewer.ui.setMD3Content
|
||||
@@ -153,6 +151,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
private var _binding: SceneFavoritesBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private var mAdapter: GalleryAdapter? = null
|
||||
private val tracker get() = mAdapter!!.tracker!!
|
||||
private val showNormalFabsRunnable = Runnable {
|
||||
updateJumpFab() // index: 0, 2
|
||||
binding.fabLayout.run {
|
||||
@@ -164,7 +163,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
private fun onItemClick(position: Int) {
|
||||
if (isDrawerOpen(GravityCompat.END)) {
|
||||
// Skip if in search mode
|
||||
if (!binding.recyclerView.isInCustomChoice) {
|
||||
if (!tracker.isInCustomChoice) {
|
||||
switchFav(position - 2)
|
||||
updateJumpFab()
|
||||
closeDrawer(GravityCompat.END)
|
||||
@@ -202,6 +201,11 @@ class FavoritesScene : SearchBarScene() {
|
||||
Settings.recentFavCat = urlBuilder.favCat
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
mAdapter?.let { tracker.saveSelection(outState) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
mAdapter?.type = Settings.listMode
|
||||
@@ -214,14 +218,13 @@ class FavoritesScene : SearchBarScene() {
|
||||
): View {
|
||||
_binding = SceneFavoritesBinding.inflate(inflater, container!!)
|
||||
setOnApplySearch {
|
||||
if (!binding.recyclerView.isInCustomChoice) {
|
||||
if (!tracker.isInCustomChoice) {
|
||||
switchFav(urlBuilder.favCat, it)
|
||||
}
|
||||
}
|
||||
binding.fastScroller.attachToRecyclerView(binding.recyclerView)
|
||||
binding.fastScroller.setHandlerDrawable(HandlerDrawable().apply { setColor(inflater.context.theme.resolveColor(androidx.appcompat.R.attr.colorPrimary)) })
|
||||
binding.fabLayout.run {
|
||||
addOnExpandListener { if (!it && binding.recyclerView.isInCustomChoice) binding.recyclerView.outOfCustomChoiceMode() }
|
||||
ViewCompat.setWindowInsetsAnimationCallback(binding.root, WindowInsetsAnimationHelper(WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP, this))
|
||||
updateJumpFab()
|
||||
val colorID = theme.resolveColor(com.google.android.material.R.attr.colorOnSurface)
|
||||
@@ -232,15 +235,15 @@ class FavoritesScene : SearchBarScene() {
|
||||
setHidePrimaryFab(false)
|
||||
setOnClickFabListener(object : OnClickFabListener {
|
||||
override fun onClickPrimaryFab(view: FabLayout, fab: FloatingActionButton) {
|
||||
if (binding.recyclerView.isInCustomChoice) {
|
||||
binding.recyclerView.outOfCustomChoiceMode()
|
||||
if (tracker.isInCustomChoice) {
|
||||
tracker.clearSelection()
|
||||
} else {
|
||||
binding.fabLayout.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClickSecondaryFab(view: FabLayout, fab: FloatingActionButton, position: Int) {
|
||||
if (!binding.recyclerView.isInCustomChoice) {
|
||||
if (!tracker.isInCustomChoice) {
|
||||
when (position) {
|
||||
0 -> showGoToDialog()
|
||||
1 -> switchFav(urlBuilder.favCat)
|
||||
@@ -254,7 +257,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
when (position) {
|
||||
// Check all
|
||||
3 -> binding.recyclerView.checkAll()
|
||||
3 -> tracker.selectAll()
|
||||
// Download
|
||||
4 -> CommonOperations.startDownload(mainActivity!!, takeCheckedInfo(), false)
|
||||
// Delete
|
||||
@@ -283,6 +286,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
addDelete.setDelete(ANIMATE_TIME)
|
||||
} else {
|
||||
addDelete.setAdd(ANIMATE_TIME)
|
||||
if (tracker.isInCustomChoice) tracker.clearSelection()
|
||||
}
|
||||
}
|
||||
addAboveSnackView(this)
|
||||
@@ -293,7 +297,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
|
||||
override fun onEndDragHandler() {
|
||||
if (!binding.recyclerView.isInCustomChoice) {
|
||||
if (!tracker.isInCustomChoice) {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
|
||||
}
|
||||
showSearchBar()
|
||||
@@ -303,25 +307,16 @@ class FavoritesScene : SearchBarScene() {
|
||||
mAdapter = GalleryAdapter(
|
||||
this@run,
|
||||
false,
|
||||
{ info, pos ->
|
||||
if (binding.recyclerView.isInCustomChoice) {
|
||||
binding.recyclerView.toggleItemChecked(pos)
|
||||
} else {
|
||||
navAnimated(
|
||||
R.id.galleryDetailScene,
|
||||
bundleOf(
|
||||
GalleryDetailScene.KEY_ACTION to GalleryDetailScene.ACTION_GALLERY_INFO,
|
||||
GalleryDetailScene.KEY_GALLERY_INFO to info,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
{ _, pos ->
|
||||
if (!binding.recyclerView.isInCustomChoice) {
|
||||
binding.recyclerView.intoCustomChoiceMode()
|
||||
}
|
||||
binding.recyclerView.toggleItemChecked(pos)
|
||||
{ info ->
|
||||
navAnimated(
|
||||
R.id.galleryDetailScene,
|
||||
bundleOf(
|
||||
GalleryDetailScene.KEY_ACTION to GalleryDetailScene.ACTION_GALLERY_INFO,
|
||||
GalleryDetailScene.KEY_GALLERY_INFO to info,
|
||||
),
|
||||
)
|
||||
},
|
||||
{},
|
||||
).also { adapter ->
|
||||
val drawable = ContextCompat.getDrawable(context, R.drawable.big_sad_pandroid)!!
|
||||
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
@@ -353,32 +348,34 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
}
|
||||
}
|
||||
gidGetter = { adapter.peek(it)?.gid }
|
||||
allGid = { adapter.snapshot().items.map { it.gid } }
|
||||
adapter.tracker = GallerySelectionTracker(
|
||||
"favorite-selection",
|
||||
this,
|
||||
{ adapter.snapshot().items },
|
||||
{ (this as GalleryHolder).galleryId },
|
||||
).apply {
|
||||
addCustomChoiceListener({
|
||||
showSelectionFab()
|
||||
binding.fabLayout.setAutoCancel(false)
|
||||
// Delay expanding action to make layout work fine
|
||||
SimpleHandler.post { binding.fabLayout.isExpanded = true }
|
||||
binding.refreshLayout.isEnabled = false
|
||||
// Lock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END)
|
||||
}) {
|
||||
showNormalFab()
|
||||
binding.fabLayout.setAutoCancel(true)
|
||||
binding.fabLayout.isExpanded = false
|
||||
binding.refreshLayout.isEnabled = true
|
||||
// Unlock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
|
||||
}
|
||||
restoreSelection(savedInstanceState)
|
||||
}
|
||||
}
|
||||
switchFav(Settings.recentFavCat)
|
||||
setCustomCheckedListener(object : CustomChoiceListener {
|
||||
override fun onIntoCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||
showSelectionFab()
|
||||
binding.fabLayout.setAutoCancel(false)
|
||||
// Delay expanding action to make layout work fine
|
||||
SimpleHandler.post { binding.fabLayout.isExpanded = true }
|
||||
binding.refreshLayout.isEnabled = false
|
||||
// Lock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END)
|
||||
}
|
||||
|
||||
override fun onOutOfCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||
showNormalFab()
|
||||
binding.fabLayout.setAutoCancel(true)
|
||||
binding.fabLayout.isExpanded = false
|
||||
binding.refreshLayout.isEnabled = true
|
||||
// Unlock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
|
||||
}
|
||||
})
|
||||
}
|
||||
setAllowEmptySearch(false)
|
||||
return binding.root
|
||||
@@ -463,12 +460,9 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun takeCheckedInfo() = run {
|
||||
val snapshot = mAdapter?.snapshot()?.items
|
||||
binding.recyclerView.checkedItemGid.mapNotNull { gid -> snapshot?.find { it.gid == gid } }
|
||||
}.also { binding.recyclerView.outOfCustomChoiceMode() }
|
||||
private fun takeCheckedInfo() = tracker.getAndClearSelection()
|
||||
|
||||
private fun checkedSize() = binding.recyclerView.checkedItemCount
|
||||
private fun checkedSize() = tracker.selectionSize
|
||||
|
||||
private fun showNormalFab() {
|
||||
// Delay showing normal fab to avoid mutation
|
||||
|
||||
@@ -44,8 +44,8 @@ private val diffCallback = object : DiffUtil.ItemCallback<GalleryInfo>() {
|
||||
class GalleryAdapter(
|
||||
private val recyclerView: RecyclerView,
|
||||
private val showFavorite: Boolean,
|
||||
private val onItemClick: (GalleryInfo, Int) -> Unit,
|
||||
private val onItemLongClick: (GalleryInfo, Int) -> Unit,
|
||||
private val onItemClick: (GalleryInfo) -> Unit,
|
||||
private val onItemLongClick: (GalleryInfo) -> Unit,
|
||||
) : PagingDataAdapter<GalleryInfo, GalleryHolder>(diffCallback) {
|
||||
private val resources = recyclerView.context.resources
|
||||
private val layoutManager: AutoStaggeredGridLayoutManager = AutoStaggeredGridLayoutManager(0, StaggeredGridLayoutManager.VERTICAL)
|
||||
@@ -53,6 +53,7 @@ class GalleryAdapter(
|
||||
private var mListDecoration: ItemDecoration? = null
|
||||
private var mGirdDecoration: MarginItemDecoration? = null
|
||||
private var mType = TYPE_INVALID
|
||||
var tracker: GallerySelectionTracker<GalleryInfo>? = null
|
||||
|
||||
var type: Int
|
||||
get() = mType
|
||||
@@ -161,10 +162,12 @@ class GalleryAdapter(
|
||||
|
||||
override fun onBindViewHolder(holder: GalleryHolder, position: Int) {
|
||||
val gi = getItem(position) ?: return
|
||||
holder.galleryId = gi.gid
|
||||
holder.bind(
|
||||
gi,
|
||||
{ onItemClick(gi, position) },
|
||||
{ onItemLongClick(gi, position) },
|
||||
tracker?.isSelected(gi.gid) ?: false,
|
||||
{ onItemClick(gi) },
|
||||
{ onItemLongClick(gi) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,11 @@ import com.hippo.ehviewer.ui.main.GalleryInfoListItem
|
||||
import eu.kanade.tachiyomi.util.system.pxToDp
|
||||
|
||||
abstract class GalleryHolder(composeView: CheckableComposeView) : RecyclerView.ViewHolder(composeView) {
|
||||
var galleryId = 0L
|
||||
|
||||
abstract fun bind(
|
||||
galleryInfo: GalleryInfo,
|
||||
isChecked: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
)
|
||||
@@ -53,7 +56,8 @@ class ListGalleryHolder(private val composeView: CheckableComposeView, private v
|
||||
private val height = (3 * listThumbSize * 3).pxToDp.dp
|
||||
private val showPages = Settings.showGalleryPages
|
||||
|
||||
override fun bind(galleryInfo: GalleryInfo, onClick: () -> Unit, onLongClick: () -> Unit) {
|
||||
override fun bind(galleryInfo: GalleryInfo, isChecked: Boolean, onClick: () -> Unit, onLongClick: () -> Unit) {
|
||||
composeView.isChecked = isChecked
|
||||
composeView.setMD3Content {
|
||||
GalleryInfoListItem(
|
||||
onClick = onClick,
|
||||
@@ -68,7 +72,8 @@ class ListGalleryHolder(private val composeView: CheckableComposeView, private v
|
||||
}
|
||||
|
||||
class GridGalleryHolder(private val composeView: CheckableComposeView) : GalleryHolder(composeView) {
|
||||
override fun bind(galleryInfo: GalleryInfo, onClick: () -> Unit, onLongClick: () -> Unit) {
|
||||
override fun bind(galleryInfo: GalleryInfo, isChecked: Boolean, onClick: () -> Unit, onLongClick: () -> Unit) {
|
||||
composeView.isChecked = isChecked
|
||||
composeView.setMD3Content {
|
||||
GalleryInfoGridItem(
|
||||
onClick = onClick,
|
||||
|
||||
@@ -369,7 +369,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
mAdapter = GalleryAdapter(
|
||||
binding.recyclerView,
|
||||
true,
|
||||
{ info, _ ->
|
||||
{ info ->
|
||||
navAnimated(
|
||||
R.id.galleryDetailScene,
|
||||
bundleOf(
|
||||
@@ -378,7 +378,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
),
|
||||
)
|
||||
},
|
||||
{ info, _ ->
|
||||
{ info ->
|
||||
lifecycleScope.launchIO {
|
||||
dialogState.doGalleryInfoAction(info, requireContext())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.MotionEvent
|
||||
import androidx.recyclerview.selection.ItemDetailsLookup
|
||||
import androidx.recyclerview.selection.ItemKeyProvider
|
||||
import androidx.recyclerview.selection.SelectionTracker
|
||||
import androidx.recyclerview.selection.StorageStrategy
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
|
||||
class GallerySelectionTracker<T : GalleryInfo>(
|
||||
selectionId: String,
|
||||
recyclerView: RecyclerView,
|
||||
private val getItems: () -> List<T>,
|
||||
getItemKey: RecyclerView.ViewHolder.() -> Long,
|
||||
builder: SelectionTracker.Builder<Long>.() -> Unit = {},
|
||||
) {
|
||||
private val tracker: SelectionTracker<Long>
|
||||
val selectionSize get() = tracker.selection.size()
|
||||
var isInCustomChoice = false
|
||||
private set
|
||||
|
||||
init {
|
||||
tracker = SelectionTracker.Builder(
|
||||
selectionId,
|
||||
recyclerView,
|
||||
object : ItemKeyProvider<Long>(SCOPE_MAPPED) {
|
||||
override fun getKey(position: Int) = getItems()[position].gid
|
||||
override fun getPosition(key: Long) = getItems().indexOfFirst { it.gid == key }
|
||||
},
|
||||
object : ItemDetailsLookup<Long>() {
|
||||
override fun getItemDetails(e: MotionEvent) =
|
||||
recyclerView.findChildViewUnder(e.x, e.y)?.let {
|
||||
recyclerView.getChildViewHolder(it).run {
|
||||
object : ItemDetails<Long>() {
|
||||
override fun getPosition() = bindingAdapterPosition
|
||||
override fun getSelectionKey() = getItemKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
StorageStrategy.createLongStorage(),
|
||||
).apply(builder).build()
|
||||
}
|
||||
|
||||
fun addCustomChoiceListener(onIntoCustomChoiceListener: () -> Unit, onOutOfCustomChoiceListener: () -> Unit) {
|
||||
tracker.addObserver(object : SelectionTracker.SelectionObserver<Long>() {
|
||||
override fun onSelectionChanged() {
|
||||
if (tracker.selection.isEmpty) {
|
||||
if (isInCustomChoice) {
|
||||
isInCustomChoice = false
|
||||
onOutOfCustomChoiceListener()
|
||||
}
|
||||
} else if (!isInCustomChoice) {
|
||||
isInCustomChoice = true
|
||||
onIntoCustomChoiceListener()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSelectionRestored() {
|
||||
if (!tracker.selection.isEmpty) {
|
||||
isInCustomChoice = true
|
||||
onIntoCustomChoiceListener()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun isSelected(key: Long) = tracker.isSelected(key)
|
||||
fun selectAll() = tracker.setItemsSelected(getItems().map(GalleryInfo::gid), true)
|
||||
fun clearSelection() = tracker.clearSelection()
|
||||
fun saveSelection(state: Bundle) = tracker.onSaveInstanceState(state)
|
||||
fun restoreSelection(state: Bundle?) = tracker.onRestoreInstanceState(state)
|
||||
fun getAndClearSelection() = getItems().run { tracker.selection.map { gid -> first { it.gid == gid } } }
|
||||
.also { clearSelection() }
|
||||
}
|
||||
Reference in new issue
Block a user