Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -365,20 +365,13 @@ class GestureHandlerOrchestrator(
event.recycle()
}

/**
* Cancels all handlers created using API v1 and v2
*/
fun cancelAllLegacyHandlers() {
private inline fun cancelHandlersMatching(predicate: (GestureHandler) -> Boolean) {
val handlersToProcess = obtainHandlerList()
handlersToProcess.addAll(gestureHandlers)

try {
handlersToProcess.forEach {
if (it.actionType == GestureHandler.ACTION_TYPE_JS_FUNCTION_OLD_API ||
it.actionType == GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API ||
it.actionType == GestureHandler.ACTION_TYPE_REANIMATED_WORKLET ||
it.actionType == GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT
) {
if (predicate(it)) {
it.cancel()
}
}
Expand All @@ -389,6 +382,20 @@ class GestureHandlerOrchestrator(
}
}

fun cancelAllLegacyHandlers() = cancelHandlersMatching {
it.actionType == GestureHandler.ACTION_TYPE_JS_FUNCTION_OLD_API ||
it.actionType == GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API ||
it.actionType == GestureHandler.ACTION_TYPE_REANIMATED_WORKLET ||
it.actionType == GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT
}

/**
* Cancels handlers whose view opted out of surviving a native view taking over the touch stream.
*/
fun cancelHandlersOnNativeTouchGrab(grabbedMidGesture: Boolean) = cancelHandlersMatching {
it is NativeViewGestureHandler && it.shouldCancelOnNativeTouchGrab(grabbedMidGesture)
}

/**
* isViewAttachedUnderWrapper checks whether all of parents for view related to handler
* view are attached. Since there might be an issue rarely observed when view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ class NativeViewGestureHandler : GestureHandler() {

override fun wantsToAttachDirectlyToView() = true

fun shouldCancelOnNativeTouchGrab(grabbedMidGesture: Boolean): Boolean =
hook.shouldCancelOnNativeTouchGrab(grabbedMidGesture)

data class HitSlop(
val left: Float = HIT_SLOP_NONE,
val top: Float = HIT_SLOP_NONE,
Expand Down Expand Up @@ -361,6 +364,11 @@ class NativeViewGestureHandler : GestureHandler() {
*/
fun shouldRecognizeSimultaneously(handler: GestureHandler): Boolean? = null

/**
* Called after a native view grabbed the touch lock; return true to cancel the handler.
*/
fun shouldCancelOnNativeTouchGrab(grabbedMidGesture: Boolean) = false

/**
* shouldActivateOnStart and tryIntercept have priority over this method
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,10 @@ class RNGestureHandlerButtonViewManager :
// event).
private var lastEventWasInside = false

// Whether the native dispatch delivered DOWN for the current gesture. False when a native
// ancestor intercepted it — the orchestrator still delivers events then.
private var receivedNativeDown = false

override fun onHandlerUpdate(handler: NativeViewGestureHandler) {
if (managedHandlerTag == null || handler.isWithinBounds == lastEventWasInside) {
return
Expand Down Expand Up @@ -744,6 +748,11 @@ class RNGestureHandlerButtonViewManager :
val localLastEventWasInside = lastEventWasInside

if (newState == GestureHandler.STATE_BEGAN) {
// Reset here, not on gesture end: the native DOWN sets the flag even when the orchestrator
// never tracks the handler (disabled button, alpha below the traversal threshold), so a
// terminal state may never come and the stale value would survive. BEGAN precedes both the
// native dispatch of the same DOWN and the sweep that reads the flag.
receivedNativeDown = false
dispatchJSEvent(EventType.PressIn, handler)
longPressDetected = false

Expand Down Expand Up @@ -815,6 +824,16 @@ class RNGestureHandlerButtonViewManager :
}
}

override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
receivedNativeDown = true
}

return super.dispatchTouchEvent(event)
}

override fun shouldCancelOnNativeTouchGrab(grabbedMidGesture: Boolean) = grabbedMidGesture || !receivedNativeDown

override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
if (super.onInterceptTouchEvent(event)) {
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView:
private var shouldIntercept = false
private var wasIntercepting = false
private var passingTouch = false
private var passingNativeTouch = false
private var nativeTouchGrabRequested = false

init {
val registry =
Expand Down Expand Up @@ -116,14 +118,46 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView:

fun requestDisallowInterceptTouchEvent() {
// If this method gets called it means that some native view is attempting to grab lock for
// touch event delivery. In that case we cancel all gesture recognizers
// touch event delivery. Legacy handlers are cancelled right away; handlers opting into
// native-touch-grab cancellation are deferred to `onNativeDispatchEnd`.
if (orchestrator != null && !passingTouch) {
// if we are in the process of delivering touch events via GH orchestrator, we don't want to
// treat it as a native gesture capturing the lock
if (passingNativeTouch) {
// Requests may also arrive outside any dispatch pass (e.g. RN's JS responder). Those have
// no pass to classify against and must not arm the sweep for a future gesture.
nativeTouchGrabRequested = true
}
orchestrator.cancelAllLegacyHandlers()
}
}

fun onNativeDispatchStart() {
passingNativeTouch = true
}

/**
* Deferred handling of a disallow-intercept request recorded during this dispatch pass. The
* request alone doesn't say what the caller did with the event: a scrollable calls it when it
* takes over the touch, but e.g. a nested pager calls it already on DOWN, just to keep its
* ancestors from stealing a swipe it may recognize later, and the event still reaches the
* button - at request time both calls look identical. They only become
* distinguishable once the native dispatch completes (did the button receive the DOWN?), which
* is why cancellation runs here instead of in `requestDisallowInterceptTouchEvent`.
*/
Comment thread
j-piasecki marked this conversation as resolved.
fun onNativeDispatchEnd(event: MotionEvent) {
passingNativeTouch = false

if (nativeTouchGrabRequested) {
nativeTouchGrabRequested = false

val grabbedMidGesture = event.actionMasked != MotionEvent.ACTION_DOWN &&
event.actionMasked != MotionEvent.ACTION_POINTER_DOWN

orchestrator?.cancelHandlersOnNativeTouchGrab(grabbedMidGesture)
}
}

fun dispatchTouchEvent(event: MotionEvent): Boolean {
// We mark `mPassingTouch` before we get into `mOrchestrator.onTouchEvent` so that we can tell
// if `requestDisallow` has been called as a result of a normal gesture handling process or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) {
return if (rootViewEnabled && rootHelper!!.dispatchTouchEvent(event)) {
true
} else {
super.dispatchTouchEvent(event)
rootHelper?.onNativeDispatchStart()
val handled = super.dispatchTouchEvent(event)
rootHelper?.onNativeDispatchEnd(event)
Comment thread
j-piasecki marked this conversation as resolved.
handled
}
}

Expand Down
Loading