@trustchex/react-native-sdk 1.381.0 → 1.409.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 (111) hide show
  1. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +1 -12
  2. package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
  3. package/ios/Camera/TrustchexCameraView.swift +1 -12
  4. package/ios/MLKit/MLKitModule.swift +1 -1
  5. package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
  6. package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
  7. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -29
  8. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +5 -0
  9. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
  10. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +26 -6
  11. package/lib/module/Screens/Dynamic/VideoCallScreen.js +676 -0
  12. package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
  13. package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
  14. package/lib/module/Screens/Static/ResultScreen.js +27 -13
  15. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +51 -51
  16. package/lib/module/Shared/Animations/video-call.json +1 -0
  17. package/lib/module/Shared/Components/DebugNavigationPanel.js +180 -14
  18. package/lib/module/Shared/Components/EIDScanner.js +1 -4
  19. package/lib/module/Shared/Components/IdentityDocumentCamera.js +29 -8
  20. package/lib/module/Shared/Components/NavigationManager.js +15 -3
  21. package/lib/module/Shared/Contexts/AppContext.js +1 -0
  22. package/lib/module/Shared/Libs/SignalingClient.js +128 -0
  23. package/lib/module/Shared/Libs/analytics.utils.js +4 -0
  24. package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
  25. package/lib/module/Shared/Libs/http-client.js +9 -0
  26. package/lib/module/Shared/Libs/promise.utils.js +16 -2
  27. package/lib/module/Shared/Libs/status-bar.utils.js +21 -0
  28. package/lib/module/Shared/Services/DataUploadService.js +294 -0
  29. package/lib/module/Shared/Services/VideoSessionService.js +156 -0
  30. package/lib/module/Shared/Services/WebRTCService.js +510 -0
  31. package/lib/module/Shared/Types/analytics.types.js +2 -0
  32. package/lib/module/Translation/Resources/en.js +20 -0
  33. package/lib/module/Translation/Resources/tr.js +20 -0
  34. package/lib/module/Trustchex.js +10 -0
  35. package/lib/module/version.js +1 -1
  36. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
  37. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
  38. package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
  39. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  40. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  41. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  42. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  43. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
  44. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
  45. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
  46. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  47. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  48. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  49. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  50. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  51. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  52. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  53. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
  54. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  55. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
  56. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
  57. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
  58. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  59. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  60. package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
  61. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
  62. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
  63. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
  64. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
  65. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
  66. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
  67. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
  68. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
  69. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +2 -0
  70. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
  71. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +4 -1
  72. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  73. package/lib/typescript/src/Translation/Resources/en.d.ts +20 -0
  74. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  75. package/lib/typescript/src/Translation/Resources/tr.d.ts +20 -0
  76. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  77. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  78. package/lib/typescript/src/version.d.ts +1 -1
  79. package/package.json +29 -2
  80. package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
  81. package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
  82. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +59 -33
  83. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +6 -0
  84. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
  85. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +34 -6
  86. package/src/Screens/Dynamic/VideoCallScreen.tsx +764 -0
  87. package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
  88. package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
  89. package/src/Screens/Static/ResultScreen.tsx +58 -23
  90. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +58 -72
  91. package/src/Shared/Animations/video-call.json +1 -0
  92. package/src/Shared/Components/DebugNavigationPanel.tsx +185 -9
  93. package/src/Shared/Components/EIDScanner.tsx +1 -5
  94. package/src/Shared/Components/IdentityDocumentCamera.tsx +29 -8
  95. package/src/Shared/Components/NavigationManager.tsx +14 -1
  96. package/src/Shared/Contexts/AppContext.ts +2 -0
  97. package/src/Shared/Libs/SignalingClient.ts +189 -0
  98. package/src/Shared/Libs/analytics.utils.ts +4 -0
  99. package/src/Shared/Libs/deeplink.utils.ts +12 -1
  100. package/src/Shared/Libs/http-client.ts +10 -0
  101. package/src/Shared/Libs/promise.utils.ts +16 -2
  102. package/src/Shared/Libs/status-bar.utils.ts +19 -0
  103. package/src/Shared/Services/DataUploadService.ts +395 -0
  104. package/src/Shared/Services/VideoSessionService.ts +190 -0
  105. package/src/Shared/Services/WebRTCService.ts +636 -0
  106. package/src/Shared/Types/analytics.types.ts +2 -0
  107. package/src/Shared/Types/identificationInfo.ts +5 -1
  108. package/src/Translation/Resources/en.ts +25 -0
  109. package/src/Translation/Resources/tr.ts +27 -0
  110. package/src/Trustchex.tsx +12 -2
  111. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trustchex/react-native-sdk",
