@trustchex/react-native-sdk 1.267.0 → 1.354.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +8 -2
  2. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +5 -1
  3. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -1
  4. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +29 -15
  5. package/lib/module/Screens/Static/OTPVerificationScreen.js +285 -0
  6. package/lib/module/Screens/Static/ResultScreen.js +90 -26
  7. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +48 -134
  8. package/lib/module/Shared/Components/DebugNavigationPanel.js +252 -0
  9. package/lib/module/Shared/Components/EIDScanner.js +142 -17
  10. package/lib/module/Shared/Components/FaceCamera.js +23 -11
  11. package/lib/module/Shared/Components/IdentityDocumentCamera.js +295 -44
  12. package/lib/module/Shared/Components/NavigationManager.js +19 -3
  13. package/lib/module/Shared/Config/camera-enhancement.config.js +58 -0
  14. package/lib/module/Shared/Contexts/AppContext.js +1 -0
  15. package/lib/module/Shared/Libs/camera.utils.js +221 -1
  16. package/lib/module/Shared/Libs/frame-enhancement.utils.js +133 -0
  17. package/lib/module/Shared/Libs/mrz.utils.js +98 -1
  18. package/lib/module/Translation/Resources/en.js +30 -0
  19. package/lib/module/Translation/Resources/tr.js +30 -0
  20. package/lib/module/Trustchex.js +49 -39
  21. package/lib/module/version.js +1 -1
  22. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  23. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  24. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  25. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  26. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts +3 -0
  27. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -0
  28. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  29. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  30. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts +3 -0
  31. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -0
  32. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  33. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  34. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  35. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  36. package/lib/typescript/src/Shared/Config/camera-enhancement.config.d.ts +54 -0
  37. package/lib/typescript/src/Shared/Config/camera-enhancement.config.d.ts.map +1 -0
  38. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
  39. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  40. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +65 -1
  41. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
  42. package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts +25 -0
  43. package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts.map +1 -0
  44. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  45. package/lib/typescript/src/Translation/Resources/en.d.ts +30 -0
  46. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  47. package/lib/typescript/src/Translation/Resources/tr.d.ts +30 -0
  48. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  49. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  50. package/lib/typescript/src/version.d.ts +1 -1
  51. package/package.json +3 -3
  52. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +6 -2
  53. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +3 -1
  54. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +3 -1
  55. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +27 -17
  56. package/src/Screens/Static/OTPVerificationScreen.tsx +379 -0
  57. package/src/Screens/Static/ResultScreen.tsx +160 -101
  58. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +51 -196
  59. package/src/Shared/Components/DebugNavigationPanel.tsx +262 -0
  60. package/src/Shared/Components/EIDScanner.tsx +144 -19
  61. package/src/Shared/Components/FaceCamera.tsx +38 -21
  62. package/src/Shared/Components/IdentityDocumentCamera.tsx +399 -101
  63. package/src/Shared/Components/NavigationManager.tsx +19 -3
  64. package/src/Shared/Config/camera-enhancement.config.ts +46 -0
  65. package/src/Shared/Contexts/AppContext.ts +3 -0
  66. package/src/Shared/Libs/camera.utils.ts +240 -1
  67. package/src/Shared/Libs/frame-enhancement.utils.ts +217 -0
  68. package/src/Shared/Libs/mrz.utils.ts +78 -1
  69. package/src/Translation/Resources/en.ts +30 -0
  70. package/src/Translation/Resources/tr.ts +30 -0
  71. package/src/Trustchex.tsx +58 -46
  72. package/src/version.ts +1 -1
@@ -20,7 +20,7 @@ import {
20
20
  } from 'react-native';
21
21
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
22
22
  import AppContext from '../../Shared/Contexts/AppContext';
23
- import httpClient, { BadRequestError, NotFoundError, TooManyRequestsError } from '../../Shared/Libs/http-client';
23
+ import httpClient, { NotFoundError } from '../../Shared/Libs/http-client';
24
24
  import { useTranslation } from 'react-i18next';
25
25
  import LanguageSelector from '../../Shared/Components/LanguageSelector';
