Skip to content

Commit

Permalink
Fix modals (#1603)
Browse files Browse the repository at this point in the history
Fixes #139

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).

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.
  • Loading branch information
jakub-gonet authored Sep 24, 2021
1 parent d810dfa commit 5572e8d
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 67 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
}
}
}
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
13 changes: 0 additions & 13 deletions example/android/app/src/main/java/com/example/MainActivity.java
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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);
}
};
}
}
5 changes: 5 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 },
Expand Down
63 changes: 63 additions & 0 deletions example/src/release_tests/nestedGHRootViewWithModal.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<TouchableOpacity
style={styles.button}
onPress={() => setIsModalVisible((visible) => !visible)}>
<Text>{isModalVisible ? 'Close' : 'Open'} modal</Text>
</TouchableOpacity>
);
}

return (
<GestureHandlerRootView style={styles.container}>
<Text style={styles.description}>
DraggableBox inside modal should be moveable
</Text>
<ToggleModalButton />
<Modal visible={isModalVisible}>
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.modalView}>
<DraggableBox />
<ToggleModalButton />
</View>
</GestureHandlerRootView>
</Modal>
</GestureHandlerRootView>
);
}
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,
},
});
34 changes: 9 additions & 25 deletions src/GestureHandlerRootView.android.tsx
Original file line number Diff line number Diff line change
@@ -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<Record<string, unknown>>;

export default function GestureHandlerRootView({ children, ...rest }: Props) {
export default function GestureHandlerRootView({
children,
...rest
}: GestureHandlerRootViewProps) {
return (
<GestureHandlerRootViewContext.Consumer>
{(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 <View {...rest}>{children}</View>;
}

return (
<GestureHandlerRootViewContext.Provider value>
<GestureHandlerRootViewNative {...rest}>
{children}
</GestureHandlerRootViewNative>
</GestureHandlerRootViewContext.Provider>
);
}}
</GestureHandlerRootViewContext.Consumer>
<GestureHandlerRootViewNative {...rest}>
{children}
</GestureHandlerRootViewNative>
);
}
13 changes: 11 additions & 2 deletions src/GestureHandlerRootView.tsx
Original file line number Diff line number Diff line change
@@ -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<ViewProps> {}

export default function GestureHandlerRootView({
...rest
}: GestureHandlerRootViewProps) {
return <View {...rest} />;
}

1 comment on commit 5572e8d

@Humad
Copy link

@Humad Humad commented on 5572e8d Sep 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for this!

Please sign in to comment.