From 103e08b914cbc6032387a929367207f9ccd9fcaf Mon Sep 17 00:00:00 2001 From: Jakub Gonet Date: Fri, 27 Aug 2021 09:19:27 +0200 Subject: [PATCH 1/3] Fix modals 1. Removed RNGHModalUtils This class gave access to ReactModalHostView.DialogRootViewGroup in order to call #onChildStartedNativeGesture() and use instanceof on it. Checks for ReactRootView/DialogRootViewGroup can be unified by utilizing RootView (which both of those classes implement). 2. Always render GestureHandlerRootView This prevents gestures from not being delivered to RNGH when another GestureHandlerRootView is used before modal (e.g. from React Navigation). Note: This isn't an ideal fix since a new orchestrator will be created, preventing modals from interacting with handlers outside of them. However, this use case is rare. Having gestures working in modals is already an improvement. --- .../react/views/modal/RNGHModalUtils.java | 21 ------------ .../react/RNGestureHandlerRootHelper.kt | 10 +++--- .../react/RNGestureHandlerRootView.kt | 9 +++++ src/GestureHandlerRootView.android.tsx | 34 +++++-------------- src/GestureHandlerRootView.tsx | 13 +++++-- 5 files changed, 33 insertions(+), 54 deletions(-) delete mode 100644 android/src/main/java/com/facebook/react/views/modal/RNGHModalUtils.java diff --git a/android/src/main/java/com/facebook/react/views/modal/RNGHModalUtils.java b/android/src/main/java/com/facebook/react/views/modal/RNGHModalUtils.java deleted file mode 100644 index b7e9858163..0000000000 --- a/android/src/main/java/com/facebook/react/views/modal/RNGHModalUtils.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.facebook.react.views.modal; - -import android.view.MotionEvent; -import android.view.ViewGroup; -import android.view.ViewParent; - -/** - * For handling gestures inside RNGH we need to have access to some methods of - * `ReactModalHostView.DialogRootViewGroup`. This class is not available outside - * package so this file exports important features. - */ - -public class RNGHModalUtils { - public static void dialogRootViewGroupOnChildStartedNativeGesture(ViewGroup modal, MotionEvent androidEvent) { - ((ReactModalHostView.DialogRootViewGroup) modal).onChildStartedNativeGesture(androidEvent); - } - - public static boolean isDialogRootViewGroup(ViewParent modal) { - return modal instanceof ReactModalHostView.DialogRootViewGroup; - } -} diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt index f2981386ec..8c2045331f 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt @@ -5,11 +5,11 @@ import android.util.Log import android.view.MotionEvent import android.view.ViewGroup import android.view.ViewParent -import com.facebook.react.ReactRootView import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.common.ReactConstants -import com.facebook.react.views.modal.RNGHModalUtils +import com.facebook.react.uimanager.RootView +import com.facebook.react.views.modal.ReactModalHostView import com.swmansion.gesturehandler.GestureHandler import com.swmansion.gesturehandler.GestureHandlerOrchestrator @@ -71,10 +71,8 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: val event = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, 0f, 0f, 0).apply { action = MotionEvent.ACTION_CANCEL } - if (rootView is ReactRootView) { + if (rootView is RootView) { rootView.onChildStartedNativeGesture(event) - } else { - RNGHModalUtils.dialogRootViewGroupOnChildStartedNativeGesture(rootView, event) } } } @@ -122,7 +120,7 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: private fun findRootViewTag(viewGroup: ViewGroup): ViewGroup { UiThreadUtil.assertOnUiThread() var parent: ViewParent? = viewGroup - while (parent != null && parent !is ReactRootView && !RNGHModalUtils.isDialogRootViewGroup(parent)) { + while (parent != null && parent !is RootView) { parent = parent.parent } checkNotNull(parent) { diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt index f66c883dad..a1c96be41e 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt @@ -4,9 +4,12 @@ import android.content.Context import android.util.Log import android.view.MotionEvent import android.view.ViewGroup +import android.view.ViewParent import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.common.ReactConstants +import com.facebook.react.uimanager.RootView +import com.facebook.react.views.modal.ReactModalHostView import com.facebook.react.views.view.ReactViewGroup class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { @@ -51,6 +54,12 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { if (parent is RNGestureHandlerEnabledRootView || parent is RNGestureHandlerRootView) { return true } + // Checks other roots views but it's mainly for ReactModalHostView.DialogRootViewGroup + // since modals are outside RN hierachy and we have to initialize GH's root view for it + // Note that RNGestureHandlerEnabledRootView implements RootView - that's why this check has to be below + if (parent is RootView){ + return false + } parent = parent.parent } return false diff --git a/src/GestureHandlerRootView.android.tsx b/src/GestureHandlerRootView.android.tsx index 706f01eb7d..459565ee2d 100644 --- a/src/GestureHandlerRootView.android.tsx +++ b/src/GestureHandlerRootView.android.tsx @@ -1,34 +1,18 @@ import * as React from 'react'; -import { PropsWithChildren } from 'react'; -import { View, requireNativeComponent } from 'react-native'; +import { requireNativeComponent } from 'react-native'; +import { GestureHandlerRootViewProps } from './GestureHandlerRootView'; const GestureHandlerRootViewNative = requireNativeComponent( 'GestureHandlerRootView' ); -const GestureHandlerRootViewContext = React.createContext(false); - -type Props = PropsWithChildren>; - -export default function GestureHandlerRootView({ children, ...rest }: Props) { +export default function GestureHandlerRootView({ + children, + ...rest +}: GestureHandlerRootViewProps) { return ( - - {(available) => { - if (available) { - // If we already have a parent wrapped in the gesture handler root view, - // We don't need to wrap it again in root view - // We still wrap it in a normal view so our styling stays the same - return {children}; - } - - return ( - - - {children} - - - ); - }} - + + {children} + ); } diff --git a/src/GestureHandlerRootView.tsx b/src/GestureHandlerRootView.tsx index f20f2993ac..610c771be3 100644 --- a/src/GestureHandlerRootView.tsx +++ b/src/GestureHandlerRootView.tsx @@ -1,3 +1,12 @@ -import { View } from 'react-native'; +import * as React from 'react'; +import { PropsWithChildren } from 'react'; +import { View, ViewProps } from 'react-native'; -export default View; +export interface GestureHandlerRootViewProps + extends PropsWithChildren {} + +export default function GestureHandlerRootView({ + ...rest +}: GestureHandlerRootViewProps) { + return ; +} From c0f83da59f2fe23ac7a9a3f79694af2dca334641 Mon Sep 17 00:00:00 2001 From: Jakub Gonet Date: Fri, 27 Aug 2021 10:23:11 +0200 Subject: [PATCH 2/3] Add repro example, remove native GH initialization --- .../main/java/com/example/MainActivity.java | 13 ---- examples/Example/src/App.tsx | 5 ++ .../nestedGHRootViewWithModal.tsx | 63 +++++++++++++++++++ 3 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 examples/Example/src/release_tests/nestedGHRootViewWithModal.tsx diff --git a/examples/Example/android/app/src/main/java/com/example/MainActivity.java b/examples/Example/android/app/src/main/java/com/example/MainActivity.java index 7af3d4880e..557797a890 100644 --- a/examples/Example/android/app/src/main/java/com/example/MainActivity.java +++ b/examples/Example/android/app/src/main/java/com/example/MainActivity.java @@ -1,9 +1,6 @@ package com.example; import com.facebook.react.ReactActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; -import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; public class MainActivity extends ReactActivity { @@ -15,14 +12,4 @@ public class MainActivity extends ReactActivity { protected String getMainComponentName() { return "Example"; } - - @Override - protected ReactActivityDelegate createReactActivityDelegate() { - return new ReactActivityDelegate(this, getMainComponentName()) { - @Override - protected ReactRootView createRootView() { - return new RNGestureHandlerEnabledRootView(MainActivity.this); - } - }; - } } diff --git a/examples/Example/src/App.tsx b/examples/Example/src/App.tsx index de987cd8c2..0efb6aebaf 100644 --- a/examples/Example/src/App.tsx +++ b/examples/Example/src/App.tsx @@ -14,6 +14,7 @@ import DoubleDraggable from './release_tests/doubleDraggable'; import { ComboWithGHScroll } from './release_tests/combo'; import { TouchablesIndex, TouchableExample } from './release_tests/touchables'; import Rows from './release_tests/rows'; +import NestedGestureHandlerRootViewWithModal from './release_tests/nestedGHRootViewWithModal'; import { PinchableBox } from './recipes/scaleAndRotate'; import PanAndScroll from './recipes/panAndScroll'; import { BottomSheet } from './showcase/bottomSheet'; @@ -67,6 +68,10 @@ const EXAMPLES: ExamplesSection[] = [ { sectionTitle: 'Release tests', data: [ + { + name: 'Modals with nested GHRootViews - issue #139', + component: NestedGestureHandlerRootViewWithModal, + }, { name: 'Double pinch & rotate', component: DoublePinchRotate }, { name: 'Double draggable', component: DoubleDraggable }, { name: 'Rows', component: Rows }, diff --git a/examples/Example/src/release_tests/nestedGHRootViewWithModal.tsx b/examples/Example/src/release_tests/nestedGHRootViewWithModal.tsx new file mode 100644 index 0000000000..17cf26dde1 --- /dev/null +++ b/examples/Example/src/release_tests/nestedGHRootViewWithModal.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import { useState } from 'react'; +import { StyleSheet, Modal, View, Text } from 'react-native'; + +import { + GestureHandlerRootView, + TouchableOpacity, +} from 'react-native-gesture-handler'; +import { DraggableBox } from '../basic/draggable'; + +export default function App() { + const [isModalVisible, setIsModalVisible] = useState(false); + + function ToggleModalButton() { + return ( + setIsModalVisible((visible) => !visible)}> + {isModalVisible ? 'Close' : 'Open'} modal + + ); + } + + return ( + + + DraggableBox inside modal should be moveable + + + + + + + + + + + + ); +} +const styles = StyleSheet.create({ + modalView: { + margin: 20, + marginTop: 200, + backgroundColor: 'transparent', + borderRadius: 6, + padding: 20, + alignItems: 'center', + borderWidth: 2, + }, + container: { + flex: 1, + display: 'flex', + alignItems: 'center', + }, + description: { + margin: 20, + }, + button: { + borderWidth: 2, + padding: 10, + }, +}); From d343835b1f5329228b169d25c9dc7e072db49fee Mon Sep 17 00:00:00 2001 From: Jakub Gonet Date: Fri, 27 Aug 2021 10:34:11 +0200 Subject: [PATCH 3/3] Format Kotlin code --- .../swmansion/gesturehandler/react/RNGestureHandlerRootView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt index a1c96be41e..b6f3dbb467 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt @@ -57,7 +57,7 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { // Checks other roots views but it's mainly for ReactModalHostView.DialogRootViewGroup // since modals are outside RN hierachy and we have to initialize GH's root view for it // Note that RNGestureHandlerEnabledRootView implements RootView - that's why this check has to be below - if (parent is RootView){ + if (parent is RootView) { return false } parent = parent.parent