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..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 @@ -28,11 +28,10 @@ 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); - } @ReactMethod 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/components/InputField.tsx b/examples/default/src/components/InputField.tsx index feef8fd7b..f3db99ed3 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, @@ -43,11 +45,14 @@ export const InputField = forwardRef( @@ -63,9 +68,10 @@ const styles = StyleSheet.create({ borderWidth: 1, borderColor: '#ccc', paddingVertical: 10, - paddingHorizontal: 24, - fontSize: 16, + paddingHorizontal: 16, + fontSize: 12, borderRadius: 5, + color: 'black', }, errorText: { color: '#ff0000', diff --git a/examples/default/src/components/ListTile.tsx b/examples/default/src/components/ListTile.tsx index 35540dc85..97b9146f7 100644 --- a/examples/default/src/components/ListTile.tsx +++ b/examples/default/src/components/ListTile.tsx @@ -1,25 +1,47 @@ 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; + truncateSubtitle?: boolean; + testID?: string; } -export const ListTile: React.FC = ({ title, onPress, children }) => { +export const ListTile: React.FC = ({ title, + subtitle, + onPress, + children, + testID, + truncateSubtitle = false, }) => { return ( - {title} + + {title} + {subtitle && ( + + {subtitle} + + )} + {children} diff --git a/examples/default/src/components/PlatformListTile.tsx b/examples/default/src/components/PlatformListTile.tsx index 1bf1fe6e7..bbee40444 100644 --- a/examples/default/src/components/PlatformListTile.tsx +++ b/examples/default/src/components/PlatformListTile.tsx @@ -7,6 +7,7 @@ interface PlatformListTileProps extends PropsWithChildren { title: string; onPress?: () => 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..de5e6a8fe 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,12 @@ export function Select({ label, items, onValueChange }: SelectProps) { endIcon: , }}> {items.map((item) => ( - + ))} ); 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 090aa6587..ab2b8d4b6 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -2,8 +2,41 @@ 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 { + 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 { + ViewHierarchyScreen, + type ViewHierarchyScreenProp, +} from '../screens/bug-reporting/ViewHierarchyScreen'; +import { + RepliesStateScreen, + type RepliesStateScreenProp, +} from '../screens/bug-reporting/RepliesStateScreen'; +import { UserConsentScreen } from '../screens/bug-reporting/UserConsentScreen'; 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'; @@ -23,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'; @@ -31,11 +64,53 @@ 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'; +import { + NDKCrashesStateScreen, + type NDKCrashesStateScreenProp, +} 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; + + // Bug Reporting // BugReporting: undefined; + BugReportingState: BugReportingStateScreenProp; + ExtendedBugReportState: ExtendedBugReportStateScreenProp; + BugReportingTypes: BugReportingTypesScreenProp; + DisclaimerText: DisclaimerTextScreenProp; + InvocationEvents: InvocationEventsScreenProp; + SessionProfiler: SessionProfilerScreenProp; + InvocationOptions: InvocationOptionsScreenProp; + ViewHierarchy: ViewHierarchyScreenProp; + RepliesState: RepliesStateScreenProp; + UserConsent: undefined; + + // Crash Reporting // CrashReporting: undefined; + CrashReportingState: CrashReportingStateScreenProp; + NDKCrashesState: NDKCrashesStateScreenProp; + NonFatalCrashes: undefined; + FatalCrashes: undefined; + FeatureRequests: undefined; Replies: undefined; Surveys: undefined; @@ -61,6 +136,10 @@ export type HomeStackParamList = { WebViews: undefined; FullWebViews: undefined; PartialWebViews: undefined; + NetworkState: NetworkStateScreenProp; + UserStepsState: UserStepsStateScreenProp; + + CallbackScreen: undefined; }; const HomeStack = createNativeStackNavigator(); @@ -69,16 +148,91 @@ export const HomeStackNavigator: React.FC = () => { return ( + + {/* Bug Reporting */} + + + + + + + + + + + + {/* Crash Reporting */} + + + + + { 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 a8afc14a8..000000000 --- a/examples/default/src/screens/BugReportingScreen.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; - -import Instabug, { - BugReporting, - InvocationOption, - ReportType, - ExtendedBugReportMode, - WelcomeMessageMode, -} from 'instabug-reactnative'; - -import { ListTile } from '../components/ListTile'; -import { Screen } from '../components/Screen'; -import { useToast } from 'native-base'; -import { Section } from '../components/Section'; - -export const BugReportingScreen: React.FC = () => { - const toast = useToast(); - 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)} - /> - -
- - 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/CrashReportingScreen.tsx b/examples/default/src/screens/CrashReportingScreen.tsx index 397565ecd..b30f2cb5b 100644 --- a/examples/default/src/screens/CrashReportingScreen.tsx +++ b/examples/default/src/screens/CrashReportingScreen.tsx @@ -1,296 +1,72 @@ import React, { useState } from 'react'; -import { Alert, Platform, ScrollView, StyleSheet, Text, View, Switch } from 'react-native'; +import { Platform, ScrollView } from 'react-native'; -import { CrashReporting, NonFatalErrorLevel } from 'instabug-reactnative'; +import { CrashReporting } from 'instabug-reactnative'; import { ListTile } from '../components/ListTile'; import { Screen } from '../components/Screen'; -import { Section } from '../components/Section'; -import { PlatformListTile } from '../components/PlatformListTile'; -import { NativeExampleCrashReporting } from '../native/NativeCrashReporting'; -import { VerticalListTile } from '../components/VerticalListTile'; -import { Button, VStack } from 'native-base'; -import { InputField } from '../components/InputField'; -import { Select } from '../components/Select'; -import { showNotification } from '../utils/showNotification'; +import { Divider } from 'native-base'; -const styles = StyleSheet.create({ - inputWrapper: { - padding: 4, - flex: 1, - }, +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../navigation/HomeStack'; - formContainer: { - flexDirection: 'row', - alignItems: 'stretch', - }, -}); - -export const CrashReportingScreen: React.FC = () => { - 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} - /> - - - - + + + + + ); +}; 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..ef6297783 --- /dev/null +++ b/examples/default/src/screens/bug-reporting/ExtendedBugReportStateScreen.tsx @@ -0,0 +1,49 @@ +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 ExtendedBugReportState { + Disabled = 'Disabled', + EnabledWithRequiredFields = 'EnabledWithRequiredFields', + EnabledWithOptionalFields = 'EnabledWithOptionalFields', +} + +export interface ExtendedBugReportStateScreenProp { + state: ExtendedBugReportState; + setState: (state: ExtendedBugReportState) => void; +} + +export const ExtendedBugReportStateScreen: React.FC< + NativeStackScreenProps +> = ({ 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..3c334930b --- /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 +> = ({ 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..0d3ddb80e --- /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 +> = ({ 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/RepliesStateScreen.tsx b/examples/default/src/screens/bug-reporting/RepliesStateScreen.tsx new file mode 100644 index 000000000..a4f152c30 --- /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 +> = ({ 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/SessionProfilerScreen.tsx b/examples/default/src/screens/bug-reporting/SessionProfilerScreen.tsx new file mode 100644 index 000000000..63938a49d --- /dev/null +++ b/examples/default/src/screens/bug-reporting/SessionProfilerScreen.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 SessionProfilerScreenProp { + isEnabled: boolean; + setIsEnabled: (enabled: boolean) => void; +} + +export const SessionProfilerScreen: React.FC< + NativeStackScreenProps +> = ({ 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/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 + { + setCrashLevelValue(value); + }} + /> + + + + setCrashFingerprint(text)} + value={crashFingerprint} + testID="id_crash_fingerprint" + /> + + + +
+
+
+ + ); +}; 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" + />