Skip to content

Commit 757265d

Browse files
authored
Fix grabbing delegate in setPressed (#3742)
## Description On android unction `setPressed` grabs the delagate without any checks. As a result, in specific cases - such as in #3735 - pressed is registered when it should not be. ## Test plan The same as in #3735, tested on the following code. <details> ``` import { useEffect, useState } from "react"; import { StyleSheet, Text, View } from "react-native"; import { GestureHandlerRootView, Pressable, } from "react-native-gesture-handler"; import { KeyboardProvider, KeyboardStickyView, } from "react-native-keyboard-controller"; import { SafeAreaProvider, useSafeAreaInsets, } from "react-native-safe-area-context"; const rippleConfig = { color: "#666666", borderless: true, foreground: true, }; function App() { return ( <SafeAreaProvider> <GestureHandlerRootView> <KeyboardProvider statusBarTranslucent={true} navigationBarTranslucent={true} preserveEdgeToEdge={true} > <AppContent /> </KeyboardProvider> </GestureHandlerRootView> </SafeAreaProvider> ); } function Snackbar({ visible, setSnackbarVisible, }: { visible?: boolean; setSnackbarVisible: (visible: boolean) => void; }) { const safeAreaInsets = useSafeAreaInsets(); useEffect(() => { const timeout = setTimeout(() => { setSnackbarVisible(false); }, 3000); return () => clearTimeout(timeout); }, [visible, setSnackbarVisible]); return ( <KeyboardStickyView offset={{ closed: -safeAreaInsets.bottom, opened: 0 }}> {visible && ( <View style={[styles.snackbar]}> <Text style={styles.snackbarText}>Snackbar</Text> </View> )} </KeyboardStickyView> ); } function Screen({ setSnackbarVisible, }: { setSnackbarVisible: (visible: boolean) => void; }) { const safeAreaInsets = useSafeAreaInsets(); const [count, setCount] = useState(0); const incrementCount = () => { setCount(count + 1); }; const handleButtonPress = () => { setSnackbarVisible(true); }; return ( <View style={[ styles.container, { flex: 1, paddingTop: safeAreaInsets.top, paddingBottom: safeAreaInsets.bottom, }, ]} > <View style={styles.centerContent}> <Text style={styles.countText}>Count: {count}</Text> <Pressable style={styles.button} onPress={incrementCount} android_ripple={rippleConfig} > <Text style={styles.buttonText}>Increment</Text> </Pressable> </View> <View style={styles.bottomContent}> <Pressable style={styles.button} onPress={handleButtonPress} android_ripple={rippleConfig} > <Text style={styles.buttonText}>Show snackbar</Text> </Pressable> </View> </View> ); } function AppContent() { const [snackbarVisible, setSnackbarVisible] = useState(false); return ( <> <Screen setSnackbarVisible={setSnackbarVisible} /> <Snackbar visible={snackbarVisible} setSnackbarVisible={setSnackbarVisible} /> </> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#f5f5f5", }, centerContent: { flex: 1, justifyContent: "center", alignItems: "center", paddingHorizontal: 20, }, bottomContent: { paddingHorizontal: 20, paddingBottom: 20, flexDirection: "row", justifyContent: "center", }, countText: { fontSize: 24, fontWeight: "bold", marginBottom: 20, color: "#333", }, button: { backgroundColor: "#007AFF", paddingHorizontal: 30, paddingVertical: 15, borderRadius: 8, alignItems: "center", justifyContent: "center", shadowColor: "#000", shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, }, buttonText: { color: "white", fontSize: 20, fontWeight: "600", }, disabledButton: { backgroundColor: "#CCCCCC", shadowOpacity: 0.1, elevation: 2, }, disabledButtonText: { color: "#666666", }, snackbar: { position: "absolute", left: 15, right: 15, bottom: 30, height: 50, backgroundColor: "#008000", paddingHorizontal: 15, paddingVertical: 12, borderRadius: 8, alignItems: "center", justifyContent: "center", shadowColor: "#000", shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 8, }, snackbarText: { color: "white", fontSize: 14, }, }); export default App; ``` </details> * install `react-native-keyboard-controller` * Press on "Increment" to see that the button/pressable works. * Press on "Show snackbar" to show the snackbar. * While the snackbar is visible, press the "Show snackbar" button again. * When the snackbar is gone, observe that the ripple effect on the "Show snackbar" button is still visible. Don't touch this button. * Press on "Increment" and observe that it's not working anymore.
1 parent ae25b49 commit 757265d

File tree

1 file changed

+5
-10
lines changed

1 file changed

+5
-10
lines changed

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,11 @@ class RNGestureHandlerButtonViewManager :
440440
val isResponder = tryGrabbingResponder()
441441
if (isResponder) {
442442
isTouched = true
443+
// when setPressed(true) is called before canBegin it will not call super.setPressed
444+
// in this case we call it here
445+
setPressed(true)
443446
}
447+
444448
return isResponder
445449
}
446450

@@ -515,16 +519,6 @@ class RNGestureHandlerButtonViewManager :
515519
}
516520

517521
override fun setPressed(pressed: Boolean) {
518-
// there is a possibility of this method being called before NativeViewGestureHandler has
519-
// opportunity to call canStart, in that case we need to grab responder in case the gesture
520-
// will activate
521-
// when canStart is called eventually, tryGrabbingResponder will return true if the button
522-
// already is a responder
523-
if (pressed) {
524-
if (tryGrabbingResponder()) {
525-
soundResponder = this
526-
}
527-
}
528522
// button can be pressed alongside other button if both are non-exclusive and it doesn't have
529523
// any pressed children (to prevent pressing the parent when children is pressed).
530524
val canBePressedAlongsideOther = !exclusive && touchResponder?.exclusive != true && !isChildTouched()
@@ -535,6 +529,7 @@ class RNGestureHandlerButtonViewManager :
535529
isTouched = pressed
536530
super.setPressed(pressed)
537531
}
532+
538533
if (!pressed && touchResponder === this) {
539534
// if the responder is no longer pressed we release button responder
540535
isTouched = false

0 commit comments

Comments
 (0)