UNPKG

@sentry/react-native

Version:
188 lines 8.23 kB
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