Skip to content

Commit 2816e90

Browse files
authored
Merge f7675d9 into 1d75738
2 parents 1d75738 + f7675d9 commit 2816e90

33 files changed

+5347
-312
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@
88
99
## Unreleased
1010

11+
### Features
12+
13+
- Adds the `FeedbackButton` component that shows the Feedback Widget ([#4378](https://github.com/getsentry/sentry-react-native/pull/4378))
14+
- Add Feedback Widget theming ([#4677](https://github.com/getsentry/sentry-react-native/pull/4677))
15+
- Adds the `ScreenshotButton` component that takes a screenshot ([#4714](https://github.com/getsentry/sentry-react-native/issues/4714))
16+
1117
### Fixes
1218

1319
- Expo Updates Context is passed to native after native init to be available for crashes ([#4808](https://github.com/getsentry/sentry-react-native/pull/4808))
20+
- Fixes Feedback Widget accessibility issue on iOS ([#4739](https://github.com/getsentry/sentry-react-native/pull/4739))
1421

1522
### Dependencies
1623

packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.facebook.react.bridge.Arguments;
2121
import com.facebook.react.bridge.Promise;
2222
import com.facebook.react.bridge.ReactApplicationContext;
23+
import com.facebook.react.bridge.ReadableArray;
2324
import com.facebook.react.bridge.ReadableMap;
2425
import com.facebook.react.bridge.ReadableMapKeySetIterator;
2526
import com.facebook.react.bridge.ReadableType;
@@ -1038,6 +1039,15 @@ public void getDataFromUri(String uri, Promise promise) {
10381039
}
10391040
}
10401041

1042+
public void encodeToBase64(ReadableArray array, Promise promise) {
1043+
byte[] bytes = new byte[array.size()];
1044+
for (int i = 0; i < array.size(); i++) {
1045+
bytes[i] = (byte) array.getInt(i);
1046+
}
1047+
String base64String = android.util.Base64.encodeToString(bytes, android.util.Base64.DEFAULT);
1048+
promise.resolve(base64String);
1049+
}
1050+
10411051
public void crashedLastRun(Promise promise) {
10421052
promise.resolve(Sentry.isCrashedLastRun());
10431053
}

packages/core/android/src/newarch/java/io/sentry/react/RNSentryModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ public void getDataFromUri(String uri, Promise promise) {
183183
this.impl.getDataFromUri(uri, promise);
184184
}
185185

186+
@Override
187+
public void encodeToBase64(ReadableArray array, Promise promise) {
188+
this.impl.encodeToBase64(array, promise);
189+
}
190+
186191
@Override
187192
public void popTimeToDisplayFor(String key, Promise promise) {
188193
this.impl.popTimeToDisplayFor(key, promise);

packages/core/android/src/oldarch/java/io/sentry/react/RNSentryModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ public void getDataFromUri(String uri, Promise promise) {
183183
this.impl.getDataFromUri(uri, promise);
184184
}
185185

186+
@ReactMethod
187+
public void encodeToBase64(ReadableArray array, Promise promise) {
188+
this.impl.encodeToBase64(array, promise);
189+
}
190+
186191
@ReactMethod
187192
public void popTimeToDisplayFor(String key, Promise promise) {
188193
this.impl.popTimeToDisplayFor(key, promise);

packages/core/ios/RNSentry.mm

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,4 +970,28 @@ + (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys
970970
return @YES; // The return ensures that the method is synchronous
971971
}
972972

973+
RCT_EXPORT_METHOD(encodeToBase64
974+
: (NSArray *)array resolver
975+
: (RCTPromiseResolveBlock)resolve rejecter
976+
: (RCTPromiseRejectBlock)reject)
977+
{
978+
NSUInteger count = array.count;
979+
uint8_t *bytes = (uint8_t *)malloc(count);
980+
981+
if (!bytes) {
982+
reject(@"encodeToBase64", @"Memory allocation failed", nil);
983+
return;
984+
}
985+
986+
for (NSUInteger i = 0; i < count; i++) {
987+
bytes[i] = (uint8_t)[array[i] unsignedCharValue];
988+
}
989+
990+
NSData *data = [NSData dataWithBytes:bytes length:count];
991+
free(bytes);
992+
993+
NSString *base64String = [data base64EncodedStringWithOptions:0];
994+
resolve(base64String);
995+
}
996+
973997
@end

packages/core/src/js/NativeRNSentry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export interface Spec extends TurboModule {
5151
getDataFromUri(uri: string): Promise<number[]>;
5252
popTimeToDisplayFor(key: string): Promise<number | undefined | null>;
5353
setActiveSpanId(spanId: string): boolean;
54+
encodeToBase64(data: number[]): Promise<string | undefined | null>;
5455
}
5556

5657
export type NativeStackFrame = {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as React from 'react';
2+
import type { NativeEventSubscription} from 'react-native';
3+
import { Appearance, Image, Text, TouchableOpacity } from 'react-native';
4+
5+
import { defaultButtonConfiguration } from './defaults';
6+
import { defaultButtonStyles } from './FeedbackWidget.styles';
7+
import { getTheme } from './FeedbackWidget.theme';
8+
import type { FeedbackButtonProps, FeedbackButtonStyles, FeedbackButtonTextConfiguration } from './FeedbackWidget.types';
9+
import { showFeedbackWidget } from './FeedbackWidgetManager';
10+
import { feedbackIcon } from './icons';
11+
import { lazyLoadFeedbackIntegration } from './lazy';
12+
13+
/**
14+
* @beta
15+
* Implements a feedback button that opens the FeedbackForm.
16+
*/
17+
export class FeedbackButton extends React.Component<FeedbackButtonProps> {
18+
private _themeListener: NativeEventSubscription;
19+
20+
public constructor(props: FeedbackButtonProps) {
21+
super(props);
22+
lazyLoadFeedbackIntegration();
23+
}
24+
25+
/**
26+
* Adds a listener for theme changes.
27+
*/
28+
public componentDidMount(): void {
29+
this._themeListener = Appearance.addChangeListener(() => {
30+
this.forceUpdate();
31+
});
32+
}
33+
34+
/**
35+
* Removes the theme listener.
36+
*/
37+
public componentWillUnmount(): void {
38+
if (this._themeListener) {
39+
this._themeListener.remove();
40+
}
41+
}
42+
43+
/**
44+
* Renders the feedback button.
45+
*/
46+
public render(): React.ReactNode {
47+
const theme = getTheme();
48+
const text: FeedbackButtonTextConfiguration = { ...defaultButtonConfiguration, ...this.props };
49+
const styles: FeedbackButtonStyles = {
50+
triggerButton: { ...defaultButtonStyles(theme).triggerButton, ...this.props.styles?.triggerButton },
51+
triggerText: { ...defaultButtonStyles(theme).triggerText, ...this.props.styles?.triggerText },
52+
triggerIcon: { ...defaultButtonStyles(theme).triggerIcon, ...this.props.styles?.triggerIcon },
53+
};
54+
55+
return (
56+
<TouchableOpacity
57+
style={styles.triggerButton}
58+
onPress={showFeedbackWidget}
59+
accessibilityLabel={text.triggerAriaLabel}
60+
>
61+
<Image source={{ uri: feedbackIcon }} style={styles.triggerIcon}/>
62+
<Text style={styles.triggerText}>{text.triggerLabel}</Text>
63+
</TouchableOpacity>
64+
);
65+
}
66+
}

0 commit comments

Comments
 (0)