Skip to content

Commit 874b2a2

Browse files
authored
feat(feedback): Pull down to cancel (#4534)
1 parent fcbc6c6 commit 874b2a2

File tree

1 file changed

+46
-3
lines changed

1 file changed

+46
-3
lines changed

packages/core/src/js/feedback/FeedbackFormManager.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { logger } from '@sentry/core';
22
import * as React from 'react';
3-
import { Animated, KeyboardAvoidingView, Modal, Platform, View } from 'react-native';
3+
import { Animated, KeyboardAvoidingView, Modal, PanResponder, Platform } from 'react-native';
44

55
import { FeedbackForm } from './FeedbackForm';
66
import { modalBackground, modalSheetContainer, modalWrapper } from './FeedbackForm.styles';
77
import type { FeedbackFormStyles } from './FeedbackForm.types';
88
import { getFeedbackOptions } from './integration';
99
import { isModalSupported } from './utils';
1010

11+
const PULL_DOWN_CLOSE_THREESHOLD = 200;
12+
const PULL_DOWN_ANDROID_ACTIVATION_HEIGHT = 150;
13+
1114
class FeedbackFormManager {
1215
private static _isVisible = false;
1316
private static _setVisibility: (visible: boolean) => void;
@@ -43,14 +46,48 @@ interface FeedbackFormProviderProps {
4346
interface FeedbackFormProviderState {
4447
isVisible: boolean;
4548
backgroundOpacity: Animated.Value;
49+
panY: Animated.Value;
4650
}
4751

4852
class FeedbackFormProvider extends React.Component<FeedbackFormProviderProps> {
4953
public state: FeedbackFormProviderState = {
5054
isVisible: false,
5155
backgroundOpacity: new Animated.Value(0),
56+
panY: new Animated.Value(0),
5257
};
5358

59+
60+
private _panResponder = PanResponder.create({
61+
onStartShouldSetPanResponder: (evt, _gestureState) => {
62+
// On Android allow pulling down only from the top to avoid breaking native gestures
63+
return Platform.OS !== 'android' || evt.nativeEvent.pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT;
64+
},
65+
onMoveShouldSetPanResponder: (evt, _gestureState) => {
66+
return Platform.OS !== 'android' || evt.nativeEvent.pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT;
67+
},
68+
onPanResponderMove: (_, gestureState) => {
69+
if (gestureState.dy > 0) {
70+
this.state.panY.setValue(gestureState.dy);
71+
}
72+
},
73+
onPanResponderRelease: (_, gestureState) => {
74+
if (gestureState.dy > PULL_DOWN_CLOSE_THREESHOLD) { // Close on swipe below a certain threshold
75+
Animated.timing(this.state.panY, {
76+
toValue: 600,
77+
duration: 200,
78+
useNativeDriver: true,
79+
}).start(() => {
80+
this._handleClose();
81+
});
82+
} else { // Animate it back to the original position
83+
Animated.spring(this.state.panY, {
84+
toValue: 0,
85+
useNativeDriver: true,
86+
}).start();
87+
}
88+
},
89+
});
90+
5491
public constructor(props: FeedbackFormProviderProps) {
5592
super(props);
5693
FeedbackFormManager.initialize(this._setVisibilityFunction);
@@ -99,12 +136,15 @@ class FeedbackFormProvider extends React.Component<FeedbackFormProviderProps> {
99136
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
100137
style={modalBackground}
101138
>
102-
<View style={modalSheetContainer}>
139+
<Animated.View
140+
style={[modalSheetContainer, { transform: [{ translateY: this.state.panY }] }]}
141+
{...this._panResponder.panHandlers}
142+
>
103143
<FeedbackForm {...getFeedbackOptions()}
104144
onFormClose={this._handleClose}
105145
onFormSubmitted={this._handleClose}
106146
/>
107-
</View>
147+
</Animated.View>
108148
</KeyboardAvoidingView>
109149
</Modal>
110150
</Animated.View>
@@ -115,6 +155,9 @@ class FeedbackFormProvider extends React.Component<FeedbackFormProviderProps> {
115155

116156
private _setVisibilityFunction = (visible: boolean): void => {
117157
this.setState({ isVisible: visible });
158+
if (visible) {
159+
this.state.panY.setValue(0);
160+
}
118161
};
119162

120163
private _handleClose = (): void => {

0 commit comments

Comments
 (0)