@trustchex/react-native-sdk 1.478.7 → 1.483.4

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 (112) hide show
  1. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +57 -3
  2. package/ios/Camera/TrustchexCameraView.swift +20 -15
  3. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +0 -3
  4. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +21 -0
  5. package/lib/module/Screens/Dynamic/VideoCallScreen.js +1 -1
  6. package/lib/module/Screens/Static/ResultScreen.js +75 -16
  7. package/lib/module/Shared/Components/DiagnosticReportButton.js +64 -0
  8. package/lib/module/Shared/Components/EIDScanner.js +52 -19
  9. package/lib/module/Shared/Components/IdentityDocumentCamera.js +132 -8
  10. package/lib/module/Shared/Components/NavigationManager.js +7 -9
  11. package/lib/module/Shared/Contexts/AppContext.js +3 -0
  12. package/lib/module/Shared/EIDReader/eidReader.js +167 -30
  13. package/lib/module/Shared/EIDReader/lds/icao/dg11File.js +77 -0
  14. package/lib/module/Shared/Libs/MRZ_KNOWN_ISSUES.md +7 -0
  15. package/lib/module/Shared/Libs/diagnosticReport.js +133 -0
  16. package/lib/module/Shared/Libs/diagnostics.js +171 -0
  17. package/lib/module/Shared/Libs/documentDataNormalizer.js +104 -0
  18. package/lib/module/Shared/Libs/mrz.utils.js +0 -5
  19. package/lib/module/Shared/Libs/mrzFrameAggregator.js +32 -5
  20. package/lib/module/Shared/Libs/mrzTransliteration.js +80 -0
  21. package/lib/module/Shared/Libs/native-device-info.utils.js +67 -0
  22. package/lib/module/Shared/Libs/sendDiagnosticReport.js +130 -0
  23. package/lib/module/Shared/Services/DataUploadService.js +6 -3
  24. package/lib/module/Shared/Types/documentData.js +1 -0
  25. package/lib/module/Shared/Types/documentReadResult.js +3 -0
  26. package/lib/module/Translation/Resources/en.js +5 -0
  27. package/lib/module/Translation/Resources/tr.js +5 -0
  28. package/lib/module/Trustchex.js +31 -4
  29. package/lib/module/version.js +1 -1
  30. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  31. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  32. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -1
  33. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  34. package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts +20 -0
  35. package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts.map +1 -0
  36. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  37. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  38. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +10 -0
  39. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -1
  40. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  41. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +10 -0
  42. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
  43. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +5 -0
  44. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  45. package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts +2 -0
  46. package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
  47. package/lib/typescript/src/Shared/EIDReader/lds/icao/dg11File.d.ts +35 -0
  48. package/lib/typescript/src/Shared/EIDReader/lds/icao/dg11File.d.ts.map +1 -0
  49. package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts +48 -0
  50. package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts.map +1 -0
  51. package/lib/typescript/src/Shared/Libs/diagnostics.d.ts +145 -0
  52. package/lib/typescript/src/Shared/Libs/diagnostics.d.ts.map +1 -0
  53. package/lib/typescript/src/Shared/Libs/documentDataNormalizer.d.ts +12 -0
  54. package/lib/typescript/src/Shared/Libs/documentDataNormalizer.d.ts.map +1 -0
  55. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  56. package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts +33 -0
  57. package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts.map +1 -1
  58. package/lib/typescript/src/Shared/Libs/mrzTransliteration.d.ts +3 -0
  59. package/lib/typescript/src/Shared/Libs/mrzTransliteration.d.ts.map +1 -0
  60. package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts +33 -0
  61. package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts.map +1 -1
  62. package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts +30 -0
  63. package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts.map +1 -0
  64. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +3 -2
  65. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -1
  66. package/lib/typescript/src/Shared/Types/documentData.d.ts +44 -0
  67. package/lib/typescript/src/Shared/Types/documentData.d.ts.map +1 -0
  68. package/lib/typescript/src/Shared/Types/documentReadResult.d.ts +44 -0
  69. package/lib/typescript/src/Shared/Types/documentReadResult.d.ts.map +1 -0
  70. package/lib/typescript/src/Shared/Types/mrzFields.d.ts +1 -1
  71. package/lib/typescript/src/Shared/Types/mrzFields.d.ts.map +1 -1
  72. package/lib/typescript/src/Translation/Resources/en.d.ts +5 -0
  73. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  74. package/lib/typescript/src/Translation/Resources/tr.d.ts +5 -0
  75. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  76. package/lib/typescript/src/Trustchex.d.ts +9 -0
  77. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  78. package/lib/typescript/src/index.d.ts +2 -0
  79. package/lib/typescript/src/index.d.ts.map +1 -1
  80. package/lib/typescript/src/version.d.ts +1 -1
  81. package/package.json +5 -1
  82. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +0 -4
  83. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +23 -0
  84. package/src/Screens/Dynamic/VideoCallScreen.tsx +5 -1
  85. package/src/Screens/Static/ResultScreen.tsx +85 -16
  86. package/src/Shared/Components/DiagnosticReportButton.tsx +77 -0
  87. package/src/Shared/Components/EIDScanner.tsx +67 -18
  88. package/src/Shared/Components/IdentityDocumentCamera.tsx +145 -7
  89. package/src/Shared/Components/IdentityDocumentCamera.types.ts +6 -0
  90. package/src/Shared/Components/NavigationManager.tsx +7 -7
  91. package/src/Shared/Components/TrustchexCamera.tsx +6 -0
  92. package/src/Shared/Contexts/AppContext.ts +8 -0
  93. package/src/Shared/EIDReader/eidReader.ts +193 -47
  94. package/src/Shared/EIDReader/lds/icao/dg11File.ts +94 -0
  95. package/src/Shared/Libs/MRZ_KNOWN_ISSUES.md +7 -0
  96. package/src/Shared/Libs/diagnosticReport.ts +206 -0
  97. package/src/Shared/Libs/diagnostics.ts +251 -0
  98. package/src/Shared/Libs/documentDataNormalizer.ts +108 -0
  99. package/src/Shared/Libs/mrz.utils.ts +0 -5
  100. package/src/Shared/Libs/mrzFrameAggregator.ts +61 -3
  101. package/src/Shared/Libs/mrzTransliteration.ts +131 -0
  102. package/src/Shared/Libs/native-device-info.utils.ts +116 -0
  103. package/src/Shared/Libs/sendDiagnosticReport.ts +165 -0
  104. package/src/Shared/Services/DataUploadService.ts +9 -3
  105. package/src/Shared/Types/documentData.ts +52 -0
  106. package/src/Shared/Types/documentReadResult.ts +47 -0
  107. package/src/Shared/Types/mrzFields.ts +0 -5
  108. package/src/Translation/Resources/en.ts +6 -0
  109. package/src/Translation/Resources/tr.ts +6 -0
  110. package/src/Trustchex.tsx +48 -3
  111. package/src/index.tsx +7 -0
  112. package/src/version.ts +1 -1
