Skip to content

Commit 904645e

Browse files
committed
DetectDragModifier now detects long-presses
The long press initiates an item drag; otherwise the view just scrolls on drag.
1 parent ef1bbcc commit 904645e

2 files changed

Lines changed: 52 additions & 8 deletions

File tree

app/src/main/java/org/schabi/newpipe/ui/DetectDragModifier.kt

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,75 @@
11
package org.schabi.newpipe.ui
22

3+
import android.view.MotionEvent
34
import androidx.compose.foundation.gestures.awaitEachGesture
45
import androidx.compose.foundation.gestures.awaitFirstDown
56
import androidx.compose.ui.Modifier
67
import androidx.compose.ui.geometry.Offset
8+
import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
9+
import androidx.compose.ui.input.pointer.changedToUp
10+
import androidx.compose.ui.input.pointer.isOutOfBounds
711
import androidx.compose.ui.input.pointer.pointerInput
812
import androidx.compose.ui.input.pointer.positionChange
913
import androidx.compose.ui.unit.IntOffset
14+
import androidx.compose.ui.unit.IntSize
15+
import androidx.compose.ui.util.fastAll
16+
import androidx.compose.ui.util.fastAny
1017

1118
/**
1219
* Detects a drag gesture **without** trying to filter out any misclicks. This is useful in menus
1320
* where items are dragged around, where the usual misclick guardrails would cause unexpected lags
14-
* or strange behaviors when dragging stuff around quickly. For other use cases, use
15-
* [androidx.compose.foundation.gestures.detectDragGestures] or
21+
* or strange behaviors when dragging stuff around quickly. Also detects whether a drag gesture
22+
* began with a long press or not, which can be useful to decide whether an item should be dragged
23+
* around (in case of long-press) or the view should be scrolled (otherwise). For other use cases,
24+
* use [androidx.compose.foundation.gestures.detectDragGestures] or
1625
* [androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress].
1726
*
1827
* @param beginDragGesture called when the user first touches the screen (down event) with the
19-
* pointer position.
28+
* pointer position and whether a long press was detected.
2029
* @param handleDragGestureChange called with the current pointer position and the difference from
2130
* the last position, every time the user moves the finger after [beginDragGesture] has been called.
2231
* @param endDragGesture called when the drag gesture finishes, after [beginDragGesture] has been
2332
* called.
2433
*/
2534
fun Modifier.detectDragGestures(
26-
beginDragGesture: (position: IntOffset) -> Unit,
35+
beginDragGesture: (position: IntOffset, wasLongPressed: Boolean) -> Unit,
2736
handleDragGestureChange: (position: IntOffset, positionChange: Offset) -> Unit,
2837
endDragGesture: () -> Unit
2938
): Modifier {
3039
return this.pointerInput(Unit) {
3140
awaitEachGesture {
3241
val down = awaitFirstDown()
42+
val wasLongPressed = try {
43+
// code in this branch was taken from AwaitPointerEventScope.waitForLongPress(),
44+
// which unfortunately is private
45+
withTimeout(viewConfiguration.longPressTimeoutMillis) {
46+
while (true) {
47+
val event = awaitPointerEvent()
48+
if (event.changes.fastAll { it.changedToUp() }) {
49+
// All pointers are up
50+
break
51+
}
52+
53+
if (event.classification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
54+
return@withTimeout true
55+
}
56+
57+
if (
58+
event.changes.fastAny {
59+
it.isConsumed || it.isOutOfBounds(IntSize(0, 0), extendedTouchPadding)
60+
}
61+
) {
62+
break
63+
}
64+
}
65+
return@withTimeout false
66+
}
67+
} catch (_: PointerEventTimeoutCancellationException) {
68+
true
69+
}
70+
3371
val pointerId = down.id
34-
beginDragGesture(down.position.toIntOffset())
72+
beginDragGesture(down.position.toIntOffset(), wasLongPressed)
3573
while (true) {
3674
val change = awaitPointerEvent().changes.find { it.id == pointerId }
3775
if (change == null || !change.pressed) {

app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenuEditor.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ import kotlin.math.floor
9494
import kotlin.math.max
9595
import kotlin.math.min
9696

97-
const val TAG = "LongPressMenuEditor"
97+
internal const val TAG = "LongPressMenuEditor"
9898

9999
// TODO padding doesn't seem to work as expected when the list becomes scrollable?
100100
@Composable
@@ -166,7 +166,12 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
166166
}
167167

168168
// this beginDragGesture() overload is only called when moving the finger (not on DPAD's Enter)
169-
fun beginDragGesture(pos: IntOffset) {
169+
fun beginDragGesture(pos: IntOffset, wasLongPressed: Boolean) {
170+
if (!wasLongPressed) {
171+
// items can be dragged around only if they are long-pressed;
172+
// use the drag as scroll otherwise
173+
return
174+
}
170175
val rawItem = findItemForOffsetOrClosestInRow(pos) ?: return
171176
beginDragGesture(pos, rawItem)
172177
autoScrollSpeed = 0f
@@ -229,7 +234,8 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
229234
fun handleDragGestureChange(pos: IntOffset, posChangeForScrolling: Offset) {
230235
val dragItem = activeDragItem
231236
if (dragItem == null) {
232-
// when the user clicks outside of any draggable item, let the list be scrolled
237+
// when the user clicks outside of any draggable item, or if the user did not long-press
238+
// on an item to begin with, let the list be scrolled
233239
gridState.dispatchRawDelta(-posChangeForScrolling.y)
234240
return
235241
}

0 commit comments

Comments
 (0)