From 15a9c4ee45046480e2e418cdfd8d99e19b8c4d5a Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Mon, 17 Jul 2023 00:02:51 +0800 Subject: [PATCH] Move to recyclerview-selection, part 2/2 --- .../SelectableGalleryInfoRecyclerView.kt | 120 ------------- .../hippo/ehviewer/ui/scene/DownloadsScene.kt | 169 ++++++------------ app/src/main/res/layout/scene_download.xml | 2 +- 3 files changed, 59 insertions(+), 232 deletions(-) delete mode 100644 app/src/main/java/com/hippo/ehviewer/ui/legacy/SelectableGalleryInfoRecyclerView.kt diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SelectableGalleryInfoRecyclerView.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SelectableGalleryInfoRecyclerView.kt deleted file mode 100644 index 2e0965f00..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SelectableGalleryInfoRecyclerView.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.hippo.ehviewer.ui.legacy - -import android.content.Context -import android.os.Parcelable -import android.util.AttributeSet -import android.view.View -import android.widget.Checkable -import androidx.core.view.forEach -import androidx.recyclerview.widget.RecyclerView -import kotlinx.parcelize.Parcelize - -class SelectableGalleryInfoRecyclerView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0, -) : RecyclerView(context, attrs, defStyle) { - private var customChoiceListener: CustomChoiceListener? = null - private var outOfCustomChoiceModifying = false - var isInCustomChoice = false - private set - val checkedItemCount - get() = checkedGid.size - lateinit var gidGetter: (Int) -> Long? - lateinit var allGid: () -> List - private var checkedGid = mutableSetOf() - - private fun checkChild(child: View) { - val position = getChildAdapterPosition(child).takeIf { it != NO_POSITION } ?: return - val gid = gidGetter(position) ?: return - setViewChecked(child, gid in checkedGid) - } - - override fun onChildAttachedToWindow(child: View) { - super.onChildAttachedToWindow(child) - checkChild(child) - } - - val checkedItemGid: Set - get() = checkedGid - - fun intoCustomChoiceMode() { - if (!isInCustomChoice) { - isInCustomChoice = true - customChoiceListener?.onIntoCustomChoice(this) - } - } - - fun outOfCustomChoiceMode() { - if (isInCustomChoice && !outOfCustomChoiceModifying) { - outOfCustomChoiceModifying = true - clearChoices() - isInCustomChoice = false - customChoiceListener?.onOutOfCustomChoice(this) - outOfCustomChoiceModifying = false - } - } - - private fun clearChoices() { - checkedGid.clear() - updateOnScreenCheckedViews() - } - - fun checkAll() { - check(isInCustomChoice) { "Call intoCheckMode first" } - checkedGid.addAll(allGid()) - updateOnScreenCheckedViews() - } - - fun toggleItemChecked(position: Int) { - check(isInCustomChoice) { "Call intoCheckMode first" } - val gid = gidGetter(position) ?: return - if (gid in checkedGid) { - checkedGid.remove(gid) - } else { - checkedGid.add(gid) - } - if (checkedGid.isEmpty()) outOfCustomChoiceMode() - updateOnScreenCheckedViews() - } - - fun setCustomCheckedListener(listener: CustomChoiceListener?) { - customChoiceListener = listener - } - - private fun updateOnScreenCheckedViews() = forEach { checkChild(it) } - - override fun onSaveInstanceState() = SavedState( - isInCustomChoice, - checkedGid, - super.onSaveInstanceState(), - ) - - override fun onRestoreInstanceState(state: Parcelable) { - val ss = state as SavedState - super.onRestoreInstanceState(ss.superState) - isInCustomChoice = ss.customChoice - checkedGid = ss.checkedGid - updateOnScreenCheckedViews() - } - - interface CustomChoiceListener { - fun onIntoCustomChoice(view: SelectableGalleryInfoRecyclerView) - fun onOutOfCustomChoice(view: SelectableGalleryInfoRecyclerView) - } - - @Parcelize - class SavedState( - val customChoice: Boolean, - val checkedGid: MutableSet, - val superState: Parcelable?, - ) : Parcelable -} - -private fun setViewChecked(view: View, checked: Boolean) { - if (view is Checkable) { - view.isChecked = checked - } else { - view.isActivated = checked - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt index c72a88f9e..5fa53120b 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt @@ -82,12 +82,9 @@ import com.hippo.ehviewer.ui.legacy.CheckBoxDialogBuilder import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder import com.hippo.ehviewer.ui.legacy.FabLayout import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener -import com.hippo.ehviewer.ui.legacy.FabLayout.OnExpandListener import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener import com.hippo.ehviewer.ui.legacy.HandlerDrawable import com.hippo.ehviewer.ui.legacy.STRATEGY_MIN_SIZE -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.main.requestOf import com.hippo.ehviewer.ui.navToReader @@ -105,7 +102,6 @@ import eu.kanade.tachiyomi.util.system.pxToDp import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import rikka.core.res.resolveColor -import java.util.LinkedList import com.hippo.ehviewer.download.DownloadManager as downloadManager @SuppressLint("RtlHardcoded") @@ -127,6 +123,7 @@ class DownloadsScene : private val binding get() = _binding!! private var mViewTransition: ViewTransition? = null private var mAdapter: DownloadAdapter? = null + private val tracker get() = mAdapter!!.tracker!! private var mInitPosition = -1 private var mLabelAdapter: DownloadLabelAdapter? = null private lateinit var mLabels: MutableList @@ -140,7 +137,7 @@ class DownloadsScene : mLabels.add(getString(R.string.download_all)) mLabels.add(getString(R.string.default_download_label_name)) listLabel.forEach { - mLabels.add(it.label!!) + mLabels.add(it.label) } } @@ -192,23 +189,11 @@ class DownloadsScene : @SuppressLint("NotifyDataSetChanged") private fun updateForLabel() { - var list: List? - when (mLabel) { - null -> { - list = DownloadManager.allInfoList - } - - getString(R.string.default_download_label_name) -> { - list = DownloadManager.defaultInfoList - } - - else -> { - list = DownloadManager.getLabelDownloadInfoList(mLabel) - if (list == null) { - mLabel = null - list = DownloadManager.allInfoList - } - } + val list = when (mLabel) { + null -> DownloadManager.allInfoList + getString(R.string.default_download_label_name) -> DownloadManager.defaultInfoList + else -> DownloadManager.getLabelDownloadInfoList(mLabel) + ?: DownloadManager.allInfoList.also { mLabel = null } } mList = if (mType != -1) { list.filter { it.state == mType } @@ -245,6 +230,7 @@ class DownloadsScene : override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString(KEY_LABEL, mLabel) + mAdapter?.let { tracker.saveSelection(outState) } } override fun onCreateViewWithToolbar( @@ -262,6 +248,25 @@ class DownloadsScene : mAdapter = DownloadAdapter() mAdapter!!.setHasStableIds(true) recyclerView.adapter = mAdapter + mAdapter!!.tracker = GallerySelectionTracker( + "download-selection", + recyclerView, + { mList!! }, + { (this as DownloadHolder).itemId }, + ).apply { + addCustomChoiceListener({ + binding.fabLayout.isExpanded = true + // Lock drawer + setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START) + setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END) + }) { + binding.fabLayout.isExpanded = false + // Unlock drawer + setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START) + setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END) + } + restoreSelection(savedInstanceState) + } val layoutManager = AutoStaggeredGridLayoutManager(0, StaggeredGridLayoutManager.VERTICAL) layoutManager.setColumnSize( resources.getDimensionPixelOffset( @@ -277,9 +282,6 @@ class DownloadsScene : recyclerView.layoutManager = layoutManager recyclerView.clipToPadding = false recyclerView.clipChildren = false - recyclerView.gidGetter = { mList?.get(it)?.gid } - recyclerView.allGid = { mList?.map { it.gid }.orEmpty() } - recyclerView.setCustomCheckedListener(DownloadChoiceListener()) // Cancel change animation val itemAnimator = recyclerView.itemAnimator if (itemAnimator is SimpleItemAnimator) { @@ -330,8 +332,8 @@ class DownloadsScene : handlerDrawable.setColor(theme.resolveColor(com.google.android.material.R.attr.colorPrimary)) fastScroller.setHandlerDrawable(handlerDrawable) fastScroller.setOnDragHandlerListener(this@DownloadsScene) - fabLayout.addOnExpandListener(FabLayoutListener()) fabLayout.setExpanded(expanded = false, animation = false) + fabLayout.addOnExpandListener { if (!it && tracker.isInCustomChoice) tracker.clearSelection() } fabLayout.setHidePrimaryFab(true) fabLayout.setAutoCancel(false) fabLayout.setOnClickFabListener(this@DownloadsScene) @@ -368,7 +370,7 @@ class DownloadsScene : override fun onMenuItemClick(item: MenuItem): Boolean { // Skip when in choice mode val activity: Activity? = mainActivity - if (null == activity || binding.recyclerView.isInCustomChoice) { + if (null == activity || tracker.isInCustomChoice) { return false } when (item.itemId) { @@ -471,7 +473,7 @@ class DownloadsScene : NewLabelDialogHelper(builder, dialog) return@setOnMenuItemClickListener true } else if (id == R.id.action_default_download_label) { - val list = DownloadManager.labelList.mapNotNull { it.label }.toTypedArray() + val list = DownloadManager.labelList.map { it.label }.toTypedArray() val items = arrayOf( getString(R.string.let_me_select), getString(R.string.default_download_label_name), @@ -513,37 +515,24 @@ class DownloadsScene : override fun onEndDragHandler() { // Restore right drawer - if (!binding.recyclerView.isInCustomChoice) { + if (!tracker.isInCustomChoice) { setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END) } } fun onItemClick(position: Int): Boolean { val context = context ?: return false - return if (binding.recyclerView.isInCustomChoice) { - binding.recyclerView.toggleItemChecked(position) - true - } else { - val list = mList ?: return false - if (position < 0 || position >= list.size) { - return false - } - context.navToReader(list[position]) - true + val list = mList ?: return false + if (position < 0 || position >= list.size) { + return false } - } - - fun onItemLongClick(position: Int): Boolean { - if (!binding.recyclerView.isInCustomChoice) { - binding.recyclerView.intoCustomChoiceMode() - } - binding.recyclerView.toggleItemChecked(position) + context.navToReader(list[position]) return true } override fun onClickPrimaryFab(view: FabLayout, fab: FloatingActionButton) { - if (binding.recyclerView.isInCustomChoice) { - binding.recyclerView.outOfCustomChoiceMode() + if (tracker.isInCustomChoice) { + tracker.clearSelection() } } @@ -554,26 +543,13 @@ class DownloadsScene : return } if (0 == position) { - binding.recyclerView.checkAll() + tracker.selectAll() } else { - val list = mList ?: return - var gidList: LongList? = null - var downloadInfoList: MutableList? = null - val collectGid = position == 1 || position == 2 || position == 3 // Start, Stop, Delete - val collectDownloadInfo = position == 3 || position == 4 // Delete or Move - if (collectGid) { - gidList = LongList() - } - if (collectDownloadInfo) { - downloadInfoList = LinkedList() - } - binding.recyclerView.checkedItemGid.mapNotNull { gid -> list.find { it.gid == gid } }.forEach { - if (collectDownloadInfo) { - downloadInfoList!!.add(it) - } - if (collectGid) { - gidList!!.add(it.gid) - } + val downloadInfoList = tracker.getAndClearSelection() + val gidList = if (position in 1..3) { + LongList(downloadInfoList.map(DownloadInfo::gid).toMutableList()) + } else { + null } when (position) { 1 -> { @@ -582,17 +558,11 @@ class DownloadsScene : intent.action = DownloadService.ACTION_START_RANGE intent.putExtra(DownloadService.KEY_GID_LIST, gidList) ContextCompat.startForegroundService(activity, intent) - // Cancel check mode - binding.recyclerView.outOfCustomChoiceMode() } 2 -> { + // Stop lifecycleScope.launchIO { - withUIContext { - // Cancel check mode - binding.recyclerView.outOfCustomChoiceMode() - } - // Stop DownloadManager.stopRangeDownload(gidList!!) } } @@ -606,7 +576,7 @@ class DownloadsScene : Settings.removeImageFiles, ) val helper = DeleteRangeDialogHelper( - downloadInfoList!!, + downloadInfoList, gidList, builder, ) @@ -621,10 +591,10 @@ class DownloadsScene : val labelList: MutableList = ArrayList(labelRawList.size + 1) labelList.add(getString(R.string.default_download_label_name)) labelRawList.forEach { - labelList.add(it.label!!) + labelList.add(it.label) } val labels = labelList.toTypedArray() - val helper = MoveDialogHelper(labels, downloadInfoList!!) + val helper = MoveDialogHelper(labels, downloadInfoList) BaseDialogBuilder(context) .setTitle(R.string.download_move_dialog_title) .setItems(labels, helper) @@ -780,9 +750,6 @@ class DownloadsScene : return } - // Cancel check mode - binding.recyclerView.outOfCustomChoiceMode() - lifecycleScope.launchIO { // Delete image files val checked = mBuilder.isChecked @@ -810,9 +777,7 @@ class DownloadsScene : ) : DialogInterface.OnClickListener { @SuppressLint("NotifyDataSetChanged") override fun onClick(dialog: DialogInterface, which: Int) { - // Cancel check mode context ?: return - binding.recyclerView.outOfCustomChoiceMode() val label: String? = if (which == 0) { null } else { @@ -837,13 +802,16 @@ class DownloadsScene : // TODO cancel on click listener when select items binding.start.setOnClickListener(this) binding.stop.setOnClickListener(this) + binding.thumb.setMD3Content { + Spacer(modifier = Modifier.height(height).fillMaxWidth()) + } } override fun onClick(v: View) { val context = context val activity: Activity? = mainActivity val recyclerView = this@DownloadsScene.binding.recyclerView - if (null == context || null == activity || recyclerView.isInCustomChoice) { + if (null == context || null == activity || tracker.isInCustomChoice) { return } val list = mList ?: return @@ -880,10 +848,8 @@ class DownloadsScene : private val height = (3 * listThumbSize * 3).pxToDp.dp - fun bind(info: DownloadInfo) { - binding.thumb.setMD3Content { - Spacer(modifier = Modifier.height(height).fillMaxWidth()) - } + fun bind(info: DownloadInfo, isChecked: Boolean) { + binding.root.isChecked = isChecked lifecycleScope.launchIO { val downloadDir = getGalleryDownloadDir(info.gid) ?: run { info.putToDownloadDir() @@ -999,6 +965,7 @@ class DownloadsScene : private inner class DownloadAdapter : RecyclerView.Adapter() { private val mInflater: LayoutInflater = layoutInflater + var tracker: GallerySelectionTracker? = null override fun getItemId(position: Int): Long { return if (mList == null || position < 0 || position >= mList!!.size) { @@ -1011,12 +978,14 @@ class DownloadsScene : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadHolder { val holder = DownloadHolder(ItemDownloadBinding.inflate(mInflater, parent, false)) holder.itemView.setOnClickListener { onItemClick(holder.bindingAdapterPosition) } - holder.itemView.setOnLongClickListener { onItemLongClick(holder.bindingAdapterPosition) } return holder } override fun onBindViewHolder(holder: DownloadHolder, position: Int) { - mList?.let { holder.bind(it[position]) } + mList?.let { + val info = it[position] + holder.bind(info, tracker?.isSelected(info.gid) ?: false) + } } override fun onBindViewHolder( @@ -1040,28 +1009,6 @@ class DownloadsScene : } } - private inner class FabLayoutListener : OnExpandListener { - override fun onExpand(expanded: Boolean) { - if (!expanded && binding.recyclerView.isInCustomChoice) binding.recyclerView.outOfCustomChoiceMode() - } - } - - private inner class DownloadChoiceListener : CustomChoiceListener { - override fun onIntoCustomChoice(view: SelectableGalleryInfoRecyclerView) { - binding.fabLayout.isExpanded = true - // Lock drawer - setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START) - setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END) - } - - override fun onOutOfCustomChoice(view: SelectableGalleryInfoRecyclerView) { - binding.fabLayout.isExpanded = false - // Unlock drawer - setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START) - setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END) - } - } - private inner class RenameLabelDialogHelper( private val mBuilder: EditTextDialogBuilder, private val mDialog: AlertDialog, diff --git a/app/src/main/res/layout/scene_download.xml b/app/src/main/res/layout/scene_download.xml index cef8655c1..d04502c67 100644 --- a/app/src/main/res/layout/scene_download.xml +++ b/app/src/main/res/layout/scene_download.xml @@ -23,7 +23,7 @@ android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> -