@@ -40,6 +40,7 @@ import com.google.mlkit.vision.text.TextRecognition
40
40
  import com.google.mlkit.vision.text.latin.TextRecognizerOptions
41
41
  import java.io.ByteArrayOutputStream
42
42
  import kotlin.math.max
43
+ import kotlin.math.min
43
44
  import java.io.File
44
45
  import java.util.concurrent.ExecutorService
45
46
  import java.util.concurrent.Executors
@@ -524,8 +525,35 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
524
525
  val result = textTask.result
525
526
  frameData.putString("resultText", result.text)
526
527
 
528
+ // Per-symbol confidence aggregation (ML Kit BUNDLED library
529
+ // only — Text.Symbol.confidence is 0 on the unbundled
530
+ // play-services variant). We walk Block→Line→Element→Symbol
531
+ // and accumulate confidence over MRZ-shaped characters
532
+ // ([A-Z0-9<]) only, so surrounding non-MRZ text doesn't skew
533
+ // the weight. The mean and min are surfaced to JS as a
534
+ // per-frame quality signal that down-weights low-confidence
535
+ // frames in the multi-frame vote.
536
+ var confSum = 0f
537
+ var confCount = 0
538
+ var confMin = 1f
539
+
527
540
  val blocksArray = Arguments.createArray()
528
541
  for (block in result.textBlocks) {
542
+ for (line in block.lines) {
543
+ for (element in line.elements) {
544
+ for (symbol in element.symbols) {
545
+ val ch = symbol.text.firstOrNull() ?: continue
546
+ val upper = ch.uppercaseChar()
547
+ val isMrzChar = upper in 'A'..'Z' ||
548
+ upper in '0'..'9' || upper == '<'
549
+ if (!isMrzChar) continue
550
+ val c = symbol.confidence ?: continue
551
+ confSum += c
552
+ confCount++
553
+ if (c < confMin) confMin = c
554
+ }
555
+ }
556
+ }
529
557
  val blockMap = Arguments.createMap()
530
558
  blockMap.putString("text", block.text)
531
559
  val bb = block.boundingBox
@@ -552,6 +580,17 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
552
580
  blocksArray.pushMap(blockMap)
553
581
  }
