11import { logger } from '@sentry/core' ;
22import * 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
55import { FeedbackForm } from './FeedbackForm' ;
66import { modalBackground , modalSheetContainer , modalWrapper } from './FeedbackForm.styles' ;
77import type { FeedbackFormStyles } from './FeedbackForm.types' ;
88import { getFeedbackOptions } from './integration' ;
99import { isModalSupported } from './utils' ;
1010
11+ const PULL_DOWN_CLOSE_THREESHOLD = 200 ;
12+ const PULL_DOWN_ANDROID_ACTIVATION_HEIGHT = 150 ;
13+
1114class FeedbackFormManager {
1215 private static _isVisible = false ;
1316 private static _setVisibility : ( visible : boolean ) => void ;
@@ -43,14 +46,48 @@ interface FeedbackFormProviderProps {
4346interface FeedbackFormProviderState {
4447 isVisible : boolean ;
4548 backgroundOpacity : Animated . Value ;
49+ panY : Animated . Value ;
4650}
4751
4852class 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