Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose fromUser in drag stop callback #264

Merged
merged 3 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dpadrecyclerview/api/dpadrecyclerview.api
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public abstract interface class com/rubensousa/dpadrecyclerview/DpadDragHelper$D

public abstract interface class com/rubensousa/dpadrecyclerview/DpadDragHelper$DragCallback {
public abstract fun onDragStarted (Landroidx/recyclerview/widget/RecyclerView$ViewHolder;)V
public abstract fun onDragStopped ()V
public abstract fun onDragStopped (Z)V
}

public final class com/rubensousa/dpadrecyclerview/DpadLoopDirection : java/lang/Enum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class DragHelperGridTest {
private val numberOfItems = 50
private val spanCount = 5
private val dragStarted = mutableListOf<RecyclerView.ViewHolder>()
private var dragStopCount = 0
private val dragStopRequests = mutableListOf<DragStopRequest>()

@Before
fun setup() {
Expand Down Expand Up @@ -78,8 +78,8 @@ class DragHelperGridTest {
dragStarted.add(viewHolder)
}

override fun onDragStopped() {
dragStopCount++
override fun onDragStopped(fromUser: Boolean) {
dragStopRequests.add(DragStopRequest(fromUser))
}
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class DragHelperLinearTest {
private lateinit var testAdapter: TestAdapter
private val numberOfItems = 10
private val dragStarted = mutableListOf<RecyclerView.ViewHolder>()
private var dragStopCount = 0
private val dragStopRequests = mutableListOf<DragStopRequest>()

@Before
fun setup() {
Expand Down Expand Up @@ -80,8 +80,8 @@ class DragHelperLinearTest {
dragStarted.add(viewHolder)
}

override fun onDragStopped() {
dragStopCount++
override fun onDragStopped(fromUser: Boolean) {
dragStopRequests.add(DragStopRequest(fromUser))
}
}
)
Expand All @@ -100,7 +100,8 @@ class DragHelperLinearTest {
}

// then
assertThat(dragStopCount).isEqualTo(1)
assertThat(dragStopRequests).hasSize(1)
assertThat(dragStopRequests.first().fromUser).isFalse()
}

@Test
Expand Down Expand Up @@ -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
Expand All @@ -147,7 +147,10 @@ class DragHelperLinearTest {
}

// then
assertThat(dragStopCount).isEqualTo(cancelKeyCodes.size)
assertThat(dragStopRequests).hasSize(cancelKeyCodes.size)
dragStopRequests.forEach {
assertThat(it.fromUser).isTrue()
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.rubensousa.dpadrecyclerview.test.tests.drag

data class DragStopRequest(val fromUser: Boolean)
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(
private val adapter: DragAdapter<T>,
private val callback: DragCallback,
private val cancelKeyCodes: Set<Int> = setOf(
private val stopKeyCodes: Set<Int> = setOf(
KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_ENTER,
KeyEvent.KEYCODE_BACK
)
),
) {

/**
Expand All @@ -50,8 +53,11 @@ class DpadDragHelper<T>(
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) {
Expand Down Expand Up @@ -120,7 +126,7 @@ class DpadDragHelper<T>(

private fun startDrag(
recyclerView: DpadRecyclerView,
viewHolder: RecyclerView.ViewHolder
viewHolder: RecyclerView.ViewHolder,
) {
isDragging = true
previousKeyInterceptListener = recyclerView.getOnKeyInterceptListener()
Expand All @@ -136,6 +142,10 @@ class DpadDragHelper<T>(
* [DragCallback.onDragStopped] will be called after this method
*/
fun stopDrag() {
stopDragging(fromUser = false)
}

private fun stopDragging(fromUser: Boolean) {
if (!isDragging) {
return
}
Expand All @@ -145,7 +155,7 @@ class DpadDragHelper<T>(
}
}
isDragging = false
callback.onDragStopped()
callback.onDragStopped(fromUser)
}

private fun onKeyEvent(event: KeyEvent): Boolean {
Expand Down Expand Up @@ -198,7 +208,7 @@ class DpadDragHelper<T>(
adapter: RecyclerView.Adapter<*>,
items: MutableList<T>,
srcIndex: Int,
targetIndex: Int
targetIndex: Int,
) {
Collections.swap(items, srcIndex, targetIndex)
adapter.notifyItemMoved(srcIndex, targetIndex)
Expand All @@ -212,7 +222,7 @@ class DpadDragHelper<T>(
adapter: RecyclerView.Adapter<*>,
items: MutableList<T>,
srcIndex: Int,
targetIndex: Int
targetIndex: Int,
) {
val item = items.removeAt(srcIndex)
items.add(targetIndex, item)
Expand Down Expand Up @@ -247,8 +257,10 @@ class DpadDragHelper<T>(

/**
* 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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {

Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}
}
Expand All @@ -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))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
5 changes: 3 additions & 2 deletions sample/src/main/res/layout/horizontal_adapter_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@
android:clipChildren="false"
android:gravity="center"
android:orientation="horizontal"
app:dpadRecyclerViewChildAlignmentFraction="0.5"
app:dpadRecyclerViewChildAlignmentFraction="0"
app:dpadRecyclerViewFocusOutBack="false"
app:dpadRecyclerViewFocusOutFront="false"
app:dpadRecyclerViewItemEdgeSpacing="@dimen/list_margin_start"
app:dpadRecyclerViewItemSpacing="@dimen/item_spacing"
app:dpadRecyclerViewParentAlignmentEdge="min_max"
app:dpadRecyclerViewParentAlignmentFraction="0.5" />
app:dpadRecyclerViewParentAlignmentFraction="0"
app:dpadRecyclerViewParentAlignmentOffset="@dimen/list_margin_start" />


</LinearLayout>
Loading