1
1
import { logger } from '@sentry/core' ;
2
2
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' ;
4
4
5
5
import { FeedbackWidget } from './FeedbackWidget' ;
6
6
import { modalBackground , modalSheetContainer , modalWrapper } from './FeedbackWidget.styles' ;
7
7
import type { FeedbackWidgetStyles } from './FeedbackWidget.types' ;
8
8
import { getFeedbackOptions } from './integration' ;
9
9
import { isModalSupported } from './utils' ;
10
10
11
+ const PULL_DOWN_CLOSE_THREESHOLD = 200 ;
12
+ const PULL_DOWN_ANDROID_ACTIVATION_HEIGHT = 150 ;
13
+
11
14
class FeedbackWidgetManager {
12
15
private static _isVisible = false ;
13
16
private static _setVisibility : ( visible : boolean ) => void ;
@@ -43,14 +46,47 @@ interface FeedbackWidgetProviderProps {
43
46
interface FeedbackWidgetProviderState {
44
47
isVisible : boolean ;
45
48
backgroundOpacity : Animated . Value ;
49
+ panY : Animated . Value ;
46
50
}
47
51
48
52
class FeedbackWidgetProvider extends React . Component < FeedbackWidgetProviderProps > {
49
53
public state : FeedbackWidgetProviderState = {
50
54
isVisible : false ,
51
55
backgroundOpacity : new Animated . Value ( 0 ) ,
56
+ panY : new Animated . Value ( 0 ) ,
52
57
} ;
53
58
59
+ private _panResponder = PanResponder . create ( {
60
+ onStartShouldSetPanResponder : ( evt , _gestureState ) => {
61
+ // On Android allow pulling down only from the top to avoid breaking native gestures
62
+ return Platform . OS !== 'android' || evt . nativeEvent . pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT ;
63
+ } ,
64
+ onMoveShouldSetPanResponder : ( evt , _gestureState ) => {
65
+ return Platform . OS !== 'android' || evt . nativeEvent . pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT ;
66
+ } ,
67
+ onPanResponderMove : ( _ , gestureState ) => {
68
+ if ( gestureState . dy > 0 ) {
69
+ this . state . panY . setValue ( gestureState . dy ) ;
70
+ }
71
+ } ,
72
+ onPanResponderRelease : ( _ , gestureState ) => {
73
+ if ( gestureState . dy > PULL_DOWN_CLOSE_THREESHOLD ) { // Close on swipe below a certain threshold
74
+ Animated . timing ( this . state . panY , {
75
+ toValue : 600 ,
76
+ duration : 200 ,
77
+ useNativeDriver : true ,
78
+ } ) . start ( ( ) => {
79
+ this . _handleClose ( ) ;
80
+ } ) ;
81
+ } else { // Animate it back to the original position
82
+ Animated . spring ( this . state . panY , {
83
+ toValue : 0 ,
84
+ useNativeDriver : true ,
85
+ } ) . start ( ) ;
86
+ }
87
+ } ,
88
+ } ) ;
89
+
54
90
public constructor ( props : FeedbackWidgetProviderProps ) {
55
91
super ( props ) ;
56
92
FeedbackWidgetManager . initialize ( this . _setVisibilityFunction ) ;
@@ -99,12 +135,15 @@ class FeedbackWidgetProvider extends React.Component<FeedbackWidgetProviderProps
99
135
behavior = { Platform . OS === 'ios' ? 'padding' : 'height' }
100
136
style = { modalBackground }
101
137
>
102
- < View style = { modalSheetContainer } >
138
+ < Animated . View
139
+ style = { [ modalSheetContainer , { transform : [ { translateY : this . state . panY } ] } ] }
140
+ { ...this . _panResponder . panHandlers }
141
+ >
103
142
< FeedbackWidget { ...getFeedbackOptions ( ) }
104
143
onFormClose = { this . _handleClose }
105
144
onFormSubmitted = { this . _handleClose }
106
145
/>
107
- </ View >
146
+ </ Animated . View >
108
147
</ KeyboardAvoidingView >
109
148
</ Modal >
110
149
</ Animated . View >
@@ -115,6 +154,9 @@ class FeedbackWidgetProvider extends React.Component<FeedbackWidgetProviderProps
115
154
116
155
private _setVisibilityFunction = ( visible : boolean ) : void => {
117
156
this . setState ( { isVisible : visible } ) ;
157
+ if ( visible ) {
158
+ this . state . panY . setValue ( 0 ) ;
159
+ }
118
160
} ;
119
161
120
162
private _handleClose = ( ) : void => {
0 commit comments