From 70b9eda18e1cc751465552a31babd651d364a90b Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Wed, 4 Sep 2024 22:02:41 +0200 Subject: [PATCH] Expose fromUser in drag stop callback --- .../test/tests/drag/DragHelperGridTest.kt | 6 ++-- .../test/tests/drag/DragHelperLinearTest.kt | 17 ++++++---- .../test/tests/drag/DragStopRequest.kt | 3 ++ .../dpadrecyclerview/DpadDragHelper.kt | 32 +++++++++++++------ .../ui/screen/drag/DragAndDropFragment.kt | 26 ++++++++++++++- .../ui/screen/drag/DragAndDropGridFragment.kt | 11 ++----- .../sample/ui/screen/drag/DraggableItem.kt | 2 +- 7 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragStopRequest.kt diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragHelperGridTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragHelperGridTest.kt index 87437920..e48f62d5 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragHelperGridTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragHelperGridTest.kt @@ -42,7 +42,7 @@ class DragHelperGridTest { private val numberOfItems = 50 private val spanCount = 5 private val dragStarted = mutableListOf() - private var dragStopCount = 0 + private val dragStopRequests = mutableListOf() @Before fun setup() { @@ -78,8 +78,8 @@ class DragHelperGridTest { dragStarted.add(viewHolder) } - override fun onDragStopped() { - dragStopCount++ + override fun onDragStopped(fromUser: Boolean) { + dragStopRequests.add(DragStopRequest(fromUser)) } } ) diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragHelperLinearTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragHelperLinearTest.kt index 1ef42569..4ee88b7d 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragHelperLinearTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragHelperLinearTest.kt @@ -44,7 +44,7 @@ class DragHelperLinearTest { private lateinit var testAdapter: TestAdapter private val numberOfItems = 10 private val dragStarted = mutableListOf() - private var dragStopCount = 0 + private val dragStopRequests = mutableListOf() @Before fun setup() { @@ -80,8 +80,8 @@ class DragHelperLinearTest { dragStarted.add(viewHolder) } - override fun onDragStopped() { - dragStopCount++ + override fun onDragStopped(fromUser: Boolean) { + dragStopRequests.add(DragStopRequest(fromUser)) } } ) @@ -100,7 +100,8 @@ class DragHelperLinearTest { } // then - assertThat(dragStopCount).isEqualTo(1) + assertThat(dragStopRequests).hasSize(1) + assertThat(dragStopRequests.first().fromUser).isFalse() } @Test @@ -131,12 +132,11 @@ class DragHelperLinearTest { } @Test - fun testDragIsCanceledOnCertainKeyEvents() { + fun testDragStopsOnCertainKeyEvents() { // given val cancelKeyCodes = setOf( KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_DPAD_CENTER, - KeyEvent.KEYCODE_BACK, ) // when @@ -147,7 +147,10 @@ class DragHelperLinearTest { } // then - assertThat(dragStopCount).isEqualTo(cancelKeyCodes.size) + assertThat(dragStopRequests).hasSize(cancelKeyCodes.size) + dragStopRequests.forEach { + assertThat(it.fromUser).isTrue() + } } @Test diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragStopRequest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragStopRequest.kt new file mode 100644 index 00000000..2dcbda66 --- /dev/null +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/drag/DragStopRequest.kt @@ -0,0 +1,3 @@ +package com.rubensousa.dpadrecyclerview.test.tests.drag + +data class DragStopRequest(val fromUser: Boolean) diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadDragHelper.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadDragHelper.kt index 18a9add5..d8b17b54 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadDragHelper.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadDragHelper.kt @@ -26,15 +26,18 @@ import java.util.Collections * * To use this, your adapter needs to implement [DpadDragHelper.DragAdapter] * and expose the mutable collection via [DpadDragHelper.DragAdapter.getMutableItems]. + * + * @param adapter the adapter that backs the data that can be arranged + * @param callback the callback for notifying dragging state changes + * @param stopKeyCodes key codes that will be accepted to stop the dragging state */ class DpadDragHelper( private val adapter: DragAdapter, private val callback: DragCallback, - private val cancelKeyCodes: Set = setOf( + private val stopKeyCodes: Set = setOf( KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_ENTER, - KeyEvent.KEYCODE_BACK - ) + ), ) { /** @@ -50,8 +53,11 @@ class DpadDragHelper( if (!isDragging) { return false } - if (cancelKeyCodes.contains(event.keyCode)) { - stopDrag() + if (stopKeyCodes.contains(event.keyCode)) { + if (event.action == KeyEvent.ACTION_UP) { + // Only stop dragging after the user releases the key + stopDragging(fromUser = true) + } return true } if (event.action == KeyEvent.ACTION_UP) { @@ -120,7 +126,7 @@ class DpadDragHelper( private fun startDrag( recyclerView: DpadRecyclerView, - viewHolder: RecyclerView.ViewHolder + viewHolder: RecyclerView.ViewHolder, ) { isDragging = true previousKeyInterceptListener = recyclerView.getOnKeyInterceptListener() @@ -136,6 +142,10 @@ class DpadDragHelper( * [DragCallback.onDragStopped] will be called after this method */ fun stopDrag() { + stopDragging(fromUser = false) + } + + private fun stopDragging(fromUser: Boolean) { if (!isDragging) { return } @@ -145,7 +155,7 @@ class DpadDragHelper( } } isDragging = false - callback.onDragStopped() + callback.onDragStopped(fromUser) } private fun onKeyEvent(event: KeyEvent): Boolean { @@ -198,7 +208,7 @@ class DpadDragHelper( adapter: RecyclerView.Adapter<*>, items: MutableList, srcIndex: Int, - targetIndex: Int + targetIndex: Int, ) { Collections.swap(items, srcIndex, targetIndex) adapter.notifyItemMoved(srcIndex, targetIndex) @@ -212,7 +222,7 @@ class DpadDragHelper( adapter: RecyclerView.Adapter<*>, items: MutableList, srcIndex: Int, - targetIndex: Int + targetIndex: Int, ) { val item = items.removeAt(srcIndex) items.add(targetIndex, item) @@ -247,8 +257,10 @@ class DpadDragHelper( /** * Indicates that the dragging action has stopped + * @param fromUser true if this was triggered by the user, + * or false if [stopDrag] was called manually by the app */ - fun onDragStopped() + fun onDragStopped(fromUser: Boolean) } } diff --git a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DragAndDropFragment.kt b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DragAndDropFragment.kt index 17d136e9..b332a7f0 100644 --- a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DragAndDropFragment.kt +++ b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DragAndDropFragment.kt @@ -18,12 +18,16 @@ package com.rubensousa.dpadrecyclerview.sample.ui.screen.drag import android.os.Bundle import android.view.View +import androidx.activity.OnBackPressedCallback import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.RecyclerView import com.rubensousa.dpadrecyclerview.DpadDragHelper @@ -32,6 +36,7 @@ import com.rubensousa.dpadrecyclerview.sample.databinding.ScreenDragDropBinding import com.rubensousa.dpadrecyclerview.sample.ui.dpToPx import com.rubensousa.dpadrecyclerview.sample.ui.viewBinding import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch class DragAndDropFragment : Fragment(R.layout.screen_drag_drop) { @@ -49,17 +54,36 @@ class DragAndDropFragment : Fragment(R.layout.screen_drag_drop) { override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) { dragState.value = dragAdapter.getItem(viewHolder.bindingAdapterPosition) } - override fun onDragStopped() { + + override fun onDragStopped(fromUser: Boolean) { + if (fromUser) { + // User requested dragging to stop + } else { + // Dragging was stopped manually + } dragState.value = null } } ) + private val backPressCallback = object : OnBackPressedCallback(false) { + override fun handleOnBackPressed() { + dragHelper.stopDrag() + } + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupRecyclerView() setDragButtonContent() dragAdapter.submitList(List(20) { it }.toMutableList()) + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressCallback) + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + dragState.collect { draggingItem -> + backPressCallback.isEnabled = draggingItem != null + } + } + } } private fun setupRecyclerView() { diff --git a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DragAndDropGridFragment.kt b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DragAndDropGridFragment.kt index c57ea9c7..e814ab9a 100644 --- a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DragAndDropGridFragment.kt +++ b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DragAndDropGridFragment.kt @@ -31,7 +31,6 @@ import com.rubensousa.dpadrecyclerview.sample.R import com.rubensousa.dpadrecyclerview.sample.databinding.ScreenDragDropBinding import com.rubensousa.dpadrecyclerview.sample.ui.dpToPx import com.rubensousa.dpadrecyclerview.sample.ui.viewBinding -import com.rubensousa.dpadrecyclerview.spacing.DpadGridSpacingDecoration import kotlinx.coroutines.flow.MutableStateFlow class DragAndDropGridFragment : Fragment(R.layout.screen_drag_drop_grid) { @@ -51,7 +50,7 @@ class DragAndDropGridFragment : Fragment(R.layout.screen_drag_drop_grid) { override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) { dragState.value = dragAdapter.getItem(viewHolder.bindingAdapterPosition) } - override fun onDragStopped() { + override fun onDragStopped(fromUser: Boolean) { dragState.value = null } } @@ -72,12 +71,8 @@ class DragAndDropGridFragment : Fragment(R.layout.screen_drag_drop_grid) { // For faster moves moveDuration = 100 } - addItemDecoration( - DpadGridSpacingDecoration.create( - itemSpacing = dpToPx(16.dp), - edgeSpacing = dpToPx(48.dp), - ) - ) + setItemSpacing(dpToPx(16.dp)) + setItemEdgeSpacing(dpToPx(48.dp)) } } diff --git a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DraggableItem.kt b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DraggableItem.kt index 6a3cebf7..4a56d655 100644 --- a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DraggableItem.kt +++ b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/drag/DraggableItem.kt @@ -62,7 +62,7 @@ fun DraggableItem( } Box( modifier = modifier - .size(200.dp) + .size(100.dp) .then( if (isDragging) { Modifier.border(8.dp, Color.Blue, RoundedCornerShape(8.dp))