From 100122099da426257d92085130d1faa26d92678e Mon Sep 17 00:00:00 2001 From: Idan Atsmon Date: Thu, 28 Mar 2024 11:58:20 +0200 Subject: [PATCH] support for select and selectAll in ItemSelectionActions --- .../oneadapter/internal/InternalAdapter.kt | 25 ++++++++++--- .../selection/ItemSelectionActions.kt | 2 + .../internal/selection/OneItemDetailLookup.kt | 3 +- .../internal/selection/OneSelectionHandler.kt | 37 ++++++++++++++----- .../complete/CompleteExampleActivity.kt | 5 +++ .../features/ItemSelectionModuleActivity.kt | 4 ++ .../res/drawable/baseline_checklist_24.xml | 5 +++ sample/app/src/main/res/menu/menu_main.xml | 10 ++++- 8 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 sample/app/src/main/res/drawable/baseline_checklist_24.xml diff --git a/oneadapter/src/main/java/com/idanatz/oneadapter/internal/InternalAdapter.kt b/oneadapter/src/main/java/com/idanatz/oneadapter/internal/InternalAdapter.kt index ef7097e..b68f262 100644 --- a/oneadapter/src/main/java/com/idanatz/oneadapter/internal/InternalAdapter.kt +++ b/oneadapter/src/main/java/com/idanatz/oneadapter/internal/InternalAdapter.kt @@ -31,7 +31,7 @@ import java.lang.IllegalStateException private const val UPDATE_DATA_DELAY_MILLIS = 100L -@Suppress("UNCHECKED_CAST", "NAME_SHADOWING") +@Suppress("UNCHECKED_CAST") internal class InternalAdapter(val recyclerView: RecyclerView) : RecyclerView.Adapter>(), LoadMoreObserver, SelectionObserver, ItemSelectionActions { @@ -44,7 +44,7 @@ internal class InternalAdapter(val recyclerView: RecyclerView) : RecyclerView.Ad private val context get() = recyclerView.context - private val viewHolderCreatorsStore = ViewHolderCreatorsStore() + private val viewHolderCreatorsStore = ViewHolderCreatorsStore() private val holderPositionHandler = HolderPositionHandler() private val logger = Logger(this) @@ -137,7 +137,7 @@ internal class InternalAdapter(val recyclerView: RecyclerView) : RecyclerView.Ad override fun getItemId(position: Int): Long { val item = data[position] // javaClass is used for lettings different Diffable models share the same unique identifier - return item.javaClass.name.hashCode() + item.uniqueIdentifier + return (item.javaClass.name.hashCode() + item.uniqueIdentifier) } override fun getItemViewType(position: Int) = viewHolderCreatorsStore.getCreatorUniqueIndex(data[position].javaClass) @@ -317,7 +317,14 @@ internal class InternalAdapter(val recyclerView: RecyclerView) : RecyclerView.Ad fun enableSelection(itemSelectionModule: ItemSelectionModule) { itemSelectionModule.actions = this modules.itemSelectionModule = itemSelectionModule - oneSelectionHandler = OneSelectionHandler(itemSelectionModule, recyclerView).also { it.observer = this } + oneSelectionHandler = OneSelectionHandler( + selectionModule = itemSelectionModule, + recyclerView = recyclerView, + getItemModuleByItemId = { itemId -> + val model = data.find { getItemId(data.indexOf(it)) == itemId } ?: return@OneSelectionHandler null + modules.itemModules[model::class.java] + } + ).also { it.observer = this } } override fun onItemStateChanged(holder: OneViewHolder, position: Int, selected: Boolean) { @@ -340,7 +347,15 @@ internal class InternalAdapter(val recyclerView: RecyclerView) : RecyclerView.Ad oneSelectionHandler?.startSelection() } - override fun clearSelection(): Boolean { + override fun select(position: Int): Boolean? { + return oneSelectionHandler?.select(position) + } + + override fun selectAll(): Boolean? { + return oneSelectionHandler?.selectAll() + } + + override fun clearSelection(): Boolean { return oneSelectionHandler?.clearSelection() ?: false } diff --git a/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/ItemSelectionActions.kt b/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/ItemSelectionActions.kt index efe56d5..45ca504 100644 --- a/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/ItemSelectionActions.kt +++ b/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/ItemSelectionActions.kt @@ -5,6 +5,8 @@ import com.idanatz.oneadapter.external.interfaces.Diffable interface ItemSelectionActions { fun startSelection() + fun select(position: Int): Boolean? + fun selectAll(): Boolean? fun clearSelection(): Boolean? fun getSelectedItems(): List diff --git a/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/OneItemDetailLookup.kt b/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/OneItemDetailLookup.kt index e98f7b1..aa3da8d 100644 --- a/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/OneItemDetailLookup.kt +++ b/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/OneItemDetailLookup.kt @@ -9,8 +9,7 @@ import com.idanatz.oneadapter.internal.utils.extensions.toOneViewHolder internal class OneItemDetailLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup() { - @Nullable - override fun getItemDetails(@NotNull e: MotionEvent): ItemDetails? { + override fun getItemDetails(e: MotionEvent): ItemDetails? { return recyclerView.findChildViewUnder(e.x, e.y)?.let { val viewHolder = recyclerView.getChildViewHolder(it).toOneViewHolder() return viewHolder.createItemLookupInformation() diff --git a/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/OneSelectionHandler.kt b/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/OneSelectionHandler.kt index fa6c09a..51d1823 100644 --- a/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/OneSelectionHandler.kt +++ b/oneadapter/src/main/java/com/idanatz/oneadapter/internal/selection/OneSelectionHandler.kt @@ -3,16 +3,17 @@ package com.idanatz.oneadapter.internal.selection import androidx.recyclerview.selection.SelectionTracker import androidx.recyclerview.selection.StorageStrategy import androidx.recyclerview.widget.RecyclerView +import com.idanatz.oneadapter.external.modules.ItemModule import com.idanatz.oneadapter.external.modules.ItemSelectionModule import com.idanatz.oneadapter.external.modules.ItemSelectionModuleConfig import com.idanatz.oneadapter.external.states.SelectionStateConfig import com.idanatz.oneadapter.internal.utils.extensions.toOneViewHolder import java.util.* -@Suppress("UNCHECKED_CAST") internal class OneSelectionHandler( - selectionModule: ItemSelectionModule, - val recyclerView: RecyclerView + selectionModule: ItemSelectionModule, + val recyclerView: RecyclerView, + val getItemModuleByItemId: (Long) -> ItemModule<*>? ) : SelectionTracker.SelectionObserver() { private val ghostKey = UUID.randomUUID().mostSignificantBits @@ -29,15 +30,13 @@ internal class OneSelectionHandler( .withSelectionPredicate(object : SelectionTracker.SelectionPredicate() { override fun canSetStateForKey(key: Long, nextState: Boolean): Boolean { if (key == ghostKey) - return true // always accept let the ghost key + return true // always accept the ghost key - val forbidSelection = recyclerView.findViewHolderForItemId(key)?.toOneViewHolder()?.let { holder -> - holder.statesHooksMap?.getSelectionState()?.config?.let { selectionStateConfig -> - selectionStateConfig.selectionTrigger == SelectionStateConfig.SelectionTrigger.Manual && !isInManualSelection() - } ?: true - } ?: true + val itemModule = getItemModuleByItemId(key) ?: return false - return !forbidSelection + val isEnabled = itemModule.states.getSelectionState()?.config?.enabled ?: false + val forbidDueToManualSelection = itemModule.states.getSelectionState()?.config?.selectionTrigger == SelectionStateConfig.SelectionTrigger.Manual && !isInManualSelection() + return isEnabled && !forbidDueToManualSelection } override fun canSetStateAtPosition(position: Int, nextState: Boolean): Boolean = true @@ -56,6 +55,24 @@ internal class OneSelectionHandler( selectionTracker.select(ghostKey) } + fun select(position: Int): Boolean { + val key = itemKeyProvider.getKey(position) ?: return false + return selectionTracker.select(key) + } + + fun selectAll(): Boolean { + val itemCount = recyclerView.adapter?.itemCount ?: 0 + val toBeSelectedKeys = arrayListOf() + for (i in 0 until itemCount) { + val key = itemKeyProvider.getKey(i) + if (key != null && !selectionTracker.isSelected(key)) { + toBeSelectedKeys.add(key) + } + + } + return selectionTracker.setItemsSelected(toBeSelectedKeys, true) + } + fun clearSelection(): Boolean = selectionTracker.clearSelection() fun getSelectedPositions(): List { diff --git a/sample/app/src/main/java/com/idanatz/sample/examples/complete/CompleteExampleActivity.kt b/sample/app/src/main/java/com/idanatz/sample/examples/complete/CompleteExampleActivity.kt index 42b5ee2..e9c2788 100644 --- a/sample/app/src/main/java/com/idanatz/sample/examples/complete/CompleteExampleActivity.kt +++ b/sample/app/src/main/java/com/idanatz/sample/examples/complete/CompleteExampleActivity.kt @@ -28,6 +28,7 @@ import com.bumptech.glide.Glide import com.idanatz.oneadapter.external.event_hooks.SwipeEventHook import com.idanatz.oneadapter.external.modules.* import com.idanatz.oneadapter.external.modules.ItemSelectionModuleConfig.* +import com.idanatz.oneadapter.external.states.SelectionStateConfig import com.idanatz.sample.examples.BaseExampleActivity import com.idanatz.sample.examples.ActionsDialog.* import com.idanatz.sample.models.StoriesModel @@ -282,6 +283,10 @@ class CompleteExampleActivity : BaseExampleActivity() { oneAdapter.modules.itemSelectionModule?.actions?.startSelection() return true } + R.id.action_select_all -> { + oneAdapter.modules.itemSelectionModule?.actions?.selectAll() + return true + } else -> super.onOptionsItemSelected(item) } } diff --git a/sample/app/src/main/java/com/idanatz/sample/examples/features/ItemSelectionModuleActivity.kt b/sample/app/src/main/java/com/idanatz/sample/examples/features/ItemSelectionModuleActivity.kt index 50c883d..48d1678 100644 --- a/sample/app/src/main/java/com/idanatz/sample/examples/features/ItemSelectionModuleActivity.kt +++ b/sample/app/src/main/java/com/idanatz/sample/examples/features/ItemSelectionModuleActivity.kt @@ -119,6 +119,10 @@ class ItemSelectionModuleActivity : BaseExampleActivity() { oneAdapter.modules.itemSelectionModule?.actions?.startSelection() return true } + R.id.action_select_all -> { + oneAdapter.modules.itemSelectionModule?.actions?.selectAll() + return true + } else -> super.onOptionsItemSelected(item) } } diff --git a/sample/app/src/main/res/drawable/baseline_checklist_24.xml b/sample/app/src/main/res/drawable/baseline_checklist_24.xml new file mode 100644 index 0000000..1ab6695 --- /dev/null +++ b/sample/app/src/main/res/drawable/baseline_checklist_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/sample/app/src/main/res/menu/menu_main.xml b/sample/app/src/main/res/menu/menu_main.xml index bef1b6d..35be5c7 100644 --- a/sample/app/src/main/res/menu/menu_main.xml +++ b/sample/app/src/main/res/menu/menu_main.xml @@ -8,10 +8,18 @@ android:id="@+id/action_start_selection" android:orderInCategory="300" android:title="Start Selection" - android:visible="false" + android:visible="true" android:icon="@drawable/ic_check_box_white_24dp" app:showAsAction="ifRoom"/> + +