Skip to content

Commit

Permalink
Added: selection trigger Manual
Browse files Browse the repository at this point in the history
Fixed: selection metadata not updated correctly
  • Loading branch information
idanatz committed Oct 20, 2020
1 parent b56ead1 commit e1e22a3
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 34 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

Version 2.0.1
-------------
* Added: new selection trigger - Manual
* Fixed: selection metadata not updated correctly

Version 2.0.0
-------------
* Breaking Changes: Kotlin is now the first priority of this library and as such comes a full API change, every Module, Hook and State is now created using dedicated DSLs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@file:Suppress("ClassName")

package com.idanatz.oneadapter.tests.modules.selection.metadata

import android.graphics.Color
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.idanatz.oneadapter.external.modules.ItemModule
import com.idanatz.oneadapter.external.modules.ItemSelectionModule
import com.idanatz.oneadapter.external.states.SelectionState
import com.idanatz.oneadapter.helpers.BaseTest
import com.idanatz.oneadapter.helpers.getViewLocationOnScreen
import com.idanatz.oneadapter.models.TestModel
import com.idanatz.oneadapter.test.R
import org.amshove.kluent.shouldEqualTo
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class WhenScrollingAfterSelectingItem_ThenIsSelectedMetadata_ShouldBeCorrect : BaseTest() {

private var isSelectedCount = 0

@Test
fun test() {
configure {
prepareOnActivity {
oneAdapter
.attachItemModule(TestItemModule())
.attachItemSelectionModule(ItemSelectionModule())
}
actOnActivity {
oneAdapter.add(modelGenerator.generateModels(25)) // enough to cause a re-bind of the same model
runWithDelay {
val holderRootView = recyclerView.findViewHolderForAdapterPosition(0)?.itemView
holderRootView?.post {
val (x, y) = holderRootView.getViewLocationOnScreen()

touchSimulator.simulateLongTouch(recyclerView, x, y)

runWithDelay {
recyclerView.smoothScrollToPosition(oneAdapter.itemCount)
}
}
}
}
untilAsserted(assertDelay = 2000) {
isSelectedCount shouldEqualTo 1
}
}
}

inner class TestItemModule : ItemModule<TestModel>() {
init {
config = modulesGenerator.generateValidItemModuleConfig(R.layout.test_model_large)
onBind { _, viewBinder, metadata ->
if (metadata.isSelected) {
viewBinder.rootView.setBackgroundColor(Color.parseColor("#C7226E"))
isSelectedCount++
}
}
states += SelectionState()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ interface SelectionStateConfig : Config {
var selectionTrigger: SelectionTrigger

enum class SelectionTrigger {
Click, LongClick
Click, // click on an Item will start a selection session
LongClick, // long click on an Item will start a selection session
Manual // selection session can be started only by calling the startSelection() function
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.idanatz.oneadapter.external.interfaces.Diffable
import com.idanatz.oneadapter.external.modules.*
import com.idanatz.oneadapter.internal.animations.AnimationPositionHandler
import com.idanatz.oneadapter.internal.diffing.OneDiffUtil
import com.idanatz.oneadapter.internal.holders.*
import com.idanatz.oneadapter.internal.holders.OneViewHolder
import com.idanatz.oneadapter.internal.holders_creators.ViewHolderCreator
import com.idanatz.oneadapter.internal.holders_creators.ViewHolderCreatorsStore
Expand Down Expand Up @@ -104,11 +105,17 @@ internal class InternalAdapter(val recyclerView: RecyclerView) : RecyclerView.Ad

override fun onBindViewHolder(holder: OneViewHolder<Diffable>, position: Int) {
val model = data[position]
val shouldAnimateBind = if (holder.firstBindAnimation != null) {
animationPositionHandler.shouldAnimateBind(holder.itemViewType, position)
} else null
logger.logd { "onBindViewHolder -> holder: $holder, model: $model" }
holder.onBindViewHolder(model, position, shouldAnimateBind)
val metadata = Metadata(
position = position,
animationMetadata = object : AnimationMetadata {
override val isAnimating: Boolean = if (holder.firstBindAnimation != null) animationPositionHandler.shouldAnimateBind(holder.itemViewType, position) else false
},
selectionMetadata = object : SelectionMetadata {
override val isSelected: Boolean = isPositionSelected(position)
}
)
logger.logd { "onBindViewHolder -> holder: $holder, model: $model, metadata: $metadata" }
holder.onBindViewHolder(model, metadata)
}

private fun onBindSelection(holder: OneViewHolder<Diffable>, position: Int, selected: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,14 @@ internal abstract class OneViewHolder<M : Diffable>(

fun onCreateViewHolder() {
this.viewBinder = ViewBinder(itemView)
metadata = Metadata()
onCreated()
}

fun onBindViewHolder(model: M, position: Int, shouldAnimateBind: Boolean?) {
fun onBindViewHolder(model: M, metadata: Metadata) {
this.model = model
metadata = metadata.copy(
position = position,
animationMetadata = shouldAnimateBind?.let {
object : AnimationMetadata {
override val isAnimating: Boolean = it
}
}
)
this.metadata = metadata

handleAnimations(shouldAnimateBind)
handleAnimations()
handleEventHooks()
onBind(model)
}
Expand Down Expand Up @@ -95,11 +87,8 @@ internal abstract class OneViewHolder<M : Diffable>(
}
}

private fun handleAnimations(shouldAnimate: Boolean?) {
if (shouldAnimate == null)
return

if (shouldAnimate) {
private fun handleAnimations() {
if (metadata.isAnimating) {
firstBindAnimation?.setTarget(itemView)
firstBindAnimation?.start()
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ internal class OneItemDetail(
override fun getPosition(): Int = adapterPosition
@Nullable override fun getSelectionKey(): Long = selectionKey

// or: should any click trigger selection?
override fun inSelectionHotspot(e: MotionEvent): Boolean = when (selectionTrigger) {
SelectionStateConfig.SelectionTrigger.Click -> true
SelectionStateConfig.SelectionTrigger.LongClick -> super.inSelectionHotspot(e)
SelectionStateConfig.SelectionTrigger.LongClick, SelectionStateConfig.SelectionTrigger.Manual -> false
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package com.idanatz.oneadapter.internal.selection

import androidx.recyclerview.selection.SelectionPredicates
import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.selection.StorageStrategy
import androidx.recyclerview.widget.RecyclerView
import com.idanatz.oneadapter.external.interfaces.Diffable
import com.idanatz.oneadapter.external.modules.ItemSelectionModule
import com.idanatz.oneadapter.external.modules.ItemSelectionModuleConfig
import com.idanatz.oneadapter.internal.holders.OneViewHolder
import com.idanatz.oneadapter.external.states.SelectionStateConfig
import com.idanatz.oneadapter.internal.utils.extensions.toOneViewHolder
import java.util.*

@Suppress("UNCHECKED_CAST")
Expand All @@ -27,9 +26,26 @@ internal class OneSelectionHandler(
OneItemDetailLookup(recyclerView),
StorageStrategy.createLongStorage()
)
.withSelectionPredicate(when (selectionModule.config!!.selectionType) {
ItemSelectionModuleConfig.SelectionType.Single -> SelectionPredicates.createSelectSingleAnything()
ItemSelectionModuleConfig.SelectionType.Multiple -> SelectionPredicates.createSelectAnything()
.withSelectionPredicate(object : SelectionTracker.SelectionPredicate<Long>() {
override fun canSetStateForKey(key: Long, nextState: Boolean): Boolean {
if (key == ghostKey)
return true // always accept let 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

return !forbidSelection
}

override fun canSetStateAtPosition(position: Int, nextState: Boolean): Boolean = true

override fun canSelectMultiple(): Boolean = when (selectionModule.config.selectionType) {
ItemSelectionModuleConfig.SelectionType.Single -> false
ItemSelectionModuleConfig.SelectionType.Multiple -> true
}
})
.build()
.also { it.addObserver(this) }
Expand All @@ -55,8 +71,8 @@ internal class OneSelectionHandler(
}

override fun onItemStateChanged(key: Long, selected: Boolean) {
recyclerView.findViewHolderForItemId(key)?.let { holder ->
observer?.onItemStateChanged(holder as OneViewHolder<Diffable>, itemKeyProvider.getPosition(key), selected)
recyclerView.findViewHolderForItemId(key)?.toOneViewHolder()?.let { holder ->
observer?.onItemStateChanged(holder, itemKeyProvider.getPosition(key), selected)
}
}

Expand All @@ -71,10 +87,12 @@ internal class OneSelectionHandler(
// ghost key is not an actual item but a trick to start manual selection
// no need to include it in the selection count
val countToNotify =
if (selectionTracker.isSelected(ghostKey)) currentSelectionCount - 1
if (isInManualSelection()) currentSelectionCount - 1
else currentSelectionCount
observer?.onSelectionUpdated(countToNotify)

previousSelectionCount = currentSelectionCount
}

private fun isInManualSelection() = selectionTracker.isSelected(ghostKey)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.idanatz.oneadapter.external.interfaces.Diffable
import com.idanatz.oneadapter.internal.holders.OneViewHolder
import java.lang.IllegalStateException

Expand Down Expand Up @@ -51,4 +52,4 @@ internal fun RecyclerView.LayoutManager.findLastVisibleItemPosition(): Int {
}
}

internal fun RecyclerView.ViewHolder.toOneViewHolder() = this as OneViewHolder<*>
internal fun RecyclerView.ViewHolder.toOneViewHolder() = this as OneViewHolder<Diffable>
2 changes: 1 addition & 1 deletion versions.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ext {
MAJOR_VERSION = 2
MINOR_VERSION = 0
HOTFIX_VERSION = 0
HOTFIX_VERSION = 1

VERSION_NAME = "${MAJOR_VERSION}.${MINOR_VERSION}.${HOTFIX_VERSION}"
VERSION_CODE = MAJOR_VERSION * 10000 + MINOR_VERSION * 100 + HOTFIX_VERSION
Expand Down

0 comments on commit e1e22a3

Please sign in to comment.