3
- "version": "1.381.0",
3
+ "version": "1.409.0",
4
4
  "description": "Trustchex mobile app react native SDK for android or ios devices",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -56,6 +56,12 @@
56
56
  "example",
57
57
  "example-expo"
58
58
  ],
59
+ "resolutions": {
60
+ "@react-navigation/core": "7.12.4",
61
+ "@react-navigation/native": "7.1.18",
62
+ "@react-navigation/native-stack": "7.3.27",
63
+ "@react-navigation/elements": "2.6.5"
64
+ },
59
65
  "scripts": {
60
66
  "example": "yarn workspace @trustchex/react-native-sdk-example",
61
67
  "example:android": "yarn example android",
@@ -101,12 +107,16 @@
101
107
  "react-native-device-info": "^13.0.0",
102
108
  "react-native-fs": "^2.20.0",
103
109
  "react-native-get-random-values": "^1.11.0",
110
+ "react-native-incall-manager": "^4.2.0",
104
111
  "react-native-nfc-manager": "^3.16.2",
105
112
  "react-native-safe-area-context": "^5.5.2",
106
113
  "react-native-screens": "^4.18.0",
114
+ "react-native-sse": "^1.1.0",
107
115
  "react-native-svg": "^15.12.0",
108
116
  "react-native-tts": "^4.1.1",
109
117
  "react-native-video": "^6.16.1",
118
+ "react-native-vision-camera": "^4.7.1",
119
+ "react-native-webrtc": "^124.0.0",
110
120
  "react-native-webview": "^13.15.0",
111
121
  "release-it": "^17.10.0",
112
122
  "turbo": "^1.10.7",
@@ -132,13 +142,18 @@
132
142
  "react-native-device-info": ">=13.0.0",
133
143
  "react-native-fs": ">=2.20.0",
134
144
  "react-native-get-random-values": ">=1.11.0",
145
+ "react-native-incall-manager": ">=4.2.0",
135
146
  "react-native-nfc-manager": ">=3.16.2",
136
147
  "react-native-safe-area-context": ">=5.5.2",
137
148
  "react-native-screens": ">=4.18.0",
149
+ "react-native-sse": ">=1.1.0",
138
150
  "react-native-svg": ">=15.12.0",
139
151
  "react-native-tts": ">=4.1.1",
140
152
  "react-native-video": ">=6.16.1",
141
- "react-native-webview": ">=13.15.0"
153
+ "react-native-vision-camera": ">=4.7.1",
154
+ "react-native-webrtc": ">=124.0.0",
155
+ "react-native-webview": ">=13.15.0",
156
+ "react-native-worklets-core": ">=1.6.2"
142
157
  },
