Skip to content

feat(feedback): Feedback Widget Drop 2 #4726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

- Add Expo Updates Event Context ([#4767](https://github.com/getsentry/sentry-react-native/pull/4767))
- Automatically collects `updateId`, `channel`, Emergency Launch Reason and other Expo Updates constants
- Adds the `FeedbackButton` component that shows the Feedback Widget ([#4378](https://github.com/getsentry/sentry-react-native/pull/4378))
- Add Feedback Widget theming ([#4677](https://github.com/getsentry/sentry-react-native/pull/4677))
- Adds the `ScreenshotButton` component that takes a screenshot ([#4714](https://github.com/getsentry/sentry-react-native/issues/4714))

### Fixes

Expand Down Expand Up @@ -53,6 +56,7 @@
- Equalize TTID and TTFD duration when TTFD manual API is called and resolved before auto TTID ([#4680](https://github.com/getsentry/sentry-react-native/pull/4680))
- Avoid loading Sentry native components in Expo Go ([#4696](https://github.com/getsentry/sentry-react-native/pull/4696))
- Avoid silent failure when JS bundle was not created due to Sentry Xcode scripts failure ([#4690](https://github.com/getsentry/sentry-react-native/pull/4690))
- Fixes Feedback Widget accessibility issue on iOS ([#4739](https://github.com/getsentry/sentry-react-native/pull/4739))
- Prevent crash on iOS during profiling stop when debug images are missing ([#4738](https://github.com/getsentry/sentry-react-native/pull/4738))
- Attach only App Starts within the 60s threshold (fixed comparison units, use ms) ([#4746](https://github.com/getsentry/sentry-react-native/pull/4746))
- Add missing `popTimeToDisplayFor` in to the Android Old Arch Native interface([#4751](https://github.com/getsentry/sentry-react-native/pull/4751))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;
Expand Down Expand Up @@ -1027,6 +1028,15 @@ public void getDataFromUri(String uri, Promise promise) {
}
}

public void encodeToBase64(ReadableArray array, Promise promise) {
byte[] bytes = new byte[array.size()];
for (int i = 0; i < array.size(); i++) {
bytes[i] = (byte) array.getInt(i);
}
String base64String = android.util.Base64.encodeToString(bytes, android.util.Base64.DEFAULT);
promise.resolve(base64String);
}

public void crashedLastRun(Promise promise) {
promise.resolve(Sentry.isCrashedLastRun());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ public void getDataFromUri(String uri, Promise promise) {
this.impl.getDataFromUri(uri, promise);
}

@Override
public void encodeToBase64(ReadableArray array, Promise promise) {
this.impl.encodeToBase64(array, promise);
}

@Override
public void popTimeToDisplayFor(String key, Promise promise) {
this.impl.popTimeToDisplayFor(key, promise);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ public void getDataFromUri(String uri, Promise promise) {
this.impl.getDataFromUri(uri, promise);
}

@ReactMethod
public void encodeToBase64(ReadableArray array, Promise promise) {
this.impl.encodeToBase64(array, promise);
}

@ReactMethod
public void popTimeToDisplayFor(String key, Promise promise) {
this.impl.popTimeToDisplayFor(key, promise);
Expand Down
24 changes: 24 additions & 0 deletions packages/core/ios/RNSentry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -970,4 +970,28 @@ + (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys
return @YES; // The return ensures that the method is synchronous
}

RCT_EXPORT_METHOD(encodeToBase64
: (NSArray *)array resolver
: (RCTPromiseResolveBlock)resolve rejecter
: (RCTPromiseRejectBlock)reject)
{
NSUInteger count = array.count;
uint8_t *bytes = (uint8_t *)malloc(count);

if (!bytes) {
reject(@"encodeToBase64", @"Memory allocation failed", nil);
return;
}

for (NSUInteger i = 0; i < count; i++) {
bytes[i] = (uint8_t)[array[i] unsignedCharValue];
}

NSData *data = [NSData dataWithBytes:bytes length:count];
free(bytes);

NSString *base64String = [data base64EncodedStringWithOptions:0];
resolve(base64String);
}

@end
1 change: 1 addition & 0 deletions packages/core/src/js/NativeRNSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface Spec extends TurboModule {
getDataFromUri(uri: string): Promise<number[]>;
popTimeToDisplayFor(key: string): Promise<number | undefined | null>;
setActiveSpanId(spanId: string): boolean;
encodeToBase64(data: number[]): Promise<string | undefined | null>;
}

export type NativeStackFrame = {
Expand Down
66 changes: 66 additions & 0 deletions packages/core/src/js/feedback/FeedbackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as React from 'react';
import type { NativeEventSubscription} from 'react-native';
import { Appearance, Image, Text, TouchableOpacity } from 'react-native';

import { defaultButtonConfiguration } from './defaults';
import { defaultButtonStyles } from './FeedbackWidget.styles';
import { getTheme } from './FeedbackWidget.theme';
import type { FeedbackButtonProps, FeedbackButtonStyles, FeedbackButtonTextConfiguration } from './FeedbackWidget.types';
import { showFeedbackWidget } from './FeedbackWidgetManager';
import { feedbackIcon } from './icons';
import { lazyLoadFeedbackIntegration } from './lazy';

/**
* @beta
* Implements a feedback button that opens the FeedbackForm.
*/
export class FeedbackButton extends React.Component<FeedbackButtonProps> {
private _themeListener: NativeEventSubscription;

public constructor(props: FeedbackButtonProps) {
super(props);
lazyLoadFeedbackIntegration();
}

/**
* Adds a listener for theme changes.
*/
public componentDidMount(): void {
this._themeListener = Appearance.addChangeListener(() => {
this.forceUpdate();
});
}

/**
* Removes the theme listener.
*/
public componentWillUnmount(): void {
if (this._themeListener) {
this._themeListener.remove();
}
}

/**
* Renders the feedback button.
*/
public render(): React.ReactNode {
const theme = getTheme();
const text: FeedbackButtonTextConfiguration = { ...defaultButtonConfiguration, ...this.props };
const styles: FeedbackButtonStyles = {
triggerButton: { ...defaultButtonStyles(theme).triggerButton, ...this.props.styles?.triggerButton },
triggerText: { ...defaultButtonStyles(theme).triggerText, ...this.props.styles?.triggerText },
triggerIcon: { ...defaultButtonStyles(theme).triggerIcon, ...this.props.styles?.triggerIcon },
};

return (
<TouchableOpacity
style={styles.triggerButton}
onPress={showFeedbackWidget}
accessibilityLabel={text.triggerAriaLabel}
>
<Image source={{ uri: feedbackIcon }} style={styles.triggerIcon}/>
<Text style={styles.triggerText}>{text.triggerLabel}</Text>
</TouchableOpacity>
);
}
}
Loading
Loading