554
582
  frameData.putArray("textBlocks", blocksArray)
583
+
584
+ // Frame-level MRZ-character confidence (mean + min over MRZ
585
+ // symbols). Omitted when ML Kit returned no usable confidence
586
+ // (count == 0) so JS can fall back to band-height weighting.
587
+ if (confCount > 0) {
588
+ val confMap = Arguments.createMap()
589
+ confMap.putDouble("mean", (confSum / confCount).toDouble())
590
+ confMap.putDouble("min", confMin.toDouble())
591
+ confMap.putInt("count", confCount)
592
+ frameData.putMap("textConfidence", confMap)
593
+ }
555
594
  } else {
556
595
  frameData.putString("resultText", "")
557
596
  frameData.putArray("textBlocks", Arguments.createArray())
@@ -1085,11 +1124,26 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
1085
1124
  full.recycle()
1086
1125
 
1087
1126
  // Upscale so a ~30–44-char MRZ line clears the per-character pixel
1088
- // minimum. Cap the long edge to keep OCR latency bounded.
1127
+ // minimum — but NO FURTHER. ML Kit gains no accuracy above ~24px per
1128
+ // character (Google's guidance), and upscaling past that only adds
1129
+ // interpolation artifacts and latency. So target ~24px/char and take
1130
+ // the SMALLER of (a) the scale needed to reach that target and (b) the
1131
+ // long-edge/latency cap. When the band is already sharp enough, scale
1132
+ // stays at 1 (skip upscaling entirely).
1089
1133
  val maxLongEdge = 2200
1090
1134
  val longEdge = max(cropW, cropH)
1091
- val rawScale = if (longEdge > 0) (maxLongEdge.toFloat() / longEdge) else 1f
1092
- val scale = rawScale.coerceIn(1f, 2.5f)
1135
+ val capScale = if (longEdge > 0) {
1136
+ (maxLongEdge.toFloat() / longEdge).coerceIn(1f, 2.5f)
1137
+ } else 1f
1138
+ // Estimate current glyph height: the MRZ band is up to ~3 text lines
1139
+ // (TD1) within the crop; ~1.4 accounts for line spacing vs glyph height.
1140
+ val mrzBandLines = 3f
1141
+ val targetGlyphPx = 24f
1142
+ val estGlyphPx = (cropH / mrzBandLines) / 1.4f
1143
+ val targetScale = if (estGlyphPx > 0f) {
1144
+ (targetGlyphPx / estGlyphPx).coerceAtLeast(1f)
1145
+ } else capScale
1146
+ val scale = min(targetScale, capScale)
1093
1147
  val outW = (cropW * scale).toInt()
1094
1148
  val outH = (cropH * scale).toInt()
1095
1149
  val scaled = if (scale != 1f) {
@@ -972,19 +972,14 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
972
972
  blockY = Int(bb.origin.y / roi.scale) + roi.offsetY
973
973
  blockWidth = Int(bb.width / roi.scale)
974
974
  blockHeight = Int(bb.height / roi.scale)
975
- } else if isBufferLandscape {
976
- // Rotate from landscape (1920x1080) to portrait (1080x1920)
977
- // When rotating 90° clockwise (.right):
978
- // new_x = old_y
979
- // new_y = landscape_width - old_x - width
980
- // new_width = old_height
981
- // new_height = old_width
982
- blockX = Int(bb.origin.y)
983
- blockY = pixelWidth - Int(bb.origin.x) - Int(bb.width)
984
- blockWidth = Int(bb.height)
985
- blockHeight = Int(bb.width)
986
975
  } else {
987
- // Already portrait, use directly
976
+ // Fallback when the ROI couldn't be built: OCR ran on
977
+ // `visionImage`, which is created from `orientedImage` — already
978
+ // rotated to portrait (.right applied above when the buffer was
979
+ // landscape) with VisionImage.orientation = .up. ML Kit therefore
980
+ // returns coordinates in PORTRAIT space already, so they are used
981
+ // directly. (A prior landscape→portrait rotation here was a
982
+ // double-rotation bug, since the image was no longer landscape.)
988
983
  blockX = Int(bb.origin.x)
989
984
  blockY = Int(bb.origin.y)
990
985
  blockWidth = Int(bb.width)
@@ -1091,10 +1086,20 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
1091
1086
  // Move the crop origin to (0,0) so downstream extents are simple.
1092
1087
  img = img.transformed(by: CGAffineTransform(translationX: -cropRect.origin.x, y: -cropRect.origin.y))
1093
1088
 
1094
- // Upscale (≤2.5×, long edge capped at 2200) for more pixels per character.
1089
+ // Upscale for more pixels per character, but only up to ~24px/char — ML Kit
1090
+ // gains no accuracy above that (Google's guidance), and scaling further only
1091
+ // adds interpolation artifacts and latency. Take the SMALLER of the scale
1092
+ // needed to reach the target glyph height and the long-edge/latency cap; a
1093
+ // band that's already sharp enough stays at scale 1 (no upscaling).
1095
1094
  let longEdge = max(cropW, cropH)
1096
- let rawScale = longEdge > 0 ? (2200.0 / longEdge) : 1.0
1097
- let scale = min(max(rawScale, 1.0), 2.5)
1095
+ let capScale = longEdge > 0 ? min(max(2200.0 / longEdge, 1.0), 2.5) : 1.0
1096
+ // MRZ band is up to ~3 text lines (TD1) within the crop; ~1.4 accounts for
1097
+ // line spacing vs glyph height.
1098
+ let mrzBandLines: CGFloat = 3.0
1099
+ let targetGlyphPx: CGFloat = 24.0
1100
+ let estGlyphPx = (cropH / mrzBandLines) / 1.4
1101
+ let targetScale = estGlyphPx > 0 ? max(targetGlyphPx / estGlyphPx, 1.0) : capScale
1102
+ let scale = min(targetScale, capScale)
1098
1103
  if scale != 1.0 {
1099
1104
  img = img.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
1100
1105
  }
@@ -70,9 +70,6 @@ const IdentityDocumentEIDScanningScreen = () => {
70
70
  setAllowedDocumentTypes(appContext.currentWorkflowStep?.data?.allowedDocumentTypes ?? null);
71
71
  setAllowedCountries(appContext.currentWorkflowStep?.data?.allowedCountries ?? null);
72
72
  }, [appContext.currentWorkflowStep]);
73
- if (!appContext.currentWorkflowStep) {
74
- return null;
75
- }
76
73
  return /*#__PURE__*/_jsxs(SafeAreaView, {
77
74
  style: styles.safeAreaContainer,
78
75
  children: [documentNumber && dateOfBirth && dateOfExpiry ? /*#__PURE__*/_jsx(View, {
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
3
  import React, { useContext, useEffect, useState } from 'react';
4
+ import { buildDocumentName } from "../../Shared/Libs/mrzTransliteration.js";
5
+ import { normalizeFromMRZFields } from "../../Shared/Libs/documentDataNormalizer.js";
4
6
  import { Alert, SafeAreaView, StyleSheet, View } from 'react-native';
5
7
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
8
  import IdentityDocumentCamera from "../../Shared/Components/IdentityDocumentCamera.js";
@@ -78,6 +80,25 @@ const IdentityDocumentScanningScreen = () => {
78
80
 
79
81
  // Track successful document scan completion as funnel step
80
82
  trackFunnelStep('Document Scan Completed', 2, 5, 'contract_acceptance', true);
83
+ {
84
+ const documentData = normalizeFromMRZFields(mrzFields);
85
+ const faceImg = idFrontSideData?.faceImage || passportData?.faceImage;
86
+ const builtName = buildDocumentName(documentData.firstName, documentData.lastName, documentData.issuingCountry);
87
+ const documentReadResult = {
88
+ document: {
89
+ ...documentData,
90
+ firstName: builtName.displayFirst,
91
+ lastName: builtName.displayLast
92
+ },
93
+ name: builtName,
94
+ face: faceImg ? {
95
+ data: faceImg,
96
+ mimeType: 'image/jpeg'
97
+ } : undefined
98
+ };
99
+ appContext.setLastDocumentRead?.(documentReadResult);
100
+ appContext.onDocumentRead?.(documentReadResult);
101
+ }
81
102
  setTimeout(() => navigationManagerRef.current?.navigateToNextStep(), 1000);
82
103
  }
83
104
  }, [idFrontSideData, idBackSideData, passportData, appContext.identificationInfo, allowedCountries, allowedDocumentTypes, t, appContext]);
@@ -126,7 +126,7 @@ const VideoCallScreen = ({
126
126
  try {
127
127
  const uploadService = new DataUploadService(baseUrl);
128
128
  dataUploadServiceRef.current = uploadService;
129
- const uploaded = await uploadService.uploadCollectedData(identificationInfo);
129
+ const uploaded = await uploadService.uploadCollectedData(identificationInfo, undefined, appContext.lastDocumentRead?.name);
130
130
  if (!uploaded) {
131
131
  console.warn('[VideoCallScreen] Upload service returned false');
132
132
  if (mounted) {
@@ -18,7 +18,33 @@ import { Video as VideoCompressor } from 'react-native-compressor';
18
18
  import { encryptWithAes, getSessionKey } from "../../Shared/Libs/crypto.utils.js";
19
19
  import Video from 'react-native-video';
20
20
  import StyledButton from "../../Shared/Components/StyledButton.js";
21
+ import DiagnosticReportButton from "../../Shared/Components/DiagnosticReportButton.js";
21
22
  import NativeDeviceInfo from "../../Shared/Libs/native-device-info.utils.js";
23
+
24
+ /**
25
+ * Collect the captured document/face images (base64) as attachments for a
26
+ * diagnostic report. On an ID card the BACK image is the MRZ side; on a passport
27
+ * the front carries the MRZ. The NFC chip face arrives as `faceImage` with its
28
+ * own mime type, which may be png.
29
+ */
30
+ const collectDiagnosticImages = doc => {
31
+ if (!doc) return [];
32
+ const out = [];
33
+ const push = (name, b64, mime = 'jpg') => {
34
+ if (b64 && b64 !== '') out.push({
35
+ name,
36
+ base64: b64,
37
+ mime
38
+ });
39
+ };
40
+ push('mrz-side', doc.backImage);
41
+ push('front', doc.frontImage);
42
+ const faceMime = doc.faceImageMimeType === 'image/png' ? 'png' : 'jpg';
43
+ push('face', doc.faceImage, faceMime);
44
+ push('secondary-face', doc.secondaryFaceImage);
45
+ push('hologram', doc.hologramImage);
46
+ return out;
47
+ };
22
48
  import { trackError, trackFunnelStep, useScreenTracking } from "../../Shared/Libs/analytics.utils.js";
23
49
  import { analyticsService } from "../../Shared/Services/AnalyticsService.js";
24
50
  import { AnalyticsEventName, AnalyticsEventCategory } from "../../Shared/Types/analytics.types.js";
@@ -152,14 +178,15 @@ const ResultScreen = () => {
152
178
  if (!scannedDocument.mrzFields) {
153
179
  throw new Error('MRZ fields not found');
154
180
  }
155
-
156
- // Backend payload must contain raw MRZ data only.
157
- // Human-friendly formatting is strictly for UI rendering paths.
158
181
  const rawMrzFields = scannedDocument.mrzFields;
182
+ const docName = appContext.lastDocumentRead?.name;
159
183
  const identificationDocument = {
160
184
  type: rawMrzFields.documentCode,
161
185
  name: rawMrzFields.firstName,
162
186
  surname: rawMrzFields.lastName,
187
+ displayName: docName?.source === 'dg11' ? docName.displayFirst : undefined,
188
+ displaySurname: docName?.source === 'dg11' ? docName.displayLast : undefined,
189
+ nameSource: docName?.source === 'dg11' ? docName.source : undefined,
163
190
  gender: getGenderEnumType(rawMrzFields.sex),
164
191
  number: rawMrzFields.documentNumber,
165
192
  country: rawMrzFields.issuingState,
@@ -178,7 +205,7 @@ const ResultScreen = () => {
178
205
  encryptedData,
179
206
  nonce
180
207
  });
181
- }, [apiUrl, getGenderEnumType]);
208
+ }, [apiUrl, appContext.lastDocumentRead, getGenderEnumType]);
182
209
  const uploadIdentificationMedia = useCallback(async (identificationId, scannedIdentityDocument, livenessDetection, skipIfAlreadyUploaded) => {
183
210
  // Skip media upload if already uploaded during video call
184
211
  if (skipIfAlreadyUploaded) {
@@ -507,18 +534,30 @@ const ResultScreen = () => {
507
534
  children: [/*#__PURE__*/_jsxs(Text, {
508
535
  style: styles.mrzInfoLabel,
509
536
  children: [t('eidScannerScreen.name'), ":"]
510
- }), /*#__PURE__*/_jsx(Text, {
511
- style: styles.mrzInfoText,
512
- children: appContext.identificationInfo.scannedDocument.mrzFields?.firstName
537
+ }), /*#__PURE__*/_jsxs(View, {
538
+ style: styles.mrzInfoValueGroup,
539
+ children: [/*#__PURE__*/_jsx(Text, {
540
+ style: styles.mrzInfoText,
541
+ children: appContext.lastDocumentRead?.name.source === 'dg11' ? appContext.lastDocumentRead.name.displayFirst : appContext.identificationInfo.scannedDocument.mrzFields?.firstName
542
+ }), appContext.lastDocumentRead?.name.source === 'dg11' && /*#__PURE__*/_jsx(Text, {
543
+ style: styles.mrzInfoSubText,
544
+ children: appContext.identificationInfo.scannedDocument.mrzFields?.firstName
545
+ })]
513
546
  })]
514
547
  }), /*#__PURE__*/_jsxs(View, {
515
548
  style: styles.mrzInfoItem,
516
549
  children: [/*#__PURE__*/_jsxs(Text, {
517
550
  style: styles.mrzInfoLabel,
518
551
  children: [t('eidScannerScreen.surname'), ":"]
519
- }), /*#__PURE__*/_jsx(Text, {
520
- style: styles.mrzInfoText,
521
- children: appContext.identificationInfo.scannedDocument.mrzFields?.lastName
552
+ }), /*#__PURE__*/_jsxs(View, {
553
+ style: styles.mrzInfoValueGroup,
554
+ children: [/*#__PURE__*/_jsx(Text, {
555
+ style: styles.mrzInfoText,
556
+ children: appContext.lastDocumentRead?.name.source === 'dg11' ? appContext.lastDocumentRead.name.displayLast : appContext.identificationInfo.scannedDocument.mrzFields?.lastName
557
+ }), appContext.lastDocumentRead?.name.source === 'dg11' && /*#__PURE__*/_jsx(Text, {
558
+ style: styles.mrzInfoSubText,
559
+ children: appContext.identificationInfo.scannedDocument.mrzFields?.lastName
560
+ })]
522
561
  })]
523
562
  }), /*#__PURE__*/_jsxs(View, {
524
563
  style: styles.mrzInfoItem,
@@ -711,18 +750,28 @@ const ResultScreen = () => {
711
750
  }, `${consentVideo.videoPath}-${index}`))]
712
751
  })]
713
752
  })
714
- }), /*#__PURE__*/_jsx(View, {
715
- style: {
716
- paddingBottom: insets.bottom
717
- },
718
- children: /*#__PURE__*/_jsx(StyledButton, {
753
+ }), /*#__PURE__*/_jsxs(View, {
754
+ style: [styles.footer, {
755
+ paddingBottom: insets.bottom + 20
756
+ }],
757
+ children: [/*#__PURE__*/_jsx(StyledButton, {
719
758
  mode: "contained",
720
759
  onPress: () => {
721
760
  appContext.onCompleted?.();
722
761
  navigationManagerRef.current?.reset();
723
762
  },
724
763
  children: t('resultScreen.demoStartOver')
725
- })
764
+ }), /*#__PURE__*/_jsx(DiagnosticReportButton, {
765
+ sessionId: appContext.identificationInfo?.sessionId,
766
+ scan: {
767
+ documentType: appContext.identificationInfo?.scannedDocument?.documentType,
768
+ dataSource: appContext.identificationInfo?.scannedDocument?.dataSource,
769
+ mrzText: appContext.identificationInfo?.scannedDocument?.mrzText,
770
+ mrzFields: appContext.identificationInfo?.scannedDocument?.mrzFields,
771
+ barcodeValue: appContext.identificationInfo?.scannedDocument?.barcodeValue
772
+ },
773
+ images: collectDiagnosticImages(appContext.identificationInfo?.scannedDocument)
774
+ })]
726
775
  })]