143
158
  "peerDependenciesMeta": {
144
159
  "@react-navigation/native": {
@@ -168,6 +183,9 @@
168
183
  "react-native-get-random-values": {
169
184
  "optional": false
170
185
  },
186
+ "react-native-incall-manager": {
187
+ "optional": false
188
+ },
171
189
  "react-native-nfc-manager": {
172
190
  "optional": false
173
191
  },
@@ -177,6 +195,9 @@
177
195
  "react-native-screens": {
178
196
  "optional": false
179
197
  },
198
+ "react-native-sse": {
199
+ "optional": false
200
+ },
180
201
  "react-native-svg": {
181
202
  "optional": false
182
203
  },
@@ -186,6 +207,12 @@
186
207
  "react-native-video": {
187
208
  "optional": false
188
209
  },
210
+ "react-native-vision-camera": {
211
+ "optional": false
212
+ },
213
+ "react-native-webrtc": {
214
+ "optional": false
215
+ },
189
216
  "react-native-webview": {
190
217
  "optional": false
191
218
  }
@@ -0,0 +1,317 @@
1
+ import React, { useState, useRef, useCallback } from 'react';
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ Text,
6
+ ScrollView,
7
+ StatusBar,
8
+ TouchableOpacity,
9
+ type NativeSyntheticEvent,
10
+ type LayoutChangeEvent,
11
+ } from 'react-native';
12
+ import { SafeAreaView } from 'react-native-safe-area-context';
13
+ import {
14
+ TrustchexCamera,
15
+ type TrustchexCameraHandle,
16
+ type Frame,
17
+ type NativeBarcode,
18
+ } from '../../Shared/Components/TrustchexCamera';
19
+ import { useKeepAwake } from '../../Shared/Libs/native-keep-awake.utils';
20
+
21
+ // ML Kit barcode format constants
22
+ const BARCODE_FORMAT_NAMES: Record<number, string> = {
23
+ 1: 'Code 128',
24
+ 2: 'Code 39',
25
+ 4: 'Code 93',
26
+ 8: 'Codabar',
27
+ 16: 'Data Matrix',
28
+ 32: 'EAN-13',
29
+ 64: 'EAN-8',
30
+ 128: 'ITF',
31
+ 256: 'QR Code',
32
+ 512: 'UPC-A',
33
+ 1024: 'UPC-E',
34
+ 2048: 'PDF417',
35
+ 4096: 'Aztec',
36
+ };
37
+
38
+ function formatName(format: number): string {
39
+ return BARCODE_FORMAT_NAMES[format] ?? `Format(${format})`;
40
+ }
41
+
42
+ interface BarcodeEntry {
43
+ rawValue: string;
44
+ displayValue: string;
45
+ format: number;
46
+ timestamp: number;
47
+ }
48
+
49
+ const BarcodeTestScreen = () => {
50
+ useKeepAwake();
51
+ const cameraRef = useRef<TrustchexCameraHandle>(null);
52
+ const [barcodes, setBarcodes] = useState<NativeBarcode[]>([]);
53
+ const [history, setHistory] = useState<BarcodeEntry[]>([]);
54
+ const [isPaused, setIsPaused] = useState<boolean>(false);
55
+ const [cameraLayout, setCameraLayout] = useState({ width: 1, height: 1 });
56
+ const [frameSize, setFrameSize] = useState({ width: 1080, height: 1920 });
57
+
58
+ const handleCameraLayout = useCallback((e: LayoutChangeEvent) => {
59
+ const { width, height } = e.nativeEvent.layout;
60
+ setCameraLayout({ width, height });
61
+ }, []);
62
+
63
+ const handleFrame = useCallback(
64
+ (event: NativeSyntheticEvent<{ frame: Frame }>) => {
65
+ if (isPaused) return;
66
+
67
+ const frame = event.nativeEvent.frame;
68
+
69
+ if (frame.width && frame.height) {
70
+ setFrameSize({ width: frame.width, height: frame.height });
71
+ }
72
+
73
+ if (frame.barcodes && frame.barcodes.length > 0) {
74
+ setBarcodes(frame.barcodes);
75
+
76
+ setHistory((prev) => {
77
+ const incoming = frame.barcodes!;
78
+ const next = [...prev];
79
+ for (const b of incoming) {
80
+ const alreadyRecent = next
81
+ .slice(0, 5)
82
+ .some((h) => h.rawValue === b.rawValue);
83
+ if (!alreadyRecent) {
84
+ next.unshift({
85
+ rawValue: b.rawValue,
86
+ displayValue: b.displayValue,
87
+ format: b.format,
88
+ timestamp: Date.now(),
89
+ });
90
+ }
91
+ }
92
+ return next.slice(0, 30);
93
+ });
94
+ } else {
95
+ setBarcodes([]);
96
+ }
97
+ },
98
+ [isPaused]
99
+ );
100
+
101
+ return (
102
+ <View style={styles.container}>
103
+ <StatusBar
104
+ barStyle="light-content"
105
+ backgroundColor="transparent"
106
+ translucent
107
+ />
108
+ <View style={styles.cameraWrapper} onLayout={handleCameraLayout}>
109
+ <TrustchexCamera
110
+ ref={cameraRef}
111
+ style={StyleSheet.absoluteFill}
112
+ cameraType="back"
113
+ enableFrameProcessing={true}
114
+ enableFaceDetection={false}
115
+ enableTextRecognition={false}
116
+ enableBarcodeScanning={true}
117
+ includeBase64={false}
118
+ targetFps={15}
119
+ onFrameAvailable={handleFrame}
120
+ />
121
+ {barcodes.map((b, i) => {
122
+ const bb = b.boundingBox;
123
+ if (!bb) return null;
124
+ const scaleX = cameraLayout.width / frameSize.width;
125
+ const scaleY = cameraLayout.height / frameSize.height;
126
+ const left = bb.left * scaleX;
127
+ const top = bb.top * scaleY;
128
+ const width = (bb.right - bb.left) * scaleX;
129
+ const height = (bb.bottom - bb.top) * scaleY;
130
+ return (
131
+ <View
132
+ key={i}
133
+ style={[styles.barcodeBoundingBox, { left, top, width, height }]}
134
+ >
135
+ <View style={styles.barcodeBoxLabel}>
136
+ <Text style={styles.barcodeBoxLabelText}>
137
+ {formatName(b.format)}
138
+ </Text>
139
+ </View>
140
+ </View>
141
+ );
142
+ })}
143
+ </View>
144
+
145
+ <SafeAreaView style={styles.panel} edges={['bottom']}>
146
+ <ScrollView style={styles.scrollView}>
147
+ <View style={styles.panelContent}>
148
+ <TouchableOpacity
149
+ style={styles.pauseButton}
150
+ onPress={() => setIsPaused(!isPaused)}
151
+ >
152
+ <Text style={styles.pauseButtonText}>
153
+ {isPaused ? 'Resume Processing' : 'Pause Processing'}
154
+ </Text>
155
+ </TouchableOpacity>
156
+
157
+ <Text style={styles.title}>Barcode Scanner</Text>
158
+
159
+ <Text style={styles.sectionTitle}>Live Detections</Text>
160
+ {barcodes.length === 0 ? (
161
+ <Text style={styles.bodyText}>No barcode in frame</Text>
162
+ ) : (
163
+ barcodes.map((b, i) => (
164
+ <View key={i} style={styles.barcodeRow}>
165
+ <View style={styles.formatBadge}>
166
+ <Text style={styles.formatBadgeText}>
167
+ {formatName(b.format)}
168
+ </Text>
169
+ </View>
170
+ <Text style={styles.barcodeValue}>{b.rawValue}</Text>
171
+ </View>
172
+ ))
173
+ )}
174
+
175
+ <Text style={styles.sectionTitle}>History ({history.length})</Text>
176
+ {history.length === 0 ? (
177
+ <Text style={styles.bodyText}>No barcodes detected yet</Text>
178
+ ) : (
179
+ history.map((entry, i) => (
180
+ <View key={i} style={styles.historyRow}>
181
+ <View style={styles.historyMeta}>
182
+ <View style={styles.formatBadge}>
183
+ <Text style={styles.formatBadgeText}>
184
+ {formatName(entry.format)}
185
+ </Text>
186
+ </View>
187
+ <Text style={styles.historyTime}>
188
+ {new Date(entry.timestamp).toLocaleTimeString()}
189
+ </Text>
190
+ </View>
191
+ <Text style={styles.barcodeValue}>{entry.rawValue}</Text>
192
+ </View>
193
+ ))
194
+ )}
195
+ </View>
196
+ </ScrollView>
197
+ </SafeAreaView>
198
+ </View>
199
+ );
200
+ };
201
+
202
+ const styles = StyleSheet.create({
203
+ container: {
204
+ flex: 1,
205
+ backgroundColor: '#000000',
206
+ },
207
+ cameraWrapper: {
208
+ flex: 2,
209
+ position: 'relative',
210
+ },
211
+ barcodeBoundingBox: {
212
+ position: 'absolute',
213
+ borderWidth: 2,
214
+ borderColor: '#00C853',
215
+ backgroundColor: 'rgba(0, 200, 83, 0.07)',
216
+ },
217
+ barcodeBoxLabel: {
218
+ position: 'absolute',
219
+ top: -14,
220
+ left: 0,
221
+ backgroundColor: '#00C853',
222
+ paddingHorizontal: 4,
223
+ paddingVertical: 1,
224
+ borderRadius: 2,
225
+ },
226
+ barcodeBoxLabelText: {
227
+ color: '#000000',
228
+ fontSize: 8,
229
+ fontWeight: 'bold',
230
+ },
231
+ panel: {
232
+ flex: 1,
233
+ backgroundColor: 'rgba(0, 0, 0, 0.95)',
234
+ borderTopWidth: 2,
235
+ borderTopColor: '#00C853',
236
+ },
237
+ scrollView: {
238
+ flex: 1,
239
+ },
240
+ panelContent: {
241
+ padding: 10,
242
+ },
243
+ pauseButton: {
244
+ alignSelf: 'center',
245
+ borderWidth: 1,
246
+ borderColor: '#00C853',
247
+ borderRadius: 4,
248
+ paddingVertical: 6,
249
+ paddingHorizontal: 12,
250
+ marginBottom: 8,
251
+ },
252
+ pauseButtonText: {
253
+ color: '#00C853',
254
+ fontSize: 10,
255
+ fontWeight: 'bold',
256
+ },
257
+ title: {
258
+ color: '#00C853',
259
+ fontSize: 12,
260
+ fontWeight: 'bold',
261
+ marginBottom: 8,
262
+ textAlign: 'center',
263
+ },
264
+ sectionTitle: {
265
+ color: '#00C853',
266
+ fontSize: 10,
267
+ fontWeight: 'bold',
268
+ marginTop: 6,
269
+ marginBottom: 4,
270
+ },
271
+ bodyText: {
272
+ color: '#888888',
273
+ fontSize: 9,
274
+ fontFamily: 'monospace',
275
+ },
276
+ barcodeRow: {
277
+ marginBottom: 6,
278
+ },
279
+ historyRow: {
280
+ marginBottom: 8,
281
+ borderBottomWidth: 1,
282
+ borderBottomColor: '#333333',
283
+ paddingBottom: 6,
284
+ },
285
+ historyMeta: {
286
+ flexDirection: 'row',
287
+ alignItems: 'center',
288
+ marginBottom: 2,
289
+ gap: 6,
290
+ },
291
+ formatBadge: {
292
+ backgroundColor: '#00C853',
293
+ borderRadius: 3,
294
+ paddingHorizontal: 5,
295
+ paddingVertical: 2,
296
+ alignSelf: 'flex-start',
297
+ marginBottom: 2,
298
+ },
299
+ formatBadgeText: {
300
+ color: '#000000',
301
+ fontSize: 8,
302
+ fontWeight: 'bold',
303
+ },
304
+ barcodeValue: {
305
+ color: '#FFFFFF',
306
+ fontSize: 9,
307
+ fontFamily: 'monospace',
308
+ lineHeight: 14,
309
+ },
310
+ historyTime: {
311
+ color: '#888888',
312
+ fontSize: 8,
313
+ fontFamily: 'monospace',
314
+ },
315
+ });
316
+
317
+ export default BarcodeTestScreen;
@@ -7,22 +7,38 @@ import {
7
7
  StatusBar,
8
8
  TouchableOpacity,
9
9
  type NativeSyntheticEvent,
10
+ type LayoutChangeEvent,
10
11
  } from 'react-native';
11
12
  import { SafeAreaView } from 'react-native-safe-area-context';
12
13
  import {
13
14
  TrustchexCamera,
14
15
  type TrustchexCameraHandle,
15
16
  type Frame,
17
+ type NativeTextBlock,
16
18
  } from '../../Shared/Components/TrustchexCamera';
17
19
  import mrzUtils from '../../Shared/Libs/mrz.utils';
20
+ import { useKeepAwake } from '../../Shared/Libs/native-keep-awake.utils';
21
+
22
+ interface MrzOverlayBlock extends NativeTextBlock {
23
+ isMrz: boolean;
24
+ }
18
25
 
19
26
  const MRZTestScreen = () => {
27
+ useKeepAwake();
20
28
  const cameraRef = useRef<TrustchexCameraHandle>(null);
21
29
  const [mrzText, setMrzText] = useState<string>('Waiting for MRZ...');
22
30
  const [mrzRawText, setMrzRawText] = useState<string>('');
23
31
  const [mrzValid, setMrzValid] = useState<boolean>(false);
24
32
  const [rawHistory, setRawHistory] = useState<string[]>([]);
25
33
  const [isPaused, setIsPaused] = useState<boolean>(false);
34
+ const [overlayBlocks, setOverlayBlocks] = useState<MrzOverlayBlock[]>([]);
35
+ const [cameraLayout, setCameraLayout] = useState({ width: 1, height: 1 });
36
+ const [frameSize, setFrameSize] = useState({ width: 1080, height: 1920 });
37
+
38
+ const handleCameraLayout = useCallback((e: LayoutChangeEvent) => {
39
+ const { width, height } = e.nativeEvent.layout;
40
+ setCameraLayout({ width, height });
41
+ }, []);
26
42
 
27
43
  const handleFrame = useCallback(
28
44
  (event: NativeSyntheticEvent<{ frame: Frame }>) => {
@@ -32,6 +48,28 @@ const MRZTestScreen = () => {
32
48
 
33
49
  const frame = event.nativeEvent.frame;
34
50
 
51
+ if (frame.width && frame.height) {
52
+ setFrameSize({ width: frame.width, height: frame.height });
53
+ }
54
+
55
+ if (frame.textBlocks && frame.textBlocks.length > 0) {
56
+ const frameHeight = frame.height;
57
+ const bottomThreshold = frameHeight * 0.5;
58
+ const blocks: MrzOverlayBlock[] = frame.textBlocks.map((block) => {
59
+ const blockText = block.text || '';
60
+ return {
61
+ ...block,
62
+ isMrz:
63
+ blockText.includes('<') &&
64
+ blockText.length >= 12 &&
65
+ (block.blockFrame ? block.blockFrame.y > bottomThreshold : false),
66
+ };
67
+ });
68
+ setOverlayBlocks(blocks);
69
+ } else {
70
+ setOverlayBlocks([]);
71
+ }
72
+
35
73
  if (frame.resultText) {
36
74
  setRawHistory((prev) => {
37
75
  if (prev[0] === frame.resultText) {
@@ -86,18 +124,47 @@ const MRZTestScreen = () => {
86
124
  backgroundColor="transparent"
87
125
  translucent
88
126
  />
89
- <TrustchexCamera
90
- ref={cameraRef}
91
- style={styles.camera}
92
- cameraType="back"
93
- enableFrameProcessing={true}
94
- enableFaceDetection={false}
95
- enableTextRecognition={true}
96
- enableBarcodeScanning={false}
97
- includeBase64={false}
98
- targetFps={10}
99
- onFrameAvailable={handleFrame}
100
- />
127
+ <View style={styles.cameraWrapper} onLayout={handleCameraLayout}>
128
+ <TrustchexCamera
129
+ ref={cameraRef}
130
+ style={StyleSheet.absoluteFill}
131
+ cameraType="back"
132
+ enableFrameProcessing={true}
133
+ enableFaceDetection={false}
134
+ enableTextRecognition={true}
135
+ enableBarcodeScanning={false}
136
+ includeBase64={false}
137
+ targetFps={10}
138
+ onFrameAvailable={handleFrame}
139
+ />
140
+ {overlayBlocks.map((block, i) => {
141
+ const bf = block.blockFrame;
142
+ if (!bf) return null;
143
+ const scaleX = cameraLayout.width / frameSize.width;
144
+ const scaleY = cameraLayout.height / frameSize.height;
145
+ return (
146
+ <View
147
+ key={i}
148
+ style={[
149
+ styles.boundingBox,
150
+ block.isMrz ? styles.boundingBoxMrz : styles.boundingBoxText,
151
+ {
152
+ left: bf.x * scaleX,
153
+ top: bf.y * scaleY,
154
+ width: bf.width * scaleX,
155
+ height: bf.height * scaleY,
156
+ },
157
+ ]}
158
+ >
159
+ {block.isMrz && (
160
+ <View style={styles.blockLabel}>
161
+ <Text style={styles.blockLabelText}>MRZ</Text>
162
+ </View>
163
+ )}
164
+ </View>
165
+ );
166
+ })}
167
+ </View>
101
168
 
102
169
  <SafeAreaView style={styles.mrzPanel} edges={['bottom']}>
103
170
  <ScrollView style={styles.scrollView}>
@@ -152,8 +219,35 @@ const styles = StyleSheet.create({
152
219
  flex: 1,
153
220
  backgroundColor: '#000000',
154
221
  },
155
- camera: {
222
+ cameraWrapper: {
156
223
  flex: 2,
224
+ position: 'relative',
225
+ },
226
+ boundingBox: {
227
+ position: 'absolute',
228
+ borderWidth: 1.5,
229
+ },
230
+ boundingBoxText: {
231
+ borderColor: 'rgba(255, 255, 255, 0.25)',
232
+ },
233
+ boundingBoxMrz: {
234
+ borderColor: '#FFA500',
235
+ borderWidth: 2,
236
+ backgroundColor: 'rgba(255, 165, 0, 0.07)',
237
+ },
238
+ blockLabel: {
239
+ position: 'absolute',
240
+ top: -14,
241
+ left: 0,
242
+ backgroundColor: '#FFA500',
243
+ paddingHorizontal: 4,
244
+ paddingVertical: 1,
245
+ borderRadius: 2,
246
+ },
247
+ blockLabelText: {
248
+ color: '#000000',
249
+ fontSize: 8,
250
+ fontWeight: 'bold',
157
251
  },
158
252
  mrzPanel: {
159
253
  flex: 1,
@@ -16,6 +16,7 @@ import { getI18n, useTranslation } from 'react-i18next';
16
16
  import StyledButton from '../../Shared/Components/StyledButton';
17
17
  import NativeDeviceInfo from '../../Shared/Libs/native-device-info.utils';
18
18
  import { speakWithDebounce } from '../../Shared/Libs/tts.utils';
19
+ import { useStatusBarWhiteBackground } from '../../Shared/Libs/status-bar.utils';
19
20
  import {
20
21
  trackFunnelStep,
21
22
  useScreenTracking,
@@ -24,8 +25,10 @@ import {
24
25
  trackVerificationComplete,
25
26
  trackError,
26
27
  } from '../../Shared/Libs/analytics.utils';
28
+ import { useKeepAwake } from '../../Shared/Libs/native-keep-awake.utils';
27
29
 
28
30
  const ContractAcceptanceScreen = () => {
31
+ useKeepAwake();
29
32
  const [isEnabled, setIsEnabled] = useState(false);
30
33
  const [isReady, setIsReady] = useState(false);
31
34
  const appContext = useContext(AppContext);
@@ -39,6 +42,9 @@ const ContractAcceptanceScreen = () => {
39
42
  // Track screen view and exit
40
43
  useScreenTracking('contract_acceptance');
41
44
 
45
+ // Configure status bar for white background
46
+ useStatusBarWhiteBackground();
47
+
42
48
  useEffect(() => {
43
49
  const contracts = appContext.currentWorkflowStep?.data?.contracts;
44
50
  if (!contracts) {
@@ -93,41 +99,58 @@ const ContractAcceptanceScreen = () => {
93
99
  return await NativeDeviceInfo.generateHumanReadableIdentifier();
94
100
  }, []);
95
101
 
102
+ // Early return if workflow step data is not available (after all hooks)
103
+ if (!appContext.currentWorkflowStep || !appContext.currentWorkflowStep.data) {
104
+ return null;
105
+ }
106
+
96
107
  return (
97
108
  <SafeAreaView style={styles.container}>
98
- <WebView
99
- source={{
100
- uri: contractUrl || '',
101
- }}
102
- javaScriptEnabled={true}
103
- domStorageEnabled={false}
104
- scalesPageToFit={false}
105
- scrollEnabled={true}
106
- style={styles.webView}
107
- onError={(syntheticEvent) => {
108
- const { nativeEvent } = syntheticEvent;
109
- trackError(
110
- 'CONTRACT_WEBVIEW_ERROR',
111
- nativeEvent.description || 'Failed to load contract',
112
- 'contract_acceptance',
113
- 'medium',
114
- { recoverable: true, userAction: 'load_contract' }
115
- );
116
- }}
117
- onLoadEnd={(event) => {
118
- if (event.nativeEvent.url === contractUrl) {
119
- setIsReady(true);
120
-
121
- // Track contract acceptance started
122
- trackVerificationStart('CONTRACT_ACCEPTANCE');
123
-
124
- if (appContext.currentWorkflowStep?.data?.voiceGuidanceActive) {
125
- speakWithDebounce(t('termsOfUseAndDataPrivacyScreen.footerText'));
109
+ <View
110
+ style={[
111
+ styles.webViewContainer,
112
+ {
113
+ paddingHorizontal: Math.max(insets.left, insets.right),
114
+ paddingTop: insets.top,
115
+ },
116
+ ]}
117
+ >
118
+ <WebView
119
+ source={{
120
+ uri: contractUrl || '',
121
+ }}
122
+ javaScriptEnabled={true}
123
+ domStorageEnabled={false}
124
+ scalesPageToFit={false}
125
+ scrollEnabled={true}
126
+ style={styles.webView}
127
+ onError={(syntheticEvent) => {
128
+ const { nativeEvent } = syntheticEvent;
129
+ trackError(
130
+ 'CONTRACT_WEBVIEW_ERROR',
131
+ nativeEvent.description || 'Failed to load contract',
132
+ 'contract_acceptance',
133
+ 'medium',
134
+ { recoverable: true, userAction: 'load_contract' }
135
+ );
136
+ }}
137
+ onLoadEnd={(event) => {
138
+ if (event.nativeEvent.url === contractUrl) {
139
+ setIsReady(true);
140
+
141
+ // Track contract acceptance started
142
+ trackVerificationStart('CONTRACT_ACCEPTANCE');
143
+
144
+ if (appContext.currentWorkflowStep?.data?.voiceGuidanceActive) {
145
+ speakWithDebounce(
146
+ t('termsOfUseAndDataPrivacyScreen.footerText')
147
+ );
148
+ }
126
149
  }
127
- }
128
- }}
129
- onScroll={hasReachedEnd}
130
- />
150
+ }}
151
+ onScroll={hasReachedEnd}
152
+ />
153
+ </View>
131
154
  <View style={[styles.footer, { paddingBottom: insets.bottom }]}>
132
155
  <Text style={styles.footerText}>
133
156
  {t('termsOfUseAndDataPrivacyScreen.footerText')}
@@ -183,10 +206,13 @@ const styles = StyleSheet.create({
183
206
  container: {
184
207
  flex: 1,
185
208
  },
209
+ webViewContainer: {
210
+ flex: 1,
211
+ width: '100%',
212
+ },
186
213
  webView: {
187
214
  flex: 1,
188
215
  width: '100%',
189
- height: '100%',
190
216
  resizeMode: 'contain',
191
217
  },
192
218
  footer: {