From f84ec5856f2cc0b03d9bb597bc1850359b469887 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Tue, 29 Apr 2025 15:20:16 +0300 Subject: [PATCH 01/19] Set bug report types Set extended bug reporting options Disable/Enable bug reporting Set the disclaimer text Set the post sending dialog Set InvocationOption API --- .../default/src/components/InputField.tsx | 4 + examples/default/src/components/ListTile.tsx | 5 +- .../src/screens/BugReportingScreen.tsx | 306 +++++++++++++++--- 3 files changed, 262 insertions(+), 53 deletions(-) diff --git a/examples/default/src/components/InputField.tsx b/examples/default/src/components/InputField.tsx index feef8fd7b..1426a9d99 100644 --- a/examples/default/src/components/InputField.tsx +++ b/examples/default/src/components/InputField.tsx @@ -21,6 +21,7 @@ interface InputFieldProps { maxLength?: number; accessibilityLabel?: string; flex?: number; + testID?: string; } export const InputField = forwardRef( @@ -34,6 +35,7 @@ export const InputField = forwardRef( maxLength, keyboardType, errorText, + testID, ...restProps }, ref, @@ -45,9 +47,11 @@ export const InputField = forwardRef( placeholder={placeholder} style={[styles.textInput, style]} maxLength={maxLength} + accessible={true} accessibilityLabel={accessibilityLabel} keyboardType={keyboardType} value={value} + testID={testID} onChangeText={onChangeText} {...restProps} /> diff --git a/examples/default/src/components/ListTile.tsx b/examples/default/src/components/ListTile.tsx index 35540dc85..b0bed5f54 100644 --- a/examples/default/src/components/ListTile.tsx +++ b/examples/default/src/components/ListTile.tsx @@ -5,15 +5,18 @@ import { Box, HStack, Pressable, Text } from 'native-base'; interface ListTileProps extends PropsWithChildren { title: string; onPress?: () => void; + testID?: string; } -export const ListTile: React.FC = ({ title, onPress, children }) => { +export const ListTile: React.FC = ({ title, onPress, children, testID }) => { return ( { const toast = useToast(); + const [reportTypes, setReportTypes] = useState([]); + const [invocationOptions, setInvocationOptions] = useState([]); + + const [disclaimerText, setDisclaimerText] = useState(''); + + const toggleCheckbox = (value: string, setData: Dispatch>) => { + setData((prev) => + prev.includes(value) ? prev.filter((item) => item !== value) : [...prev, value], + ); + }; + + const handleSetReportTypesButtonPress = () => { + const selectedEnums: ReportType[] = reportTypes.map((val) => { + switch (val) { + case 'bug': + return ReportType.bug; + case 'feedback': + return ReportType.feedback; + case 'question': + return ReportType.question; + default: + throw new Error('Invalid report type selected'); + } + }); + BugReporting.setReportTypes(selectedEnums); + }; + const handleSetInvocationOptionsButtonPress = () => { + const selectedEnums: InvocationEvent[] = invocationOptions.map((val) => { + switch (val) { + case 'floatingButton': + return InvocationEvent.floatingButton; + case 'twoFingersSwipe': + return InvocationEvent.twoFingersSwipe; + case 'screenshot': + return InvocationEvent.screenshot; + case 'shake': + return InvocationEvent.shake; + + default: + throw new Error('Invalid report type selected'); + } + }); + BugReporting.setInvocationEvents(selectedEnums); + }; + + const handleSetDisclamirTextPress = () => { + BugReporting.setDisclaimerText(disclaimerText); + setDisclaimerText(''); + }; + return ( - - Instabug.show()} /> - BugReporting.show(ReportType.bug, [])} /> - BugReporting.show(ReportType.feedback, [InvocationOption.emailFieldHidden])} - /> - BugReporting.show(ReportType.question, [])} /> - - BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithRequiredFields) - } - /> - - BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithOptionalFields) - } - /> - Instabug.setSessionProfilerEnabled(true)} - /> - Instabug.showWelcomeMessage(WelcomeMessageMode.beta)} - /> - Instabug.showWelcomeMessage(WelcomeMessageMode.live)} - /> - -
+ + + Instabug.show()} /> + + BugReporting.setEnabled(true)} + testID="id_br_enable" + /> BugReporting.setEnabled(false)} + testID="id_br_disable" + /> + + BugReporting.show(ReportType.bug, [])} /> + - BugReporting.onInvokeHandler(function () { - Instabug.appendTags(['Invocation Handler tag1']); - }) + BugReporting.show(ReportType.feedback, [InvocationOption.emailFieldHidden]) } /> BugReporting.show(ReportType.question, [])} + /> + - Instabug.onReportSubmitHandler(() => { - toast.show({ - description: 'Submission succeeded', - }); - }) + BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithRequiredFields) } /> - BugReporting.onSDKDismissedHandler(function () { - Instabug.setPrimaryColor('#FF0000'); - }) + BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithOptionalFields) } /> -
-
+ Instabug.setSessionProfilerEnabled(true)} + /> + Instabug.showWelcomeMessage(WelcomeMessageMode.beta)} + /> + Instabug.showWelcomeMessage(WelcomeMessageMode.live)} + /> + + + + Bug Reporting Types + + + + + toggleCheckbox('bug', setReportTypes)} + value="bug" + accessible={true} + testID="id_br_report_type_bug" + size="md"> + Bug + + + toggleCheckbox('feedback', setReportTypes)} + value="feedback" + testID="id_br_report_type_feedback" + size="md"> + Feedback + + + toggleCheckbox('question', setReportTypes)} + value="question" + testID="id_br_report_type_question" + size="md"> + Question + + + + + + + + + + Set the disclaimer text + + + + + + + + + + + + Invocation Events + + + + + toggleCheckbox('floatingButton', setInvocationOptions)} + value="floatingButton" + testID="id_br_invoicetion_options_floatingButton" + accessible={true} + size="md"> + Floating button + + + toggleCheckbox('twoFingersSwipe', setInvocationOptions)} + value="twoFingersSwipe" + testID="id_br_invoicetion_options_twoFingersSwipe" + accessible={true} + size="md"> + Two Fingers Swipe + + + + toggleCheckbox('screenshot', setInvocationOptions)} + value="screenshot" + testID="id_br_invoicetion_options_screenshot" + accessible={true} + size="md"> + Screenshot + + toggleCheckbox('shake', setInvocationOptions)} + testID="id_br_invoicetion_options_shake" + accessible={true} + value="shake" + size="md"> + Shake + + + + + + + +
+ + BugReporting.onInvokeHandler(function () { + Instabug.appendTags(['Invocation Handler tag1']); + }) + } + /> + + Instabug.onReportSubmitHandler(() => { + toast.show({ + description: 'Submission succeeded', + }); + }) + } + /> + + BugReporting.onSDKDismissedHandler(function () { + Instabug.setPrimaryColor('#FF0000'); + }) + } + /> +
+ + ); }; From 1ede7e2c6182bfce5ec4d77008e2d9cf8d5b8ae0 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Tue, 29 Apr 2025 15:20:16 +0300 Subject: [PATCH 02/19] Set bug report types Set extended bug reporting options Disable/Enable bug reporting Set the disclaimer text Set the post sending dialog Set InvocationOption API --- .../default/src/components/InputField.tsx | 4 + examples/default/src/components/ListTile.tsx | 5 +- .../src/screens/BugReportingScreen.tsx | 306 +++++++++++++++--- 3 files changed, 262 insertions(+), 53 deletions(-) diff --git a/examples/default/src/components/InputField.tsx b/examples/default/src/components/InputField.tsx index feef8fd7b..1426a9d99 100644 --- a/examples/default/src/components/InputField.tsx +++ b/examples/default/src/components/InputField.tsx @@ -21,6 +21,7 @@ interface InputFieldProps { maxLength?: number; accessibilityLabel?: string; flex?: number; + testID?: string; } export const InputField = forwardRef( @@ -34,6 +35,7 @@ export const InputField = forwardRef( maxLength, keyboardType, errorText, + testID, ...restProps }, ref, @@ -45,9 +47,11 @@ export const InputField = forwardRef( placeholder={placeholder} style={[styles.textInput, style]} maxLength={maxLength} + accessible={true} accessibilityLabel={accessibilityLabel} keyboardType={keyboardType} value={value} + testID={testID} onChangeText={onChangeText} {...restProps} /> diff --git a/examples/default/src/components/ListTile.tsx b/examples/default/src/components/ListTile.tsx index 35540dc85..b0bed5f54 100644 --- a/examples/default/src/components/ListTile.tsx +++ b/examples/default/src/components/ListTile.tsx @@ -5,15 +5,18 @@ import { Box, HStack, Pressable, Text } from 'native-base'; interface ListTileProps extends PropsWithChildren { title: string; onPress?: () => void; + testID?: string; } -export const ListTile: React.FC = ({ title, onPress, children }) => { +export const ListTile: React.FC = ({ title, onPress, children, testID }) => { return ( { const toast = useToast(); + const [reportTypes, setReportTypes] = useState([]); + const [invocationOptions, setInvocationOptions] = useState([]); + + const [disclaimerText, setDisclaimerText] = useState(''); + + const toggleCheckbox = (value: string, setData: Dispatch>) => { + setData((prev) => + prev.includes(value) ? prev.filter((item) => item !== value) : [...prev, value], + ); + }; + + const handleSetReportTypesButtonPress = () => { + const selectedEnums: ReportType[] = reportTypes.map((val) => { + switch (val) { + case 'bug': + return ReportType.bug; + case 'feedback': + return ReportType.feedback; + case 'question': + return ReportType.question; + default: + throw new Error('Invalid report type selected'); + } + }); + BugReporting.setReportTypes(selectedEnums); + }; + const handleSetInvocationOptionsButtonPress = () => { + const selectedEnums: InvocationEvent[] = invocationOptions.map((val) => { + switch (val) { + case 'floatingButton': + return InvocationEvent.floatingButton; + case 'twoFingersSwipe': + return InvocationEvent.twoFingersSwipe; + case 'screenshot': + return InvocationEvent.screenshot; + case 'shake': + return InvocationEvent.shake; + + default: + throw new Error('Invalid report type selected'); + } + }); + BugReporting.setInvocationEvents(selectedEnums); + }; + + const handleSetDisclamirTextPress = () => { + BugReporting.setDisclaimerText(disclaimerText); + setDisclaimerText(''); + }; + return ( - - Instabug.show()} /> - BugReporting.show(ReportType.bug, [])} /> - BugReporting.show(ReportType.feedback, [InvocationOption.emailFieldHidden])} - /> - BugReporting.show(ReportType.question, [])} /> - - BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithRequiredFields) - } - /> - - BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithOptionalFields) - } - /> - Instabug.setSessionProfilerEnabled(true)} - /> - Instabug.showWelcomeMessage(WelcomeMessageMode.beta)} - /> - Instabug.showWelcomeMessage(WelcomeMessageMode.live)} - /> - -
+ + + Instabug.show()} /> + + BugReporting.setEnabled(true)} + testID="id_br_enable" + /> BugReporting.setEnabled(false)} + testID="id_br_disable" + /> + + BugReporting.show(ReportType.bug, [])} /> + - BugReporting.onInvokeHandler(function () { - Instabug.appendTags(['Invocation Handler tag1']); - }) + BugReporting.show(ReportType.feedback, [InvocationOption.emailFieldHidden]) } /> BugReporting.show(ReportType.question, [])} + /> + - Instabug.onReportSubmitHandler(() => { - toast.show({ - description: 'Submission succeeded', - }); - }) + BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithRequiredFields) } /> - BugReporting.onSDKDismissedHandler(function () { - Instabug.setPrimaryColor('#FF0000'); - }) + BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithOptionalFields) } /> -
-
+ Instabug.setSessionProfilerEnabled(true)} + /> + Instabug.showWelcomeMessage(WelcomeMessageMode.beta)} + /> + Instabug.showWelcomeMessage(WelcomeMessageMode.live)} + /> + + + + Bug Reporting Types + + + + + toggleCheckbox('bug', setReportTypes)} + value="bug" + accessible={true} + testID="id_br_report_type_bug" + size="md"> + Bug + + + toggleCheckbox('feedback', setReportTypes)} + value="feedback" + testID="id_br_report_type_feedback" + size="md"> + Feedback + + + toggleCheckbox('question', setReportTypes)} + value="question" + testID="id_br_report_type_question" + size="md"> + Question + + + + + + + + + + Set the disclaimer text + + + + + + + + + + + + Invocation Events + + + + + toggleCheckbox('floatingButton', setInvocationOptions)} + value="floatingButton" + testID="id_br_invoicetion_options_floatingButton" + accessible={true} + size="md"> + Floating button + + + toggleCheckbox('twoFingersSwipe', setInvocationOptions)} + value="twoFingersSwipe" + testID="id_br_invoicetion_options_twoFingersSwipe" + accessible={true} + size="md"> + Two Fingers Swipe + + + + toggleCheckbox('screenshot', setInvocationOptions)} + value="screenshot" + testID="id_br_invoicetion_options_screenshot" + accessible={true} + size="md"> + Screenshot + + toggleCheckbox('shake', setInvocationOptions)} + testID="id_br_invoicetion_options_shake" + accessible={true} + value="shake" + size="md"> + Shake + + + + + + + +
+ + BugReporting.onInvokeHandler(function () { + Instabug.appendTags(['Invocation Handler tag1']); + }) + } + /> + + Instabug.onReportSubmitHandler(() => { + toast.show({ + description: 'Submission succeeded', + }); + }) + } + /> + + BugReporting.onSDKDismissedHandler(function () { + Instabug.setPrimaryColor('#FF0000'); + }) + } + /> +
+ + ); }; From 7928d88748c719fe5baf29d4cf684c39d5dca325 Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Fri, 30 May 2025 05:54:12 +0300 Subject: [PATCH 03/19] feat: add bug reporting state management screen - Added `BugReportingStateScreen` to toggle between enabled and disabled states for bug reporting. - Updated `ListTile` component to support subtitles for better UI clarity. - Adjusted navigation to include new bug reporting screens. --- examples/default/ios/Podfile.lock | 50 ++++++++-------- examples/default/src/components/ListTile.tsx | 20 ++++++- examples/default/src/navigation/HomeStack.tsx | 12 +++- .../BugReportingScreen.tsx | 59 ++++++++++++++----- .../bug-reporting/BugReportingStateScreen.tsx | 36 +++++++++++ 5 files changed, 132 insertions(+), 45 deletions(-) rename examples/default/src/screens/{ => bug-reporting}/BugReportingScreen.tsx (85%) create mode 100644 examples/default/src/screens/bug-reporting/BugReportingStateScreen.tsx diff --git a/examples/default/ios/Podfile.lock b/examples/default/ios/Podfile.lock index 0d7d7a1ff..3faeec581 100644 --- a/examples/default/ios/Podfile.lock +++ b/examples/default/ios/Podfile.lock @@ -2025,21 +2025,21 @@ SPEC CHECKSUMS: Instabug: 3e7af445c14d7823fcdecba223f09b5f7c0c6ce1 instabug-reactnative-ndk: d765ac289d56e8896398d02760d9abf2562fc641 OCMock: 589f2c84dacb1f5aaf6e4cec1f292551fe748e74 - RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 + RCT-Folly: 34124ae2e667a0e5f0ea378db071d27548124321 RCTDeprecation: 726d24248aeab6d7180dac71a936bbca6a994ed1 RCTRequired: a94e7febda6db0345d207e854323c37e3a31d93b RCTTypeSafety: 28e24a6e44f5cbf912c66dde6ab7e07d1059a205 React: c2830fa483b0334bda284e46a8579ebbe0c5447e React-callinvoker: 4aecde929540c26b841a4493f70ebf6016691eb8 - React-Core: 9c059899f00d46b5cec3ed79251f77d9c469553d - React-CoreModules: 9fac2d31803c0ed03e4ddaa17f1481714f8633a5 - React-cxxreact: a979810a3ca4045ceb09407a17563046a7f71494 + React-Core: 32a581847d74ce9b5f51d9d11a4e4d132ad61553 + React-CoreModules: f53e0674e1747fa41c83bc970e82add97b14ad87 + React-cxxreact: 86f3b1692081fd954a0cb27cc90d14674645b64b React-debug: 3d21f69d8def0656f8b8ec25c0f05954f4d862c5 - React-defaultsnativemodule: 2fa2bdb7bd03ff9764facc04aa8520ebf14febae - React-domnativemodule: 986e6fe7569e1383dce452a7b013b6c843a752df - React-Fabric: 3bc7be9e3a6b7581fc828dc2aa041e107fc8ffb8 - React-FabricComponents: 668e0cb02344c2942e4c8921a643648faa6dc364 - React-FabricImage: 3f44dd25a2b020ed5215d4438a1bb1f3461cd4f1 + React-defaultsnativemodule: 2ed121c5a1edeab09cff382b8d9b538260f07848 + React-domnativemodule: 4393dd5dd7e13dbe42e69ebc791064a616990f91 + React-Fabric: cbf38ceefb1ac6236897abdb538130228e126695 + React-FabricComponents: dd4b01c4a60920d8dc15f3b5594c6fe9e7648a38 + React-FabricImage: 8b13aedfbd20f349b9b3314baf993c71c02995d9 React-featureflags: ee1abd6f71555604a36cda6476e3c502ca9a48e5 React-featureflagsnativemodule: 7ccc0cd666c2a6257401dceb7920818ac2b42803 React-graphics: d7dd9c8d75cad5af19e19911fa370f78f2febd96 @@ -2063,25 +2063,25 @@ SPEC CHECKSUMS: react-native-slider: 4a0f3386a38fc3d2d955efc515aef7096f7d1ee4 react-native-webview: c0b91a4598bd54e9fbc70353aebf1e9bab2e5bb9 React-nativeconfig: 8c83d992b9cc7d75b5abe262069eaeea4349f794 - React-NativeModulesApple: 9f7920224a3b0c7d04d77990067ded14cee3c614 + React-NativeModulesApple: 97f606f09fd9840b3868333984d6a0e7bcc425b5 React-perflogger: 59e1a3182dca2cee7b9f1f7aab204018d46d1914 - React-performancetimeline: a9d05533ff834c6aa1f532e05e571f3fd2e3c1ed + React-performancetimeline: 3e3f5c5576fe1cc2dd5fcfb1ae2046d5dceda3d7 React-RCTActionSheet: d80e68d3baa163e4012a47c1f42ddd8bcd9672cc - React-RCTAnimation: bde981f6bd7f8493696564da9b3bd05721d3b3cc - React-RCTAppDelegate: 0176615c51476c88212bf3edbafb840d39ea7631 - React-RCTBlob: 520a0382bf8e89b9153d60e3c6293e51615834e9 - React-RCTFabric: c9da097b19b30017a99498b8c66a69c72f3ce689 - React-RCTImage: 90448d2882464af6015ed57c98f463f8748be465 - React-RCTLinking: 1bd95d0a704c271d21d758e0f0388cced768d77d - React-RCTNetwork: 218af6e63eb9b47935cc5a775b7a1396cf10ff91 - React-RCTSettings: e10b8e42b0fce8a70fbf169de32a2ae03243ef6b - React-RCTText: e7bf9f4997a1a0b45c052d4ad9a0fe653061cf29 - React-RCTVibration: 5b70b7f11e48d1c57e0d4832c2097478adbabe93 + React-RCTAnimation: 051f0781709c5ed80ba8aa2b421dfb1d72a03162 + React-RCTAppDelegate: 106d225d076988b06aa4834e68d1ab754f40cacf + React-RCTBlob: 895eaf8bca2e76ee1c95b479235c6ccebe586fc6 + React-RCTFabric: 8d01df202ee9e933f9b5dd44b72ec89a7ac6ee01 + React-RCTImage: b73149c0cd54b641dba2d6250aaf168fee784d9f + React-RCTLinking: 23e519712285427e50372fbc6e0265d422abf462 + React-RCTNetwork: a5d06d122588031989115f293654b13353753630 + React-RCTSettings: 87d03b5d94e6eadd1e8c1d16a62f790751aafb55 + React-RCTText: 75e9dd39684f4bcd1836134ac2348efaca7437b3 + React-RCTVibration: 033c161fe875e6fa096d0d9733c2e2501682e3d4 React-rendererconsistency: f620c6e003e3c4593e6349d8242b8aeb3d4633f0 - React-rendererdebug: e697680f4dd117becc5daf9ea9800067abcee91c + React-rendererdebug: 5be7b834677b2a7a263f4d2545f0d4966cafad82 React-rncore: c22bd84cc2f38947f0414fab6646db22ff4f80cd - React-RuntimeApple: de0976836b90b484305638616898cbc665c67c13 - React-RuntimeCore: 3c4a5aa63d9e7a3c17b7fb23f32a72a8bcfccf57 + React-RuntimeApple: 71160e6c02efa07d198b84ef5c3a52a7d9d0399d + React-RuntimeCore: f88f79ec995c12af56a265d7505c7630733d9d82 React-runtimeexecutor: ea90d8e3a9e0f4326939858dafc6ab17c031a5d3 React-RuntimeHermes: c6b0afdf1f493621214eeb6517fb859ce7b21b81 React-runtimescheduler: 84f0d876d254bce6917a277b3930eb9bc29df6c7 @@ -2100,4 +2100,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 837b933596e1616ff02cc206bb17dee4f611fdbc -COCOAPODS: 1.14.0 +COCOAPODS: 1.16.2 diff --git a/examples/default/src/components/ListTile.tsx b/examples/default/src/components/ListTile.tsx index b0bed5f54..62772c7f7 100644 --- a/examples/default/src/components/ListTile.tsx +++ b/examples/default/src/components/ListTile.tsx @@ -1,14 +1,21 @@ import React, { PropsWithChildren } from 'react'; -import { Box, HStack, Pressable, Text } from 'native-base'; +import { Box, HStack, Pressable, Text, VStack } from 'native-base'; interface ListTileProps extends PropsWithChildren { title: string; + subtitle?: string; onPress?: () => void; testID?: string; } -export const ListTile: React.FC = ({ title, onPress, children, testID }) => { +export const ListTile: React.FC = ({ + title, + subtitle, + onPress, + children, + testID, +}) => { return ( = ({ title, onPress, children, te bg="coolGray.100" _pressed={{ bg: 'coolGray.200' }}> - {title} + + {title} + {subtitle && ( + + {subtitle} + + )} + {children} diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx index 090aa6587..8ce7462bc 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -2,7 +2,11 @@ import React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { BugReportingScreen } from '../screens/BugReportingScreen'; +import { BugReportingScreen } from '../screens/bug-reporting/BugReportingScreen'; +import { + BugReportingStateScreen, + type BugReportingStateScreenProp, +} from '../screens/bug-reporting/BugReportingStateScreen'; import { CrashReportingScreen } from '../screens/CrashReportingScreen'; import { FeatureRequestsScreen } from '../screens/FeatureRequestsScreen'; import { HomeScreen } from '../screens/HomeScreen'; @@ -35,6 +39,7 @@ import { PartialWebViewsScreen } from '../screens/apm/webViews/PartialWebViewsSc export type HomeStackParamList = { Home: undefined; BugReporting: undefined; + BugReportingState: BugReportingStateScreenProp; CrashReporting: undefined; FeatureRequests: undefined; Replies: undefined; @@ -74,6 +79,11 @@ export const HomeStackNavigator: React.FC = () => { component={BugReportingScreen} options={{ title: 'Bug Reporting' }} /> + { +export const BugReportingScreen: React.FC< + NativeStackScreenProps +> = ({ navigation }) => { const toast = useToast(); const [reportTypes, setReportTypes] = useState([]); const [invocationOptions, setInvocationOptions] = useState([]); + const [isBugReportingEnabled, setIsBugReportingEnabled] = useState(true); const [disclaimerText, setDisclaimerText] = useState(''); @@ -70,19 +87,26 @@ export const BugReportingScreen: React.FC = () => { return ( - Instabug.show()} /> - - BugReporting.setEnabled(true)} - testID="id_br_enable" - /> BugReporting.setEnabled(false)} - testID="id_br_disable" + title="Bug Reporting State" + subtitle={isBugReportingEnabled ? 'Enabled' : 'Disabled'} + onPress={() => { + navigation.navigate('BugReportingState', { + state: isBugReportingEnabled ? BugReportingState.Enabled : BugReportingState.Disabled, + setState: (newState: BugReportingState) => { + const isEnabled = newState === BugReportingState.Enabled; + setIsBugReportingEnabled(isEnabled); + BugReporting.setEnabled(isEnabled); + navigation.goBack(); + }, + }); + }} + testID="id_br_state" /> + + + Instabug.show()} testID="id_br_show_button" /> BugReporting.show(ReportType.bug, [])} /> { title="Ask a Question" onPress={() => BugReporting.show(ReportType.question, [])} /> + + + diff --git a/examples/default/src/screens/bug-reporting/BugReportingStateScreen.tsx b/examples/default/src/screens/bug-reporting/BugReportingStateScreen.tsx new file mode 100644 index 000000000..8b6eaff4c --- /dev/null +++ b/examples/default/src/screens/bug-reporting/BugReportingStateScreen.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +import { ListTile } from '../../components/ListTile'; +import { Screen } from '../../components/Screen'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; + +export enum BugReportingState { + Enabled = 'Enabled', + Disabled = 'Disabled', +} + +export interface BugReportingStateScreenProp { + state: BugReportingState; + setState: (state: BugReportingState) => void; +} + +export const BugReportingStateScreen: React.FC< + NativeStackScreenProps +> = ({ navigation, route }) => { + const { state, setState } = route.params; + return ( + + setState(BugReportingState.Enabled)} + /> + setState(BugReportingState.Disabled)} + /> + + ); +}; From 79caa2c22c24f81ac397fd07ddc3ec5db66561e3 Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Sat, 31 May 2025 22:52:56 +0300 Subject: [PATCH 04/19] feat: enhance bug reporting functionality - Added new screens for managing bug reporting types, disclaimer text, invocation events, options, and session profiler. - Updated `ListTile` component to support subtitle truncation. - Integrated new bug reporting state management features into navigation. - Refactored `BugReportingScreen` to streamline state handling and improve UI interactions. --- examples/default/src/components/ListTile.tsx | 8 +- examples/default/src/navigation/HomeStack.tsx | 66 ++++ .../bug-reporting/BugReportingScreen.tsx | 363 ++++++++---------- .../bug-reporting/BugReportingStateScreen.tsx | 6 +- .../bug-reporting/BugReportingTypesScreen.tsx | 63 +++ .../bug-reporting/DisclaimerTextScreen.tsx | 49 +++ .../ExtendedBugReportStateScreen.tsx | 46 +++ .../bug-reporting/InvocationEventsScreen.tsx | 81 ++++ .../bug-reporting/InvocationOptionsScreen.tsx | 69 ++++ .../bug-reporting/SessionProfilerScreen.tsx | 34 ++ 10 files changed, 576 insertions(+), 209 deletions(-) create mode 100644 examples/default/src/screens/bug-reporting/BugReportingTypesScreen.tsx create mode 100644 examples/default/src/screens/bug-reporting/DisclaimerTextScreen.tsx create mode 100644 examples/default/src/screens/bug-reporting/ExtendedBugReportStateScreen.tsx create mode 100644 examples/default/src/screens/bug-reporting/InvocationEventsScreen.tsx create mode 100644 examples/default/src/screens/bug-reporting/InvocationOptionsScreen.tsx create mode 100644 examples/default/src/screens/bug-reporting/SessionProfilerScreen.tsx diff --git a/examples/default/src/components/ListTile.tsx b/examples/default/src/components/ListTile.tsx index 62772c7f7..165259819 100644 --- a/examples/default/src/components/ListTile.tsx +++ b/examples/default/src/components/ListTile.tsx @@ -7,6 +7,7 @@ interface ListTileProps extends PropsWithChildren { subtitle?: string; onPress?: () => void; testID?: string; + truncateSubtitle?: boolean; } export const ListTile: React.FC = ({ @@ -15,6 +16,7 @@ export const ListTile: React.FC = ({ onPress, children, testID, + truncateSubtitle = false, }) => { return ( = ({ {title} {subtitle && ( - + {subtitle} )} diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx index 8ce7462bc..8affca99a 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -7,6 +7,22 @@ import { BugReportingStateScreen, type BugReportingStateScreenProp, } from '../screens/bug-reporting/BugReportingStateScreen'; +import { + ExtendedBugReportStateScreen, + type ExtendedBugReportStateScreenProp, +} from '../screens/bug-reporting/ExtendedBugReportStateScreen'; +import { + BugReportingTypesScreen, + type BugReportingTypesScreenProp, +} from '../screens/bug-reporting/BugReportingTypesScreen'; +import { + DisclaimerTextScreen, + type DisclaimerTextScreenProp, +} from '../screens/bug-reporting/DisclaimerTextScreen'; +import { + InvocationOptionsScreen, + type InvocationOptionsScreenProp, +} from '../screens/bug-reporting/InvocationOptionsScreen'; import { CrashReportingScreen } from '../screens/CrashReportingScreen'; import { FeatureRequestsScreen } from '../screens/FeatureRequestsScreen'; import { HomeScreen } from '../screens/HomeScreen'; @@ -35,11 +51,28 @@ import { HttpScreen } from '../screens/apm/HttpScreen'; import { WebViewsScreen } from '../screens/apm/webViews/WebViewsScreen'; import { FullWebViewsScreen } from '../screens/apm/webViews/FullWebViewsScreen'; import { PartialWebViewsScreen } from '../screens/apm/webViews/PartialWebViewsScreen'; +import { + InvocationEventsScreen, + type InvocationEventsScreenProp, +} from '../screens/bug-reporting/InvocationEventsScreen'; +import { + SessionProfilerScreen, + type SessionProfilerScreenProp, +} from '../screens/bug-reporting/SessionProfilerScreen'; export type HomeStackParamList = { Home: undefined; + + // Bug Reporting // BugReporting: undefined; BugReportingState: BugReportingStateScreenProp; + ExtendedBugReportState: ExtendedBugReportStateScreenProp; + BugReportingTypes: BugReportingTypesScreenProp; + DisclaimerText: DisclaimerTextScreenProp; + InvocationEvents: InvocationEventsScreenProp; + SessionProfiler: SessionProfilerScreenProp; + InvocationOptions: InvocationOptionsScreenProp; + CrashReporting: undefined; FeatureRequests: undefined; Replies: undefined; @@ -74,6 +107,8 @@ export const HomeStackNavigator: React.FC = () => { return ( + + {/* Bug Reporting */} { component={BugReportingStateScreen} options={{ title: 'Bug Reporting State' }} /> + + + + + + + > = ({ navigation }) => { const toast = useToast(); - const [reportTypes, setReportTypes] = useState([]); + const [reportTypes, setReportTypes] = useState([ + ReportType.bug, + ReportType.feedback, + ReportType.question, + ]); + const [invocationEvents, setInvocationEvents] = useState(['floatingButton']); const [invocationOptions, setInvocationOptions] = useState([]); const [isBugReportingEnabled, setIsBugReportingEnabled] = useState(true); - + const [extendedBugReportState, setExtendedBugReportState] = useState( + ExtendedBugReportState.Disabled, + ); const [disclaimerText, setDisclaimerText] = useState(''); - - const toggleCheckbox = (value: string, setData: Dispatch>) => { - setData((prev) => - prev.includes(value) ? prev.filter((item) => item !== value) : [...prev, value], - ); - }; - - const handleSetReportTypesButtonPress = () => { - const selectedEnums: ReportType[] = reportTypes.map((val) => { - switch (val) { - case 'bug': - return ReportType.bug; - case 'feedback': - return ReportType.feedback; - case 'question': - return ReportType.question; - default: - throw new Error('Invalid report type selected'); - } - }); - BugReporting.setReportTypes(selectedEnums); - }; - const handleSetInvocationOptionsButtonPress = () => { - const selectedEnums: InvocationEvent[] = invocationOptions.map((val) => { - switch (val) { - case 'floatingButton': - return InvocationEvent.floatingButton; - case 'twoFingersSwipe': - return InvocationEvent.twoFingersSwipe; - case 'screenshot': - return InvocationEvent.screenshot; - case 'shake': - return InvocationEvent.shake; - - default: - throw new Error('Invalid report type selected'); - } - }); - BugReporting.setInvocationEvents(selectedEnums); - }; - - const handleSetDisclamirTextPress = () => { - BugReporting.setDisclaimerText(disclaimerText); - setDisclaimerText(''); - }; + const [isSessionProfilerEnabled, setIsSessionProfilerEnabled] = useState(true); return ( @@ -104,39 +56,166 @@ export const BugReportingScreen: React.FC< testID="id_br_state" /> - + { + navigation.navigate('ExtendedBugReportState', { + state: extendedBugReportState, + setState: (newState: ExtendedBugReportState) => { + setExtendedBugReportState(newState); + switch (newState) { + case ExtendedBugReportState.Disabled: + BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.disabled); + break; + case ExtendedBugReportState.EnabledWithRequiredFields: + BugReporting.setExtendedBugReportMode( + ExtendedBugReportMode.enabledWithRequiredFields, + ); + break; + case ExtendedBugReportState.EnabledWithOptionalFields: + BugReporting.setExtendedBugReportMode( + ExtendedBugReportMode.enabledWithOptionalFields, + ); + break; + } + navigation.goBack(); + }, + }); + }} + testID="id_extended_br_state" + /> - Instabug.show()} testID="id_br_show_button" /> - BugReporting.show(ReportType.bug, [])} /> - BugReporting.show(ReportType.feedback, [InvocationOption.emailFieldHidden]) - } + title="Bug Reporting Types" + subtitle={reportTypes.length > 0 ? reportTypes.join(', ') : 'None selected'} + onPress={() => { + navigation.navigate('BugReportingTypes', { + selectedTypes: reportTypes, + setSelectedTypes: (types: ReportType[]) => { + setReportTypes(types); + BugReporting.setReportTypes(types); + navigation.goBack(); + }, + }); + }} + testID="id_br_types" /> + BugReporting.show(ReportType.question, [])} + title="Disclaimer Text" + subtitle={disclaimerText || 'Not set'} + truncateSubtitle={true} + onPress={() => { + navigation.navigate('DisclaimerText', { + initialText: disclaimerText, + setText: (text: string) => { + setDisclaimerText(text); + }, + }); + }} + testID="id_disclaimer_text" + /> + + 0 ? invocationEvents.join(', ') : 'None selected'} + onPress={() => { + navigation.navigate('InvocationEvents', { + selectedEvents: invocationEvents, + setSelectedEvents: (events: string[]) => { + setInvocationEvents(events); + const selectedEnums: InvocationEvent[] = events.map((val) => { + switch (val) { + case 'floatingButton': + return InvocationEvent.floatingButton; + case 'twoFingersSwipe': + return InvocationEvent.twoFingersSwipe; + case 'screenshot': + return InvocationEvent.screenshot; + case 'shake': + return InvocationEvent.shake; + default: + throw new Error('Invalid invocation event selected'); + } + }); + BugReporting.setInvocationEvents(selectedEnums); + navigation.goBack(); + }, + }); + }} + testID="id_invocation_events" + /> + + 0 ? invocationOptions.join(', ') : 'None selected'} + onPress={() => { + navigation.navigate('InvocationOptions', { + selectedOptions: invocationOptions, + setSelectedOptions: (options: string[]) => { + setInvocationOptions(options); + const selectedEnums: InvocationOption[] = options.map((val) => { + switch (val) { + case 'commentFieldRequired': + return InvocationOption.commentFieldRequired; + case 'emailFieldHidden': + return InvocationOption.emailFieldHidden; + case 'emailFieldOptional': + return InvocationOption.emailFieldOptional; + case 'disablePostSendingDialog': + return InvocationOption.disablePostSendingDialog; + default: + throw new Error('Invalid invocation option selected'); + } + }); + BugReporting.setOptions(selectedEnums); + navigation.goBack(); + }, + }); + }} + testID="id_invocation_options" + /> + + { + navigation.navigate('SessionProfiler', { + isEnabled: isSessionProfilerEnabled, + setIsEnabled: (enabled: boolean) => { + setIsSessionProfilerEnabled(enabled); + Instabug.setSessionProfilerEnabled(enabled); + navigation.goBack(); + }, + }); + }} + testID="id_session_profiler" /> + Instabug.show()} testID="id_show_button" /> - BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithRequiredFields) - } + title="Send Bug Report" + onPress={() => BugReporting.show(ReportType.bug, [])} + testID="id_send_bug_report" /> - BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithOptionalFields) + BugReporting.show(ReportType.feedback, [InvocationOption.emailFieldHidden]) } + testID="id_send_feedback" /> Instabug.setSessionProfilerEnabled(true)} + title="Ask a Question" + onPress={() => BugReporting.show(ReportType.question, [])} + testID="id_send_question" /> + + + Instabug.showWelcomeMessage(WelcomeMessageMode.beta)} @@ -146,135 +225,7 @@ export const BugReportingScreen: React.FC< onPress={() => Instabug.showWelcomeMessage(WelcomeMessageMode.live)} /> - - - Bug Reporting Types - - - - - toggleCheckbox('bug', setReportTypes)} - value="bug" - accessible={true} - testID="id_br_report_type_bug" - size="md"> - Bug - - - toggleCheckbox('feedback', setReportTypes)} - value="feedback" - testID="id_br_report_type_feedback" - size="md"> - Feedback - - - toggleCheckbox('question', setReportTypes)} - value="question" - testID="id_br_report_type_question" - size="md"> - Question - - - - - - - - - - Set the disclaimer text - - - - - - - - - - - - Invocation Events - - - - - toggleCheckbox('floatingButton', setInvocationOptions)} - value="floatingButton" - testID="id_br_invoicetion_options_floatingButton" - accessible={true} - size="md"> - Floating button - - - toggleCheckbox('twoFingersSwipe', setInvocationOptions)} - value="twoFingersSwipe" - testID="id_br_invoicetion_options_twoFingersSwipe" - accessible={true} - size="md"> - Two Fingers Swipe - - - - toggleCheckbox('screenshot', setInvocationOptions)} - value="screenshot" - testID="id_br_invoicetion_options_screenshot" - accessible={true} - size="md"> - Screenshot - - toggleCheckbox('shake', setInvocationOptions)} - testID="id_br_invoicetion_options_shake" - accessible={true} - value="shake" - size="md"> - Shake - - - - - - +
setState(BugReportingState.Enabled)} + subtitle={state === BugReportingState.Enabled ? 'Selected' : undefined} /> setState(BugReportingState.Disabled)} + subtitle={state === BugReportingState.Disabled ? 'Selected' : undefined} /> ); diff --git a/examples/default/src/screens/bug-reporting/BugReportingTypesScreen.tsx b/examples/default/src/screens/bug-reporting/BugReportingTypesScreen.tsx new file mode 100644 index 000000000..ad0da1ecc --- /dev/null +++ b/examples/default/src/screens/bug-reporting/BugReportingTypesScreen.tsx @@ -0,0 +1,63 @@ +import React from 'react'; + +import { ListTile } from '../../components/ListTile'; +import { Screen } from '../../components/Screen'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; +import { ReportType } from 'instabug-reactnative'; + +export interface BugReportingTypesScreenProp { + selectedTypes: ReportType[]; + setSelectedTypes: (types: ReportType[]) => void; +} + +export const BugReportingTypesScreen: React.FC< + NativeStackScreenProps +> = ({ navigation, route }) => { + const { selectedTypes, setSelectedTypes } = route.params; + + const isSelected = (types: ReportType[]) => { + return ( + types.length === selectedTypes.length && types.every((type) => selectedTypes.includes(type)) + ); + }; + + return ( + + setSelectedTypes([ReportType.bug])} + subtitle={isSelected([ReportType.bug]) ? 'Selected' : undefined} + /> + setSelectedTypes([ReportType.feedback])} + subtitle={isSelected([ReportType.feedback]) ? 'Selected' : undefined} + /> + setSelectedTypes([ReportType.question])} + subtitle={isSelected([ReportType.question]) ? 'Selected' : undefined} + /> + setSelectedTypes([ReportType.bug, ReportType.question])} + subtitle={isSelected([ReportType.bug, ReportType.question]) ? 'Selected' : undefined} + /> + setSelectedTypes([ReportType.bug, ReportType.feedback, ReportType.question])} + subtitle={ + isSelected([ReportType.bug, ReportType.feedback, ReportType.question]) + ? 'Selected' + : undefined + } + /> + + ); +}; diff --git a/examples/default/src/screens/bug-reporting/DisclaimerTextScreen.tsx b/examples/default/src/screens/bug-reporting/DisclaimerTextScreen.tsx new file mode 100644 index 000000000..ef7f0236b --- /dev/null +++ b/examples/default/src/screens/bug-reporting/DisclaimerTextScreen.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { View } from 'react-native'; + +import { Screen } from '../../components/Screen'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; +import { BugReporting } from 'instabug-reactnative'; +import { Input, Button, VStack } from 'native-base'; + +export interface DisclaimerTextScreenProp { + initialText: string; + setText: (text: string) => void; +} + +export const DisclaimerTextScreen: React.FC< + NativeStackScreenProps +> = ({ navigation, route }) => { + const { initialText, setText } = route.params; + const [text, setLocalText] = useState(initialText); + + const handleSave = () => { + setText(text); + BugReporting.setDisclaimerText(text); + navigation.goBack(); + }; + + return ( + + + + + + + + ); +}; diff --git a/examples/default/src/screens/bug-reporting/ExtendedBugReportStateScreen.tsx b/examples/default/src/screens/bug-reporting/ExtendedBugReportStateScreen.tsx new file mode 100644 index 000000000..059187706 --- /dev/null +++ b/examples/default/src/screens/bug-reporting/ExtendedBugReportStateScreen.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +import { ListTile } from '../../components/ListTile'; +import { Screen } from '../../components/Screen'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; +import { ExtendedBugReportMode } from 'instabug-reactnative'; + +export enum ExtendedBugReportState { + Disabled = 'Disabled', + EnabledWithRequiredFields = 'EnabledWithRequiredFields', + EnabledWithOptionalFields = 'EnabledWithOptionalFields', +} + +export interface ExtendedBugReportStateScreenProp { + state: ExtendedBugReportState; + setState: (state: ExtendedBugReportState) => void; +} + +export const ExtendedBugReportStateScreen: React.FC< + NativeStackScreenProps +> = ({ navigation, route }) => { + const { state, setState } = route.params; + return ( + + setState(ExtendedBugReportState.Disabled)} + subtitle={state === ExtendedBugReportState.Disabled ? 'Selected' : undefined} + /> + setState(ExtendedBugReportState.EnabledWithRequiredFields)} + subtitle={state === ExtendedBugReportState.EnabledWithRequiredFields ? 'Selected' : undefined} + /> + setState(ExtendedBugReportState.EnabledWithOptionalFields)} + subtitle={state === ExtendedBugReportState.EnabledWithOptionalFields ? 'Selected' : undefined} + /> + + ); +}; diff --git a/examples/default/src/screens/bug-reporting/InvocationEventsScreen.tsx b/examples/default/src/screens/bug-reporting/InvocationEventsScreen.tsx new file mode 100644 index 000000000..108237bc7 --- /dev/null +++ b/examples/default/src/screens/bug-reporting/InvocationEventsScreen.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Screen } from '../../components/Screen'; +import { ListTile } from '../../components/ListTile'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; + +export interface InvocationEventsScreenProp { + selectedEvents: string[]; + setSelectedEvents: (events: string[]) => void; +} + +export const InvocationEventsScreen: React.FC< + NativeStackScreenProps +> = ({ navigation, route }) => { + const { selectedEvents, setSelectedEvents } = route.params; + + const isSelected = (events: string[]) => { + return ( + events.length === selectedEvents.length && + events.every((event) => selectedEvents.includes(event)) + ); + }; + + return ( + + setSelectedEvents(['floatingButton'])} + testID="id_floating_button" + subtitle={isSelected(['floatingButton']) ? 'Selected' : undefined} + /> + + setSelectedEvents(['twoFingersSwipe'])} + testID="id_two_fingers_swipe" + subtitle={isSelected(['twoFingersSwipe']) ? 'Selected' : undefined} + /> + + setSelectedEvents(['screenshot'])} + testID="id_screenshot" + subtitle={isSelected(['screenshot']) ? 'Selected' : undefined} + /> + + setSelectedEvents(['shake'])} + testID="id_shake" + subtitle={isSelected(['shake']) ? 'Selected' : undefined} + /> + + setSelectedEvents(['floatingButton', 'shake', 'screenshot'])} + testID="id_common" + subtitle={isSelected(['floatingButton', 'shake', 'screenshot']) ? 'Selected' : undefined} + /> + + + setSelectedEvents(['floatingButton', 'twoFingersSwipe', 'screenshot', 'shake']) + } + testID="id_all" + subtitle={ + isSelected(['floatingButton', 'twoFingersSwipe', 'screenshot', 'shake']) + ? 'Selected' + : undefined + } + /> + setSelectedEvents([])} + testID="id_none" + subtitle={isSelected([]) ? 'Selected' : undefined} + /> + + ); +}; diff --git a/examples/default/src/screens/bug-reporting/InvocationOptionsScreen.tsx b/examples/default/src/screens/bug-reporting/InvocationOptionsScreen.tsx new file mode 100644 index 000000000..0f8dfc5ff --- /dev/null +++ b/examples/default/src/screens/bug-reporting/InvocationOptionsScreen.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Screen } from '../../components/Screen'; +import { ListTile } from '../../components/ListTile'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; + +export interface InvocationOptionsScreenProp { + selectedOptions: string[]; + setSelectedOptions: (options: string[]) => void; +} + +export const InvocationOptionsScreen: React.FC< + NativeStackScreenProps +> = ({ navigation, route }) => { + const { selectedOptions, setSelectedOptions } = route.params; + + const isSelected = (options: string[]) => { + return ( + options.length === selectedOptions.length && + options.every((option) => selectedOptions.includes(option)) + ); + }; + + return ( + + setSelectedOptions(['commentFieldRequired'])} + testID="id_comment_field_required" + subtitle={isSelected(['commentFieldRequired']) ? 'Selected' : undefined} + /> + + setSelectedOptions(['emailFieldHidden'])} + testID="id_email_field_hidden" + subtitle={isSelected(['emailFieldHidden']) ? 'Selected' : undefined} + /> + + setSelectedOptions(['emailFieldOptional'])} + testID="id_email_field_optional" + subtitle={isSelected(['emailFieldOptional']) ? 'Selected' : undefined} + /> + + setSelectedOptions(['disablePostSendingDialog'])} + testID="id_disable_post_sending_dialog" + subtitle={isSelected(['disablePostSendingDialog']) ? 'Selected' : undefined} + /> + + setSelectedOptions(['commentFieldRequired', 'emailFieldHidden'])} + testID="id_comment_field_required_email_field_hidden" + subtitle={isSelected(['commentFieldRequired', 'emailFieldHidden']) ? 'Selected' : undefined} + /> + + setSelectedOptions([])} + testID="id_none" + subtitle={isSelected([]) ? 'Selected' : undefined} + /> + + ); +}; diff --git a/examples/default/src/screens/bug-reporting/SessionProfilerScreen.tsx b/examples/default/src/screens/bug-reporting/SessionProfilerScreen.tsx new file mode 100644 index 000000000..14b275c0a --- /dev/null +++ b/examples/default/src/screens/bug-reporting/SessionProfilerScreen.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Screen } from '../../components/Screen'; +import { ListTile } from '../../components/ListTile'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; +import Instabug from 'instabug-reactnative'; + +export interface SessionProfilerScreenProp { + isEnabled: boolean; + setIsEnabled: (enabled: boolean) => void; +} + +export const SessionProfilerScreen: React.FC< + NativeStackScreenProps +> = ({ navigation, route }) => { + const { isEnabled, setIsEnabled } = route.params; + + return ( + + setIsEnabled(true)} + testID="id_enabled" + subtitle={isEnabled ? 'Selected' : undefined} + /> + setIsEnabled(false)} + testID="id_disabled" + subtitle={!isEnabled ? 'Selected' : undefined} + /> + + ); +}; From 1aac4ba75ddd8407282073c04bad51f57399a44e Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Sat, 31 May 2025 23:45:51 +0300 Subject: [PATCH 05/19] feat: add View Hierarchy and Replies state management screens - Introduced `ViewHierarchyScreen` and `RepliesStateScreen` for managing respective states. - Updated `HomeStack` to include new screens in navigation. - Enhanced `BugReportingScreen` with options to navigate to the new screens and manage their states. - Added state management for enabling/disabling View Hierarchy and Replies features. --- examples/default/src/navigation/HomeStack.tsx | 20 ++++++++++ .../bug-reporting/BugReportingScreen.tsx | 37 ++++++++++++++++++- .../bug-reporting/RepliesStateScreen.tsx | 33 +++++++++++++++++ .../bug-reporting/ViewHierarchyScreen.tsx | 33 +++++++++++++++++ 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 examples/default/src/screens/bug-reporting/RepliesStateScreen.tsx create mode 100644 examples/default/src/screens/bug-reporting/ViewHierarchyScreen.tsx diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx index 8affca99a..6aafff8d8 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -23,6 +23,14 @@ import { InvocationOptionsScreen, type InvocationOptionsScreenProp, } from '../screens/bug-reporting/InvocationOptionsScreen'; +import { + ViewHierarchyScreen, + type ViewHierarchyScreenProp, +} from '../screens/bug-reporting/ViewHierarchyScreen'; +import { + RepliesStateScreen, + type RepliesStateScreenProp, +} from '../screens/bug-reporting/RepliesStateScreen'; import { CrashReportingScreen } from '../screens/CrashReportingScreen'; import { FeatureRequestsScreen } from '../screens/FeatureRequestsScreen'; import { HomeScreen } from '../screens/HomeScreen'; @@ -72,6 +80,8 @@ export type HomeStackParamList = { InvocationEvents: InvocationEventsScreenProp; SessionProfiler: SessionProfilerScreenProp; InvocationOptions: InvocationOptionsScreenProp; + ViewHierarchy: ViewHierarchyScreenProp; + RepliesState: RepliesStateScreenProp; CrashReporting: undefined; FeatureRequests: undefined; @@ -149,6 +159,16 @@ export const HomeStackNavigator: React.FC = () => { component={InvocationOptionsScreen} options={{ title: 'Invocation Options' }} /> + + (''); const [isSessionProfilerEnabled, setIsSessionProfilerEnabled] = useState(true); + const [isViewHierarchyEnabled, setIsViewHierarchyEnabled] = useState(false); + const [isRepliesEnabled, setIsRepliesEnabled] = useState(true); return ( @@ -193,6 +196,38 @@ export const BugReportingScreen: React.FC< testID="id_session_profiler" /> + { + navigation.navigate('ViewHierarchy', { + isEnabled: isViewHierarchyEnabled, + setIsEnabled: (enabled: boolean) => { + setIsViewHierarchyEnabled(enabled); + BugReporting.setViewHierarchyEnabled(enabled); + navigation.goBack(); + }, + }); + }} + testID="id_view_hierarchy" + /> + + { + navigation.navigate('RepliesState', { + isEnabled: isRepliesEnabled, + setIsEnabled: (enabled: boolean) => { + setIsRepliesEnabled(enabled); + Replies.setEnabled(enabled); + navigation.goBack(); + }, + }); + }} + testID="id_replies" + /> + Instabug.show()} testID="id_show_button" /> @@ -214,8 +249,6 @@ export const BugReportingScreen: React.FC< testID="id_send_question" /> - - Instabug.showWelcomeMessage(WelcomeMessageMode.beta)} diff --git a/examples/default/src/screens/bug-reporting/RepliesStateScreen.tsx b/examples/default/src/screens/bug-reporting/RepliesStateScreen.tsx new file mode 100644 index 000000000..a42dbeefa --- /dev/null +++ b/examples/default/src/screens/bug-reporting/RepliesStateScreen.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Screen } from '../../components/Screen'; +import { ListTile } from '../../components/ListTile'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; + +export interface RepliesStateScreenProp { + isEnabled: boolean; + setIsEnabled: (enabled: boolean) => void; +} + +export const RepliesStateScreen: React.FC< + NativeStackScreenProps +> = ({ navigation, route }) => { + const { isEnabled, setIsEnabled } = route.params; + + return ( + + setIsEnabled(true)} + testID="id_enabled" + subtitle={isEnabled ? 'Selected' : undefined} + /> + setIsEnabled(false)} + testID="id_disabled" + subtitle={!isEnabled ? 'Selected' : undefined} + /> + + ); +}; diff --git a/examples/default/src/screens/bug-reporting/ViewHierarchyScreen.tsx b/examples/default/src/screens/bug-reporting/ViewHierarchyScreen.tsx new file mode 100644 index 000000000..62b1a6ab3 --- /dev/null +++ b/examples/default/src/screens/bug-reporting/ViewHierarchyScreen.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Screen } from '../../components/Screen'; +import { ListTile } from '../../components/ListTile'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; + +export interface ViewHierarchyScreenProp { + isEnabled: boolean; + setIsEnabled: (enabled: boolean) => void; +} + +export const ViewHierarchyScreen: React.FC< + NativeStackScreenProps +> = ({ navigation, route }) => { + const { isEnabled, setIsEnabled } = route.params; + + return ( + + setIsEnabled(true)} + testID="id_enabled" + subtitle={isEnabled ? 'Selected' : undefined} + /> + setIsEnabled(false)} + testID="id_disabled" + subtitle={!isEnabled ? 'Selected' : undefined} + /> + + ); +}; From b1dfdb4de594d1a45746d7ea49e688fae49bbb80 Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Sun, 1 Jun 2025 04:42:09 +0300 Subject: [PATCH 06/19] fix: update crash reporting method for non-fatal native crashes - Modified `sendNativeNonFatal` method to remove the parameter and use a test exception directly. --- .../react/example/RNInstabugExampleCrashReportingModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/default/android/app/src/main/java/com/instabug/react/example/RNInstabugExampleCrashReportingModule.java b/examples/default/android/app/src/main/java/com/instabug/react/example/RNInstabugExampleCrashReportingModule.java index 4ccb7ad3c..fa62793a1 100644 --- a/examples/default/android/app/src/main/java/com/instabug/react/example/RNInstabugExampleCrashReportingModule.java +++ b/examples/default/android/app/src/main/java/com/instabug/react/example/RNInstabugExampleCrashReportingModule.java @@ -28,7 +28,7 @@ public String getName() { } @ReactMethod - public void sendNativeNonFatal(final String exceptionObject) { + public void sendNativeNonFatal() { final IBGNonFatalException exception = new IBGNonFatalException.Builder(new IllegalStateException("Test exception")) .build(); CrashReporting.report(exception); From 41383b1204b797438383261a0c99fbca56f6cb02 Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Mon, 2 Jun 2025 05:14:44 +0300 Subject: [PATCH 07/19] feat: add crash reporting screens and enhance navigation - Introduced `CrashReportingStateScreen`, `NDKCrashesStateScreen`, `NonFatalCrashesScreen`, and `FatalCrashesScreen` for managing crash reporting states and handling different crash types. - Updated `HomeStack` to include new screens for crash reporting. - Enhanced `CrashReportingScreen` to navigate to the new state management screens and improved UI interactions. - Added test IDs for better integration with testing frameworks. --- ...RNInstabugExampleCrashReportingModule.java | 1 - .../default/src/components/InputField.tsx | 6 +- .../src/components/PlatformListTile.tsx | 5 +- examples/default/src/components/Select.tsx | 6 +- examples/default/src/navigation/HomeStack.tsx | 38 ++ .../src/screens/CrashReportingScreen.tsx | 340 +++--------------- .../CrashReportingStateScreen.tsx | 33 ++ .../crash-reporting/FatalCrashesScreen.tsx | 152 ++++++++ .../crash-reporting/NDKCrashesStateScreen.tsx | 33 ++ .../crash-reporting/NonFatalCrashesScreen.tsx | 191 ++++++++++ 10 files changed, 517 insertions(+), 288 deletions(-) create mode 100644 examples/default/src/screens/crash-reporting/CrashReportingStateScreen.tsx create mode 100644 examples/default/src/screens/crash-reporting/FatalCrashesScreen.tsx create mode 100644 examples/default/src/screens/crash-reporting/NDKCrashesStateScreen.tsx create mode 100644 examples/default/src/screens/crash-reporting/NonFatalCrashesScreen.tsx diff --git a/examples/default/android/app/src/main/java/com/instabug/react/example/RNInstabugExampleCrashReportingModule.java b/examples/default/android/app/src/main/java/com/instabug/react/example/RNInstabugExampleCrashReportingModule.java index fa62793a1..d10291dd3 100644 --- a/examples/default/android/app/src/main/java/com/instabug/react/example/RNInstabugExampleCrashReportingModule.java +++ b/examples/default/android/app/src/main/java/com/instabug/react/example/RNInstabugExampleCrashReportingModule.java @@ -32,7 +32,6 @@ public void sendNativeNonFatal() { final IBGNonFatalException exception = new IBGNonFatalException.Builder(new IllegalStateException("Test exception")) .build(); CrashReporting.report(exception); - } @ReactMethod diff --git a/examples/default/src/components/InputField.tsx b/examples/default/src/components/InputField.tsx index 1426a9d99..f3db99ed3 100644 --- a/examples/default/src/components/InputField.tsx +++ b/examples/default/src/components/InputField.tsx @@ -45,6 +45,7 @@ export const InputField = forwardRef( void; platform?: 'ios' | 'android'; + testID?: string; } export const PlatformListTile: React.FC = ({ @@ -14,6 +15,7 @@ export const PlatformListTile: React.FC = ({ onPress, platform, children, + testID, }) => { if (Platform.OS === platform || !platform) { return ( @@ -25,7 +27,8 @@ export const PlatformListTile: React.FC = ({ borderBottomWidth="1" borderColor="coolGray.300" bg="coolGray.100" - _pressed={{ bg: 'coolGray.200' }}> + _pressed={{ bg: 'coolGray.200' }} + testID={testID}> {title} {children} diff --git a/examples/default/src/components/Select.tsx b/examples/default/src/components/Select.tsx index 206aa74e0..0d81e655e 100644 --- a/examples/default/src/components/Select.tsx +++ b/examples/default/src/components/Select.tsx @@ -6,15 +6,17 @@ interface SelectItem { label: string; value: T; isInitial?: boolean; + testID?: string; } interface SelectProps { label: string; items: SelectItem[]; onValueChange: (value: T) => void; + testID?: string; } -export function Select({ label, items, onValueChange }: SelectProps) { +export function Select({ label, items, onValueChange, testID }: SelectProps) { const initialItem = items.find((i) => i.isInitial) ?? items[0]; const [selectedItem, setSelectedItem] = useState(initialItem); @@ -35,7 +37,7 @@ export function Select({ label, items, onValueChange }: SelectProps) { endIcon: , }}> {items.map((item) => ( - + ))} ); diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx index 6aafff8d8..59fff3bb7 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -32,6 +32,10 @@ import { type RepliesStateScreenProp, } from '../screens/bug-reporting/RepliesStateScreen'; import { CrashReportingScreen } from '../screens/CrashReportingScreen'; +import { + CrashReportingStateScreen, + type CrashReportingStateScreenProp, +} from '../screens/crash-reporting/CrashReportingStateScreen'; import { FeatureRequestsScreen } from '../screens/FeatureRequestsScreen'; import { HomeScreen } from '../screens/HomeScreen'; import { RepliesScreen } from '../screens/RepliesScreen'; @@ -67,6 +71,12 @@ import { SessionProfilerScreen, type SessionProfilerScreenProp, } from '../screens/bug-reporting/SessionProfilerScreen'; +import { + NDKCrashesStateScreen, + type NDKCrashesStateScreenProp, +} from '../screens/crash-reporting/NDKCrashesStateScreen'; +import { NonFatalCrashesScreen } from '../screens/crash-reporting/NonFatalCrashesScreen'; +import { FatalCrashesScreen } from '../screens/crash-reporting/FatalCrashesScreen'; export type HomeStackParamList = { Home: undefined; @@ -83,7 +93,13 @@ export type HomeStackParamList = { ViewHierarchy: ViewHierarchyScreenProp; RepliesState: RepliesStateScreenProp; + // Crash Reporting // CrashReporting: undefined; + CrashReportingState: CrashReportingStateScreenProp; + NDKCrashesState: NDKCrashesStateScreenProp; + NonFatalCrashes: undefined; + FatalCrashes: undefined; + FeatureRequests: undefined; Replies: undefined; Surveys: undefined; @@ -170,11 +186,33 @@ export const HomeStackNavigator: React.FC = () => { options={{ title: 'Replies State' }} /> + {/* Crash Reporting */} + + + + + { - function throwHandledException(error: Error) { - try { - if (!error.message) { - const appName = 'Instabug Test App'; - error.message = `Handled ${error.name} From ${appName}`; - } - throw error; - } catch (err) { - if (err instanceof Error) { - CrashReporting.reportError(err, { level: NonFatalErrorLevel.critical }).then(() => - Alert.alert(`Crash report for ${error.name} is Sent!`), - ); - } - } - } - - function throwUnhandledException(error: Error, isPromise: boolean = false) { - const appName = 'Instabug Test App'; - const rejectionType = isPromise ? 'Promise Rejection ' : ''; - const errorMessage = `Unhandled ${rejectionType}${error.name} from ${appName}`; - - if (!error.message) { - console.log(`IBG-CRSH | Error message: ${error.message}`); - error.message = errorMessage; - } - - if (isPromise) { - console.log('IBG-CRSH | Promise'); - Promise.reject(error).then(() => - Alert.alert(`Promise Rejection Crash report for ${error.name} is Sent!`), - ); - } else { - throw error; - } - } - const [isEnabled, setIsEnabled] = useState(false); - - const [userAttributeKey, setUserAttributeKey] = useState(''); - const [userAttributeValue, setUserAttributeValue] = useState(''); - const [crashNameValue, setCrashNameValue] = useState(''); - const [crashFingerprint, setCrashFingerprint] = useState(''); - const [crashLevelValue, setCrashLevelValue] = useState( - NonFatalErrorLevel.error, - ); - - const toggleSwitch = (value: boolean) => { - setIsEnabled(value); - CrashReporting.setEnabled(value); - showNotification('Crash Reporting status', 'Crash Reporting enabled set to ' + value); - }; - - function sendCrash() { - try { - const error = new Error(crashNameValue); - - throw error; - } catch (err) { - if (err instanceof Error) { - const attrMap: { [k: string]: string } = {}; - attrMap[userAttributeKey] = userAttributeValue; - - const userAttributes: Record = {}; - if (userAttributeKey && userAttributeValue) { - userAttributes[userAttributeKey] = userAttributeValue; - } - const fingerprint = crashFingerprint.length === 0 ? undefined : crashFingerprint; - - CrashReporting.reportError(err, { - userAttributes: userAttributes, - fingerprint: fingerprint, - level: crashLevelValue, - }).then(() => { - Alert.alert(`Crash report for ${crashNameValue} is Sent!`); - }); - } - } - } +export const CrashReportingScreen: React.FC< + NativeStackScreenProps +> = ({ navigation }) => { + const [isEnabled, setIsEnabled] = useState(true); + const [isNDKEnabled, setIsNDKEnabled] = useState(true); return ( - - Crash Reporting Enabled: - - -
+ + + { + navigation.navigate('CrashReportingState', { + isEnabled, + setIsEnabled: (enabled: boolean) => { + setIsEnabled(enabled); + CrashReporting.setEnabled(enabled); + navigation.goBack(); + }, + }); + }} + testID="id_cr_state" + /> + + {Platform.OS === 'android' && ( throwHandledException(new Error())} + title="NDK Crashes State" + subtitle={isNDKEnabled ? 'Enabled' : 'Disabled'} + onPress={() => { + navigation.navigate('NDKCrashesState', { + isEnabled: isNDKEnabled, + setIsEnabled: (enabled: boolean) => { + setIsNDKEnabled(enabled); + CrashReporting.setNDKCrashesEnabled(enabled); + navigation.goBack(); + }, + }); + }} + testID="id_ndk_cr_state" /> - throwHandledException(new SyntaxError())} - /> - throwHandledException(new RangeError())} - /> - throwHandledException(new ReferenceError())} - /> - throwHandledException(new URIError())} - /> - NativeExampleCrashReporting.sendNativeNonFatal()} - /> - - - - setCrashNameValue(key)} - value={crashNameValue} - /> - - - - setUserAttributeKey(key)} - value={userAttributeKey} - /> - - - setUserAttributeValue(value)} - value={userAttributeValue} - /> - - - - { + setCrashLevelValue(value); + }} + /> + + + + setCrashFingerprint(text)} + value={crashFingerprint} + testID="id_crash_fingerprint" + /> + + + +
+
+
+
+ ); +}; From 6b448ce4b11c38a52a2f214492ec3ac8543f4752 Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Mon, 2 Jun 2025 22:37:57 +0300 Subject: [PATCH 08/19] feat: add User Consent screen and update navigation - Introduced `UserConsentScreen` for managing user consent details, including key, description, mandatory status, checked status, and action type. - Updated `HomeStack` to include the new `UserConsent` route. - Enhanced `BugReportingScreen` with a new option to navigate to the `UserConsentScreen`. - Added test IDs for improved testing integration. --- examples/default/src/navigation/HomeStack.tsx | 7 + .../bug-reporting/BugReportingScreen.tsx | 7 + .../bug-reporting/UserConsentScreen.tsx | 145 ++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 examples/default/src/screens/bug-reporting/UserConsentScreen.tsx diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx index 59fff3bb7..2fefa8439 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -31,6 +31,7 @@ import { RepliesStateScreen, type RepliesStateScreenProp, } from '../screens/bug-reporting/RepliesStateScreen'; +import { UserConsentScreen } from '../screens/bug-reporting/UserConsentScreen'; import { CrashReportingScreen } from '../screens/CrashReportingScreen'; import { CrashReportingStateScreen, @@ -92,6 +93,7 @@ export type HomeStackParamList = { InvocationOptions: InvocationOptionsScreenProp; ViewHierarchy: ViewHierarchyScreenProp; RepliesState: RepliesStateScreenProp; + UserConsent: undefined; // Crash Reporting // CrashReporting: undefined; @@ -185,6 +187,11 @@ export const HomeStackNavigator: React.FC = () => { component={RepliesStateScreen} options={{ title: 'Replies State' }} /> + {/* Crash Reporting */} + + navigation.navigate('UserConsent')} + testID="id_user_consent" + /> + Instabug.show()} testID="id_show_button" /> diff --git a/examples/default/src/screens/bug-reporting/UserConsentScreen.tsx b/examples/default/src/screens/bug-reporting/UserConsentScreen.tsx new file mode 100644 index 000000000..8845cef94 --- /dev/null +++ b/examples/default/src/screens/bug-reporting/UserConsentScreen.tsx @@ -0,0 +1,145 @@ +import React, { useState } from 'react'; +import { ScrollView, StyleSheet, View, Text, Alert } from 'react-native'; +import { BugReporting, userConsentActionType } from 'instabug-reactnative'; +import { Screen } from '../../components/Screen'; +import { Section } from '../../components/Section'; +import { Button, VStack } from 'native-base'; +import { InputField } from '../../components/InputField'; +import { Select } from '../../components/Select'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; + +const styles = StyleSheet.create({ + inputWrapper: { + padding: 4, + flex: 1, + }, + inputTitle: { + fontSize: 12, + fontWeight: 'bold', + color: 'black', + paddingLeft: 4, + paddingBottom: 4, + }, +}); + +export const UserConsentScreen: React.FC< + NativeStackScreenProps +> = ({ navigation }) => { + const [key, setKey] = useState(''); + const [description, setDescription] = useState(''); + const [mandatory, setMandatory] = useState(false); + const [checked, setChecked] = useState(false); + const [actionType, setActionType] = useState(undefined); + + const handleSubmit = () => { + BugReporting.addUserConsent(key, description, mandatory, checked, actionType); + Alert.alert('User Consent Added', 'User consent added successfully'); + navigation.goBack(); + }; + + return ( + + +
+ + + Key + + + + Description + + + + Mandatory + setChecked(value === 'true')} + /> + + + Action Type + diff --git a/examples/default/src/screens/bug-reporting/ExtendedBugReportStateScreen.tsx b/examples/default/src/screens/bug-reporting/ExtendedBugReportStateScreen.tsx index 464622c74..ef6297783 100644 --- a/examples/default/src/screens/bug-reporting/ExtendedBugReportStateScreen.tsx +++ b/examples/default/src/screens/bug-reporting/ExtendedBugReportStateScreen.tsx @@ -4,7 +4,6 @@ import { ListTile } from '../../components/ListTile'; import { Screen } from '../../components/Screen'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { HomeStackParamList } from '../../navigation/HomeStack'; -import { ExtendedBugReportMode } from 'instabug-reactnative'; export enum ExtendedBugReportState { Disabled = 'Disabled', @@ -19,7 +18,7 @@ export interface ExtendedBugReportStateScreenProp { export const ExtendedBugReportStateScreen: React.FC< NativeStackScreenProps -> = ({ navigation, route }) => { +> = ({ route }) => { const { state, setState } = route.params; return ( diff --git a/examples/default/src/screens/bug-reporting/InvocationEventsScreen.tsx b/examples/default/src/screens/bug-reporting/InvocationEventsScreen.tsx index 108237bc7..3c334930b 100644 --- a/examples/default/src/screens/bug-reporting/InvocationEventsScreen.tsx +++ b/examples/default/src/screens/bug-reporting/InvocationEventsScreen.tsx @@ -11,7 +11,7 @@ export interface InvocationEventsScreenProp { export const InvocationEventsScreen: React.FC< NativeStackScreenProps -> = ({ navigation, route }) => { +> = ({ route }) => { const { selectedEvents, setSelectedEvents } = route.params; const isSelected = (events: string[]) => { diff --git a/examples/default/src/screens/bug-reporting/InvocationOptionsScreen.tsx b/examples/default/src/screens/bug-reporting/InvocationOptionsScreen.tsx index 0f8dfc5ff..0d3ddb80e 100644 --- a/examples/default/src/screens/bug-reporting/InvocationOptionsScreen.tsx +++ b/examples/default/src/screens/bug-reporting/InvocationOptionsScreen.tsx @@ -11,7 +11,7 @@ export interface InvocationOptionsScreenProp { export const InvocationOptionsScreen: React.FC< NativeStackScreenProps -> = ({ navigation, route }) => { +> = ({ route }) => { const { selectedOptions, setSelectedOptions } = route.params; const isSelected = (options: string[]) => { diff --git a/examples/default/src/screens/bug-reporting/RepliesStateScreen.tsx b/examples/default/src/screens/bug-reporting/RepliesStateScreen.tsx index a42dbeefa..a4f152c30 100644 --- a/examples/default/src/screens/bug-reporting/RepliesStateScreen.tsx +++ b/examples/default/src/screens/bug-reporting/RepliesStateScreen.tsx @@ -11,7 +11,7 @@ export interface RepliesStateScreenProp { export const RepliesStateScreen: React.FC< NativeStackScreenProps -> = ({ navigation, route }) => { +> = ({ route }) => { const { isEnabled, setIsEnabled } = route.params; return ( diff --git a/examples/default/src/screens/bug-reporting/SessionProfilerScreen.tsx b/examples/default/src/screens/bug-reporting/SessionProfilerScreen.tsx index 14b275c0a..63938a49d 100644 --- a/examples/default/src/screens/bug-reporting/SessionProfilerScreen.tsx +++ b/examples/default/src/screens/bug-reporting/SessionProfilerScreen.tsx @@ -3,7 +3,6 @@ import { Screen } from '../../components/Screen'; import { ListTile } from '../../components/ListTile'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { HomeStackParamList } from '../../navigation/HomeStack'; -import Instabug from 'instabug-reactnative'; export interface SessionProfilerScreenProp { isEnabled: boolean; @@ -12,7 +11,7 @@ export interface SessionProfilerScreenProp { export const SessionProfilerScreen: React.FC< NativeStackScreenProps -> = ({ navigation, route }) => { +> = ({ route }) => { const { isEnabled, setIsEnabled } = route.params; return ( diff --git a/examples/default/src/screens/bug-reporting/ViewHierarchyScreen.tsx b/examples/default/src/screens/bug-reporting/ViewHierarchyScreen.tsx index 62b1a6ab3..80f565fc7 100644 --- a/examples/default/src/screens/bug-reporting/ViewHierarchyScreen.tsx +++ b/examples/default/src/screens/bug-reporting/ViewHierarchyScreen.tsx @@ -11,7 +11,7 @@ export interface ViewHierarchyScreenProp { export const ViewHierarchyScreen: React.FC< NativeStackScreenProps -> = ({ navigation, route }) => { +> = ({ route }) => { const { isEnabled, setIsEnabled } = route.params; return ( diff --git a/examples/default/src/screens/crash-reporting/CrashReportingStateScreen.tsx b/examples/default/src/screens/crash-reporting/CrashReportingStateScreen.tsx index 7ac6d90c8..9741c2436 100644 --- a/examples/default/src/screens/crash-reporting/CrashReportingStateScreen.tsx +++ b/examples/default/src/screens/crash-reporting/CrashReportingStateScreen.tsx @@ -11,7 +11,7 @@ export interface CrashReportingStateScreenProp { export const CrashReportingStateScreen: React.FC< NativeStackScreenProps -> = ({ navigation, route }) => { +> = ({ route }) => { const { isEnabled, setIsEnabled } = route.params; return ( diff --git a/examples/default/src/screens/crash-reporting/FatalCrashesScreen.tsx b/examples/default/src/screens/crash-reporting/FatalCrashesScreen.tsx index 0fb1290dc..867e85fcc 100644 --- a/examples/default/src/screens/crash-reporting/FatalCrashesScreen.tsx +++ b/examples/default/src/screens/crash-reporting/FatalCrashesScreen.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Alert, Platform, ScrollView, StyleSheet, Text, View } from 'react-native'; +import { Alert, Platform, ScrollView, StyleSheet, Text } from 'react-native'; import { ListTile } from '../../components/ListTile'; import { Screen } from '../../components/Screen'; import { Section } from '../../components/Section'; diff --git a/examples/default/src/screens/crash-reporting/NDKCrashesStateScreen.tsx b/examples/default/src/screens/crash-reporting/NDKCrashesStateScreen.tsx index 7abe19e40..669969da0 100644 --- a/examples/default/src/screens/crash-reporting/NDKCrashesStateScreen.tsx +++ b/examples/default/src/screens/crash-reporting/NDKCrashesStateScreen.tsx @@ -11,7 +11,7 @@ export interface NDKCrashesStateScreenProp { export const NDKCrashesStateScreen: React.FC< NativeStackScreenProps -> = ({ navigation, route }) => { +> = ({ route }) => { const { isEnabled, setIsEnabled } = route.params; return ( diff --git a/examples/default/src/screens/crash-reporting/NonFatalCrashesScreen.tsx b/examples/default/src/screens/crash-reporting/NonFatalCrashesScreen.tsx index bb3be7c50..e66c34794 100644 --- a/examples/default/src/screens/crash-reporting/NonFatalCrashesScreen.tsx +++ b/examples/default/src/screens/crash-reporting/NonFatalCrashesScreen.tsx @@ -5,7 +5,6 @@ import { ListTile } from '../../components/ListTile'; import { Screen } from '../../components/Screen'; import { Section } from '../../components/Section'; import { NativeExampleCrashReporting } from '../../native/NativeCrashReporting'; -import { VerticalListTile } from '../../components/VerticalListTile'; import { Button, Divider, VStack } from 'native-base'; import { InputField } from '../../components/InputField'; import { Select } from '../../components/Select'; From ee3ffbb41545bf4f0ddd5b293650bcdef7391ddd Mon Sep 17 00:00:00 2001 From: ahmed alaa <154802748+ahmedAlaaInstabug@users.noreply.github.com> Date: Fri, 15 Aug 2025 19:19:54 +0300 Subject: [PATCH 16/19] add new scenarios --- examples/default/package.json | 3 + examples/default/src/App.tsx | 5 +- .../default/src/contexts/callbackContext.tsx | 56 ++++ examples/default/src/navigation/HomeStack.tsx | 31 +- examples/default/src/navigation/RootTab.tsx | 2 +- .../src/screens/BugReportingScreen.tsx | 284 ------------------ .../src/screens/CallbackHandlersScreen.tsx | 109 +++++++ examples/default/src/screens/HomeScreen.tsx | 1 + .../src/screens/SessionReplayScreen.tsx | 83 ++++- .../default/src/screens/apm/HttpScreen.tsx | 12 +- .../apm/{ => network}/NetworkScreen.tsx | 80 ++++- .../apm/network/NetworkStateScreen.tsx | 38 +++ .../bug-reporting/BugReportingScreen.tsx | 63 +++- .../screens/{ => settings}/SettingsScreen.tsx | 132 +++++++- .../screens/settings/UserStepsStateScreen.tsx | 38 +++ examples/default/yarn.lock | 25 ++ 16 files changed, 647 insertions(+), 315 deletions(-) create mode 100644 examples/default/src/contexts/callbackContext.tsx delete mode 100644 examples/default/src/screens/BugReportingScreen.tsx create mode 100644 examples/default/src/screens/CallbackHandlersScreen.tsx rename examples/default/src/screens/apm/{ => network}/NetworkScreen.tsx (68%) create mode 100644 examples/default/src/screens/apm/network/NetworkStateScreen.tsx rename examples/default/src/screens/{ => settings}/SettingsScreen.tsx (63%) create mode 100644 examples/default/src/screens/settings/UserStepsStateScreen.tsx diff --git a/examples/default/package.json b/examples/default/package.json index 7e6bc4c4e..528486455 100644 --- a/examples/default/package.json +++ b/examples/default/package.json @@ -14,6 +14,7 @@ "@react-native-community/netinfo": "^11.4.1", "@react-native-community/slider": "^4.5.5", "@react-navigation/bottom-tabs": "^6.5.7", + "@react-navigation/material-top-tabs": "6.5.3", "@react-navigation/native": "^6.1.6", "@react-navigation/native-stack": "^6.9.12", "axios": "^1.7.4", @@ -28,10 +29,12 @@ "react-native-config": "^1.5.3", "react-native-gesture-handler": "^2.13.4", "react-native-maps": "1.10.3", + "react-native-pager-view": "^6.9.1", "react-native-reanimated": "^3.16.1", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "^3.35.0", "react-native-svg": "^15.8.0", + "react-native-tab-view": "^3.5.2", "react-native-vector-icons": "^10.2.0", "react-native-webview": "^13.13.2", "react-query": "^3.39.3" diff --git a/examples/default/src/App.tsx b/examples/default/src/App.tsx index ceef8bc19..5b3435fc8 100644 --- a/examples/default/src/App.tsx +++ b/examples/default/src/App.tsx @@ -21,6 +21,7 @@ import { nativeBaseTheme } from './theme/nativeBaseTheme'; import { navigationTheme } from './theme/navigationTheme'; import { QueryClient, QueryClientProvider } from 'react-query'; +import { CallbackHandlersProvider } from './contexts/callbackContext'; const queryClient = new QueryClient(); @@ -89,7 +90,9 @@ export const App: React.FC = () => { - + + + diff --git a/examples/default/src/contexts/callbackContext.tsx b/examples/default/src/contexts/callbackContext.tsx new file mode 100644 index 000000000..eaa431feb --- /dev/null +++ b/examples/default/src/contexts/callbackContext.tsx @@ -0,0 +1,56 @@ +import React, { createContext, useContext, useState } from 'react'; + +// A single key/value pair +export type KeyValuePair = { key: string; value: string }; + +// An item that contains multiple key/value pairs +export type Item = { + id: string; + fields: KeyValuePair[]; // list of key/value pairs +}; + +// CallbackHandlersType = { [title: string]: Item[] } +export type CallbackHandlersType = Record; + +type CallbackHandlersContextType = { + callbackHandlers: CallbackHandlersType; + clearList: (title: string) => void; + addItem: (title: string, item: Item) => void; +}; + +const CallbackHandlersContext = createContext(undefined); + +export const CallbackHandlersProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [callbackHandlers, setCallbackHandlers] = useState({}); + + // Clears all items under a specific title + const clearList = (title: string) => { + setCallbackHandlers((prev) => ({ ...prev, [title]: [] })); + }; + + // Adds an item (with multiple key/value pairs) to a specific title list + const addItem = (title: string, item: Item) => { + setCallbackHandlers((prev) => { + const existingList = prev[title] || []; + return { + ...prev, + [title]: [...existingList, item], + }; + }); + }; + + return ( + + {children} + + ); +}; + +// Hook to use the context +export const useCallbackHandlers = () => { + const ctx = useContext(CallbackHandlersContext); + if (!ctx) { + throw new Error('useCallbackHandlers must be used within CallbackHandlersProvider'); + } + return ctx; +}; diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx index 2fefa8439..ab2b8d4b6 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -56,7 +56,7 @@ import { GoogleMapsScreen } from '../screens/user-steps/GoogleMapsScreen'; import { LargeImageListScreen } from '../screens/user-steps/LargeImageListScreen'; import { APMScreen } from '../screens/apm/APMScreen'; import { TracesScreen } from '../screens/apm/TracesScreen'; -import { NetworkScreen } from '../screens/apm/NetworkScreen'; +import { NetworkScreen } from '../screens/apm/network/NetworkScreen'; import { FlowsScreen } from '../screens/apm/FlowsScreen'; import { SessionReplayScreen } from '../screens/SessionReplayScreen'; import { LegacyModeScreen } from '../screens/LegacyModeScreen'; @@ -78,6 +78,15 @@ import { } from '../screens/crash-reporting/NDKCrashesStateScreen'; import { NonFatalCrashesScreen } from '../screens/crash-reporting/NonFatalCrashesScreen'; import { FatalCrashesScreen } from '../screens/crash-reporting/FatalCrashesScreen'; +import CallbackScreen from '../screens/CallbackHandlersScreen'; +import { + NetworkStateScreen, + type NetworkStateScreenProp, +} from '../screens/apm/network/NetworkStateScreen'; +import { + UserStepsStateScreen, + type UserStepsStateScreenProp, +} from '../screens/settings/UserStepsStateScreen'; export type HomeStackParamList = { Home: undefined; @@ -127,6 +136,10 @@ export type HomeStackParamList = { WebViews: undefined; FullWebViews: undefined; PartialWebViews: undefined; + NetworkState: NetworkStateScreenProp; + UserStepsState: UserStepsStateScreenProp; + + CallbackScreen: undefined; }; const HomeStack = createNativeStackNavigator(); @@ -304,6 +317,22 @@ export const HomeStackNavigator: React.FC = () => { component={PartialWebViewsScreen} options={{ title: 'PartialWebViews' }} /> + + + + ); }; diff --git a/examples/default/src/navigation/RootTab.tsx b/examples/default/src/navigation/RootTab.tsx index f085576a9..9f2487503 100644 --- a/examples/default/src/navigation/RootTab.tsx +++ b/examples/default/src/navigation/RootTab.tsx @@ -6,7 +6,7 @@ import { } from '@react-navigation/bottom-tabs'; import Icon from 'react-native-vector-icons/Ionicons'; -import { SettingsScreen } from '../screens/SettingsScreen'; +import { SettingsScreen } from '../screens/settings/SettingsScreen'; import { HomeStackNavigator } from './HomeStack'; import { Platform } from 'react-native'; diff --git a/examples/default/src/screens/BugReportingScreen.tsx b/examples/default/src/screens/BugReportingScreen.tsx deleted file mode 100644 index e9e4ac89b..000000000 --- a/examples/default/src/screens/BugReportingScreen.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import React, { type Dispatch, type SetStateAction, useState } from 'react'; - -import Instabug, { - BugReporting, - InvocationOption, - ReportType, - ExtendedBugReportMode, - WelcomeMessageMode, - InvocationEvent, -} from 'instabug-reactnative'; - -import { ListTile } from '../components/ListTile'; -import { Screen } from '../components/Screen'; -import { useToast, Checkbox, Box, Text, VStack, Button, ScrollView, HStack } from 'native-base'; -import { Section } from '../components/Section'; -import { InputField } from '../components/InputField'; - -export const BugReportingScreen: React.FC = () => { - const toast = useToast(); - const [reportTypes, setReportTypes] = useState([]); - const [invocationOptions, setInvocationOptions] = useState([]); - - const [disclaimerText, setDisclaimerText] = useState(''); - - const toggleCheckbox = (value: string, setData: Dispatch>) => { - setData((prev) => - prev.includes(value) ? prev.filter((item) => item !== value) : [...prev, value], - ); - }; - - const handleSetReportTypesButtonPress = () => { - const selectedEnums: ReportType[] = reportTypes.map((val) => { - switch (val) { - case 'bug': - return ReportType.bug; - case 'feedback': - return ReportType.feedback; - case 'question': - return ReportType.question; - default: - throw new Error('Invalid report type selected'); - } - }); - BugReporting.setReportTypes(selectedEnums); - }; - const handleSetInvocationOptionsButtonPress = () => { - const selectedEnums: InvocationEvent[] = invocationOptions.map((val) => { - switch (val) { - case 'floatingButton': - return InvocationEvent.floatingButton; - case 'twoFingersSwipe': - return InvocationEvent.twoFingersSwipe; - case 'screenshot': - return InvocationEvent.screenshot; - case 'shake': - return InvocationEvent.shake; - - default: - throw new Error('Invalid report type selected'); - } - }); - BugReporting.setInvocationEvents(selectedEnums); - }; - - const handleSetDisclamirTextPress = () => { - BugReporting.setDisclaimerText(disclaimerText); - setDisclaimerText(''); - }; - - return ( - - - Instabug.show()} /> - - BugReporting.setEnabled(true)} - testID="id_br_enable" - /> - BugReporting.setEnabled(false)} - testID="id_br_disable" - /> - - BugReporting.show(ReportType.bug, [])} /> - - BugReporting.show(ReportType.feedback, [InvocationOption.emailFieldHidden]) - } - /> - BugReporting.show(ReportType.question, [])} - /> - - BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithRequiredFields) - } - /> - - BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithOptionalFields) - } - /> - Instabug.setSessionProfilerEnabled(true)} - /> - Instabug.showWelcomeMessage(WelcomeMessageMode.beta)} - /> - Instabug.showWelcomeMessage(WelcomeMessageMode.live)} - /> - - - - Bug Reporting Types - - - - - toggleCheckbox('bug', setReportTypes)} - value="bug" - accessible={true} - testID="id_br_report_type_bug" - size="md"> - Bug - - - toggleCheckbox('feedback', setReportTypes)} - value="feedback" - testID="id_br_report_type_feedback" - size="md"> - Feedback - - - toggleCheckbox('question', setReportTypes)} - value="question" - testID="id_br_report_type_question" - size="md"> - Question - - - - - - - - - - Set the disclaimer text - - - - - - - - - - - - Invocation Events - - - - - toggleCheckbox('floatingButton', setInvocationOptions)} - value="floatingButton" - testID="id_br_invoicetion_options_floatingButton" - accessible={true} - size="md"> - Floating button - - - toggleCheckbox('twoFingersSwipe', setInvocationOptions)} - value="twoFingersSwipe" - testID="id_br_invoicetion_options_twoFingersSwipe" - accessible={true} - size="md"> - Two Fingers Swipe - - - - toggleCheckbox('screenshot', setInvocationOptions)} - value="screenshot" - testID="id_br_invoicetion_options_screenshot" - accessible={true} - size="md"> - Screenshot - - toggleCheckbox('shake', setInvocationOptions)} - testID="id_br_invoicetion_options_shake" - accessible={true} - value="shake" - size="md"> - Shake - - - - - - - -
- - BugReporting.onInvokeHandler(function () { - Instabug.appendTags(['Invocation Handler tag1']); - }) - } - /> - - Instabug.onReportSubmitHandler(() => { - toast.show({ - description: 'Submission succeeded', - }); - }) - } - /> - - BugReporting.onSDKDismissedHandler(function () { - Instabug.setPrimaryColor('#FF0000'); - }) - } - /> -
-
-
- ); -}; diff --git a/examples/default/src/screens/CallbackHandlersScreen.tsx b/examples/default/src/screens/CallbackHandlersScreen.tsx new file mode 100644 index 000000000..086e89fdc --- /dev/null +++ b/examples/default/src/screens/CallbackHandlersScreen.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native'; +import { useCallbackHandlers } from '../contexts/callbackContext'; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; + +type CallBackScreenProps = { title: string }; + +const CallbackScreen: React.FC = () => { + const { callbackHandlers } = useCallbackHandlers(); + const titles = Object.keys(callbackHandlers); + const Tab = createMaterialTopTabNavigator(); + + return ( + + {titles.length > 0 ? ( + titles.map((title) => ( + + {() => } + + )) + ) : ( + ( + No Data + ), + }}> + {() => ( + + No callback handlers yet + + )} + + )} + + ); +}; + +const CallBackTabScreen: React.FC = ({ title }) => { + const { callbackHandlers, clearList } = useCallbackHandlers(); + const items = callbackHandlers[title] || []; + + return ( + + + Items: {items.length} + clearList(title)}> + Clear Data + + + + `${item.id}-${index}`} + renderItem={({ item }) => ( + + {item.fields.map((field, idx) => ( + + {field.key}: + {field.value} + + ))} + + )} + ListEmptyComponent={No items} + /> + + ); +}; + +export default CallbackScreen; + +const styles = StyleSheet.create({ + container: { flex: 1, padding: 16, backgroundColor: '#fff' }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 10, + }, + countText: { fontSize: 16, fontWeight: 'bold' }, + clearButton: { backgroundColor: '#ff5555', padding: 8, borderRadius: 8 }, + clearText: { color: '#fff', fontWeight: 'bold' }, + item: { + padding: 12, + backgroundColor: '#f0f0f0', + borderRadius: 6, + marginBottom: 8, + }, + fieldRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 4, + }, + keyText: { fontWeight: 'bold', color: '#333' }, + valueText: { color: '#555', marginLeft: 8 }, + empty: { textAlign: 'center', color: '#888', marginTop: 20 }, + emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + emptyText: { fontSize: 16, color: '#888' }, +}); diff --git a/examples/default/src/screens/HomeScreen.tsx b/examples/default/src/screens/HomeScreen.tsx index 7a5aeeb46..012db7020 100644 --- a/examples/default/src/screens/HomeScreen.tsx +++ b/examples/default/src/screens/HomeScreen.tsx @@ -20,6 +20,7 @@ export const HomeScreen: React.FC navigation.navigate('APM')} /> navigation.navigate('SessionReplay')} /> navigation.navigate('LegacyMode')} /> + navigation.navigate('CallbackScreen')} />
); }; diff --git a/examples/default/src/screens/SessionReplayScreen.tsx b/examples/default/src/screens/SessionReplayScreen.tsx index 3a3d49ed9..9c7729779 100644 --- a/examples/default/src/screens/SessionReplayScreen.tsx +++ b/examples/default/src/screens/SessionReplayScreen.tsx @@ -1,13 +1,24 @@ -import React from 'react'; +import React, { useState } from 'react'; import { SessionReplay } from 'instabug-reactnative'; import { useToast } from 'native-base'; import { ListTile } from '../components/ListTile'; import { Screen } from '../components/Screen'; +import { UserStepsState } from './settings/UserStepsStateScreen'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../navigation/HomeStack'; -export const SessionReplayScreen: React.FC = () => { +export const SessionReplayScreen: React.FC< + NativeStackScreenProps +> = ({ navigation }) => { const toast = useToast(); + + const [isSessionReplayEnabled, setIsSessionReplayEnabled] = useState(true); + const [isSessionNetworkLogsEnabled, setIsSessionNetworkLogsEnabled] = useState(true); + const [isSessionInstabugLogsEnabled, setIsSessionInstabugLogsEnabled] = useState(true); + const [isSessionUserStepsEnabled, setIsSessionUSerStepsEnabled] = useState(true); + return ( { } }} /> + + { + navigation.navigate('UserStepsState', { + state: isSessionReplayEnabled ? UserStepsState.Enabled : UserStepsState.Disabled, + setState: (newState: UserStepsState) => { + const isEnabled = newState === UserStepsState.Enabled; + setIsSessionReplayEnabled(isEnabled); + SessionReplay.setEnabled(isEnabled); + navigation.goBack(); + }, + }); + }} + testID="id_steps_replay_state" + /> + + { + navigation.navigate('UserStepsState', { + state: isSessionNetworkLogsEnabled ? UserStepsState.Enabled : UserStepsState.Disabled, + setState: (newState: UserStepsState) => { + const isEnabled = newState === UserStepsState.Enabled; + setIsSessionNetworkLogsEnabled(isEnabled); + SessionReplay.setNetworkLogsEnabled(isEnabled); + navigation.goBack(); + }, + }); + }} + testID="id_steps_replay_network_state" + /> + + { + navigation.navigate('UserStepsState', { + state: isSessionInstabugLogsEnabled ? UserStepsState.Enabled : UserStepsState.Disabled, + setState: (newState: UserStepsState) => { + const isEnabled = newState === UserStepsState.Enabled; + setIsSessionInstabugLogsEnabled(isEnabled); + SessionReplay.setInstabugLogsEnabled(isEnabled); + navigation.goBack(); + }, + }); + }} + testID="id_steps_replay_instabug_lgos_state" + /> + + { + navigation.navigate('UserStepsState', { + state: isSessionUserStepsEnabled ? UserStepsState.Enabled : UserStepsState.Disabled, + setState: (newState: UserStepsState) => { + const isEnabled = newState === UserStepsState.Enabled; + setIsSessionUSerStepsEnabled(isEnabled); + SessionReplay.setUserStepsEnabled(isEnabled); + navigation.goBack(); + }, + }); + }} + testID="id_steps_replay_usersteps_state" + /> ); }; diff --git a/examples/default/src/screens/apm/HttpScreen.tsx b/examples/default/src/screens/apm/HttpScreen.tsx index 48f972ae4..61b02ac0a 100644 --- a/examples/default/src/screens/apm/HttpScreen.tsx +++ b/examples/default/src/screens/apm/HttpScreen.tsx @@ -156,12 +156,12 @@ export const HttpScreen: React.FC = () => { return ( {loading && } - - - - - - + + + + + + ); }; diff --git a/examples/default/src/screens/apm/NetworkScreen.tsx b/examples/default/src/screens/apm/network/NetworkScreen.tsx similarity index 68% rename from examples/default/src/screens/apm/NetworkScreen.tsx rename to examples/default/src/screens/apm/network/NetworkScreen.tsx index 4225e6185..accab6d86 100644 --- a/examples/default/src/screens/apm/NetworkScreen.tsx +++ b/examples/default/src/screens/apm/network/NetworkScreen.tsx @@ -1,16 +1,20 @@ import React, { useState } from 'react'; import { Image, ScrollView, StyleSheet, Text, useWindowDimensions, View } from 'react-native'; -import { Section } from '../../components/Section'; -import { Screen } from '../../components/Screen'; -import { ClipboardTextInput } from '../../components/ClipboardTextInput'; +import { Section } from '../../../components/Section'; +import { Screen } from '../../../components/Screen'; +import { ClipboardTextInput } from '../../../components/ClipboardTextInput'; import { useQuery } from 'react-query'; import { HStack, VStack } from 'native-base'; import { gql, GraphQLClient } from 'graphql-request'; -import { CustomButton } from '../../components/CustomButton'; +import { CustomButton } from '../../../components/CustomButton'; import axios from 'axios'; -import type { HomeStackParamList } from '../../navigation/HomeStack'; +import type { HomeStackParamList } from '../../../navigation/HomeStack'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import { useNetInfo } from '@react-native-community/netinfo'; +import { ListTile } from '../../../components/ListTile'; +import { NetworkLogger } from 'instabug-reactnative'; +import { NetworkState } from './NetworkStateScreen'; +import { useCallbackHandlers } from '../../../contexts/callbackContext'; export const NetworkScreen: React.FC< NativeStackScreenProps @@ -27,6 +31,7 @@ export const NetworkScreen: React.FC< 'https://fastly.picsum.photos/id/57/200/300.jpg?hmac=l908G1qVr4r7dP947-tak2mY8Vvic_vEYzCXUCKKskY', 'https://fastly.picsum.photos/id/619/200/300.jpg?hmac=WqBGwlGjuY9RCdpzRaG9G-rc9Fi7TGUINX_-klAL2kA', ]; + const { addItem } = useCallbackHandlers(); async function sendRequestToUrl() { let urlToSend: string; @@ -153,6 +158,7 @@ export const NetworkScreen: React.FC< throw error; } } + async function makeParallelApiCalls(urls: string[]): Promise { const fetchPromises = urls.map((url) => fetch(url).then((response) => response.json())); @@ -164,9 +170,27 @@ export const NetworkScreen: React.FC< } } + const [isNetworkEnabled, setIsNetworkEnabled] = useState(true); + return ( + { + navigation.navigate('NetworkState', { + state: isNetworkEnabled ? NetworkState.Enabled : NetworkState.Disabled, + setState: (newState: NetworkState) => { + const isEnabled = newState === NetworkState.Enabled; + setIsNetworkEnabled(isEnabled); + NetworkLogger.setEnabled(isEnabled); + navigation.goBack(); + }, + }); + }} + testID="id_network_state" + />
+ +
+ + NetworkLogger.setNetworkDataObfuscationHandler(async (networkData) => { + addItem('Network Obfuscated', { + id: `event-${Math.random()}`, + fields: [ + { key: 'Date', value: new Date().toLocaleString() }, + { key: 'Url', value: networkData.url }, + { key: 'Method', value: networkData.method }, + { key: 'Request Body', value: networkData.requestBody }, + { key: 'Response', value: networkData.responseBody?.toString() ?? '' }, + { key: 'Request Headers', value: networkData.requestHeaders.toString() }, + { key: 'Response Headers', value: networkData.requestHeaders.toString() }, + ], + }); + return networkData; + }) + } + /> + + + NetworkLogger.setNetworkDataObfuscationHandler(async (networkData) => { + addItem('Network Obfuscated', { + id: `event-${Math.random()}`, + fields: [ + { key: 'Date', value: new Date().toLocaleString() }, + { key: 'Url', value: networkData.url }, + { key: 'Method', value: networkData.method }, + { key: 'Request Body', value: networkData.requestBody }, + { key: 'Response', value: networkData.responseBody?.toString() ?? '' }, + { key: 'Request Headers', value: networkData.requestHeaders.toString() }, + { key: 'Response Headers', value: networkData.requestHeaders.toString() }, + ], + }); + throw Error(' Error from Network Obfuscated'); + }) + } + /> +
); diff --git a/examples/default/src/screens/apm/network/NetworkStateScreen.tsx b/examples/default/src/screens/apm/network/NetworkStateScreen.tsx new file mode 100644 index 000000000..b58954b2d --- /dev/null +++ b/examples/default/src/screens/apm/network/NetworkStateScreen.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../../navigation/HomeStack'; +import { ListTile } from '../../../components/ListTile'; +import { Screen } from '../../../components/Screen'; + +export enum NetworkState { + Enabled = 'Enabled', + Disabled = 'Disabled', +} + +export interface NetworkStateScreenProp { + state: NetworkState; + setState: (state: NetworkState) => void; +} + +export const NetworkStateScreen: React.FC< + NativeStackScreenProps +> = ({ route }) => { + const { state, setState } = route.params; + return ( + + setState(NetworkState.Enabled)} + subtitle={state === NetworkState.Enabled ? 'Selected' : undefined} + /> + setState(NetworkState.Disabled)} + subtitle={state === NetworkState.Disabled ? 'Selected' : undefined} + /> + + ); +}; diff --git a/examples/default/src/screens/bug-reporting/BugReportingScreen.tsx b/examples/default/src/screens/bug-reporting/BugReportingScreen.tsx index e0ddf1bc2..90c7410c6 100644 --- a/examples/default/src/screens/bug-reporting/BugReportingScreen.tsx +++ b/examples/default/src/screens/bug-reporting/BugReportingScreen.tsx @@ -18,6 +18,7 @@ import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { HomeStackParamList } from '../../navigation/HomeStack'; import { BugReportingState } from './BugReportingStateScreen'; import { ExtendedBugReportState } from './ExtendedBugReportStateScreen'; +import { useCallbackHandlers } from '../../contexts/callbackContext'; export const BugReportingScreen: React.FC< NativeStackScreenProps @@ -38,6 +39,7 @@ export const BugReportingScreen: React.FC< const [isSessionProfilerEnabled, setIsSessionProfilerEnabled] = useState(true); const [isViewHierarchyEnabled, setIsViewHierarchyEnabled] = useState(false); const [isRepliesEnabled, setIsRepliesEnabled] = useState(true); + const { addItem } = useCallbackHandlers(); return ( @@ -268,29 +270,74 @@ export const BugReportingScreen: React.FC<
BugReporting.onInvokeHandler(function () { - Instabug.appendTags(['Invocation Handler tag1']); + toast.show({ + description: 'Invoke Callback Handler', + }); + + addItem('Invoke Handler', { + id: `event-${Math.random()}`, + fields: [{ key: 'Date', value: new Date().toLocaleString() }], + }); }) } /> + + BugReporting.onInvokeHandler(function () {})} + /> + - Instabug.onReportSubmitHandler(() => { + BugReporting.onInvokeHandler(function () { + addItem('Invoke Handler', { + id: `event-${Math.random()}`, + fields: [{ key: 'Date', value: new Date().toLocaleString() }], + }); + throw new Error('💥 Crash inside onInvokeHandler'); + }) + } + /> + + BugReporting.onSDKDismissedHandler(function () { toast.show({ - description: 'Submission succeeded', + description: 'onSDKDismissedHandler Callback Handler', + }); + addItem('onSDKDismissedHandler', { + id: `event-${Math.random()}`, + fields: [{ key: 'Date', value: new Date().toLocaleString() }], }); }) } /> + BugReporting.onSDKDismissedHandler(function () {})} + /> + + BugReporting.onSDKDismissedHandler(function () { - Instabug.setPrimaryColor('#FF0000'); + addItem('onSDKDismissedHandler', { + id: `event-${Math.random()}`, + fields: [{ key: 'Date', value: new Date().toLocaleString() }], + }); + + throw new Error('💥 Crash inside onSDKDismissedHandler'); }) } /> diff --git a/examples/default/src/screens/SettingsScreen.tsx b/examples/default/src/screens/settings/SettingsScreen.tsx similarity index 63% rename from examples/default/src/screens/SettingsScreen.tsx rename to examples/default/src/screens/settings/SettingsScreen.tsx index 764753e70..e8e1a7e60 100644 --- a/examples/default/src/screens/SettingsScreen.tsx +++ b/examples/default/src/screens/settings/SettingsScreen.tsx @@ -5,18 +5,25 @@ import Instabug, { ColorTheme, InvocationEvent, Locale, + NetworkLogger, ReproStepsMode, } from 'instabug-reactnative'; import { InputGroup, InputLeftAddon, useToast, VStack, Button } from 'native-base'; -import { ListTile } from '../components/ListTile'; -import { Screen } from '../components/Screen'; -import { Select } from '../components/Select'; +import { ListTile } from '../../components/ListTile'; +import { Screen } from '../../components/Screen'; +import { Select } from '../../components/Select'; import { StyleSheet, View, ScrollView } from 'react-native'; -import { VerticalListTile } from '../components/VerticalListTile'; -import { InputField } from '../components/InputField'; +import { VerticalListTile } from '../../components/VerticalListTile'; +import { InputField } from '../../components/InputField'; +import { useCallbackHandlers } from '../../contexts/callbackContext'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; +import { UserStepsState } from './UserStepsStateScreen'; -export const SettingsScreen: React.FC = () => { +export const SettingsScreen: React.FC> = ({ + navigation, +}) => { const [color, setColor] = useState('1D82DC'); const [userEmail, setUserEmail] = useState(''); const [userName, setUserName] = useState(''); @@ -25,10 +32,12 @@ export const SettingsScreen: React.FC = () => { const [userAttributeValue, setUserAttributeValue] = useState(''); const [featureFlagName, setFeatureFlagName] = useState(''); const [featureFlagVariant, setfeatureFlagVariant] = useState(''); + const [isUserStepEnabled, setIsUserStepEnabled] = useState(true); const toast = useToast(); const [userAttributesFormError, setUserAttributesFormError] = useState({}); const [featureFlagFormError, setFeatureFlagFormError] = useState({}); + const { addItem } = useCallbackHandlers(); const validateUserAttributeForm = () => { const errors: any = {}; @@ -46,6 +55,7 @@ export const SettingsScreen: React.FC = () => { if (featureFlagName.length === 0) { errors.featureFlagName = 'Value is required'; } + setFeatureFlagFormError(errors); return Object.keys(errors).length === 0; }; @@ -182,7 +192,22 @@ export const SettingsScreen: React.FC = () => { /> - + { + navigation.navigate('UserStepsState', { + state: isUserStepEnabled ? UserStepsState.Enabled : UserStepsState.Disabled, + setState: (newState: UserStepsState) => { + const isEnabled = newState === UserStepsState.Enabled; + setIsUserStepEnabled(isEnabled); + Instabug.setTrackUserSteps(isEnabled); + navigation.goBack(); + }, + }); + }} + testID="id_user_steps_state" + />