@trustchex/react-native-sdk 1.381.0 → 1.464.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 (204) hide show
  1. package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKModule.kt +2 -8
  2. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +60 -13
  3. package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
  4. package/ios/Camera/TrustchexCameraView.swift +10 -13
  5. package/ios/MLKit/MLKitModule.swift +1 -1
  6. package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
  7. package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
  8. package/lib/module/Screens/Debug/NFCScanTestScreen.js +635 -0
  9. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -32
  10. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +22 -4
  11. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
  12. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +126 -27
  13. package/lib/module/Screens/Dynamic/VerbalConsentScreen.js +1079 -0
  14. package/lib/module/Screens/Dynamic/VideoCallScreen.js +678 -0
  15. package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
  16. package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
  17. package/lib/module/Screens/Static/ResultScreen.js +154 -34
  18. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +59 -51
  19. package/lib/module/Shared/Animations/recording.json +1 -0
  20. package/lib/module/Shared/Animations/video-call.json +1 -0
  21. package/lib/module/Shared/Components/DebugNavigationPanel.js +231 -67
  22. package/lib/module/Shared/Components/EIDScanner.js +213 -112
  23. package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +5 -3
  24. package/lib/module/Shared/Components/IdentityDocumentCamera.js +77 -39
  25. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +13 -4
  26. package/lib/module/Shared/Components/NavigationManager.js +39 -19
  27. package/lib/module/Shared/Contexts/AppContext.js +1 -0
  28. package/lib/module/Shared/EIDReader/aesSecureMessagingWrapper.js +51 -0
  29. package/lib/module/Shared/EIDReader/apduLevelPACECapable.js +3 -0
  30. package/lib/module/Shared/EIDReader/bacKey.js +16 -2
  31. package/lib/module/Shared/EIDReader/eidReader.js +354 -13
  32. package/lib/module/Shared/EIDReader/eidService.js +25 -1
  33. package/lib/module/Shared/EIDReader/nfcManagerCardService.js +4 -7
  34. package/lib/module/Shared/EIDReader/paceInfo.js +85 -0
  35. package/lib/module/Shared/EIDReader/paceKeySpec.js +51 -0
  36. package/lib/module/Shared/EIDReader/protocol/paceAPDUSender.js +100 -0
  37. package/lib/module/Shared/EIDReader/protocol/paceProtocol.js +655 -0
  38. package/lib/module/Shared/EIDReader/protocol/paceResult.js +37 -0
  39. package/lib/module/Shared/EIDReader/secureMessagingWrapper.js +27 -4
  40. package/lib/module/Shared/EIDReader/smartcards/commandAPDU.js +2 -1
  41. package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +1 -1
  42. package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +6 -3
  43. package/lib/module/Shared/EIDReader/utils/aesCrypto.utils.js +189 -0
  44. package/lib/module/Shared/Libs/SignalingClient.js +128 -0
  45. package/lib/module/Shared/Libs/analytics.utils.js +8 -0
  46. package/lib/module/Shared/Libs/contains.js +1 -40
  47. package/lib/module/Shared/Libs/country-display.utils.js +34 -0
  48. package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
  49. package/lib/module/Shared/Libs/demo.utils.js +8 -0
  50. package/lib/module/Shared/Libs/http-client.js +9 -0
  51. package/lib/module/Shared/Libs/mrz.utils.js +3 -2
  52. package/lib/module/Shared/Libs/promise.utils.js +16 -2
  53. package/lib/module/Shared/Libs/status-bar.utils.js +23 -0
  54. package/lib/module/Shared/Services/DataUploadService.js +294 -0
  55. package/lib/module/Shared/Services/VideoSessionService.js +156 -0
  56. package/lib/module/Shared/Services/WebRTCService.js +510 -0
  57. package/lib/module/Shared/Types/analytics.types.js +4 -0
  58. package/lib/module/Translation/Resources/en.js +61 -2
  59. package/lib/module/Translation/Resources/tr.js +61 -2
  60. package/lib/module/Trustchex.js +64 -20
  61. package/lib/module/version.js +1 -1
  62. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
  63. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
  64. package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
  65. package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts +3 -0
  66. package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts.map +1 -0
  67. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  68. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  69. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  70. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  71. package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts +3 -0
  72. package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts.map +1 -0
  73. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
  74. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
  75. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
  76. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  77. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  78. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  79. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  80. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  81. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  82. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +1 -1
  83. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -1
  84. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +5 -0
  85. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
  86. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  87. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
  88. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  89. package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts +18 -0
  90. package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts.map +1 -0
  91. package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts +23 -0
  92. package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts.map +1 -0
  93. package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts +6 -0
  94. package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts.map +1 -1
  95. package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
  96. package/lib/typescript/src/Shared/EIDReader/eidService.d.ts +9 -0
  97. package/lib/typescript/src/Shared/EIDReader/eidService.d.ts.map +1 -1
  98. package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts.map +1 -1
  99. package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts +50 -0
  100. package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts.map +1 -0
  101. package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts +30 -0
  102. package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts.map +1 -0
  103. package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts +17 -0
  104. package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts.map +1 -0
  105. package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts +105 -0
  106. package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts.map +1 -0
  107. package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts +24 -0
  108. package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts.map +1 -0
  109. package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts +15 -0
  110. package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts.map +1 -1
  111. package/lib/typescript/src/Shared/EIDReader/smartcards/commandAPDU.d.ts.map +1 -1
  112. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
  113. package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts +39 -0
  114. package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts.map +1 -0
  115. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
  116. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
  117. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
  118. package/lib/typescript/src/Shared/Libs/contains.d.ts +0 -7
  119. package/lib/typescript/src/Shared/Libs/contains.d.ts.map +1 -1
  120. package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts +2 -0
  121. package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts.map +1 -0
  122. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  123. package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
  124. package/lib/typescript/src/Shared/Libs/http-client.d.ts +1 -1
  125. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  126. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  127. package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
  128. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
  129. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
  130. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
  131. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
  132. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
  133. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
  134. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
  135. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
  136. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +4 -0
  137. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
  138. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +13 -1
  139. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  140. package/lib/typescript/src/Translation/Resources/en.d.ts +60 -1
  141. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  142. package/lib/typescript/src/Translation/Resources/tr.d.ts +60 -1
  143. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  144. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  145. package/lib/typescript/src/version.d.ts +1 -1
  146. package/package.json +35 -5
  147. package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
  148. package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
  149. package/src/Screens/Debug/NFCScanTestScreen.tsx +692 -0
  150. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +58 -35
  151. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +27 -4
  152. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
  153. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +156 -27
  154. package/src/Screens/Dynamic/VerbalConsentScreen.tsx +1401 -0
  155. package/src/Screens/Dynamic/VideoCallScreen.tsx +766 -0
  156. package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
  157. package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
  158. package/src/Screens/Static/ResultScreen.tsx +235 -48
  159. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +67 -72
  160. package/src/Shared/Animations/recording.json +1 -0
  161. package/src/Shared/Animations/video-call.json +1 -0
  162. package/src/Shared/Components/DebugNavigationPanel.tsx +252 -51
  163. package/src/Shared/Components/EIDScanner.tsx +223 -116
  164. package/src/Shared/Components/IdentityDocumentCamera.flows.ts +7 -4
  165. package/src/Shared/Components/IdentityDocumentCamera.tsx +224 -188
  166. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +13 -4
  167. package/src/Shared/Components/NavigationManager.tsx +41 -19
  168. package/src/Shared/Contexts/AppContext.ts +2 -0
  169. package/src/Shared/EIDReader/aesSecureMessagingWrapper.ts +69 -0
  170. package/src/Shared/EIDReader/apduLevelPACECapable.ts +34 -0
  171. package/src/Shared/EIDReader/bacKey.ts +24 -8
  172. package/src/Shared/EIDReader/eidReader.ts +398 -12
  173. package/src/Shared/EIDReader/eidService.ts +49 -1
  174. package/src/Shared/EIDReader/nfcManagerCardService.ts +4 -6
  175. package/src/Shared/EIDReader/paceInfo.ts +159 -0
  176. package/src/Shared/EIDReader/paceKeySpec.ts +56 -0
  177. package/src/Shared/EIDReader/protocol/paceAPDUSender.ts +163 -0
  178. package/src/Shared/EIDReader/protocol/paceProtocol.ts +946 -0
  179. package/src/Shared/EIDReader/protocol/paceResult.ts +62 -0
  180. package/src/Shared/EIDReader/secureMessagingWrapper.ts +28 -10
  181. package/src/Shared/EIDReader/smartcards/commandAPDU.ts +2 -1
  182. package/src/Shared/EIDReader/tlv/tlv.helpers.ts +1 -1
  183. package/src/Shared/EIDReader/tlv/tlv.utils.ts +8 -5
  184. package/src/Shared/EIDReader/utils/aesCrypto.utils.ts +217 -0
  185. package/src/Shared/Libs/SignalingClient.ts +189 -0
  186. package/src/Shared/Libs/analytics.utils.ts +8 -0
  187. package/src/Shared/Libs/contains.ts +0 -53
  188. package/src/Shared/Libs/country-display.utils.ts +55 -0
  189. package/src/Shared/Libs/crypto.utils.ts +2 -2
  190. package/src/Shared/Libs/deeplink.utils.ts +12 -1
  191. package/src/Shared/Libs/demo.utils.ts +10 -0
  192. package/src/Shared/Libs/http-client.ts +19 -1
  193. package/src/Shared/Libs/mrz.utils.ts +3 -2
  194. package/src/Shared/Libs/promise.utils.ts +16 -2
  195. package/src/Shared/Libs/status-bar.utils.ts +21 -0
  196. package/src/Shared/Services/DataUploadService.ts +395 -0
  197. package/src/Shared/Services/VideoSessionService.ts +190 -0
  198. package/src/Shared/Services/WebRTCService.ts +636 -0
  199. package/src/Shared/Types/analytics.types.ts +4 -0
  200. package/src/Shared/Types/identificationInfo.ts +16 -1
  201. package/src/Translation/Resources/en.ts +88 -3
  202. package/src/Translation/Resources/tr.ts +89 -3
  203. package/src/Trustchex.tsx +65 -19
  204. package/src/version.ts +1 -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')}