727
776
  }) : /*#__PURE__*/_jsxs(_Fragment, {
728
777
  children: [/*#__PURE__*/_jsx(LottieView, {
@@ -846,6 +895,16 @@ const styles = StyleSheet.create({
846
895
  fontSize: 10,
847
896
  fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace'
848
897
  },
898
+ mrzInfoValueGroup: {
899
+ flexDirection: 'column',
900
+ alignItems: 'flex-end'
901
+ },
902
+ mrzInfoSubText: {
903
+ color: '#aaa',
904
+ fontSize: 9,
905
+ fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace',
906
+ marginTop: 1
907
+ },
849
908
  mrzTextContainer: {
850
909
  padding: 12,
851
910
  backgroundColor: '#F3F4F6',
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ import React, { useState } from 'react';
4
+ import { Alert } from 'react-native';
5
+ import { useTranslation } from 'react-i18next';
6
+ import StyledButton from "./StyledButton.js";
7
+ import { sendDiagnosticReport, isDiagnosticSharingAvailable } from "../Libs/sendDiagnosticReport.js";
8
+ import { SDK_VERSION } from "../../version.js";
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ /**
11
+ * "Report a scanning issue" button for the result screen.
12
+ *
13
+ * Shows a brief notice describing what will be shared (the draft itself is the
14
+ * user's review/consent step), then opens the OS share sheet — the user picks
15
+ * email / WhatsApp / Drive / etc. and sends the diagnostic bundle to support.
16
+ * Self-contained so the result screen only needs to render it with the scan
17
+ * data + captured images.
18
+ */
19
+ const DiagnosticReportButton = ({
20
+ scan,
21
+ sessionId,
22
+ images
23
+ }) => {
24
+ const {
25
+ t
26
+ } = useTranslation();
27
+ const [sending, setSending] = useState(false);
28
+
29
+ // `react-native-share` is optional — hide the button entirely when the host
30
+ // app hasn't installed it, so apps that don't want diagnostics see no UI.
31
+ if (!isDiagnosticSharingAvailable()) {
32
+ return null;
33
+ }
34
+ const onPress = () => {
35
+ if (sending) return;
36
+ // Brief notice — the share draft is the actual review/consent step.
37
+ Alert.alert(t('diagnosticReport.noticeTitle'), t('diagnosticReport.noticeBody'), [{
38
+ text: t('diagnosticReport.cancel'),
39
+ style: 'cancel'
40
+ }, {
41
+ text: t('diagnosticReport.continue'),
42
+ onPress: async () => {
43
+ setSending(true);
44
+ try {
45
+ await sendDiagnosticReport({
46
+ scan,
47
+ sdkVersion: SDK_VERSION,
48
+ sessionId,
49
+ images
50
+ });
51
+ } finally {
52
+ setSending(false);
53
+ }
54
+ }
55
+ }]);
56
+ };
57
+ return /*#__PURE__*/_jsx(StyledButton, {
58
+ mode: "outlined",
59
+ onPress: onPress,
60
+ disabled: sending,
61
+ children: t('diagnosticReport.button')
62
+ });
63
+ };
64
+ export default DiagnosticReportButton;
@@ -9,6 +9,8 @@ import PermissionManager from "../Libs/permissions.utils.js";
9
9
  import { debugError } from "../Libs/debug.utils.js";
10
10
  import { eidReader } from "../EIDReader/eidReader.js";
11
11
  import NativeProgressBar from "./NativeProgressBar.js";
12
+ import { buildDocumentName } from "../Libs/mrzTransliteration.js";
13
+ import { normalizeFromMRZInfo } from "../Libs/documentDataNormalizer.js";
12
14
  import { useTranslation } from 'react-i18next';
13
15
  import AppContext from "../Contexts/AppContext.js";
14
16
  import StyledButton from "./StyledButton.js";
@@ -34,6 +36,7 @@ const EIDScanner = ({
34
36
  const [documentFaceImage, setDocumentFaceImage] = React.useState();
35
37
  const [documentMRZInfo, setDocumentMRZInfo] = React.useState();
36
38
  const [documentFaceImageMimeType, setDocumentFaceImageMimeType] = React.useState();
39
+ const [documentName, setDocumentName] = React.useState();
37
40
  const [progress, setProgress] = React.useState(0);
38
41
  const [progressStage, setProgressStage] = React.useState('');
39
42
 
@@ -99,6 +102,20 @@ const EIDScanner = ({
99
102
  }, [i18n.language]);
100
103
  const [voiceGuidanceMessage, setVoiceGuidanceMessage] = useState();
101
104
  const [attemptNumber, setAttemptNumber] = useState(0);
105
+ const getFieldsFromMRZ = useCallback(mrz => {
106
+ return {
107
+ documentCode: mrz.getDocumentCode(),
108
+ personalNumber: mrz.getPersonalNumber() || mrz.getOptionalData1(),
109
+ documentNumber: mrz.getDocumentNumber(),
110
+ firstName: mrz.getSecondaryIdentifier(),
111
+ lastName: mrz.getPrimaryIdentifier(),
112
+ birthDate: mrz.getDateOfBirth(),
113
+ expirationDate: mrz.getDateOfExpiry(),
114
+ issuingState: mrz.getIssuingState(),
115
+ sex: mrz.getGender().getStrCode(),
116
+ nationality: mrz.getNationality()
117
+ };
118
+ }, []);
102
119
  const readNFC = useCallback(async () => {
103
120
  const startTime = Date.now();
104
121
  const currentAttempt = attemptNumber + 1;
@@ -160,6 +177,25 @@ const EIDScanner = ({
160
177
  setDocumentMRZInfo(passportData.mrz);
161
178
  setDocumentFaceImageMimeType(passportData.mimeType);
162
179
  setIsScanned(true);
180
+ {
181
+ const documentData = normalizeFromMRZInfo(passportData.mrz);
182
+ const builtName = buildDocumentName(documentData.firstName, documentData.lastName, documentData.issuingCountry, passportData.dg11FirstName, passportData.dg11LastName);
183
+ setDocumentName(builtName);
184
+ const documentReadResult = {
185
+ document: {
186
+ ...documentData,
187
+ firstName: builtName.displayFirst,
188
+ lastName: builtName.displayLast
189
+ },
190
+ name: builtName,
191
+ face: {
192
+ data: passportData.faceImage,
193
+ mimeType: passportData.mimeType
194
+ }
195
+ };
196
+ appContext.setLastDocumentRead?.(documentReadResult);
197
+ appContext.onDocumentRead?.(documentReadResult);
198
+ }
163
199
  await trackEIDScanComplete(docType, scanDuration, currentAttempt).catch(() => {});
164
200
  }
165
201
  } else {
@@ -196,21 +232,7 @@ const EIDScanner = ({
196
232
  setProgressStage('');
197
233
  setHasGuideShown(false);
198
234
  }
199
- }, [documentNumber, dateOfBirth, dateOfExpiry, documentType, hasNfc, isEnabled, attemptNumber, t]);
200
- const getFieldsFromMRZ = useCallback(mrz => {
201
- return {
202
- documentCode: mrz.getDocumentCode(),
203
- personalNumber: mrz.getPersonalNumber() || mrz.getOptionalData1(),
204
- documentNumber: mrz.getDocumentNumber(),
205
- firstName: mrz.getSecondaryIdentifier(),
206
- lastName: mrz.getPrimaryIdentifier(),
207
- birthDate: mrz.getDateOfBirth(),
208
- expirationDate: mrz.getDateOfExpiry(),
209
- issuingState: mrz.getIssuingState(),
210
- sex: mrz.getGender().getStrCode(),
211
- nationality: mrz.getNationality()
212
- };
213
- }, []);
235
+ }, [documentNumber, dateOfBirth, dateOfExpiry, documentType, hasNfc, isEnabled, attemptNumber, t, getFieldsFromMRZ, appContext]);
214
236
  useEffect(() => {
215
237
  const checkIsSupported = async () => {
216
238
  const deviceIsSupported = await NFCManager.isSupported();
@@ -367,10 +389,12 @@ const EIDScanner = ({
367
389
  style: styles.idCardFields,
368
390
  children: [/*#__PURE__*/_jsx(IDCardField, {
369
391
  label: t('eidScannerScreen.surname'),
370
- value: documentMRZInfo.getPrimaryIdentifier()
392
+ value: documentName?.displayLast ?? documentMRZInfo.getPrimaryIdentifier(),
393
+ subValue: documentName?.source === 'dg11' ? documentMRZInfo.getPrimaryIdentifier() : undefined
371
394
  }), /*#__PURE__*/_jsx(IDCardField, {
372
395
  label: t('eidScannerScreen.name'),
373
- value: documentMRZInfo.getSecondaryIdentifier()
396
+ value: documentName?.displayFirst ?? documentMRZInfo.getSecondaryIdentifier(),
397
+ subValue: documentName?.source === 'dg11' ? documentMRZInfo.getSecondaryIdentifier() : undefined
374
398
  }), /*#__PURE__*/_jsx(IDCardField, {
375
399
  label: t('eidScannerScreen.documentNumber'),
376
400
  value: documentMRZInfo.getDocumentNumber()
@@ -627,6 +651,11 @@ const styles = StyleSheet.create({
627
651
  flexShrink: 1,
628
652
  flexWrap: 'wrap'
629
653
  },
654
+ idCardFieldSubValue: {
655
+ fontSize: 9,
656
+ color: '#aaa',
657
+ marginTop: 1
658
+ },
630
659
  guide: {
631
660
  flex: 1,
632
661
  display: 'flex',
@@ -658,7 +687,8 @@ const styles = StyleSheet.create({
658
687
  });
659
688
  const IDCardField = ({
660
689
  label,
661
- value
690
+ value,
691
+ subValue
662
692
  }) => /*#__PURE__*/_jsxs(View, {
663
693
  style: styles.idCardFieldItem,
664
694
  children: [/*#__PURE__*/_jsx(Text, {
@@ -668,6 +698,9 @@ const IDCardField = ({
668
698
  }), /*#__PURE__*/_jsx(Text, {
669
699
  style: styles.idCardFieldValue,
670
700
  children: value || '—'
671
- })]
701
+ }), subValue ? /*#__PURE__*/_jsx(Text, {
702
+ style: styles.idCardFieldSubValue,
703
+ children: subValue
704
+ }) : null]
672
705
  });
673
706
  export default EIDScanner;