Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,10 @@ class RNGestureHandlerButtonViewManager :
// container) instead of pressing right away.
private var pressDeferredByPlatform = false

// Uptime of the DOWN that began the current press sequence; the long press timer is measured from it.
private var pressDownTime = 0L
private var pressStarted = false

override fun onHandlerUpdate(handler: NativeViewGestureHandler) {
if (managedHandlerTag == null || handler.isWithinBounds == lastEventWasInside) {
return
Expand Down Expand Up @@ -773,17 +777,16 @@ class RNGestureHandlerButtonViewManager :
// native dispatch of the same DOWN and the sweep that reads the flag.
receivedNativeDown = false
flushPendingPressOut()
dispatchJSEvent(EventType.PressIn, handler)
pressDownTime = SystemClock.uptimeMillis()
pressStarted = false
longPressDetected = false

if (hasLongPressHandler && longPressDuration >= 0) {
val runnable = Runnable {
pendingLongPress = null
longPressDetected = true
dispatchJSEvent(EventType.LongPress, handler)
}
pendingLongPress = runnable
this.handler?.postDelayed(runnable, longPressDuration.toLong())
// Touch-driven presses report PressIn and arm the long press timer from `setPressed`, when the
// press feedback starts. A synthetic activation (TalkBack, keyboard) has no touch and no
// pressed state to follow, so it reports PressIn right away. Nothing deferred it either.
if (!handler.hasTrackedPointers) {
pressDeferredByPlatform = false
dispatchJSEvent(EventType.PressIn, handler)
}
}

Expand Down Expand Up @@ -819,6 +822,42 @@ class RNGestureHandlerButtonViewManager :
}
}

// The framework defers the pressed state inside scrolling containers and drops it when a parent
// intercepts the DOWN (e.g. a fling catch), so PressIn and the long press timer follow the pressed
// state, not BEGAN. Like `View.CheckForTap`, long press is still measured from the DOWN.
private fun onPressedStateStarted() {
val moduleId = moduleId ?: return
val handlerTag = managedHandlerTag ?: return
val handler =
RNGestureHandlerModule.registries[moduleId]?.getHandler(handlerTag) as? NativeViewGestureHandler ?: return

if (handler.state != GestureHandler.STATE_BEGAN && handler.state != GestureHandler.STATE_ACTIVE) {
return
}

if (!lastEventWasInside) {
dispatchJSEvent(EventType.PressIn, handler)
}

// Re-entry after leaving (cancelOnLeave off) reports PressIn again but never re-arms the timer.
if (pressStarted) {
return
}

pressStarted = true

if (hasLongPressHandler && longPressDuration >= 0) {
val elapsed = SystemClock.uptimeMillis() - pressDownTime
val runnable = Runnable {
pendingLongPress = null
longPressDetected = true
dispatchJSEvent(EventType.LongPress, handler)
}
pendingLongPress = runnable
this.handler?.postDelayed(runnable, (longPressDuration - elapsed).coerceAtLeast(0))
}
}

private fun dispatchJSEvent(type: EventType, handler: NativeViewGestureHandler) {
dispatchEvent(RNGestureHandlerButtonEvent.obtain(this, handler, type))

Expand Down Expand Up @@ -1595,6 +1634,7 @@ class RNGestureHandlerButtonViewManager :

if (pressed) {
animatePressIn()
onPressedStateStarted()
Comment thread
j-piasecki marked this conversation as resolved.
Resolved
} else {
animatePressOut()
}
Expand Down
Loading