@sentry/react-native
Version:
Official Sentry SDK for react-native
188 lines • 8.23 kB
JavaScript
import { logger } from '@sentry/core';
import * as React from 'react';
import { Animated, Dimensions, Easing, Modal, PanResponder, Platform, ScrollView, View } from 'react-native';
import { notWeb } from '../utils/environment';
import { FeedbackWidget } from './FeedbackWidget';
import { modalSheetContainer, modalWrapper, topSpacer } from './FeedbackWidget.styles';
import { getFeedbackOptions } from './integration';
import { lazyLoadAutoInjectFeedbackIntegration } from './lazy';
import { isModalSupported } from './utils';
const PULL_DOWN_CLOSE_THRESHOLD = 200;
const SLIDE_ANIMATION_DURATION = 200;
const BACKGROUND_ANIMATION_DURATION = 200;
class FeedbackWidgetManager {
static initialize(setVisibility) {
this._setVisibility = setVisibility;
}
/**
* For testing purposes only.
*/
static reset() {
this._isVisible = false;
this._setVisibility = undefined;
}
static show() {
if (this._setVisibility) {
this._isVisible = true;
this._setVisibility(true);
}
else {
// This message should be always shown otherwise it's not possible to use the widget.
// eslint-disable-next-line no-console
console.warn('[Sentry] FeedbackWidget requires `Sentry.wrap(RootComponent)` to be called before `showFeedbackWidget()`.');
}
}
static hide() {
if (this._setVisibility) {
this._isVisible = false;
this._setVisibility(false);
}
else {
// This message should be always shown otherwise it's not possible to use the widget.
// eslint-disable-next-line no-console
console.warn('[Sentry] FeedbackWidget requires `Sentry.wrap(RootComponent)` before interacting with the widget.');
}
}
static isFormVisible() {
return this._isVisible;
}
}
FeedbackWidgetManager._isVisible = false;
class FeedbackWidgetProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
isVisible: false,
backgroundOpacity: new Animated.Value(0),
panY: new Animated.Value(Dimensions.get('screen').height),
isScrollAtTop: true,
};
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (_, gestureState) => {
return notWeb() && this.state.isScrollAtTop && gestureState.dy > 0;
},
onMoveShouldSetPanResponder: (_, gestureState) => {
return notWeb() && this.state.isScrollAtTop && gestureState.dy > 0;
},
onPanResponderMove: (_, gestureState) => {
if (gestureState.dy > 0) {
this.state.panY.setValue(gestureState.dy);
}
},
onPanResponderRelease: (_, gestureState) => {
if (gestureState.dy > PULL_DOWN_CLOSE_THRESHOLD) {
// Close on swipe below a certain threshold
Animated.timing(this.state.panY, {
toValue: Dimensions.get('screen').height,
duration: SLIDE_ANIMATION_DURATION,
useNativeDriver: true,
}).start(() => {
this._handleClose();
});
}
else {
// Animate it back to the original position
Animated.spring(this.state.panY, {
toValue: 0,
useNativeDriver: true,
}).start();
}
},
});
this._handleScroll = (event) => {
this.setState({ isScrollAtTop: event.nativeEvent.contentOffset.y <= 0 });
};
this._setVisibilityFunction = (visible) => {
const updateState = () => {
this.setState({ isVisible: visible });
};
if (!visible) {
Animated.parallel([
Animated.timing(this.state.panY, {
toValue: Dimensions.get('screen').height,
duration: SLIDE_ANIMATION_DURATION,
useNativeDriver: true,
easing: Easing.out(Easing.quad),
}),
Animated.timing(this.state.backgroundOpacity, {
toValue: 0,
duration: BACKGROUND_ANIMATION_DURATION,
useNativeDriver: true,
easing: Easing.out(Easing.quad),
})
]).start(() => {
// Change of the state unmount the component
// which would cancel the animation
updateState();
});
}
else {
updateState();
}
};
this._handleClose = () => {
FeedbackWidgetManager.hide();
};
FeedbackWidgetManager.initialize(this._setVisibilityFunction);
}
/**
* Animates the background opacity when the modal is shown.
*/
componentDidUpdate(_prevProps, prevState) {
if (!prevState.isVisible && this.state.isVisible) {
Animated.parallel([
Animated.timing(this.state.backgroundOpacity, {
toValue: 1,
duration: BACKGROUND_ANIMATION_DURATION,
useNativeDriver: true,
easing: Easing.in(Easing.quad),
}),
Animated.timing(this.state.panY, {
toValue: 0,
duration: SLIDE_ANIMATION_DURATION,
useNativeDriver: true,
easing: Easing.in(Easing.quad),
})
]).start(() => {
logger.info('FeedbackWidgetProvider componentDidUpdate');
});
}
else if (prevState.isVisible && !this.state.isVisible) {
this.state.backgroundOpacity.setValue(0);
}
}
/**
* Renders the feedback form modal.
*/
render() {
if (!isModalSupported()) {
logger.error('FeedbackWidget Modal is not supported in React Native < 0.71 with Fabric renderer.');
return React.createElement(React.Fragment, null, this.props.children);
}
const { isVisible, backgroundOpacity } = this.state;
const backgroundColor = backgroundOpacity.interpolate({
inputRange: [0, 1],
outputRange: ['rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 0.9)'],
});
// Wrapping the `Modal` component in a `View` component is necessary to avoid
// issues like https://github.com/software-mansion/react-native-reanimated/issues/6035
return (React.createElement(React.Fragment, null,
this.props.children,
isVisible &&
React.createElement(Animated.View, { style: [modalWrapper, { backgroundColor }] },
React.createElement(Modal, { visible: isVisible, transparent: true, animationType: "none", onRequestClose: this._handleClose, testID: "feedback-form-modal" },
React.createElement(View, { style: topSpacer }),
React.createElement(Animated.View, Object.assign({ style: [modalSheetContainer, { transform: [{ translateY: this.state.panY }] }] }, this._panResponder.panHandlers),
React.createElement(ScrollView, { bounces: false, keyboardShouldPersistTaps: "handled", automaticallyAdjustKeyboardInsets: Platform.OS === 'ios', onScroll: this._handleScroll },
React.createElement(FeedbackWidget, Object.assign({}, getFeedbackOptions(), { onFormClose: this._handleClose, onFormSubmitted: this._handleClose }))))))));
}
}
const showFeedbackWidget = () => {
lazyLoadAutoInjectFeedbackIntegration();
FeedbackWidgetManager.show();
};
const resetFeedbackWidgetManager = () => {
FeedbackWidgetManager.reset();
};
export { showFeedbackWidget, FeedbackWidgetProvider, resetFeedbackWidgetManager };
//# sourceMappingURL=FeedbackWidgetManager.js.map