@@ -436,6 +436,7 @@ class RNGestureHandlerButtonViewManager :
436436 private var underlayDrawable: PaintDrawable ? = null
437437 private var pressInTimestamp = 0L
438438 private var pendingPressOut: Runnable ? = null
439+ private var pendingLongPress: Runnable ? = null
439440 private var pendingHoverOut: Choreographer .FrameCallback ? = null
440441 private var isPointerInsideBounds = false
441442 private var isHovered = false
@@ -520,6 +521,93 @@ class RNGestureHandlerButtonViewManager :
520521 }
521522 }
522523
524+ private var longPressDetected = false
525+
526+ // Cannot rely on isPointerInsideBounds because it's set to false during motion event processing
527+ // which happens before the final state change is handled (state change is triggered by the motion
528+ // event).
529+ private var lastEventWasInside = false
530+
531+ override fun onHandlerUpdate (handler : NativeViewGestureHandler ) {
532+ if (handler.isWithinBounds == lastEventWasInside) {
533+ return
534+ }
535+
536+ if (handler.isWithinBounds) {
537+ dispatchJSEvent(EventType .PressIn )
538+ } else {
539+ dispatchJSEvent(EventType .PressOut )
540+
541+ pendingLongPress?.let {
542+ this .handler?.removeCallbacks(it)
543+ pendingLongPress = null
544+ }
545+ }
546+ }
547+
548+ override fun onHandlerStateChange (handler : NativeViewGestureHandler , newState : Int , prevState : Int ) {
549+ // Capture local copy, since lastEventWasInside can change during this method
550+ // Specifically PressOut -> Press scenario on STATE_END
551+ val localLastEventWasInside = lastEventWasInside
552+
553+ if (newState == GestureHandler .STATE_BEGAN ) {
554+ dispatchJSEvent(EventType .PressIn )
555+
556+ val runnable = Runnable {
557+ pendingLongPress = null
558+ longPressDetected = true
559+ dispatchJSEvent(EventType .LongPress )
560+ }
561+ longPressDetected = false
562+ pendingLongPress = runnable
563+ this .handler?.postDelayed(runnable, longPressDuration.toLong())
564+ }
565+
566+ if (newState == GestureHandler .STATE_END ||
567+ newState == GestureHandler .STATE_FAILED ||
568+ newState == GestureHandler .STATE_CANCELLED
569+ ) {
570+ if (localLastEventWasInside) {
571+ dispatchJSEvent(EventType .PressOut )
572+ }
573+
574+ pendingLongPress?.let {
575+ this .handler?.removeCallbacks(it)
576+ pendingLongPress = null
577+ }
578+ }
579+
580+ if (newState == GestureHandler .STATE_END && ! longPressDetected && localLastEventWasInside) {
581+ dispatchJSEvent(EventType .Press )
582+ }
583+
584+ if (newState == GestureHandler .STATE_END ||
585+ newState == GestureHandler .STATE_FAILED ||
586+ newState == GestureHandler .STATE_CANCELLED
587+ ) {
588+ dispatchJSEvent(EventType .InteractionFinished )
589+ }
590+ }
591+
592+ private fun dispatchJSEvent (type : EventType ) {
593+ when (type) {
594+ EventType .Press -> {
595+ Log .w(" RNGH" , " onPress" )
596+ }
597+ EventType .PressIn -> {
598+ lastEventWasInside = true
599+ Log .w(" RNGH" , " onPressIn" )
600+ }
601+ EventType .PressOut -> {
602+ lastEventWasInside = false
603+ Log .w(" RNGH" , " onPressOut" )
604+ }
605+ EventType .LongPress -> {
606+ Log .w(" RNGH" , " longPress" )
607+ }
608+ }
609+ }
610+
523611 override fun onInterceptTouchEvent (event : MotionEvent ): Boolean {
524612 if (super .onInterceptTouchEvent(event)) {
525613 return true
@@ -912,6 +1000,8 @@ class RNGestureHandlerButtonViewManager :
9121000 override fun onDetachedFromWindow () {
9131001 pendingPressOut?.let { handler?.removeCallbacks(it) }
9141002 pendingPressOut = null
1003+ pendingLongPress?.let { handler?.removeCallbacks(it) }
1004+ pendingLongPress = null
9151005 cancelPendingHoverOut()
9161006 currentAnimator?.cancel()
9171007 currentAnimator = null
@@ -1104,6 +1194,13 @@ class RNGestureHandlerButtonViewManager :
11041194 }
11051195 }
11061196 }
1197+
1198+ enum class EventType {
1199+ Press ,
1200+ PressIn ,
1201+ PressOut ,
1202+ LongPress
1203+ }
11071204 }
11081205
11091206 companion object {
0 commit comments