@trustchex/react-native-sdk 1.512.0 → 1.513.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.
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +9 -2
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +54 -0
- package/ios/Camera/TrustchexCameraView.swift +6 -2
- package/ios/OpenCV/OpenCVModule.mm +59 -0
- package/lib/module/Screens/Dynamic/AddressCaptureScreen.js +427 -112
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +9 -4
- package/lib/module/Screens/Static/ResultScreen.js +46 -0
- package/lib/module/Shared/Animations/address-scan.json +1 -0
- package/lib/module/Shared/Components/DebugNavigationPanel.js +10 -0
- package/lib/module/Shared/Components/EIDScanner.js +16 -4
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +100 -19
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +47 -0
- package/lib/module/Shared/Libs/addressProofHeuristic.js +130 -0
- package/lib/module/Shared/Libs/demo.utils.js +4 -0
- package/lib/module/Shared/Libs/sendDiagnosticReport.js +47 -1
- package/lib/module/Shared/Libs/vizName.js +138 -16
- package/lib/module/Translation/Resources/en.js +16 -3
- package/lib/module/Translation/Resources/tr.js +16 -3
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Dynamic/AddressCaptureScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts +8 -3
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +25 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/addressProofHeuristic.d.ts +34 -0
- package/lib/typescript/src/Shared/Libs/addressProofHeuristic.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/vizName.d.ts +31 -6
- package/lib/typescript/src/Shared/Libs/vizName.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +0 -5
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +13 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +13 -0
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Screens/Dynamic/AddressCaptureScreen.tsx +434 -75
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +14 -4
- package/src/Screens/Static/ResultScreen.tsx +59 -0
- package/src/Shared/Animations/address-scan.json +1 -0
- package/src/Shared/Components/DebugNavigationPanel.tsx +13 -0
- package/src/Shared/Components/EIDScanner.tsx +38 -12
- package/src/Shared/Components/IdentityDocumentCamera.tsx +109 -17
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +53 -0
- package/src/Shared/Libs/addressProofHeuristic.ts +197 -0
- package/src/Shared/Libs/demo.utils.ts +5 -0
- package/src/Shared/Libs/sendDiagnosticReport.ts +65 -7
- package/src/Shared/Libs/vizName.ts +158 -20
- package/src/Shared/Types/identificationInfo.ts +0 -5
- package/src/Translation/Resources/en.ts +28 -3
- package/src/Translation/Resources/tr.ts +28 -3
- package/src/version.ts +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { SafeAreaView, View, Text, StyleSheet,
|
|
4
|
+
import { SafeAreaView, View, Text, StyleSheet, Image, TouchableOpacity, ActivityIndicator, NativeModules } from 'react-native';
|
|
5
5
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import LottieView from 'lottie-react-native';
|
|
7
8
|
import { TrustchexCamera } from "../../Shared/Components/TrustchexCamera.js";
|
|
8
9
|
import NavigationManager from "../../Shared/Components/NavigationManager.js";
|
|
9
10
|
import AppContext from "../../Shared/Contexts/AppContext.js";
|
|
@@ -11,6 +12,50 @@ import StyledButton from "../../Shared/Components/StyledButton.js";
|
|
|
11
12
|
import { useStatusBarWhiteBackground } from "../../Shared/Libs/status-bar.utils.js";
|
|
12
13
|
import { useScreenTracking, trackVerificationStart, trackVerificationComplete } from "../../Shared/Libs/analytics.utils.js";
|
|
13
14
|
import { useKeepAwake } from "../../Shared/Libs/native-keep-awake.utils.js";
|
|
15
|
+
import { assessAddressProof } from "../../Shared/Libs/addressProofHeuristic.js";
|
|
16
|
+
import { recognizeVizBlocks } from "../../Shared/Libs/vizName.js";
|
|
17
|
+
import { MIN_BRIGHTNESS_THRESHOLD } from "../../Shared/Components/IdentityDocumentCamera.constants.js";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
const {
|
|
20
|
+
OpenCVModule
|
|
21
|
+
} = NativeModules;
|
|
22
|
+
|
|
23
|
+
// Quality thresholds for the captured proof photo. The darkness floor is shared
|
|
24
|
+
// with the document camera (MIN_BRIGHTNESS_THRESHOLD) so a tuning pass applies
|
|
25
|
+
// to both. The upper bound here is a FULL-FRAME mean-brightness cap for a
|
|
26
|
+
// washed-out capture — deliberately looser than the document camera's
|
|
27
|
+
// MAX_BRIGHTNESS_THRESHOLD (210), which is a REGION glare threshold, not a
|
|
28
|
+
// full-frame mean.
|
|
29
|
+
const MIN_BRIGHTNESS = MIN_BRIGHTNESS_THRESHOLD; // below → too dark
|
|
30
|
+
const MAX_BRIGHTNESS = 245; // full-frame mean above → washed out / glare
|
|
31
|
+
const BLUR_THRESHOLD = 50; // Laplacian variance below → too blurry
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Assess a captured proof photo. Brightness comes from the camera Frame (Y-plane
|
|
35
|
+
* luminance, computed natively per frame — no extra decode); only the blur check
|
|
36
|
+
* needs the image (native OpenCV Laplacian variance). Best-effort: returns ok
|
|
37
|
+
* when the native check is unavailable or throws, so an error never blocks.
|
|
38
|
+
*/
|
|
39
|
+
const assessImageQuality = async (base64Image, frameBrightness) => {
|
|
40
|
+
// Prefer the frame's native brightness; only fall back to a decode when the
|
|
41
|
+
// frame didn't carry one.
|
|
42
|
+
try {
|
|
43
|
+
let brightness = frameBrightness;
|
|
44
|
+
if (brightness == null && OpenCVModule) {
|
|
45
|
+
brightness = await OpenCVModule.getAverageBrightness(base64Image);
|
|
46
|
+
}
|
|
47
|
+
if (brightness != null) {
|
|
48
|
+
if (brightness < MIN_BRIGHTNESS) return 'dark';
|
|
49
|
+
if (brightness > MAX_BRIGHTNESS) return 'bright';
|
|
50
|
+
}
|
|
51
|
+
if (!OpenCVModule) return null;
|
|
52
|
+
const isBlurry = await OpenCVModule.checkBlurry(base64Image, BLUR_THRESHOLD);
|
|
53
|
+
if (isBlurry) return 'blurry';
|
|
54
|
+
return null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
14
59
|
|
|
15
60
|
/**
|
|
16
61
|
* ADDRESS_CAPTURE step (MASAK Tebliğ No. 32 Article 4/C(1)(d)). The user confirms
|
|
@@ -18,7 +63,6 @@ import { useKeepAwake } from "../../Shared/Libs/native-keep-awake.utils.js";
|
|
|
18
63
|
* captures a proof document (residence cert / utility bill ≤3 months / public-
|
|
19
64
|
* authority doc). The address is verified later by an operator (manual review).
|
|
20
65
|
*/
|
|
21
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
22
66
|
const AddressCaptureScreen = () => {
|
|
23
67
|
useKeepAwake();
|
|
24
68
|
useStatusBarWhiteBackground();
|
|
@@ -35,24 +79,103 @@ const AddressCaptureScreen = () => {
|
|
|
35
79
|
// collects a PROOF-DOCUMENT photo for the operator's manual review. We show
|
|
36
80
|
// the chip address read-only for context when available.
|
|
37
81
|
const chipAddress = appContext.identificationInfo.address?.addressText ?? appContext.lastDocumentRead?.document.address ?? '';
|
|
82
|
+
|
|
83
|
+
// Holder name parts (chip/MRZ), used to check the captured proof actually
|
|
84
|
+
// shows this person's name. Prefer the corrected display name when available.
|
|
85
|
+
const docName = appContext.lastDocumentRead?.name;
|
|
86
|
+
const nameParts = [docName?.displayFirst, docName?.displayLast, docName?.rawFirst, docName?.rawLast];
|
|
87
|
+
|
|
88
|
+
// Whether the proof is mandatory is governed by the step's own `required`
|
|
89
|
+
// flag (like every other step) — there is no separate config. A required
|
|
90
|
+
// step blocks Continue until a proof photo is captured; an optional step
|
|
91
|
+
// lets the user proceed (and skip) without one. The skip affordance is the
|
|
92
|
+
// NavigationManager's, rendered on the guide for optional steps.
|
|
93
|
+
|
|
38
94
|
const [proofImage, setProofImage] = useState();
|
|
39
95
|
const [isCameraOpen, setIsCameraOpen] = useState(false);
|
|
96
|
+
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
|
97
|
+
const [capturing, setCapturing] = useState(false);
|
|
98
|
+
const [qualityReason, setQualityReason] = useState(null);
|
|
99
|
+
// Soft content warning shown on the preview when the OCR text doesn't look
|
|
100
|
+
// like an address document with the holder's name. Never blocks Continue.
|
|
101
|
+
const [contentWarning, setContentWarning] = useState(null);
|
|
40
102
|
const captureNextFrame = useRef(false);
|
|
103
|
+
// Mirrors proofImage for the async OCR guard (a stale OCR result must only
|
|
104
|
+
// apply to the photo currently being previewed).
|
|
105
|
+
const proofImageRef = useRef(undefined);
|
|
106
|
+
proofImageRef.current = proofImage;
|
|
41
107
|
useEffect(() => {
|
|
42
108
|
trackVerificationStart('ADDRESS_CAPTURE');
|
|
43
109
|
}, []);
|
|
110
|
+
const openCamera = () => {
|
|
111
|
+
setQualityReason(null);
|
|
112
|
+
setContentWarning(null);
|
|
113
|
+
setIsPreviewOpen(false);
|
|
114
|
+
setIsCameraOpen(true);
|
|
115
|
+
};
|
|
44
116
|
const onFrameAvailable = frame => {
|
|
45
|
-
if (!captureNextFrame.current) return;
|
|
117
|
+
if (!captureNextFrame.current || capturing) return;
|
|
46
118
|
const b64 = frame.base64Image;
|
|
47
|
-
if (b64)
|
|
48
|
-
|
|
119
|
+
if (!b64) return;
|
|
120
|
+
captureNextFrame.current = false;
|
|
121
|
+
const frameBrightness = frame.brightness;
|
|
122
|
+
setCapturing(true);
|
|
123
|
+
// Gate acceptance on a quality check: a too-dark / too-bright / blurry
|
|
124
|
+
// photo keeps the camera open with a retake message instead of accepting it.
|
|
125
|
+
assessImageQuality(b64, frameBrightness).then(reason => {
|
|
126
|
+
setCapturing(false);
|
|
127
|
+
if (reason) {
|
|
128
|
+
setQualityReason(reason);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
setQualityReason(null);
|
|
132
|
+
setContentWarning(null);
|
|
49
133
|
setProofImage(b64);
|
|
134
|
+
// Show the captured photo immediately (Retake / Continue) — don't make
|
|
135
|
+
// the user wait on OCR before seeing their photo.
|
|
50
136
|
setIsCameraOpen(false);
|
|
51
|
-
|
|
137
|
+
setIsPreviewOpen(true);
|
|
138
|
+
// Soft content check runs in the background and fills in the warning. We
|
|
139
|
+
// OCR the FULL captured image (not frame.resultText, which the native
|
|
140
|
+
// camera crops to the MRZ ROI), so the name/address is actually in view.
|
|
141
|
+
// Best-effort: any failure → no warning, never blocks Continue.
|
|
142
|
+
assessProofContent(b64).then(warning => {
|
|
143
|
+
// Only apply if the user is still viewing THIS captured photo.
|
|
144
|
+
if (proofImageRef.current === b64) setContentWarning(warning);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
52
147
|
};
|
|
53
148
|
|
|
54
|
-
|
|
55
|
-
|
|
149
|
+
/// OCR the full captured image and run the address-proof heuristic. Returns
|
|
150
|
+
/// the warning key ('noName' | 'noAddress' | 'noText') or null when it looks
|
|
151
|
+
/// fine or the check can't run.
|
|
152
|
+
const assessProofContent = async b64 => {
|
|
153
|
+
try {
|
|
154
|
+
const blocks = await recognizeVizBlocks(b64);
|
|
155
|
+
const ocrText = blocks.map(b => b.text).join('\n');
|
|
156
|
+
const signals = assessAddressProof(ocrText, {
|
|
157
|
+
nameParts,
|
|
158
|
+
chipAddress
|
|
159
|
+
});
|
|
160
|
+
if (signals.ok) return null;
|
|
161
|
+
if (!signals.hasName && !signals.hasAddress) return 'noText';
|
|
162
|
+
return signals.hasName ? 'noAddress' : 'noName';
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const qualityMessage = () => {
|
|
168
|
+
switch (qualityReason) {
|
|
169
|
+
case 'dark':
|
|
170
|
+
return t('addressCaptureScreen.retakeTooDark');
|
|
171
|
+
case 'bright':
|
|
172
|
+
return t('addressCaptureScreen.retakeTooBright');
|
|
173
|
+
case 'blurry':
|
|
174
|
+
return t('addressCaptureScreen.retakeBlurry');
|
|
175
|
+
default:
|
|
176
|
+
return t('addressCaptureScreen.proofCameraHint');
|
|
177
|
+
}
|
|
178
|
+
};
|
|
56
179
|
const onContinue = () => {
|
|
57
180
|
// Attach the proof to the existing (chip-sourced) address without changing
|
|
58
181
|
// the address text or fromChip flag. If for some reason no address object
|
|
@@ -64,175 +187,367 @@ const AddressCaptureScreen = () => {
|
|
|
64
187
|
proofImage,
|
|
65
188
|
proofImageMimeType: proofImage ? 'image/jpeg' : undefined
|
|
66
189
|
};
|
|
190
|
+
setIsPreviewOpen(false);
|
|
67
191
|
trackVerificationComplete('ADDRESS_CAPTURE', true, 1);
|
|
68
192
|
navigationManagerRef.current?.navigateToNextStep();
|
|
69
193
|
};
|
|
70
|
-
|
|
194
|
+
|
|
195
|
+
// Post-capture preview — confirm the photo, then Continue or Retake.
|
|
196
|
+
if (isPreviewOpen && proofImage) {
|
|
71
197
|
return /*#__PURE__*/_jsxs(SafeAreaView, {
|
|
198
|
+
style: styles.previewContainer,
|
|
199
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
200
|
+
style: styles.previewImageWrap,
|
|
201
|
+
children: /*#__PURE__*/_jsx(Image, {
|
|
202
|
+
source: {
|
|
203
|
+
uri: `data:image/jpeg;base64,${proofImage}`
|
|
204
|
+
},
|
|
205
|
+
style: styles.previewImage,
|
|
206
|
+
resizeMode: "contain"
|
|
207
|
+
})
|
|
208
|
+
}), contentWarning ? /*#__PURE__*/_jsx(View, {
|
|
209
|
+
style: styles.previewWarning,
|
|
210
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
211
|
+
style: styles.previewWarningText,
|
|
212
|
+
children: contentWarning === 'noName' ? t('addressCaptureScreen.warnNoName') : contentWarning === 'noAddress' ? t('addressCaptureScreen.warnNoAddress') : t('addressCaptureScreen.warnNoText')
|
|
213
|
+
})
|
|
214
|
+
}) : /*#__PURE__*/_jsx(Text, {
|
|
215
|
+
style: styles.previewHint,
|
|
216
|
+
children: t('addressCaptureScreen.previewHint')
|
|
217
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
218
|
+
style: [styles.previewFooter, {
|
|
219
|
+
paddingBottom: insets.bottom + 12
|
|
220
|
+
}],
|
|
221
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
222
|
+
style: styles.previewActions,
|
|
223
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
224
|
+
style: styles.previewActionItem,
|
|
225
|
+
children: /*#__PURE__*/_jsx(StyledButton, {
|
|
226
|
+
mode: "outlined",
|
|
227
|
+
onPress: openCamera,
|
|
228
|
+
children: t('addressCaptureScreen.retakeProof')
|
|
229
|
+
})
|
|
230
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
231
|
+
style: styles.previewActionItem,
|
|
232
|
+
children: /*#__PURE__*/_jsx(StyledButton, {
|
|
233
|
+
onPress: onContinue,
|
|
234
|
+
children: t('general.continue')
|
|
235
|
+
})
|
|
236
|
+
})]
|
|
237
|
+
}), /*#__PURE__*/_jsx(NavigationManager, {
|
|
238
|
+
ref: navigationManagerRef
|
|
239
|
+
})]
|
|
240
|
+
})]
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (isCameraOpen) {
|
|
244
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
72
245
|
style: styles.cameraContainer,
|
|
73
246
|
children: [/*#__PURE__*/_jsx(TrustchexCamera, {
|
|
74
|
-
style:
|
|
247
|
+
style: StyleSheet.absoluteFill,
|
|
248
|
+
cameraType: "back"
|
|
249
|
+
// The native camera emits onFrameAvailable when frame processing is on
|
|
250
|
+
// AND either a detector is enabled or includeBase64 is set. We only
|
|
251
|
+
// want the JPEG (no per-frame OCR), so includeBase64 alone keeps frames
|
|
252
|
+
// flowing; the proof photo is OCR'd once, on the still, at capture.
|
|
253
|
+
,
|
|
254
|
+
enableFrameProcessing: true,
|
|
75
255
|
includeBase64: true,
|
|
256
|
+
targetFps: 8,
|
|
76
257
|
onFrameAvailable: e => onFrameAvailable(e.nativeEvent.frame)
|
|
258
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
259
|
+
style: [styles.frameGuide, {
|
|
260
|
+
top: insets.top + 64,
|
|
261
|
+
bottom: insets.bottom + 210
|
|
262
|
+
}],
|
|
263
|
+
pointerEvents: "none"
|
|
264
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
265
|
+
style: [styles.cancelButton, {
|
|
266
|
+
top: insets.top + 12
|
|
267
|
+
}],
|
|
268
|
+
accessibilityLabel: t('general.cancel'),
|
|
269
|
+
onPress: () => {
|
|
270
|
+
captureNextFrame.current = false;
|
|
271
|
+
// Back to the guide (the only non-camera/non-preview screen).
|
|
272
|
+
setIsCameraOpen(false);
|
|
273
|
+
},
|
|
274
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
275
|
+
style: styles.cancelGlyph,
|
|
276
|
+
children: "\u2715"
|
|
277
|
+
})
|
|
77
278
|
}), /*#__PURE__*/_jsxs(View, {
|
|
78
|
-
style: [styles.
|
|
79
|
-
paddingBottom: insets.bottom +
|
|
279
|
+
style: [styles.cameraBottom, {
|
|
280
|
+
paddingBottom: insets.bottom + 28
|
|
80
281
|
}],
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}), /*#__PURE__*/_jsx(StyledButton, {
|
|
85
|
-
onPress: () => captureNextFrame.current = true,
|
|
86
|
-
children: t('addressCaptureScreen.capture')
|
|
87
|
-
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
88
|
-
onPress: () => setIsCameraOpen(false),
|
|
282
|
+
pointerEvents: "box-none",
|
|
283
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
284
|
+
style: [styles.cameraHintPill, qualityReason ? styles.cameraHintPillError : null],
|
|
89
285
|
children: /*#__PURE__*/_jsx(Text, {
|
|
90
|
-
style: styles.
|
|
91
|
-
children:
|
|
286
|
+
style: styles.cameraHint,
|
|
287
|
+
children: qualityMessage()
|
|
288
|
+
})
|
|
289
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
290
|
+
style: styles.shutter,
|
|
291
|
+
disabled: capturing,
|
|
292
|
+
accessibilityLabel: t('addressCaptureScreen.capture'),
|
|
293
|
+
onPress: () => {
|
|
294
|
+
captureNextFrame.current = true;
|
|
295
|
+
},
|
|
296
|
+
children: capturing ? /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
297
|
+
color: "#111"
|
|
298
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
299
|
+
style: styles.shutterInner
|
|
92
300
|
})
|
|
93
301
|
})]
|
|
94
302
|
})]
|
|
95
303
|
});
|
|
96
304
|
}
|
|
305
|
+
|
|
306
|
+
// No interim form: the guide is the only non-camera/non-preview screen.
|
|
307
|
+
// Reached only if the camera was cancelled — fall back to the guide.
|
|
97
308
|
return /*#__PURE__*/_jsxs(SafeAreaView, {
|
|
98
|
-
style: styles.
|
|
99
|
-
children: [/*#__PURE__*/_jsxs(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
children: t('addressCaptureScreen.title')
|
|
309
|
+
style: styles.guideContainer,
|
|
310
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
311
|
+
style: styles.guideContent,
|
|
312
|
+
children: [/*#__PURE__*/_jsx(LottieView, {
|
|
313
|
+
source: require('../../Shared/Animations/address-scan.json'),
|
|
314
|
+
style: styles.guideAnimation,
|
|
315
|
+
loop: true,
|
|
316
|
+
autoPlay: true
|
|
107
317
|
}), /*#__PURE__*/_jsx(Text, {
|
|
108
|
-
style: styles.
|
|
109
|
-
children: t('addressCaptureScreen.
|
|
110
|
-
}),
|
|
318
|
+
style: styles.guideHeader,
|
|
319
|
+
children: t('addressCaptureScreen.guideHeader')
|
|
320
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
321
|
+
style: styles.guidePoints,
|
|
322
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
323
|
+
style: styles.guideText,
|
|
324
|
+
children: t('addressCaptureScreen.guideText')
|
|
325
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
326
|
+
style: styles.guidePoint,
|
|
327
|
+
children: ["\u2022 ", t('addressCaptureScreen.guidePointWholeDocument')]
|
|
328
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
329
|
+
style: styles.guidePoint,
|
|
330
|
+
children: ["\u2022 ", t('addressCaptureScreen.guidePointLighting')]
|
|
331
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
332
|
+
style: styles.guidePoint,
|
|
333
|
+
children: ["\u2022 ", t('addressCaptureScreen.guidePointSteady')]
|
|
334
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
335
|
+
style: styles.guidePoint,
|
|
336
|
+
children: ["\u2022 ", t('addressCaptureScreen.guidePointDocuments')]
|
|
337
|
+
})]
|
|
338
|
+
}), chipAddress ? /*#__PURE__*/_jsxs(View, {
|
|
339
|
+
style: styles.chipAddressBox,
|
|
111
340
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
112
|
-
style: styles.
|
|
341
|
+
style: styles.chipAddressLabel,
|
|
113
342
|
children: t('addressCaptureScreen.addressLabel')
|
|
114
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
115
|
-
style: styles.readonlyAddress,
|
|
116
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
117
|
-
style: styles.readonlyAddressText,
|
|
118
|
-
children: chipAddress
|
|
119
|
-
})
|
|
120
343
|
}), /*#__PURE__*/_jsx(Text, {
|
|
121
|
-
style: styles.
|
|
344
|
+
style: styles.chipAddressText,
|
|
345
|
+
children: chipAddress
|
|
346
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
347
|
+
style: styles.chipAddressHint,
|
|
122
348
|
children: t('addressCaptureScreen.prefilledFromChip')
|
|
123
349
|
})]
|
|
124
|
-
}) : null, /*#__PURE__*/_jsx(
|
|
125
|
-
style: styles.
|
|
126
|
-
children:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}), proofImage ? /*#__PURE__*/_jsx(Image, {
|
|
131
|
-
source: {
|
|
132
|
-
uri: `data:image/jpeg;base64,${proofImage}`
|
|
133
|
-
},
|
|
134
|
-
style: styles.proofPreview,
|
|
135
|
-
resizeMode: "cover"
|
|
136
|
-
}) : null, /*#__PURE__*/_jsx(StyledButton, {
|
|
137
|
-
onPress: () => setIsCameraOpen(true),
|
|
138
|
-
children: proofImage ? t('addressCaptureScreen.retakeProof') : t('addressCaptureScreen.captureProof')
|
|
350
|
+
}) : null, /*#__PURE__*/_jsx(View, {
|
|
351
|
+
style: styles.guideButton,
|
|
352
|
+
children: /*#__PURE__*/_jsx(StyledButton, {
|
|
353
|
+
onPress: openCamera,
|
|
354
|
+
children: t('general.letsGo')
|
|
355
|
+
})
|
|
139
356
|
})]
|
|
140
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
141
|
-
style: [styles.footer, {
|
|
142
|
-
paddingBottom: insets.bottom + 12
|
|
143
|
-
}],
|
|
144
|
-
children: /*#__PURE__*/_jsx(StyledButton, {
|
|
145
|
-
disabled: !canContinue,
|
|
146
|
-
onPress: onContinue,
|
|
147
|
-
children: t('general.continue')
|
|
148
|
-
})
|
|
149
357
|
}), /*#__PURE__*/_jsx(NavigationManager, {
|
|
150
358
|
ref: navigationManagerRef
|
|
151
359
|
})]
|
|
152
360
|
});
|
|
153
361
|
};
|
|
154
362
|
const styles = StyleSheet.create({
|
|
155
|
-
|
|
363
|
+
// Start guide — centered vertically + horizontally, like the other guides.
|
|
364
|
+
guideContainer: {
|
|
156
365
|
flex: 1,
|
|
157
366
|
backgroundColor: '#FFFFFF'
|
|
158
367
|
},
|
|
159
|
-
|
|
160
|
-
|
|
368
|
+
// Inner wrapper carries the horizontal padding + centering, so it always
|
|
369
|
+
// applies regardless of the SafeAreaView's own inset handling on iOS.
|
|
370
|
+
guideContent: {
|
|
371
|
+
flex: 1,
|
|
372
|
+
paddingHorizontal: 24,
|
|
373
|
+
justifyContent: 'center',
|
|
374
|
+
alignItems: 'center'
|
|
161
375
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
color: '#111',
|
|
376
|
+
guideAnimation: {
|
|
377
|
+
width: 220,
|
|
378
|
+
height: 220,
|
|
166
379
|
marginBottom: 8
|
|
167
380
|
},
|
|
168
|
-
|
|
169
|
-
fontSize:
|
|
170
|
-
|
|
171
|
-
|
|
381
|
+
guideHeader: {
|
|
382
|
+
fontSize: 18,
|
|
383
|
+
fontWeight: '700',
|
|
384
|
+
color: '#000',
|
|
385
|
+
textAlign: 'center',
|
|
386
|
+
marginBottom: 10
|
|
172
387
|
},
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
388
|
+
// Subtitle + bullets: left-aligned block, full width.
|
|
389
|
+
guidePoints: {
|
|
390
|
+
alignSelf: 'stretch',
|
|
391
|
+
marginTop: 4
|
|
392
|
+
},
|
|
393
|
+
guideText: {
|
|
394
|
+
fontSize: 14,
|
|
395
|
+
color: '#000',
|
|
396
|
+
textAlign: 'left',
|
|
176
397
|
marginBottom: 12
|
|
177
398
|
},
|
|
178
|
-
|
|
399
|
+
guidePoint: {
|
|
179
400
|
fontSize: 14,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
401
|
+
color: '#000',
|
|
402
|
+
textAlign: 'left',
|
|
403
|
+
lineHeight: 20,
|
|
404
|
+
marginTop: 10
|
|
184
405
|
},
|
|
185
|
-
|
|
406
|
+
chipAddressBox: {
|
|
407
|
+
alignSelf: 'stretch',
|
|
408
|
+
marginTop: 16,
|
|
409
|
+
padding: 12,
|
|
186
410
|
borderWidth: 1,
|
|
187
411
|
borderColor: '#E0E0E0',
|
|
188
412
|
backgroundColor: '#F7F7F7',
|
|
189
|
-
borderRadius: 8
|
|
190
|
-
padding: 12,
|
|
191
|
-
minHeight: 56
|
|
413
|
+
borderRadius: 8
|
|
192
414
|
},
|
|
193
|
-
|
|
415
|
+
chipAddressLabel: {
|
|
416
|
+
fontSize: 13,
|
|
417
|
+
fontWeight: '600',
|
|
418
|
+
color: '#222',
|
|
419
|
+
marginBottom: 4
|
|
420
|
+
},
|
|
421
|
+
chipAddressText: {
|
|
194
422
|
fontSize: 15,
|
|
195
423
|
color: '#111'
|
|
196
424
|
},
|
|
197
|
-
|
|
198
|
-
fontSize:
|
|
199
|
-
color: '#
|
|
200
|
-
|
|
425
|
+
chipAddressHint: {
|
|
426
|
+
fontSize: 12,
|
|
427
|
+
color: '#2E7D32',
|
|
428
|
+
marginTop: 4
|
|
201
429
|
},
|
|
202
|
-
|
|
430
|
+
guideButton: {
|
|
431
|
+
alignSelf: 'stretch',
|
|
432
|
+
marginTop: 24
|
|
433
|
+
},
|
|
434
|
+
// Post-capture preview — white screen, matching the rest of the app.
|
|
435
|
+
previewContainer: {
|
|
436
|
+
flex: 1,
|
|
437
|
+
backgroundColor: '#FFFFFF'
|
|
438
|
+
},
|
|
439
|
+
previewImageWrap: {
|
|
440
|
+
flex: 1,
|
|
441
|
+
padding: 16
|
|
442
|
+
},
|
|
443
|
+
previewImage: {
|
|
444
|
+
flex: 1,
|
|
203
445
|
width: '100%',
|
|
204
|
-
|
|
205
|
-
|
|
446
|
+
borderRadius: 12
|
|
447
|
+
},
|
|
448
|
+
previewHint: {
|
|
449
|
+
color: '#555',
|
|
450
|
+
fontSize: 13,
|
|
451
|
+
textAlign: 'center',
|
|
452
|
+
paddingHorizontal: 20,
|
|
453
|
+
marginBottom: 12
|
|
454
|
+
},
|
|
455
|
+
previewWarning: {
|
|
456
|
+
marginHorizontal: 16,
|
|
206
457
|
marginBottom: 12,
|
|
207
|
-
|
|
458
|
+
paddingHorizontal: 14,
|
|
459
|
+
paddingVertical: 10,
|
|
460
|
+
borderRadius: 8,
|
|
461
|
+
backgroundColor: '#FFF4E5',
|
|
462
|
+
borderWidth: 1,
|
|
463
|
+
borderColor: '#F0B27A'
|
|
208
464
|
},
|
|
209
|
-
|
|
465
|
+
previewWarningText: {
|
|
466
|
+
color: '#92400E',
|
|
467
|
+
fontSize: 13,
|
|
468
|
+
textAlign: 'center'
|
|
469
|
+
},
|
|
470
|
+
previewFooter: {
|
|
210
471
|
paddingHorizontal: 20,
|
|
211
472
|
paddingTop: 8,
|
|
212
|
-
backgroundColor: '#FFFFFF'
|
|
473
|
+
backgroundColor: '#FFFFFF',
|
|
474
|
+
gap: 4
|
|
475
|
+
},
|
|
476
|
+
previewActions: {
|
|
477
|
+
flexDirection: 'row',
|
|
478
|
+
gap: 12
|
|
479
|
+
},
|
|
480
|
+
previewActionItem: {
|
|
481
|
+
flex: 1
|
|
213
482
|
},
|
|
214
483
|
cameraContainer: {
|
|
215
484
|
flex: 1,
|
|
216
485
|
backgroundColor: '#000'
|
|
217
486
|
},
|
|
218
|
-
|
|
219
|
-
|
|
487
|
+
frameGuide: {
|
|
488
|
+
position: 'absolute',
|
|
489
|
+
left: 20,
|
|
490
|
+
right: 20,
|
|
491
|
+
borderWidth: 2,
|
|
492
|
+
borderColor: 'rgba(255,255,255,0.7)',
|
|
493
|
+
borderRadius: 12,
|
|
494
|
+
zIndex: 1
|
|
220
495
|
},
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
496
|
+
cancelButton: {
|
|
497
|
+
position: 'absolute',
|
|
498
|
+
left: 16,
|
|
499
|
+
width: 44,
|
|
500
|
+
height: 44,
|
|
501
|
+
borderRadius: 22,
|
|
502
|
+
backgroundColor: 'rgba(0,0,0,0.45)',
|
|
503
|
+
alignItems: 'center',
|
|
504
|
+
justifyContent: 'center',
|
|
505
|
+
zIndex: 2
|
|
225
506
|
},
|
|
226
|
-
|
|
507
|
+
cancelGlyph: {
|
|
227
508
|
color: '#FFF',
|
|
228
|
-
|
|
229
|
-
|
|
509
|
+
fontSize: 22,
|
|
510
|
+
fontWeight: '600',
|
|
511
|
+
lineHeight: 24
|
|
230
512
|
},
|
|
231
|
-
|
|
513
|
+
cameraBottom: {
|
|
514
|
+
position: 'absolute',
|
|
515
|
+
left: 0,
|
|
516
|
+
right: 0,
|
|
517
|
+
bottom: 0,
|
|
518
|
+
alignItems: 'center'
|
|
519
|
+
},
|
|
520
|
+
cameraHintPill: {
|
|
521
|
+
maxWidth: '88%',
|
|
522
|
+
paddingHorizontal: 16,
|
|
523
|
+
paddingVertical: 10,
|
|
524
|
+
borderRadius: 10,
|
|
525
|
+
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
526
|
+
marginBottom: 20
|
|
527
|
+
},
|
|
528
|
+
cameraHintPillError: {
|
|
529
|
+
backgroundColor: 'rgba(176,0,32,0.85)'
|
|
530
|
+
},
|
|
531
|
+
cameraHint: {
|
|
232
532
|
color: '#FFF',
|
|
233
533
|
textAlign: 'center',
|
|
234
|
-
|
|
235
|
-
|
|
534
|
+
fontSize: 14
|
|
535
|
+
},
|
|
536
|
+
shutter: {
|
|
537
|
+
width: 74,
|
|
538
|
+
height: 74,
|
|
539
|
+
borderRadius: 37,
|
|
540
|
+
borderWidth: 4,
|
|
541
|
+
borderColor: 'rgba(255,255,255,0.85)',
|
|
542
|
+
backgroundColor: 'rgba(255,255,255,0.25)',
|
|
543
|
+
alignItems: 'center',
|
|
544
|
+
justifyContent: 'center'
|
|
545
|
+
},
|
|
546
|
+
shutterInner: {
|
|
547
|
+
width: 56,
|
|
548
|
+
height: 56,
|
|
549
|
+
borderRadius: 28,
|
|
550
|
+
backgroundColor: '#FFF'
|
|
236
551
|
}
|
|
237
552
|
});
|
|
238
553
|
export default AddressCaptureScreen;
|
|
@@ -88,11 +88,16 @@ const IdentityDocumentEIDScanningScreen = () => {
|
|
|
88
88
|
passportData?.mrzFields ?? idBackSideData?.mrzFields ?? idFrontSideData?.mrzFields ?? appContext.identificationInfo.scannedDocument?.mrzFields,
|
|
89
89
|
vizImage:
|
|
90
90
|
// Full data-page image from the EID step's camera scan. EIDScanner
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
91
|
+
// OCRs it to recover the diacritic printed name when the chip's
|
|
92
|
+
// DG11 is absent or one-sided (e.g. Turkish passports). The live
|
|
93
|
+
// camera only OCRs a cropped MRZ region, so the printed name must
|
|
94
|
+
// be recovered from the full image here.
|
|
95
95
|
passportData?.image ?? idFrontSideData?.image ?? idBackSideData?.image,
|
|
96
|
+
vizImages:
|
|
97
|
+
// All available captures — EIDScanner OCRs each and votes on the
|
|
98
|
+
// recovered name so a single noisy pass can't corrupt it. De-dup
|
|
99
|
+
// happens in EIDScanner, so overlap with `vizImage` is harmless.
|
|
100
|
+
[passportData?.image, idFrontSideData?.image, idBackSideData?.image],
|
|
96
101
|
onScanSuccess: (mrzFields, faceImage, mimeType) => {
|
|
97
102
|
if (mrzFields && faceImage && mimeType) {
|
|
98
103
|
if (!appContext.identificationInfo.scannedDocument) {
|