Skip to content

Commit b74349e

Browse files
authored
Merge 8ff7db3 into 3f05680
2 parents 3f05680 + 8ff7db3 commit b74349e

31 files changed

+3667
-1
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010

1111
### Features
1212

13+
- User Feedback Widget Beta ([#4435](https://github.com/getsentry/sentry-react-native/pull/4435))
14+
15+
To collect user feedback from inside your application call `Sentry.showFeedbackWidget()` or add the `FeedbackWidget` component.
16+
17+
```jsx
18+
import { FeedbackWidget } from "@sentry/react-native";
19+
...
20+
<FeedbackWidget/>
21+
```
22+
1323
- Adds Sentry Android Gradle Plugin as an experimental Expo plugin feature ([#4440](https://github.com/getsentry/sentry-react-native/pull/4440))
1424

1525
To enable the plugin add the `enableAndroidGradlePlugin` in the `@sentry/react-native/expo` of the Expo application configuration.

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import android.content.pm.PackageInfo;
1111
import android.content.pm.PackageManager;
1212
import android.content.res.AssetManager;
13+
import android.net.Uri;
1314
import android.util.SparseIntArray;
1415
import androidx.core.app.FrameMetricsAggregator;
1516
import androidx.fragment.app.FragmentActivity;
@@ -72,6 +73,7 @@
7273
import io.sentry.vendor.Base64;
7374
import java.io.BufferedInputStream;
7475
import java.io.BufferedReader;
76+
import java.io.ByteArrayOutputStream;
7577
import java.io.File;
7678
import java.io.FileNotFoundException;
7779
import java.io.FileReader;
@@ -970,6 +972,39 @@ public String fetchNativePackageName() {
970972
return packageInfo.packageName;
971973
}
972974

975+
public void getDataFromUri(String uri, Promise promise) {
976+
try {
977+
Uri contentUri = Uri.parse(uri);
978+
try (InputStream is =
979+
getReactApplicationContext().getContentResolver().openInputStream(contentUri)) {
980+
if (is == null) {
981+
String msg = "File not found for uri: " + uri;
982+
logger.log(SentryLevel.ERROR, msg);
983+
promise.reject(new Exception(msg));
984+
return;
985+
}
986+
987+
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
988+
int bufferSize = 1024;
989+
byte[] buffer = new byte[bufferSize];
990+
int len;
991+
while ((len = is.read(buffer)) != -1) {
992+
byteBuffer.write(buffer, 0, len);
993+
}
994+
byte[] byteArray = byteBuffer.toByteArray();
995+
WritableArray jsArray = Arguments.createArray();
996+
for (byte b : byteArray) {
997+
jsArray.pushInt(b & 0xFF);
998+
}
999+
promise.resolve(jsArray);
1000+
}
1001+
} catch (IOException e) {
1002+
String msg = "Error reading uri: " + uri + ": " + e.getMessage();
1003+
logger.log(SentryLevel.ERROR, msg);
1004+
promise.reject(new Exception(msg));
1005+
}
1006+
}
1007+
9731008
public void crashedLastRun(Promise promise) {
9741009
promise.resolve(Sentry.isCrashedLastRun());
9751010
}

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
@@ -177,4 +177,9 @@ public void crashedLastRun(Promise promise) {
177177
public void getNewScreenTimeToDisplay(Promise promise) {
178178
this.impl.getNewScreenTimeToDisplay(promise);
179179
}
180+
181+
@Override
182+
public void getDataFromUri(String uri, Promise promise) {
183+
this.impl.getDataFromUri(uri, promise);
184+
}
180185
}

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
@@ -152,6 +152,11 @@ public String fetchNativePackageName() {
152152
return this.impl.fetchNativePackageName();
153153
}
154154

155+
@ReactMethod
156+
public void getDataFromUri(String uri, Promise promise) {
157+
this.impl.getDataFromUri(uri, promise);
158+
}
159+
155160
@ReactMethod(isBlockingSynchronousMethod = true)
156161
public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) {
157162
// Not used on Android

packages/core/ios/RNSentry.mm

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,35 @@ - (NSDictionary *)fetchNativeStackFramesBy:(NSArray<NSNumber *> *)instructionsAd
745745
#endif
746746
}
747747

748+
RCT_EXPORT_METHOD(getDataFromUri
749+
: (NSString *_Nonnull)uri resolve
750+
: (RCTPromiseResolveBlock)resolve rejecter
751+
: (RCTPromiseRejectBlock)reject)
752+
{
753+
#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST
754+
NSURL *fileURL = [NSURL URLWithString:uri];
755+
if (![fileURL isFileURL]) {
756+
reject(@"SentryReactNative", @"The provided URI is not a valid file:// URL", nil);
757+
return;
758+
}
759+
NSError *error = nil;
760+
NSData *fileData = [NSData dataWithContentsOfURL:fileURL options:0 error:&error];
761+
if (error || !fileData) {
762+
reject(@"SentryReactNative", @"Failed to read file data", error);
763+
return;
764+
}
765+
NSMutableArray *byteArray = [NSMutableArray arrayWithCapacity:fileData.length];
766+
const unsigned char *bytes = (const unsigned char *)fileData.bytes;
767+
768+
for (NSUInteger i = 0; i < fileData.length; i++) {
769+
[byteArray addObject:@(bytes[i])];
770+
}
771+
resolve(byteArray);
772+
#else
773+
resolve(nil);
774+
#endif
775+
}
776+
748777
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getCurrentReplayId)
749778
{
750779
#if SENTRY_TARGET_REPLAY_SUPPORTED

packages/core/src/js/NativeRNSentry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface Spec extends TurboModule {
4848
captureReplay(isHardCrash: boolean): Promise<string | undefined | null>;
4949
getCurrentReplayId(): string | undefined | null;
5050
crashedLastRun(): Promise<boolean | undefined | null>;
51+
getDataFromUri(uri: string): Promise<number[]>;
5152
}
5253

5354
export type NativeStackFrame = {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import type { ViewStyle } from 'react-native';
2+
3+
import type { FeedbackWidgetStyles } from './FeedbackWidget.types';
4+
5+
const PURPLE = 'rgba(88, 74, 192, 1)';
6+
const FORGROUND_COLOR = '#2b2233';
7+
const BACKROUND_COLOR = '#ffffff';
8+
const BORDER_COLOR = 'rgba(41, 35, 47, 0.13)';
9+
10+
const defaultStyles: FeedbackWidgetStyles = {
11+
container: {
12+
flex: 1,
13+
padding: 20,
14+
backgroundColor: BACKROUND_COLOR,
15+
},
16+
title: {
17+
fontSize: 24,
18+
fontWeight: 'bold',
19+
marginBottom: 20,
20+
textAlign: 'left',
21+
flex: 1,
22+
color: FORGROUND_COLOR,
23+
},
24+
label: {
25+
marginBottom: 4,
26+
fontSize: 16,
27+
color: FORGROUND_COLOR,
28+
},
29+
input: {
30+
height: 50,
31+
borderColor: BORDER_COLOR,
32+
borderWidth: 1,
33+
borderRadius: 5,
34+
paddingHorizontal: 10,
35+
marginBottom: 15,
36+
fontSize: 16,
37+
color: FORGROUND_COLOR,
38+
},
39+
textArea: {
40+
height: 100,
41+
textAlignVertical: 'top',
42+
color: FORGROUND_COLOR,
43+
},
44+
screenshotButton: {
45+
backgroundColor: '#eee',
46+
padding: 15,
47+
borderRadius: 5,
48+
alignItems: 'center',
49+
flex: 1,
50+
},
51+
screenshotContainer: {
52+
flexDirection: 'row',
53+
alignItems: 'center',
54+
width: '100%',
55+
marginBottom: 20,
56+
},
57+
screenshotThumbnail: {
58+
width: 50,
59+
height: 50,
60+
borderRadius: 5,
61+
marginRight: 10,
62+
},
63+
screenshotText: {
64+
color: '#333',
65+
fontSize: 16,
66+
},
67+
submitButton: {
68+
backgroundColor: PURPLE,
69+
paddingVertical: 15,
70+
borderRadius: 5,
71+
alignItems: 'center',
72+
marginBottom: 10,
73+
},
74+
submitText: {
75+
color: BACKROUND_COLOR,
76+
fontSize: 18,
77+
},
78+
cancelButton: {
79+
paddingVertical: 15,
80+
alignItems: 'center',
81+
},
82+
cancelText: {
83+
color: FORGROUND_COLOR,
84+
fontSize: 16,
85+
},
86+
titleContainer: {
87+
flexDirection: 'row',
88+
width: '100%',
89+
},
90+
sentryLogo: {
91+
width: 40,
92+
height: 40,
93+
},
94+
};
95+
96+
export const modalWrapper: ViewStyle = {
97+
position: 'absolute',
98+
top: 0,
99+
left: 0,
100+
right: 0,
101+
bottom: 0,
102+
};
103+
104+
export const modalBackground: ViewStyle = {
105+
flex: 1,
106+
justifyContent: 'flex-end',
107+
};
108+
109+
export const modalSheetContainer: ViewStyle = {
110+
backgroundColor: '#ffffff',
111+
borderTopLeftRadius: 16,
112+
borderTopRightRadius: 16,
113+
overflow: 'hidden',
114+
alignSelf: 'stretch',
115+
height: '92%',
116+
shadowColor: '#000',
117+
shadowOffset: { width: 0, height: -3 },
118+
shadowOpacity: 0.1,
119+
shadowRadius: 4,
120+
elevation: 5,
121+
};
122+
123+
export default defaultStyles;

0 commit comments

Comments
 (0)