Scroll back to changed ListItem when using undo/redo

This commit is contained in:
PhilKes 2025-03-05 17:40:35 +01:00
parent 0215bcd676
commit a02e45ad98
3 changed files with 33 additions and 25 deletions

View file

@ -44,7 +44,7 @@ class ListItemDragCallback(private val elevation: Float, internal val listManage
internal fun move(from: Int, to: Int): Boolean {
if (positionFrom == null) {
positionFrom = from
stateBefore = listManager.getState()
stateBefore = listManager.getState(selectedPos = from)
val item = listManager.getItem(from)
parentBefore = if (item.isChild) listManager.findParent(item)?.second else null
}

View file

@ -72,38 +72,40 @@ class ListManager(
this.itemsChecked?.let { Log.d(TAG, "itemsChecked:\n${it}") }
}
internal fun getState(): ListState {
internal fun getState(selectedPos: Int? = null): ListState {
val (pos, cursorPos) = recyclerView.getFocusedPositionAndCursor()
return ListState(
items.cloneList(),
itemsChecked?.toMutableList()?.cloneList(),
pos,
selectedPos ?: pos,
cursorPos,
)
}
internal fun setState(state: ListState) {
adapter.submitList(state.items)
this.itemsChecked?.setItems(state.checkedItems!!)
// Focus item's EditText and set cursor position
state.focusedItemPos?.let { itemPos ->
recyclerView.postDelayed(
{
(recyclerView.findViewHolderForAdapterPosition(itemPos) as? ListItemVH?)?.let {
viewHolder ->
inputMethodManager?.let { inputManager ->
val maxCursorPos = viewHolder.binding.EditText.length()
viewHolder.focusEditText(
selectionStart =
state.cursorPos?.coerceIn(0, maxCursorPos) ?: maxCursorPos,
inputMethodManager = inputManager,
)
}
adapter.submitList(state.items) {
// Focus item's EditText and set cursor position
state.focusedItemPos?.let { itemPos ->
recyclerView.post {
if (itemPos in 0..items.size) {
recyclerView.smoothScrollToPosition(itemPos)
(recyclerView.findViewHolderForAdapterPosition(itemPos) as? ListItemVH?)
?.let { viewHolder ->
inputMethodManager?.let { inputManager ->
val maxCursorPos = viewHolder.binding.EditText.length()
viewHolder.focusEditText(
selectionStart =
state.cursorPos?.coerceIn(0, maxCursorPos)
?: maxCursorPos,
inputMethodManager = inputManager,
)
}
}
}
},
20,
) // Delay is needed, otherwise focus is overwritten by submitList()
}
}
}
this.itemsChecked?.setItems(state.checkedItems!!)
}
fun add(
@ -141,7 +143,7 @@ class ListManager(
adapter.notifyItemRangeInserted(insertPos, count)
items.notifyPreviousFirstItem(insertPos, count)
if (pushChange) {
changeHistory.push(ListAddChange(stateBefore, getState(), this))
changeHistory.push(ListAddChange(stateBefore, getState(selectedPos = insertPos), this))
}
recyclerView.post {
@ -205,7 +207,7 @@ class ListManager(
/** @return position of the moved item afterwards and the moved item count. */
fun move(positionFrom: Int, positionTo: Int): Pair<Int, Int> {
val stateBefore = getState()
val itemsCheckedBefore = itemsChecked?.toMutableList()?.cloneList()
val list = items.toMutableList()
val movedItem = list[positionFrom]
// Do not allow to move parent into its own children
@ -229,7 +231,7 @@ class ListManager(
Pair(toOrder until fromOrder, itemCount)
}
shiftItemOrders(orderRange, valueToAdd, items = list)
stateBefore.checkedItems?.shiftItemOrders(orderRange, valueToAdd)
itemsCheckedBefore?.shiftItemOrders(orderRange, valueToAdd)
list.removeFromParent(movedItem)
list.removeWithChildren(movedItem)

View file

@ -88,6 +88,12 @@ open class ListManagerTestBase {
}
.`when`(adapter)
.submitList(any())
doAnswer { invocation ->
val listArgument = invocation.getArgument<MutableList<ListItem>>(0)
itemsInternal = listArgument
}
.`when`(adapter)
.submitList(any(), any())
listManager.init(adapter, itemsChecked, adapterChecked)
adapter.submitList(items)