26
26
  import NavigationManager, {
@@ -49,11 +49,8 @@ import {
49
49
 
50
50
  const VerificationSessionCheckScreen = () => {
51
51
  const [sessionCode, setSessionCode] = useState('');
52
- const [code, setCode] = useState('');
53
52
  const [isCheckingSession, setIsCheckingSession] = useState(false);
54
- const [isSendAgainEnabled, setIsSendAgainEnabled] = useState(false);
55
- const [isCodeSent, setIsCodeSent] = useState(false);
56
- const [isCodeGettingVerified, setIsCodeGettingVerified] = useState(false);
53
+ const isCheckingSessionRef = useRef(false);
57
54
  const appContext = useContext(AppContext);
58
55
  const { t } = useTranslation();
59
56
  const navigationManagerRef = React.useRef<NavigationManagerRef>(null);
@@ -76,11 +73,12 @@ const VerificationSessionCheckScreen = () => {
76
73
  }, []);
77
74
 
78
75
  const getSession = useCallback(
79
- async (id: string) => {
76
+ async (id: string, isDemoMode?: boolean) => {
77
+ const isDemo = isDemoMode ?? appContext.isDemoSession;
80
78
  try {
81
79
  const response = await httpClient.get<VerificationSession>(
82
80
  `${apiUrl}/verification-sessions/${id}`,
83
- appContext.isDemoSession
81
+ isDemo
84
82
  ? getSimulatedDemoData<VerificationSession | undefined, void>(
85
83
  'GET_SESSION'
86
84
  )
@@ -111,11 +109,12 @@ const VerificationSessionCheckScreen = () => {
111
109
  const getSessionByCode = useCallback(
112
110
  async (inputCode: string) => {
113
111
  try {
114
- appContext.isDemoSession = isDemoSession(inputCode);
115
- analyticsService.setDemoSession(appContext.isDemoSession);
112
+ const isDemoMode = isDemoSession(inputCode);
113
+ appContext.setIsDemoSession?.(isDemoMode);
114
+ analyticsService.setDemoSession(isDemoMode);
116
115
  const response = await httpClient.get<{ id: string }>(
117
116
  `${apiUrl}/verification-sessions?sessionCode=${inputCode.toUpperCase()}`,
118
- appContext.isDemoSession
117
+ isDemoMode
119
118
  ? getSimulatedDemoData<{ id: string } | undefined, void>(
120
119
  'GET_SESSION_BY_CODE'
121
120
  )
@@ -151,121 +150,19 @@ const VerificationSessionCheckScreen = () => {
151
150
  [apiUrl, appContext, t]
152
151
  );
153
152
 
154
- const sendVerificationCode = useCallback(
155
- async (sessionId: string) => {
156
- try {
157
- await httpClient.post(
158
- `${apiUrl}/verification-sessions/${sessionId}`,
159
- {},
160
- appContext.isDemoSession
161
- ? getSimulatedDemoData('SEND_VERIFICATION_CODE')
162
- : undefined
163
- );
164
- return true;
165
- } catch (error) {
166
- if (error instanceof NotFoundError) {
167
- // Session expired or invalid - expected behavior, not actionable
168
- Alert.alert(
169
- t('general.error'),
170
- t('verificationSessionCheckScreen.noVerificationSessionFound')
171
- );
172
- } else if (error instanceof TooManyRequestsError) {
173
- // Code was already sent recently - this is expected behavior
174
- // Return true since the code was already sent
175
- return true;
176
- } else {
177
- trackError(
178
- 'VERIFICATION_CODE_SEND_FAILED',
179
- 'Failed to send verification code',
180
- 'verification_session_check',
181
- 'high',
182
- { recoverable: true, userAction: 'send_verification_code' }
183
- );
184
- Alert.alert(
185
- t('general.error'),
186
- t('verificationSessionCheckScreen.cannotSendVerificationCode')
187
- );
188
- }
189
- }
190
- return false;
153
+ const navigateToOTPScreen = useCallback(
154
+ (sessionId: string) => {
155
+ (navigation as any).navigate('OTPVerificationScreen', { sessionId });
191
156
  },
192
- [apiUrl, appContext.isDemoSession, t]
193
- );
194
-
195
- const getVerifiedSession = useCallback(
196
- async (sessionId: string, verificationCode: string) => {
197
- try {
198
- const response = await httpClient.post<
199
- VerificationSession,
200
- { code: string }
201
- >(
202
- `${apiUrl}/verification-sessions/${sessionId}`,
203
- {
204
- code: verificationCode,
205
- },
206
- appContext.isDemoSession
207
- ? getSimulatedDemoData<
208
- VerificationSession | undefined,
209
- { code: string }
210
- >('GET_VERIFIED_SESSION', { code: verificationCode })
211
- : undefined
212
- );
213
-
214
- return response;
215
- } catch (error) {
216
- if (error instanceof NotFoundError) {
217
- // Session expired or invalid - expected behavior, not actionable
218
- Alert.alert(
219
- t('general.error'),
220
- t('verificationSessionCheckScreen.noVerificationSessionFound')
221
- );
222
- } else if (error instanceof BadRequestError) {
223
- // Wrong OTP code - expected user behavior, not actionable
224
- } else {
225
- trackError(
226
- 'VERIFIED_SESSION_CHECK_ERROR',
227
- error instanceof Error ? error.message : 'Unknown error',
228
- 'verification_session_check',
229
- 'high',
230
- { recoverable: false, userAction: 'check_verified_session' }
231
- );
232
- }
233
- }
234
- },
235
- [apiUrl, appContext.isDemoSession, t]
236
- );
237
-
238
- const sendOTPCode = useCallback(
239
- async (sessionId: string) => {
240
- if (!sessionId) {
241
- return false;
242
- }
243
-
244
- const isSent = await sendVerificationCode(sessionId);
245
-
246
- if (!isSent) {
247
- return false;
248
- }
249
-
250
- setIsSendAgainEnabled(false);
251
-
252
- setTimeout(
253
- () => {
254
- setIsSendAgainEnabled(true);
255
- },
256
- 3 * 60 * 1000
257
- );
258
-
259
- setIsCodeSent(true);
260
-
261
- return true;
262
- },
263
- [sendVerificationCode]
157
+ [navigation]
264
158
  );
265
159
 
266
160
  useEffect(() => {
267
- if (!initialized.current && appContext.identificationInfo.sessionId) {
161
+ // Only run this effect if sessionId exists AND we're not currently checking a session
162
+ // This prevents race condition when session code is entered
163
+ if (!initialized.current && appContext.identificationInfo.sessionId && !isCheckingSessionRef.current) {
268
164
  initialized.current = true;
165
+ isCheckingSessionRef.current = true;
269
166
  setIsCheckingSession(true);
270
167
  getSession(appContext.identificationInfo.sessionId)
271
168
  .then((session) => {
@@ -275,15 +172,18 @@ const VerificationSessionCheckScreen = () => {
275
172
  }
276
173
 
277
174
  if (session?.sendOTP) {
278
- return sendOTPCode(session.id);
279
- } else if (session?.identificationId) {
280
- appContext.identificationInfo.identificationId =
281
- session?.identificationId;
175
+ navigateToOTPScreen(session.id);
176
+ } else {
177
+ if (session?.identificationId) {
178
+ appContext.identificationInfo.identificationId =
179
+ session?.identificationId;
180
+ }
282
181
  navigationManagerRef.current?.navigateToNextStep();
283
182
  }
284
183
  })
285
184
  .finally(() => {
286
185
  setTimeout(() => {
186
+ isCheckingSessionRef.current = false;
287
187
  setIsCheckingSession(false);
288
188
  }, 1000);
289
189
  });
@@ -292,7 +192,7 @@ const VerificationSessionCheckScreen = () => {
292
192
  appContext,
293
193
  appContext.identificationInfo.sessionId,
294
194
  getSession,
295
- sendOTPCode,
195
+ navigateToOTPScreen,
296
196
  ]);
297
197
 
298
198
  return (
@@ -323,7 +223,7 @@ const VerificationSessionCheckScreen = () => {
323
223
  />
324
224
  )}
325
225
  </View>
326
- {isCheckingSession || isCodeGettingVerified ? (
226
+ {isCheckingSession ? (
327
227
  <LottieView
328
228
  source={require('../../Shared/Animations/loading.json')}
329
229
  style={styles.loadingAnimation}
@@ -331,7 +231,7 @@ const VerificationSessionCheckScreen = () => {
331
231
  loop={true}
332
232
  resizeMode="cover"
333
233
  />
334
- ) : !isCodeSent ? (
234
+ ) : (
335
235
  <>
336
236
  <Text style={styles.mainText}>
337
237
  {t('verificationSessionCheckScreen.mainText')}
@@ -357,23 +257,42 @@ const VerificationSessionCheckScreen = () => {
357
257
  alphanumericText.length === 8
358
258
  ) {
359
259
  try {
260
+ isCheckingSessionRef.current = true;
360
261
  setIsCheckingSession(true);
361
- const session =
262
+ const isDemoMode = isDemoSession(alphanumericText);
263
+ const sessionResponse =
362
264
  await getSessionByCode(alphanumericText);
363
- if (session?.id) {
265
+ if (sessionResponse?.id) {
364
266
  if (appContext.setSessionId) {
365
- appContext.setSessionId(session.id);
267
+ appContext.setSessionId(sessionResponse.id);
366
268
  } else {
367
269
  appContext.identificationInfo.sessionId =
368
- session.id;
270
+ sessionResponse.id;
271
+ }
272
+
273
+ const session = await getSession(sessionResponse.id, isDemoMode);
274
+ appContext.workflowSteps = session?.workflowSteps;
275
+ if (session?.branding) {
276
+ appContext.branding = appContext.branding || session.branding;
277
+ }
278
+
279
+ if (session?.sendOTP) {
280
+ navigateToOTPScreen(sessionResponse.id);
281
+ } else {
282
+ if (session?.identificationId) {
283
+ appContext.identificationInfo.identificationId =
284
+ session?.identificationId;
285
+ }
286
+ navigationManagerRef.current?.navigateToNextStep();
369
287
  }
370
288
  } else {
371
289
  setSessionCode('');
372
290
  }
373
- } catch {
291
+ } catch (error) {
374
292
  setSessionCode('');
375
293
  } finally {
376
294
  setTimeout(() => {
295
+ isCheckingSessionRef.current = false;
377
296
  setIsCheckingSession(false);
378
297
  }, 1000);
379
298
  }
@@ -408,64 +327,6 @@ const VerificationSessionCheckScreen = () => {
408
327
  {t('verificationSessionCheckScreen.scanQRCode')}
409
328
  </StyledButton>
410
329
  </>
411
- ) : (
412
- <>
413
- <Text style={styles.mainText}>
414
- {t('verificationSessionCheckScreen.codeText')}
415
- </Text>
416
- <StyledTextInput
417
- autoFocus={true}
418
- placeholder=""
419
- borderColor={primaryColor}
420
- focusedBorderColor={primaryColor}
421
- inputStyle={styles.otpCodeTextInput}
422
- keyboardType="number-pad"
423
- textContentType="oneTimeCode"
424
- autoComplete="sms-otp"
425
- maxLength={6}
426
- onChangeText={(text) => {
427
- const numericText = text.replace(/[^0-9]/g, '');
428
- if (numericText.length <= 6) {
429
- setCode(numericText);
430
- if (numericText.length === 6) {
431
- (async () => {
432
- setIsCodeGettingVerified(true);
433
- const verifiedSession = await getVerifiedSession(
434
- appContext.identificationInfo.sessionId,
435
- numericText
436
- );
437
- if (verifiedSession?.identificationId) {
438
- appContext.identificationInfo.identificationId =
439
- verifiedSession?.identificationId;
440
- setCode('');
441
- setIsCodeSent(false);
442
- navigationManagerRef.current?.navigateToNextStep();
443
- } else {
444
- // User entered wrong OTP code - expected behavior, not actionable
445
- appContext.onError?.('Invalid OTP code');
446
- Alert.alert(
447
- t('general.error'),
448
- t('verificationSessionCheckScreen.codeError')
449
- );
450
- setCode('');
451
- setIsCodeGettingVerified(false);
452
- }
453
- })();
454
- }
455
- }
456
- }}
457
- value={code}
458
- />
459
- <StyledButton
460
- mode="contained"
461
- disabled={!isSendAgainEnabled}
462
- onPress={() => {
463
- sendOTPCode(appContext.identificationInfo.sessionId);
464
- }}
465
- >
466
- {t('verificationSessionCheckScreen.sendCodeAgain')}
467
- </StyledButton>
468
- </>
469
330
  )}
470
331
  </View>
471
332
  {appContext.branding.logoUrl && (
@@ -593,12 +454,6 @@ const styles = StyleSheet.create({
593
454
  textAlign: 'center',
594
455
  textTransform: 'none',
595
456
  },
596
- otpCodeTextInput: {
597
- fontWeight: '800',
598
- fontSize: 28,
599
- textAlign: 'center',
600
- letterSpacing: 8,
601
- },
602
457
  });
603
458
 
604
459
  export default VerificationSessionCheckScreen;
@@ -0,0 +1,262 @@
1
+ import React, { useCallback, useContext, useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ Platform,
8
+ } from 'react-native';
9
+ import { useNavigation } from '@react-navigation/native';
10
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
11
+ import AppContext from '../Contexts/AppContext';
12
+ import { getSimulatedDemoData } from '../Libs/demo.utils';
13
+ import type { VerificationSession } from '../Types/verificationSession';
14
+
15
+ type ScreenOption = {
16
+ screen: string;
17
+ label: string;
18
+ icon: string;
19
+ setupDemo?: () => void;
20
+ params?: Record<string, unknown>;
21
+ };
22
+
23
+ // Icon mapping matching Flutter SDK Material Icons
24
+ const ICONS = {
25
+ LOGIN: '🔑', // Icons.login
26
+ HANDSHAKE: '🤝', // Icons.handshake
27
+ CREDIT_CARD: '💳', // Icons.credit_card
28
+ NFC: '📡', // Icons.nfc
29
+ FACE: '👤', // Icons.face
30
+ CHECK_CIRCLE: '✅', // Icons.check_circle
31
+ BUG_REPORT: '🔧', // Icons.bug_report (using wrench for debug)
32
+ PHONE: '📱', // Icons.phone
33
+ } as const;
34
+
35
+ const DebugNavigationPanel = () => {
36
+ const [isExpanded, setIsExpanded] = useState(false);
37
+ const navigation = useNavigation();
38
+ const appContext = useContext(AppContext);
39
+ const insets = useSafeAreaInsets();
40
+
41
+ const setupDemoSession = useCallback((setDebugFlag: boolean = false) => {
42
+ appContext.setIsDemoSession?.(true);
43
+ appContext.identificationInfo.sessionId = 'demo-verification-id';
44
+ appContext.identificationInfo.identificationId = 'demo-identification-id';
45
+ appContext.isDebugNavigated = setDebugFlag;
46
+
47
+ // Populate workflow steps with demo data (including contract URLs)
48
+ const demoSession = getSimulatedDemoData<VerificationSession, never>('GET_SESSION');
49
+ appContext.workflowSteps = demoSession.workflowSteps;
50
+ }, [appContext]);
51
+
52
+ const screens: ScreenOption[] = [
53
+ {
54
+ screen: 'VerificationSessionCheckScreen',
55
+ label: 'Session Check',
56
+ icon: ICONS.LOGIN,
57
+ },
58
+ {
59
+ screen: 'OTPVerificationScreen',
60
+ label: 'OTP',
61
+ icon: ICONS.PHONE,
62
+ setupDemo: () => {
63
+ setupDemoSession(true);
64
+ },
65
+ params: { sessionId: 'demo-verification-id' },
66
+ },
67
+ {
68
+ screen: 'ContractAcceptanceScreen',
69
+ label: 'Consent',
70
+ icon: ICONS.HANDSHAKE,
71
+ setupDemo: () => {
72
+ setupDemoSession(true);
73
+ // Set current workflow step to contract acceptance so demo contract URLs are loaded
74
+ const contractStep = appContext.workflowSteps?.find(
75
+ (step) => step.type === 'CONTRACT_ACCEPTANCE'
76
+ );
77
+ if (contractStep) {
78
+ appContext.currentWorkflowStep = contractStep;
79
+ }
80
+ },
81
+ },
82
+ {
83
+ screen: 'IdentityDocumentScanningScreen',
84
+ label: 'ID Scan',
85
+ icon: ICONS.CREDIT_CARD,
86
+ setupDemo: () => {
87
+ setupDemoSession(true);
88
+ },
89
+ },
90
+ {
91
+ screen: 'IdentityDocumentEIDScanningScreen',
92
+ label: 'eID Scan',
93
+ icon: ICONS.NFC,
94
+ setupDemo: () => {
95
+ setupDemoSession(true);
96
+ },
97
+ },
98
+ {
99
+ screen: 'LivenessDetectionScreen',
100
+ label: 'Liveness',
101
+ icon: ICONS.FACE,
102
+ setupDemo: () => {
103
+ setupDemoSession(true);
104
+ },
105
+ },
106
+ {
107
+ screen: 'ResultScreen',
108
+ label: 'Result',
109
+ icon: ICONS.CHECK_CIRCLE,
110
+ setupDemo: () => {
111
+ setupDemoSession();
112
+ },
113
+ },
114
+ ];
115
+
116
+ const jumpToScreen = useCallback(
117
+ (screen: ScreenOption) => {
118
+ if (screen.setupDemo) {
119
+ screen.setupDemo();
120
+ }
121
+ (navigation as any).navigate(screen.screen, screen.params);
122
+ setIsExpanded(false);
123
+ },
124
+ [navigation]
125
+ );
126
+
127
+ // Only show in development mode
128
+ if (!__DEV__) {
129
+ return null;
130
+ }
131
+
132
+ return (
133
+ <View style={[styles.container, { bottom: 20 + insets.bottom }]}>
134
+ {isExpanded && (
135
+ <View style={styles.panel}>
136
+ <Text style={styles.title}>SDK JUMP TO SCREEN:</Text>
137
+ {screens.map((screen) => (
138
+ <TouchableOpacity
139
+ key={screen.screen}
140
+ style={styles.screenButton}
141
+ onPress={() => jumpToScreen(screen)}
142
+ >
143
+ <Text style={styles.screenIcon}>{screen.icon}</Text>
144
+ <Text style={styles.screenLabel}>{screen.label}</Text>
145
+ </TouchableOpacity>
146
+ ))}
147
+ </View>
148
+ )}
149
+ <TouchableOpacity
150
+ style={styles.toggleButton}
151
+ onPress={() => setIsExpanded(!isExpanded)}
152
+ >
153
+ <Text style={styles.bugIcon}>{ICONS.BUG_REPORT}</Text>
154
+ {isExpanded && (
155
+ <>
156
+ <View style={styles.spacing} />
157
+ <Text style={styles.toggleText}>DEBUG NAV</Text>
158
+ </>
159
+ )}
160
+ <View style={styles.spacing} />
161
+ <Text style={styles.arrow}>{isExpanded ? '▼' : '▲'}</Text>
162
+ </TouchableOpacity>
163
+ </View>
164
+ );
165
+ };
166
+
167
+ const styles = StyleSheet.create({
168
+ container: {
169
+ position: 'absolute',
170
+ right: 20,
171
+ alignItems: 'flex-end',
172
+ zIndex: 9999,
173
+ },
174
+ toggleButton: {
175
+ flexDirection: 'row',
176
+ alignItems: 'center',
177
+ backgroundColor: '#FF5252',
178
+ paddingHorizontal: 12,
179
+ paddingVertical: 10,
180
+ borderRadius: 10,
181
+ borderWidth: 2,
182
+ borderColor: 'white',
183
+ ...Platform.select({
184
+ ios: {
185
+ shadowColor: '#000',
186
+ shadowOffset: { width: 0, height: 4 },
187
+ shadowOpacity: 0.3,
188
+ shadowRadius: 8,
189
+ },
190
+ android: {
191
+ elevation: 8,
192
+ },
193
+ }),
194
+ },
195
+ bugIcon: {
196
+ fontSize: 24,
197
+ },
198
+ spacing: {
199
+ width: 8,
200
+ },
201
+ toggleText: {
202
+ color: 'white',
203
+ fontSize: 14,
204
+ fontWeight: 'bold',
205
+ },
206
+ arrow: {
207
+ color: 'white',
208
+ fontSize: 20,
209
+ },
210
+ panel: {
211
+ backgroundColor: 'rgba(0, 0, 0, 0.9)',
212
+ borderRadius: 12,
213
+ padding: 12,
214
+ marginBottom: 10,
215
+ borderWidth: 1,
216
+ borderColor: 'rgba(255, 82, 82, 0.5)',
217
+ minWidth: 180,
218
+ ...Platform.select({
219
+ ios: {
220
+ shadowColor: '#000',
221
+ shadowOffset: { width: 0, height: 4 },
222
+ shadowOpacity: 0.5,
223
+ shadowRadius: 15,
224
+ },
225
+ android: {
226
+ elevation: 15,
227
+ },
228
+ }),
229
+ },
230
+ title: {
231
+ color: 'rgba(255, 255, 255, 0.7)',
232
+ fontSize: 11,
233
+ fontWeight: 'bold',
234
+ letterSpacing: 1.2,
235
+ marginBottom: 12,
236
+ textAlign: 'right',
237
+ },
238
+ screenButton: {
239
+ flexDirection: 'row',
240
+ alignItems: 'center',
241
+ justifyContent: 'flex-start',
242
+ backgroundColor: '#616161',
243
+ paddingHorizontal: 12,
244
+ paddingVertical: 8,
245
+ borderRadius: 4,
246
+ marginBottom: 6,
247
+ },
248
+ screenIcon: {
249
+ fontSize: 16,
250
+ marginRight: 8,
251
+ width: 20,
252
+ textAlign: 'center',
253
+ },
254
+ screenLabel: {
255
+ color: 'white',
256
+ fontSize: 12,
257
+ fontWeight: '500',
258
+ flex: 1,
259
+ },
260
+ });
261
+
262
+ export default DebugNavigationPanel;