From 35f9e6f41735a14790efbac6cd03219091d6ab40 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Tue, 29 Apr 2025 15:20:16 +0300 Subject: [PATCH 1/7] 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 feef8fd7b5..1426a9d99c 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 35540dc85a..b0bed5f540 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 ed929e3a0f0e91fbe3a5165838338bcde8e2013a Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Fri, 30 May 2025 05:54:12 +0300 Subject: [PATCH 2/7] 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 9cde707ae3..8204614422 100644 --- a/examples/default/ios/Podfile.lock +++ b/examples/default/ios/Podfile.lock @@ -2027,21 +2027,21 @@ SPEC CHECKSUMS: Instabug: 9e81b71be68626dafc74759f3458f7c5894dd2e1 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 @@ -2065,25 +2065,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 @@ -2102,4 +2102,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a1b532d67a1a86843e1f086101751ad55afa52da -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 b0bed5f540..62772c7f76 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 090aa65873..8ce7462bca 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 0000000000..8b6eaff4c6 --- /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 1bc3fd95638a922f02c5c12f1ab6bcfaaed13b82 Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Sat, 31 May 2025 22:52:56 +0300 Subject: [PATCH 3/7] 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 62772c7f76..1652598196 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 8ce7462bca..8affca99a5 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 0000000000..ad0da1eccb --- /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 0000000000..ef7f0236b9 --- /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 0000000000..0591877064 --- /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 0000000000..108237bc7c --- /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 0000000000..0f8dfc5ff3 --- /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 0000000000..14b275c0a5 --- /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 d666d9aabd2c3f78dd187f4e4aa87b60259a6f01 Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Sat, 31 May 2025 23:45:51 +0300 Subject: [PATCH 4/7] 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 8affca99a5..6aafff8d87 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 0000000000..a42dbeefac --- /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 0000000000..62b1a6ab3a --- /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 b7b951a93ca37e81a9dbea8dfb0432e1a20744b2 Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Sun, 1 Jun 2025 04:42:09 +0300 Subject: [PATCH 5/7] 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 4ccb7ad3ce..fa62793a16 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 d46a9413f14c129cd75bdb9917b7dd3440409dec Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Mon, 2 Jun 2025 05:14:44 +0300 Subject: [PATCH 6/7] 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 fa62793a16..d10291dd31 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 1426a9d99c..f3db99ed38 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 206aa74e0e..0d81e655e2 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 6aafff8d87..59fff3bb72 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 74c0f9928ee082ae4de15ac79d3bfb8da4f0251d Mon Sep 17 00:00:00 2001 From: Mohamed Kamal Date: Mon, 2 Jun 2025 22:37:57 +0300 Subject: [PATCH 7/7] 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 59fff3bb72..2fefa84394 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 0000000000..8845cef94f --- /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 +