Skip to content

Commit 7f8e673

Browse files
antoniskrystofwoldrichlucas-zimerman
authored
ref(feedback): Extracts FeedbackWidgetProvider in a separate file (#4718)
* Update the client implementation to use the new capture feedback js api * Updates SDK API * Adds new feedback button in the sample * Adds changelog * Removes unused mock * Update CHANGELOG.md Co-authored-by: Krystof Woldrich <[email protected]> * Directly use captureFeedback from sentry/core * Use import from core * Fixes imports order lint issue * Fixes build issue * Adds captureFeedback tests from sentry-javascript * Update CHANGELOG.md * Only deprecate client captureUserFeedback * Add simple form UI * Adds basic form functionality * Update imports * Update imports * Remove useState hook to avoid multiple react instances issues * Move types and styles in different files * Removes attachment button to be added back separately along with the implementation * Add basic field validation * Adds changelog * Updates changelog * Updates changelog * Trim whitespaces from the submitted feedback * Adds tests * Renames FeedbackFormScreen to FeedbackForm * Add beta label * Extract default text to constants * Moves constant to a separate file and aligns naming with JS * Adds input text labels * Close screen before sending the feedback to minimise wait time Co-authored-by: LucasZF <[email protected]> * Rename file for consistency * Flatten configuration hierarchy and clean up * Align required values with JS * Use Sentry user email and name when set * Simplifies email validation * Show success alert message * Aligns naming with JS and unmounts the form by default * Use the minimum config without props in the changelog * Adds development not for unimplemented function * Show email and name conditionally * Adds sentry branding (png logo) * Adds sentry logo resource * Add assets in module exports * Revert "Add assets in module exports" This reverts commit 5292475. * Revert "Adds sentry logo resource" This reverts commit d6e9229. * Revert "Adds sentry branding (png logo)" This reverts commit 8c56753. * Add last event id * Mock lastEventId * Adds beta note in the changelog * Autoinject feedback form * Updates changelog * Align colors with JS * Update CHANGELOG.md Co-authored-by: Krystof Woldrich <[email protected]> * Update CHANGELOG.md Co-authored-by: Krystof Woldrich <[email protected]> * Update CHANGELOG.md Co-authored-by: Krystof Woldrich <[email protected]> * Use regular fonts for both buttons * Handle keyboard properly * Adds an option on whether the email should be validated * Merge properties only once * Loads current user data on form construction * Remove unneeded extra padding * Fix background color issue * Adds feedback button * Updates the changelog * Fixes changelog typo * Updates styles background color Co-authored-by: Krystof Woldrich <[email protected]> * Use defaultProps * Correct defaultProps * Adds test to verify when getUser is called * Use smaller image Co-authored-by: LucasZF <[email protected]> * Add margin next to the icon * Adds bottom spacing in the ErrorScreen so that the feedback button does not hide the scrollview buttons * (2.2) feat: Add Feedback Form UI Branding logo (#4357) * Adds sentry branding logo as a base64 encoded png --------- Co-authored-by: LucasZF <[email protected]> * Autoinject feedback form (#4370) * Align changelog entry * Update changelog * Disable bouncing * Add modal ui appearance * Update snapshot tests * Fix bottom margin * Fix sheet height * Remove extra modal border * Do not expose modal styles * Animate background color * Avoid keyboard in modal * Update changelog * Fix changelog * Updates comment * Extract FeedbackButtonProps * Add public function description to satisfy lint check * Adds tests * Fix tests * Add hardcoded dark and light color themes * Rename theme options * Update snapshot tests * Include in the feedback integration * Fix circular dependency * Add theme integration options * Adds changelog * Add comment note * Align with JS api * Remove unneeded line Co-authored-by: Krystof Woldrich <[email protected]> * Place widget button below the feedback widget shadow * Expose showFeedbackButton/hideFeedbackButton methods * Add dummy integration for tracking usage * Adds button border * Fixes tests * Add accentBackground and accentForeground colors * Extract integration getter in a helper function * Adds dynamic theming support * Add snapshot tests * Show screenshot button UI * Add screenshot button integration * Add screenshot icon * Adds Take a screenshot button in FeedbackWidget * Updates snapshot tests * Fix circularDepCheck * Fix circularDepCheck * Attache captured screenshot * Hide the take screenshot button when there is a screenshot * Convert uint8Array to Base64 on the native side * ref(feedback): Extracts FeedbackWidgetProvider in a separate file * Updates exposed comments * Adds snapshot tests * Disable functionality on the Web * Add screenshot button in the sample expo app * Adds system theme tests * Test dynamically changed theme * Remove showScreenshotButton and hideScreenshotButton from the exposed api * Fix function name typo * Adds enableTakeScreenshot option * Adds happy flow test * Make flow tests more granular * Increate wait time out to fix flakiness on ci * Reset widget state after each test * Fix CI flakiness * Remove flaky test --------- Co-authored-by: Krystof Woldrich <[email protected]> Co-authored-by: LucasZF <[email protected]>
1 parent 4c988a8 commit 7f8e673

7 files changed

+254
-234
lines changed

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

+1-6
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,10 @@ import { defaultButtonConfiguration } from './defaults';
66
import { defaultButtonStyles } from './FeedbackWidget.styles';
77
import { getTheme } from './FeedbackWidget.theme';
88
import type { FeedbackButtonProps, FeedbackButtonStyles, FeedbackButtonTextConfiguration } from './FeedbackWidget.types';
9+
import { showFeedbackWidget } from './FeedbackWidgetManager';
910
import { feedbackIcon } from './icons';
1011
import { lazyLoadFeedbackIntegration } from './lazy';
1112

12-
const showFeedbackWidget = (): void => {
13-
// eslint-disable-next-line @typescript-eslint/no-var-requires
14-
const { showFeedbackWidget } = require('./FeedbackWidgetManager');
15-
showFeedbackWidget();
16-
};
17-
1813
/**
1914
* @beta
2015
* Implements a feedback button that opens the FeedbackForm.

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { defaultConfiguration } from './defaults';
2323
import defaultStyles from './FeedbackWidget.styles';
2424
import { getTheme } from './FeedbackWidget.theme';
2525
import type { FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackWidgetProps, FeedbackWidgetState, FeedbackWidgetStyles, ImagePickerConfiguration } from './FeedbackWidget.types';
26+
import { hideFeedbackButton, showScreenshotButton } from './FeedbackWidgetManager';
2627
import { lazyLoadFeedbackIntegration } from './lazy';
2728
import { getCapturedScreenshot } from './ScreenshotButton';
2829
import { base64ToUint8Array, feedbackAlertDialog, isValidEmail } from './utils';
@@ -335,8 +336,6 @@ export class FeedbackWidget extends React.Component<FeedbackWidgetProps, Feedbac
335336
)}
336337
{notWeb() && config.enableTakeScreenshot && !this.state.attachmentUri && (
337338
<TouchableOpacity style={styles.takeScreenshotButton} onPress={() => {
338-
// eslint-disable-next-line @typescript-eslint/no-var-requires
339-
const { hideFeedbackButton, showScreenshotButton } = require('./FeedbackWidgetManager');
340339
hideFeedbackButton();
341340
onCancel();
342341
showScreenshotButton();
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
11
import { logger } from '@sentry/core';
2-
import * as React from 'react';
3-
import type { NativeEventSubscription, NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
4-
import { Animated, Appearance, Dimensions, Easing, Modal, PanResponder, Platform, ScrollView, View } from 'react-native';
52

6-
import { isWeb,notWeb } from '../utils/environment';
7-
import { FeedbackButton } from './FeedbackButton';
8-
import { FeedbackWidget } from './FeedbackWidget';
9-
import { modalSheetContainer, modalWrapper, topSpacer } from './FeedbackWidget.styles';
10-
import { getTheme } from './FeedbackWidget.theme';
11-
import type { FeedbackWidgetStyles } from './FeedbackWidget.types';
12-
import { getFeedbackButtonOptions, getFeedbackOptions, getScreenshotButtonOptions } from './integration';
3+
import { isWeb } from '../utils/environment';
134
import { lazyLoadAutoInjectFeedbackButtonIntegration,lazyLoadAutoInjectFeedbackIntegration, lazyLoadAutoInjectScreenshotButtonIntegration } from './lazy';
14-
import { ScreenshotButton } from './ScreenshotButton';
15-
import { isModalSupported } from './utils';
165

17-
const PULL_DOWN_CLOSE_THRESHOLD = 200;
18-
const SLIDE_ANIMATION_DURATION = 200;
19-
const BACKGROUND_ANIMATION_DURATION = 200;
6+
export const PULL_DOWN_CLOSE_THRESHOLD = 200;
7+
export const SLIDE_ANIMATION_DURATION = 200;
8+
export const BACKGROUND_ANIMATION_DURATION = 200;
209

2110
abstract class FeedbackManager {
2211
protected static _isVisible = false;
@@ -65,226 +54,40 @@ abstract class FeedbackManager {
6554
}
6655
}
6756

68-
class FeedbackWidgetManager extends FeedbackManager {
57+
/**
58+
* Provides functionality to show and hide the feedback widget.
59+
*/
60+
export class FeedbackWidgetManager extends FeedbackManager {
61+
/**
62+
* Returns the name of the feedback component.
63+
*/
6964
protected static get _feedbackComponentName(): string {
7065
return 'FeedbackWidget';
7166
}
7267
}
7368

74-
class FeedbackButtonManager extends FeedbackManager {
69+
/**
70+
* Provides functionality to show and hide the feedback button.
71+
*/
72+
export class FeedbackButtonManager extends FeedbackManager {
73+
/**
74+
* Returns the name of the feedback component.
75+
*/
7576
protected static get _feedbackComponentName(): string {
7677
return 'FeedbackButton';
7778
}
7879
}
7980

80-
class ScreenshotButtonManager extends FeedbackManager {
81-
protected static get _feedbackComponentName(): string {
82-
return 'ScreenshotButton';
83-
}
84-
}
85-
86-
interface FeedbackWidgetProviderProps {
87-
children: React.ReactNode;
88-
styles?: FeedbackWidgetStyles;
89-
}
90-
91-
interface FeedbackWidgetProviderState {
92-
isButtonVisible: boolean;
93-
isScreenshotButtonVisible: boolean;
94-
isVisible: boolean;
95-
backgroundOpacity: Animated.Value;
96-
panY: Animated.Value;
97-
isScrollAtTop: boolean;
98-
}
99-
100-
class FeedbackWidgetProvider extends React.Component<FeedbackWidgetProviderProps> {
101-
public state: FeedbackWidgetProviderState = {
102-
isButtonVisible: false,
103-
isScreenshotButtonVisible: false,
104-
isVisible: false,
105-
backgroundOpacity: new Animated.Value(0),
106-
panY: new Animated.Value(Dimensions.get('screen').height),
107-
isScrollAtTop: true,
108-
};
109-
110-
private _themeListener: NativeEventSubscription;
111-
112-
private _panResponder = PanResponder.create({
113-
onStartShouldSetPanResponder: (_, gestureState) => {
114-
return notWeb() && this.state.isScrollAtTop && gestureState.dy > 0;
115-
},
116-
onMoveShouldSetPanResponder: (_, gestureState) => {
117-
return notWeb() && this.state.isScrollAtTop && gestureState.dy > 0;
118-
},
119-
onPanResponderMove: (_, gestureState) => {
120-
if (gestureState.dy > 0) {
121-
this.state.panY.setValue(gestureState.dy);
122-
}
123-
},
124-
onPanResponderRelease: (_, gestureState) => {
125-
if (gestureState.dy > PULL_DOWN_CLOSE_THRESHOLD) {
126-
// Close on swipe below a certain threshold
127-
Animated.timing(this.state.panY, {
128-
toValue: Dimensions.get('screen').height,
129-
duration: SLIDE_ANIMATION_DURATION,
130-
useNativeDriver: true,
131-
}).start(() => {
132-
this._handleClose();
133-
});
134-
} else {
135-
// Animate it back to the original position
136-
Animated.spring(this.state.panY, {
137-
toValue: 0,
138-
useNativeDriver: true,
139-
}).start();
140-
}
141-
},
142-
});
143-
144-
public constructor(props: FeedbackWidgetProviderProps) {
145-
super(props);
146-
FeedbackButtonManager.initialize(this._setButtonVisibilityFunction);
147-
ScreenshotButtonManager.initialize(this._setScreenshotButtonVisibilityFunction);
148-
FeedbackWidgetManager.initialize(this._setVisibilityFunction);
149-
}
150-
151-
/**
152-
* Add a listener to the theme change event.
153-
*/
154-
public componentDidMount(): void {
155-
this._themeListener = Appearance.addChangeListener(() => {
156-
this.forceUpdate();
157-
});
158-
}
159-
160-
/**
161-
* Clean up the theme listener.
162-
*/
163-
public componentWillUnmount(): void {
164-
if (this._themeListener) {
165-
this._themeListener.remove();
166-
}
167-
}
168-
169-
/**
170-
* Animates the background opacity when the modal is shown.
171-
*/
172-
public componentDidUpdate(_prevProps: any, prevState: FeedbackWidgetProviderState): void {
173-
if (!prevState.isVisible && this.state.isVisible) {
174-
Animated.parallel([
175-
Animated.timing(this.state.backgroundOpacity, {
176-
toValue: 1,
177-
duration: BACKGROUND_ANIMATION_DURATION,
178-
useNativeDriver: true,
179-
easing: Easing.in(Easing.quad),
180-
}),
181-
Animated.timing(this.state.panY, {
182-
toValue: 0,
183-
duration: SLIDE_ANIMATION_DURATION,
184-
useNativeDriver: true,
185-
easing: Easing.in(Easing.quad),
186-
})
187-
]).start(() => {
188-
logger.info('FeedbackWidgetProvider componentDidUpdate');
189-
});
190-
} else if (prevState.isVisible && !this.state.isVisible) {
191-
this.state.backgroundOpacity.setValue(0);
192-
}
193-
}
194-
81+
/**
82+
* Provides functionality to show and hide the screenshot button.
83+
*/
84+
export class ScreenshotButtonManager extends FeedbackManager {
19585
/**
196-
* Renders the feedback form modal.
86+
* Returns the name of the feedback component.
19787
*/
198-
public render(): React.ReactNode {
199-
if (!isModalSupported()) {
200-
logger.error('FeedbackWidget Modal is not supported in React Native < 0.71 with Fabric renderer.');
201-
return <>{this.props.children}</>;
202-
}
203-
204-
const theme = getTheme();
205-
206-
const { isButtonVisible, isScreenshotButtonVisible, isVisible, backgroundOpacity } = this.state;
207-
208-
const backgroundColor = backgroundOpacity.interpolate({
209-
inputRange: [0, 1],
210-
outputRange: ['rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 0.9)'],
211-
});
212-
213-
// Wrapping the `Modal` component in a `View` component is necessary to avoid
214-
// issues like https://github.com/software-mansion/react-native-reanimated/issues/6035
215-
return (
216-
<>
217-
{this.props.children}
218-
{isButtonVisible && <FeedbackButton {...getFeedbackButtonOptions()} />}
219-
{isScreenshotButtonVisible && <ScreenshotButton {...getScreenshotButtonOptions()}/>}
220-
{isVisible &&
221-
<Animated.View style={[modalWrapper, { backgroundColor }]} >
222-
<Modal visible={isVisible} transparent animationType="none" onRequestClose={this._handleClose} testID="feedback-form-modal">
223-
<View style={topSpacer}/>
224-
<Animated.View
225-
style={[modalSheetContainer(theme), { transform: [{ translateY: this.state.panY }] }]}
226-
{...this._panResponder.panHandlers}>
227-
<ScrollView
228-
bounces={false}
229-
keyboardShouldPersistTaps="handled"
230-
automaticallyAdjustKeyboardInsets={Platform.OS === 'ios'}
231-
onScroll={this._handleScroll}>
232-
<FeedbackWidget {...getFeedbackOptions()}
233-
onFormClose={this._handleClose}
234-
onFormSubmitted={this._handleClose}
235-
/>
236-
</ScrollView>
237-
</Animated.View>
238-
</Modal>
239-
</Animated.View>
240-
}
241-
</>
242-
);
88+
protected static get _feedbackComponentName(): string {
89+
return 'ScreenshotButton';
24390
}
244-
245-
private _handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>): void => {
246-
this.setState({ isScrollAtTop: event.nativeEvent.contentOffset.y <= 0 });
247-
};
248-
249-
private _setVisibilityFunction = (visible: boolean): void => {
250-
const updateState = (): void => {
251-
this.setState({ isVisible: visible });
252-
};
253-
if (!visible) {
254-
Animated.parallel([
255-
Animated.timing(this.state.panY, {
256-
toValue: Dimensions.get('screen').height,
257-
duration: SLIDE_ANIMATION_DURATION,
258-
useNativeDriver: true,
259-
easing: Easing.out(Easing.quad),
260-
}),
261-
Animated.timing(this.state.backgroundOpacity, {
262-
toValue: 0,
263-
duration: BACKGROUND_ANIMATION_DURATION,
264-
useNativeDriver: true,
265-
easing: Easing.out(Easing.quad),
266-
})
267-
]).start(() => {
268-
// Change of the state unmount the component
269-
// which would cancel the animation
270-
updateState();
271-
});
272-
} else {
273-
updateState();
274-
}
275-
};
276-
277-
private _setButtonVisibilityFunction = (visible: boolean): void => {
278-
this.setState({ isButtonVisible: visible });
279-
};
280-
281-
private _setScreenshotButtonVisibilityFunction = (visible: boolean): void => {
282-
this.setState({ isScreenshotButtonVisible: visible });
283-
};
284-
285-
private _handleClose = (): void => {
286-
FeedbackWidgetManager.hide();
287-
};
28891
}
28992

29093
const showFeedbackWidget = (): void => {
@@ -326,4 +129,4 @@ const resetScreenshotButtonManager = (): void => {
326129
ScreenshotButtonManager.reset();
327130
};
328131

329-
export { showFeedbackButton, hideFeedbackButton, showFeedbackWidget, showScreenshotButton, hideScreenshotButton, FeedbackWidgetProvider, resetFeedbackButtonManager, resetFeedbackWidgetManager, resetScreenshotButtonManager };
132+
export { showFeedbackButton, hideFeedbackButton, showFeedbackWidget, showScreenshotButton, hideScreenshotButton, resetFeedbackButtonManager, resetFeedbackWidgetManager, resetScreenshotButtonManager };

0 commit comments

Comments
 (0)