diff --git a/android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetContentView.kt b/android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetContentView.kt index e859a97bb6..698ae8b48b 100644 --- a/android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetContentView.kt +++ b/android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetContentView.kt @@ -2,12 +2,20 @@ package com.swmansion.rnscreens.gamma.modals.formsheet import android.annotation.SuppressLint import android.content.Context +import android.view.MotionEvent +import android.view.View import android.view.ViewTreeObserver import com.facebook.react.bridge.UIManager import com.facebook.react.bridge.UIManagerListener import com.facebook.react.common.annotations.UnstableReactNativeAPI +import com.facebook.react.config.ReactFeatureFlags +import com.facebook.react.uimanager.JSPointerDispatcher +import com.facebook.react.uimanager.JSTouchDispatcher +import com.facebook.react.uimanager.RootView import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.common.UIManagerType +import com.facebook.react.uimanager.events.EventDispatcher import com.facebook.react.views.view.ReactViewGroup import com.swmansion.rnscreens.gamma.helpers.getFabricUIManagerNotNull @@ -17,16 +25,30 @@ class FormSheetContentView( context: Context, private val onSizeChangedCallback: (width: Int, height: Int) -> Unit, ) : ReactViewGroup(context), + RootView, UIManagerListener, ViewTreeObserver.OnPreDrawListener { - private val themedReactContext = context as? ThemedReactContext + private val themedReactContext: ThemedReactContext + get() = context as ThemedReactContext + private val jsTouchDispatcher = JSTouchDispatcher(this) + private var jsPointerDispatcher: JSPointerDispatcher? = null + + // TODO: @t0maboro - refactor in EventEmitter PR + private val eventDispatcher: EventDispatcher? + get() = UIManagerHelper.getEventDispatcher(themedReactContext, UIManagerType.FABRIC) private var isWaitingForFabricMount = false + init { + if (ReactFeatureFlags.dispatchPointerEvents) { + jsPointerDispatcher = JSPointerDispatcher(this) + } + } + override fun onAttachedToWindow() { super.onAttachedToWindow() viewTreeObserver.addOnPreDrawListener(this) - themedReactContext?.let { themedContext -> + themedReactContext.let { themedContext -> UIManagerHelper .getFabricUIManagerNotNull(themedContext) .addUIManagerEventListener(this) @@ -36,7 +58,7 @@ class FormSheetContentView( override fun onDetachedFromWindow() { super.onDetachedFromWindow() viewTreeObserver.removeOnPreDrawListener(this) - themedReactContext?.let { themedContext -> + themedReactContext.let { themedContext -> UIManagerHelper .getFabricUIManagerNotNull(themedContext) .removeUIManagerEventListener(this) @@ -73,4 +95,61 @@ class FormSheetContentView( override fun didMountItems(uiManager: UIManager) = Unit override fun didScheduleMountItems(uiManager: UIManager) = Unit + + override fun onInterceptTouchEvent(event: MotionEvent): Boolean { + eventDispatcher?.let { eventDispatcher -> + jsTouchDispatcher.handleTouchEvent(event, eventDispatcher, themedReactContext) + jsPointerDispatcher?.handleMotionEvent(event, eventDispatcher, true) + } + return super.onInterceptTouchEvent(event) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + eventDispatcher?.let { eventDispatcher -> + jsTouchDispatcher.handleTouchEvent(event, eventDispatcher, themedReactContext) + jsPointerDispatcher?.handleMotionEvent(event, eventDispatcher, false) + } + super.onTouchEvent(event) + // In case when there is no children interested in handling touch event, we return true from + // the root view in order to receive subsequent events related to that gesture + return true + } + + override fun onInterceptHoverEvent(event: MotionEvent): Boolean { + eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, true) } + return super.onInterceptHoverEvent(event) + } + + override fun onHoverEvent(event: MotionEvent): Boolean { + eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, false) } + return super.onHoverEvent(event) + } + + override fun onChildStartedNativeGesture( + childView: View?, + ev: MotionEvent, + ) { + eventDispatcher?.let { eventDispatcher -> + jsTouchDispatcher.onChildStartedNativeGesture(ev, eventDispatcher, themedReactContext) + jsPointerDispatcher?.onChildStartedNativeGesture(childView, ev, eventDispatcher) + } + } + + override fun onChildEndedNativeGesture( + childView: View, + ev: MotionEvent, + ) { + eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) } + jsPointerDispatcher?.onChildEndedNativeGesture() + } + + override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + // No-op - override in order to still receive events to onInterceptTouchEvent + // even when some other view disallow that + } + + override fun handleException(t: Throwable) { + themedReactContext.reactApplicationContext.handleException(RuntimeException(t)) + } } diff --git a/android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetHost.kt b/android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetHost.kt index d2bdf2708d..92ac4f0bd3 100644 --- a/android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetHost.kt +++ b/android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetHost.kt @@ -4,13 +4,16 @@ import android.content.Context import android.view.View import android.view.ViewGroup import com.facebook.react.bridge.ReactContext +import com.facebook.react.uimanager.PointerEvents +import com.facebook.react.uimanager.ReactPointerEventsView import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerHelper import com.swmansion.rnscreens.gamma.common.ShadowStateProxy class FormSheetHost( context: Context, -) : ViewGroup(context) { +) : ViewGroup(context), + ReactPointerEventsView { private val dialogManager = FormSheetDialogManager(context, this) private var isOpen = false @@ -66,6 +69,11 @@ class FormSheetHost( super.onDetachedFromWindow() } + // The React children are teleported into the dialog window. This host occupies space in the + // main window, but holds no content there. NONE makes the host subtree invisible to + // hit-testing so touches reach the views behind it. + override val pointerEvents: PointerEvents = PointerEvents.NONE + override fun onLayout( changed: Boolean, l: Int,