@@ -176,17 +199,17 @@ const ContractAcceptanceScreen = () => {
176
199
  };
177
200
 
178
201
  const styles = StyleSheet.create({
179
- safeAreaContainer: {
202
+ container: {
180
203
  flex: 1,
181
204
  backgroundColor: 'white',
182
205
  },
183
- container: {
206
+ webViewContainer: {
184
207
  flex: 1,
208
+ width: '100%',
185
209
  },
186
210
  webView: {
187
211
  flex: 1,
188
212
  width: '100%',
189
- height: '100%',
190
213
  resizeMode: 'contain',
191
214
  },
192
215
  footer: {
@@ -17,8 +17,10 @@ import {
17
17
  trackVerificationStart,
18
18
  trackVerificationComplete,
19
19
  } from '../../Shared/Libs/analytics.utils';
20
+ import { useKeepAwake } from '../../Shared/Libs/native-keep-awake.utils';
20
21
 
21
22
  const IdentityDocumentEIDScanningScreen = () => {
23
+ useKeepAwake();
22
24
  const appContext = useContext(AppContext);
23
25
  const navigationManagerRef = React.useRef<NavigationManagerRef>(null);
24
26
  const insets = useSafeAreaInsets();
@@ -28,10 +30,27 @@ const IdentityDocumentEIDScanningScreen = () => {
28
30
  useState<DocumentScannedData | null>(null);
29
31
  const [passportData, setPassportData] =
30
32
  useState<DocumentScannedData | null>();
31
- const [documentNumber, setDocumentNumber] = useState<string>();
32
- const [dateOfBirth, setDateOfBirth] = useState<string>();
33
- const [dateOfExpiry, setDateOfExpiry] = useState<string>();
34
- const [documentType, setDocumentType] = useState<string>();
33
+ const [documentNumber, setDocumentNumber] = useState<string | undefined>(
34
+ () => {
35
+ const mrzFields =
36
+ appContext.identificationInfo.scannedDocument?.mrzFields;
37
+ return mrzFields?.documentNumber ?? undefined;
38
+ }
39
+ );
40
+ const [dateOfBirth, setDateOfBirth] = useState<string | undefined>(() => {
41
+ const mrzFields = appContext.identificationInfo.scannedDocument?.mrzFields;
42
+ return mrzFields?.birthDate ?? undefined;
43
+ });
44
+ const [dateOfExpiry, setDateOfExpiry] = useState<string | undefined>(() => {
45
+ const mrzFields = appContext.identificationInfo.scannedDocument?.mrzFields;
46
+ return mrzFields?.expirationDate ?? undefined;
47
+ });
48
+ const [documentType, setDocumentType] = useState<string | undefined>(() => {
49
+ const dc =
50
+ appContext.identificationInfo.scannedDocument?.mrzFields?.documentCode;
51
+ if (!dc) return undefined;
52
+ return dc === 'I' ? 'ID' : dc === 'P' ? 'PASSPORT' : 'UNKNOWN';
53
+ });
35
54
  const [isNFCSupported, setIsNFCSupported] = useState<boolean>(false);
36
55
  const { t } = useTranslation();
37
56
  const [allowedDocumentTypes, setAllowedDocumentTypes] = useState<
@@ -77,6 +96,10 @@ const IdentityDocumentEIDScanningScreen = () => {
77
96
  );
78
97
  }, [appContext.currentWorkflowStep]);
79
98
 
99
+ if (!appContext.currentWorkflowStep) {
100
+ return null;
101
+ }
102
+
80
103
  return (
81
104
  <SafeAreaView style={styles.safeAreaContainer}>
82
105
  {documentNumber && dateOfBirth && dateOfExpiry ? (
@@ -17,8 +17,10 @@ import {
17
17
  trackVerificationStart,
18
18
  trackVerificationComplete,
19
19
  } from '../../Shared/Libs/analytics.utils';
20
+ import { useKeepAwake } from '../../Shared/Libs/native-keep-awake.utils';
20
21
 
21
22
  const IdentityDocumentScanningScreen = () => {
23
+ useKeepAwake();
22
24
  const [idFrontSideData, setIDFrontSideData] =
23
25
  useState<DocumentScannedData | null>(null);
24
26
  const [idBackSideData, setIDBackSideData] =
@@ -145,6 +147,10 @@ const IdentityDocumentScanningScreen = () => {
145
147
  );
146
148
  }, [appContext.currentWorkflowStep]);
147
149
 
150
+ if (!appContext.currentWorkflowStep) {
151
+ return null;
152
+ }
153
+
148
154
  return (
149
155
  <SafeAreaView style={styles.container}>
150
156
  <IdentityDocumentCamera
@@ -8,7 +8,17 @@ import React, {
8
8
  useCallback,
9
9
  useRef,
10
10
  } from 'react';
11
- import { StyleSheet, Text, View, Dimensions, Vibration } from 'react-native';
11
+ import {
12
+ Animated,
13
+ Easing,
14
+ StyleSheet,
15
+ Text,
16
+ View,
17
+ Dimensions,
18
+ Platform,
19
+ Vibration,
20
+ StatusBar,
21
+ } from 'react-native';
12
22
  import {
13
23
  useSafeAreaInsets,
14
24
  SafeAreaView,
@@ -20,21 +30,23 @@ import NavigationManager, {
20
30
  type NavigationManagerRef,
21
31
  } from '../../Shared/Components/NavigationManager';
22
32
  import AppContext from '../../Shared/Contexts/AppContext';
23
- import { contains, type Rect } from '../../Shared/Libs/contains';
33
+ import { type Rect } from '../../Shared/Libs/contains';
24
34
  import { useTranslation } from 'react-i18next';
25
35
  import StyledButton from '../../Shared/Components/StyledButton';
26
36
  import LottieView from 'lottie-react-native';
27
37
  import { speak, resetLastMessage } from '../../Shared/Libs/tts.utils';
38
+ import { useStatusBarWhiteBackground } from '../../Shared/Libs/status-bar.utils';
28
39
  import {
29
40
  trackFunnelStep,
30
41
  useScreenTracking,
31
42
  trackVerificationStart,
32
43
  trackVerificationComplete,
33
44
  } from '../../Shared/Libs/analytics.utils';
45
+ import { useKeepAwake } from '../../Shared/Libs/native-keep-awake.utils';
34
46
 
35
47
  const { width: windowWidth, height: windowHeight } = Dimensions.get('window');
36
48
 
37
- const PREVIEW_SIZE = windowWidth * 0.8;
49
+ const PREVIEW_SIZE = windowWidth * 0.95;
38
50
  const PREVIEW_RECT: Rect = {
39
51
  minX: (windowWidth - PREVIEW_SIZE) / 2,
40
52
  minY: (windowHeight - PREVIEW_SIZE) / 2,
@@ -78,6 +90,7 @@ type PossibleActions = {
78
90
  }[keyof Actions];
79
91
 
80
92
  const LivenessDetectionScreen = () => {
93
+ useKeepAwake();
81
94
  const [camera, setCamera] = useState<TrustchexCameraHandle | null>(null);
82
95
  const navigation = useNavigation();
83
96
  const appContext = useContext(AppContext);
@@ -86,10 +99,39 @@ const LivenessDetectionScreen = () => {
86
99
  const [isRecording, setIsRecording] = useState(false);
87
100
  const insets = useSafeAreaInsets();
88
101
  const referenceFaceTrackingId = useRef<number | null>(null);
102
+ const recordingDotOpacity = useRef(new Animated.Value(1)).current;
89
103
 
90
104
  // Track screen view and exit
91
105
  useScreenTracking('liveness_detection');
92
106
 
107
+ useEffect(() => {
108
+ if (isRecording) {
109
+ const pulse = Animated.loop(
110
+ Animated.sequence([
111
+ Animated.timing(recordingDotOpacity, {
112
+ toValue: 0.2,
113
+ duration: 600,
114
+ easing: Easing.inOut(Easing.ease),
115
+ useNativeDriver: true,
116
+ }),
117
+ Animated.timing(recordingDotOpacity, {
118
+ toValue: 1,
119
+ duration: 600,
120
+ easing: Easing.inOut(Easing.ease),
121
+ useNativeDriver: true,
122
+ }),
123
+ ])
124
+ );
125
+ pulse.start();
126
+ return () => pulse.stop();
127
+ } else {
128
+ recordingDotOpacity.setValue(1);
129
+ }
130
+ }, [isRecording, recordingDotOpacity]);
131
+
132
+ // Configure status bar for white background
133
+ useStatusBarWhiteBackground();
134
+
93
135
  const [initialState, setInitialState] = useState<StateType>({
94
136
  brightnessLow: false,
95
137
  faceDetected: false,
@@ -142,7 +184,23 @@ const LivenessDetectionScreen = () => {
142
184
  const [instructionList, setInstructionList] = useState<string[]>([]);
143
185
  const [hasGuideShown, setHasGuideShown] = useState(false);
144
186
  const stoppingRecordingRef = useRef(false); // Track if we're already stopping to prevent multiple calls
145
- const lastVoiceGuidanceMessage = useRef<string>('');
187
+
188
+ // Configure status bar for white background
189
+ useStatusBarWhiteBackground();
190
+
191
+ // Update status bar when guide visibility changes
192
+ useEffect(() => {
193
+ if (hasGuideShown) {
194
+ // Camera view - use light icons
195
+ StatusBar.setBarStyle('light-content', true);
196
+ } else {
197
+ // Guide screen with white background - use dark icons
198
+ StatusBar.setBarStyle('dark-content', true);
199
+ if (Platform.OS === 'android') {
200
+ StatusBar.setBackgroundColor('#ffffff', true);
201
+ }
202
+ }
203
+ }, [hasGuideShown]);
146
204
 
147
205
  useEffect(() => {
148
206
  const il = Object.keys(instructions)
@@ -175,7 +233,6 @@ const LivenessDetectionScreen = () => {
175
233
  processComplete: false,
176
234
  videoPath: '',
177
235
  });
178
- lastVoiceGuidanceMessage.current = '';
179
236
  resetLastMessage();
180
237
  }, [
181
238
  instructions,
@@ -263,6 +320,14 @@ const LivenessDetectionScreen = () => {
263
320
  return leftOpen >= 0.8 && rightOpen >= 0.8;
264
321
  };
265
322
 
323
+ const areEyesVisible = (face: Face) => {
324
+ // Relaxed check: eyes just need to be detected (not fully closed).
325
+ // Smiling naturally causes squinting, so a lower threshold is used.
326
+ const leftOpen = face.leftEyeOpenProbability ?? 0;
327
+ const rightOpen = face.rightEyeOpenProbability ?? 0;
328
+ return leftOpen >= 0.3 && rightOpen >= 0.3;
329
+ };
330
+
266
331
  const instructionReducer = (
267
332
  state: StateType,
268
333
  action: PossibleActions
@@ -350,7 +415,6 @@ const LivenessDetectionScreen = () => {
350
415
  const nextInstruction = state.instructionList[nextInstructionIndex];
351
416
 
352
417
  // Reset TTS state when moving to any new instruction to ensure it speaks
353
- lastVoiceGuidanceMessage.current = '';
354
418
  resetLastMessage();
355
419
 
356
420
  // Calculate progress based on actual action steps (excluding START and FINISH)
@@ -404,8 +468,7 @@ const LivenessDetectionScreen = () => {
404
468
  // Let the instruction advance first, then speak the next instruction
405
469
 
406
470
  // Only speak if message changed and is not empty
407
- if (text && text !== lastVoiceGuidanceMessage.current) {
408
- lastVoiceGuidanceMessage.current = text;
471
+ if (text) {
409
472
  // Bypass interval for liveness instructions to ensure all instructions are spoken
410
473
  speak(text, true);
411
474
  }
@@ -502,26 +565,42 @@ const LivenessDetectionScreen = () => {
502
565
  );
503
566
  }
504
567
 
505
- // Calculate preview rect in frame coordinates (not screen coordinates)
506
- // Preview circle is 80% of frame width, centered
507
- const previewSizeInFrame = frameWidth * 0.8;
568
+ // Map the on-screen circle into frame coordinates.
569
+ // Camera uses FILL_CENTER / resizeAspectFill (cover): uniform scale filling
570
+ // the view, cropping overflow symmetrically.
571
+ const coverScale = Math.max(
572
+ windowWidth / frameWidth,
573
+ windowHeight / frameHeight
574
+ );
575
+ const offsetX = (frameWidth - windowWidth / coverScale) / 2;
576
+ const offsetY = (frameHeight - windowHeight / coverScale) / 2;
508
577
  const previewRectInFrame: Rect = {
509
- minX: (frameWidth - previewSizeInFrame) / 2,
510
- minY: (frameHeight - previewSizeInFrame) / 2,
511
- width: previewSizeInFrame,
512
- height: previewSizeInFrame,
578
+ minX: PREVIEW_RECT.minX / coverScale + offsetX,
579
+ minY: PREVIEW_RECT.minY / coverScale + offsetY,
580
+ width: PREVIEW_RECT.width / coverScale,
581
+ height: PREVIEW_RECT.height / coverScale,
513
582
  };
583
+ const previewSizeInFrame = PREVIEW_RECT.width / coverScale;
514
584
 
515
- const faceRectSmaller: Rect = {
516
- width: face.bounds.width - PREVIEW_EDGE_OFFSET,
517
- height: face.bounds.height - PREVIEW_EDGE_OFFSET,
518
- minY: face.bounds.y + PREVIEW_EDGE_OFFSET / 2,
519
- minX: face.bounds.x + PREVIEW_EDGE_OFFSET / 2,
520
- };
521
- const previewContainsFace = contains({
522
- outside: previewRectInFrame,
523
- inside: faceRectSmaller,
524
- });
585
+ // Check containment using face center + yaw-stable core radius.
586
+ // When the head turns (yaw), the bounding box grows wider but height stays
587
+ // constant — so min(width, height)/2 gives a stable size that doesn't
588
+ // falsely trigger an outside-circle reset during TURN_HEAD_LEFT/RIGHT.
589
+ const faceCenterX = face.bounds.x + face.bounds.width / 2;
590
+ const faceCenterY = face.bounds.y + face.bounds.height / 2;
591
+ const faceCoreRadius = Math.max(
592
+ 0,
593
+ Math.min(face.bounds.width, face.bounds.height) / 2 -
594
+ PREVIEW_EDGE_OFFSET / 2
595
+ );
596
+ const circleCX = previewRectInFrame.minX + previewRectInFrame.width / 2;
597
+ const circleCY = previewRectInFrame.minY + previewRectInFrame.height / 2;
598
+ const circleR = previewSizeInFrame / 2;
599
+ const faceDx = faceCenterX - circleCX;
600
+ const faceDy = faceCenterY - circleCY;
601
+ const previewContainsFace =
602
+ faceDx * faceDx + faceDy * faceDy <=
603
+ (circleR - faceCoreRadius) * (circleR - faceCoreRadius);
525
604
  const multipleFacesDetected = faces.length > 1;
526
605
 
527
606
  if (!isImageBright) {
@@ -670,11 +749,12 @@ const LivenessDetectionScreen = () => {
670
749
  instructions.SMILE.minAngle < face.pitchAngle &&
671
750
  face.pitchAngle < instructions.SMILE.maxAngle;
672
751
 
673
- // Check if smiling with sufficient probability AND looking at camera AND eyes open
752
+ // Check if smiling with sufficient probability AND looking at camera AND eyes visible
753
+ // Use relaxed eye check — smiling naturally causes squinting
674
754
  if (
675
755
  smilingProb >= instructions.SMILE.minProbability &&
676
756
  isFacingCamera &&
677
- areEyesOpen(face)
757
+ areEyesVisible(face)
678
758
  ) {
679
759
  instructions.SMILE.photo = image;
680
760
  dispatch({ type: 'NEXT_INSTRUCTION', payload: 'SMILE' });
@@ -818,6 +898,10 @@ const LivenessDetectionScreen = () => {
818
898
  // eslint-disable-next-line react-hooks/exhaustive-deps
819
899
  }, []); // Empty array = only run on mount/unmount
820
900
 
901
+ if (!appContext.currentWorkflowStep || !appContext.currentWorkflowStep.data) {
902
+ return null;
903
+ }
904
+
821
905
  return (
822
906
  <>
823
907
  {hasGuideShown ? (
@@ -862,6 +946,24 @@ const LivenessDetectionScreen = () => {
862
946
  mask="url(#hole-mask)"
863
947
  />
864
948
  </Svg>
949
+ {isRecording && (
950
+ <View
951
+ style={[
952
+ styles.recordingIndicatorContainer,
953
+ { top: PREVIEW_RECT.minY - 40 },
954
+ ]}
955
+ >
956
+ <View style={styles.recordingIndicator}>
957
+ <Animated.View
958
+ style={[
959
+ styles.recordingDot,
960
+ { opacity: recordingDotOpacity },
961
+ ]}
962
+ />
963
+ <Text style={styles.recordingText}>REC</Text>
964
+ </View>
965
+ </View>
966
+ )}
865
967
  <View
866
968
  style={[
867
969
  styles.instructionsContainerBottom,
@@ -986,6 +1088,33 @@ const styles = StyleSheet.create({
986
1088
  width: '100%',
987
1089
  zIndex: 1,
988
1090
  },
1091
+ recordingIndicatorContainer: {
1092
+ position: 'absolute',
1093
+ width: '100%',
1094
+ alignItems: 'center',
1095
+ zIndex: 2,
1096
+ },
1097
+ recordingIndicator: {
1098
+ flexDirection: 'row',
1099
+ alignItems: 'center',
1100
+ gap: 8,
1101
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
1102
+ paddingVertical: 6,
1103
+ paddingHorizontal: 14,
1104
+ borderRadius: 20,
1105
+ },
1106
+ recordingDot: {
1107
+ width: 10,
1108
+ height: 10,
1109
+ borderRadius: 5,
1110
+ backgroundColor: '#ef4444',
1111
+ },
1112
+ recordingText: {
1113
+ color: '#ffffff',
1114
+ fontSize: 14,
1115
+ fontWeight: '700',
1116
+ letterSpacing: 1,
1117
+ },
989
1118
  guide: {
990
1119
  flex: 1,
991
1120
  display: 'flex',