Skip to content

Commit d7e16cf

Browse files
committed
Merge branch 'main' into @mbert/monorepo-80rc2
2 parents bf2f65d + 3afdaa7 commit d7e16cf

12 files changed

Lines changed: 303 additions & 288 deletions

File tree

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"ignorePatterns": [
2121
"packages/react-native-gesture-handler/lib/**/*",
2222
"**/*.config.js",
23-
"scripts/*.js"
23+
"scripts/*.js",
24+
"**/node_modules/**/*"
2425
],
2526
"rules": {
2627
// removed in new jest-eslint-plugin, referenced in satya config

apps/common-app/App.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
import React, { useEffect } from 'react';
2-
import {
3-
Text,
4-
View,
5-
StyleSheet,
6-
Platform,
7-
Dimensions,
8-
StatusBar,
9-
SafeAreaView,
10-
} from 'react-native';
2+
import { Text, View, StyleSheet, Platform, Dimensions } from 'react-native';
113
import {
124
createStackNavigator,
135
StackNavigationProp,
@@ -19,6 +11,7 @@ import {
1911
RectButton,
2012
Switch,
2113
} from 'react-native-gesture-handler';
14+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
2215
import AsyncStorage from '@react-native-async-storage/async-storage';
2316
import OverflowParent from './src/release_tests/overflowParent';
2417
import DoublePinchRotate from './src/release_tests/doubleScalePinchAndRotate';
@@ -260,7 +253,6 @@ const Stack = createStackNavigator<RootStackParamList>();
260253
export default function App() {
261254
return (
262255
<GestureHandlerRootView>
263-
<StatusBar barStyle="dark-content" />
264256
<NavigationContainer>
265257
<Stack.Navigator
266258
screenOptions={{
@@ -306,6 +298,8 @@ function navigate(
306298
}
307299

308300
function MainScreen({ navigation }: StackScreenProps<ParamListBase>) {
301+
const insets = useSafeAreaInsets();
302+
309303
useEffect(() => {
310304
void AsyncStorage.multiGet([OPEN_LAST_EXAMPLE_KEY, LAST_EXAMPLE_KEY]).then(
311305
([openLastExample, lastExample]) => {
@@ -321,12 +315,16 @@ function MainScreen({ navigation }: StackScreenProps<ParamListBase>) {
321315
}, []);
322316

323317
return (
324-
<SafeAreaView style={styles.container}>
318+
<View style={styles.container}>
325319
<ListWithHeader
326320
style={styles.list}
327321
sections={EXAMPLES}
328322
keyExtractor={(example) => example.name}
329323
ListHeaderComponent={OpenLastExampleSetting}
324+
contentContainerStyle={{
325+
paddingBottom: insets.bottom,
326+
paddingTop: insets.top,
327+
}}
330328
renderItem={({ item }) => (
331329
<MainScreenItem
332330
name={item.name}
@@ -339,7 +337,7 @@ function MainScreen({ navigation }: StackScreenProps<ParamListBase>) {
339337
)}
340338
ItemSeparatorComponent={() => <View style={styles.separator} />}
341339
/>
342-
</SafeAreaView>
340+
</View>
343341
);
344342
}
345343

apps/common-app/src/ListWithHeader/ListWithHeader.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
ScrollViewProps,
55
SectionList,
66
SectionListProps,
7+
StyleSheet,
78
} from 'react-native';
89
import Animated, {
910
SharedValue,
@@ -73,9 +74,22 @@ export function ListWithHeader<ItemT, SectionT>(
7374
};
7475
});
7576

77+
const contentContainerStyle =
78+
StyleSheet.flatten(props.contentContainerStyle) || {};
79+
if (typeof contentContainerStyle.paddingTop !== 'number') {
80+
contentContainerStyle.paddingTop = 0;
81+
console.error(
82+
'ListWithHeader: contentContainerStyle.paddingTop should be a number.'
83+
);
84+
}
85+
7686
return (
7787
<GestureDetector gesture={dragGesture}>
78-
<Animated.View style={[{ flex: 1 }, containerProps]}>
88+
<Animated.View
89+
style={[
90+
{ flex: 1, marginTop: contentContainerStyle.paddingTop },
91+
containerProps,
92+
]}>
7993
<Header scrollOffset={scrollOffset} />
8094
<SectionList
8195
{...props}
@@ -137,7 +151,10 @@ const ScrollComponentWithOffset = ({
137151
<Animated.ScrollView
138152
{...props}
139153
ref={scrollRef}
140-
contentContainerStyle={{ paddingTop: HEADER_HEIGHT }}
154+
contentContainerStyle={[
155+
props.contentContainerStyle,
156+
{ paddingTop: HEADER_HEIGHT },
157+
]}
141158
scrollEventThrottle={1}
142159
animatedProps={scrollProps}
143160
/>

apps/expo-example/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
"@react-navigation/stack": "^7.2.10",
2222
"@swmansion/icons": "^0.0.1",
2323
"common-app": "workspace:*",
24-
"expo": "~53.0.0-preview.7",
24+
"expo": "^53.0.9",
2525
"expo-camera": "~16.1.1",
2626
"expo-status-bar": "~2.2.1",
2727
"react": "19.0.0",
2828
"react-dom": "19.0.0",
29-
"react-native": "0.79.0",
29+
"react-native": "0.79.2",
3030
"react-native-gesture-handler": "workspace:*",
3131
"react-native-reanimated": "3.17.5",
3232
"react-native-safe-area-context": "5.4.0",

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
685685

686686
fun fail() {
687687
if (state == STATE_ACTIVE || state == STATE_UNDETERMINED || state == STATE_BEGAN) {
688+
onFail()
688689
moveToState(STATE_FAILED)
689690
}
690691
}
@@ -738,6 +739,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
738739
protected open fun onStateChange(newState: Int, previousState: Int) {}
739740
protected open fun onReset() {}
740741
protected open fun onCancel() {}
742+
protected open fun onFail() {}
741743

742744
private fun isButtonInConfig(clickedButton: Int): Boolean {
743745
if (mouseButton == 0) {
@@ -852,7 +854,11 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
852854
abstract class Factory<T : GestureHandler<T>> {
853855
abstract val type: Class<T>
854856
abstract val name: String
855-
abstract fun create(context: Context?): T
857+
858+
protected abstract fun create(context: Context?): T
859+
860+
fun create(context: Context?, handlerTag: Int): T = create(context).also { it.tag = handlerTag }
861+
856862
open fun setConfig(handler: T, config: ReadableMap) {
857863
handler.resetConfig()
858864
if (config.hasKey(KEY_SHOULD_CANCEL_WHEN_OUTSIDE)) {

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
150150
}
151151
}
152152

153-
override fun onCancel() {
153+
private fun dispatchCancelEventToView() {
154154
val time = SystemClock.uptimeMillis()
155155
val event = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, 0f, 0f, 0).apply {
156156
action = MotionEvent.ACTION_CANCEL
@@ -159,6 +159,10 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
159159
event.recycle()
160160
}
161161

162+
override fun onCancel() = dispatchCancelEventToView()
163+
164+
override fun onFail() = dispatchCancelEventToView()
165+
162166
override fun onReset() {
163167
this.hook = defaultHook
164168
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package com.swmansion.gesturehandler.react
2+
3+
import android.view.MotionEvent
4+
import com.facebook.react.bridge.ReactApplicationContext
5+
import com.facebook.react.bridge.WritableMap
6+
import com.facebook.react.uimanager.events.Event
7+
import com.swmansion.gesturehandler.BuildConfig
8+
import com.swmansion.gesturehandler.ReanimatedEventDispatcher
9+
import com.swmansion.gesturehandler.core.GestureHandler
10+
import com.swmansion.gesturehandler.core.OnTouchEventListener
11+
import com.swmansion.gesturehandler.dispatchEvent
12+
13+
class RNGestureHandlerEventDispatcher(private val reactApplicationContext: ReactApplicationContext) :
14+
OnTouchEventListener {
15+
private val reanimatedEventDispatcher = ReanimatedEventDispatcher()
16+
17+
override fun <T : GestureHandler<T>> onHandlerUpdate(handler: T, event: MotionEvent) {
18+
this.dispatchHandlerUpdateEvent(handler)
19+
}
20+
21+
override fun <T : GestureHandler<T>> onStateChange(handler: T, newState: Int, oldState: Int) {
22+
this.dispatchStateChangeEvent(handler, newState, oldState)
23+
}
24+
25+
override fun <T : GestureHandler<T>> onTouchEvent(handler: T) {
26+
this.dispatchTouchEvent(handler)
27+
}
28+
29+
private fun <T : GestureHandler<T>> dispatchHandlerUpdateEvent(handler: T) {
30+
// triggers onUpdate and onChange callbacks on the JS side
31+
32+
// root containers use negative tags, we don't need to dispatch events for them to the JS
33+
if (handler.tag < 0 || handler.state != GestureHandler.STATE_ACTIVE) {
34+
return
35+
}
36+
37+
val handlerFactory = RNGestureHandlerFactoryUtil.findFactoryForHandler(handler) ?: return
38+
when (handler.actionType) {
39+
GestureHandler.ACTION_TYPE_REANIMATED_WORKLET -> {
40+
// Reanimated worklet
41+
val event = RNGestureHandlerEvent.obtain(
42+
handler,
43+
handlerFactory.createEventBuilder(handler),
44+
)
45+
sendEventForReanimated(event)
46+
}
47+
GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT -> {
48+
// Animated with useNativeDriver: true
49+
val event = RNGestureHandlerEvent.obtain(
50+
handler,
51+
handlerFactory.createEventBuilder(handler),
52+
true,
53+
)
54+
sendEventForNativeAnimatedEvent(event)
55+
}
56+
GestureHandler.ACTION_TYPE_JS_FUNCTION_OLD_API -> {
57+
// JS function, Animated.event with useNativeDriver: false using old API
58+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
59+
val data = RNGestureHandlerEvent.createEventData(
60+
handlerFactory.createEventBuilder(handler),
61+
)
62+
sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data)
63+
} else {
64+
val event = RNGestureHandlerEvent.obtain(
65+
handler,
66+
handlerFactory.createEventBuilder(handler),
67+
)
68+
sendEventForDirectEvent(event)
69+
}
70+
}
71+
GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API -> {
72+
// JS function, Animated.event with useNativeDriver: false using new API
73+
val data =
74+
RNGestureHandlerEvent.createEventData(handlerFactory.createEventBuilder(handler))
75+
sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data)
76+
}
77+
}
78+
}
79+
80+
private fun <T : GestureHandler<T>> dispatchStateChangeEvent(handler: T, newState: Int, oldState: Int) {
81+
// triggers onBegin, onStart, onEnd, onFinalize callbacks on the JS side
82+
83+
if (handler.tag < 0) {
84+
// root containers use negative tags, we don't need to dispatch events for them to the JS
85+
return
86+
}
87+
val handlerFactory = RNGestureHandlerFactoryUtil.findFactoryForHandler(handler) ?: return
88+
89+
when (handler.actionType) {
90+
GestureHandler.ACTION_TYPE_REANIMATED_WORKLET -> {
91+
// Reanimated worklet
92+
val event = RNGestureHandlerStateChangeEvent.obtain(
93+
handler,
94+
newState,
95+
oldState,
96+
handlerFactory.createEventBuilder(handler),
97+
)
98+
sendEventForReanimated(event)
99+
}
100+
GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT, GestureHandler.ACTION_TYPE_JS_FUNCTION_OLD_API -> {
101+
// JS function or Animated.event with useNativeDriver: false with old API
102+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
103+
val data = RNGestureHandlerStateChangeEvent.createEventData(
104+
handlerFactory.createEventBuilder(handler),
105+
newState,
106+
oldState,
107+
)
108+
sendEventForDeviceEvent(RNGestureHandlerStateChangeEvent.EVENT_NAME, data)
109+
} else {
110+
val event = RNGestureHandlerStateChangeEvent.obtain(
111+
handler,
112+
newState,
113+
oldState,
114+
handlerFactory.createEventBuilder(handler),
115+
)
116+
sendEventForDirectEvent(event)
117+
}
118+
}
119+
GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API -> {
120+
// JS function or Animated.event with useNativeDriver: false with new API
121+
val data = RNGestureHandlerStateChangeEvent.createEventData(
122+
handlerFactory.createEventBuilder(handler),
123+
newState,
124+
oldState,
125+
)
126+
sendEventForDeviceEvent(RNGestureHandlerStateChangeEvent.EVENT_NAME, data)
127+
}
128+
}
129+
}
130+
131+
private fun <T : GestureHandler<T>> dispatchTouchEvent(handler: T) {
132+
// triggers onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled callbacks on the JS side
133+
134+
if (handler.tag < 0) {
135+
// root containers use negative tags, we don't need to dispatch events for them to the JS
136+
return
137+
}
138+
139+
if (handler.state != GestureHandler.STATE_BEGAN &&
140+
handler.state != GestureHandler.STATE_ACTIVE &&
141+
handler.state != GestureHandler.STATE_UNDETERMINED &&
142+
handler.view == null
143+
) {
144+
return
145+
}
146+
147+
when (handler.actionType) {
148+
GestureHandler.ACTION_TYPE_REANIMATED_WORKLET -> {
149+
// Reanimated worklet
150+
val event = RNGestureHandlerTouchEvent.obtain(handler)
151+
sendEventForReanimated(event)
152+
}
153+
GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API -> {
154+
// JS function, Animated.event with useNativeDriver: false with new API
155+
val data = RNGestureHandlerTouchEvent.createEventData(handler)
156+
sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data)
157+
}
158+
}
159+
}
160+
161+
private fun <T : Event<T>> sendEventForReanimated(event: T) {
162+
// Delivers the event to Reanimated.
163+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
164+
// Send event directly to Reanimated
165+
reanimatedEventDispatcher.sendEvent(event, reactApplicationContext)
166+
} else {
167+
// In the old architecture, Reanimated subscribes for specific direct events.
168+
sendEventForDirectEvent(event)
169+
}
170+
}
171+
172+
private fun sendEventForNativeAnimatedEvent(event: RNGestureHandlerEvent) {
173+
// Delivers the event to NativeAnimatedModule.
174+
// TODO: send event directly to NativeAnimated[Turbo]Module
175+
// ReactContext.dispatchEvent is an extension function, depending on the architecture it will
176+
// dispatch event using UIManagerModule or FabricUIManager.
177+
reactApplicationContext.dispatchEvent(event)
178+
}
179+
180+
private fun <T : Event<T>> sendEventForDirectEvent(event: T) {
181+
// Delivers the event to JS as a direct event. This method is called only on Paper.
182+
reactApplicationContext.dispatchEvent(event)
183+
}
184+
185+
private fun sendEventForDeviceEvent(eventName: String, data: WritableMap) {
186+
// Delivers the event to JS as a device event.
187+
reactApplicationContext.deviceEventEmitter.emit(eventName, data)
188+
}
189+
}

0 commit comments

Comments
 (0)