@trustchex/react-native-sdk 1.355.1 → 1.357.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 (254) hide show
  1. package/README.md +2 -9
  2. package/TrustchexSDK.podspec +5 -4
  3. package/android/build.gradle +6 -4
  4. package/android/src/main/AndroidManifest.xml +1 -1
  5. package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +45 -25
  6. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraManager.kt +168 -0
  7. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +871 -0
  8. package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +245 -0
  9. package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidationModule.kt +785 -0
  10. package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidator.kt +419 -0
  11. package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +818 -0
  12. package/ios/Camera/TrustchexCameraManager.m +37 -0
  13. package/ios/Camera/TrustchexCameraManager.swift +125 -0
  14. package/ios/Camera/TrustchexCameraView.swift +1176 -0
  15. package/ios/MLKit/MLKitModule.m +23 -0
  16. package/ios/MLKit/MLKitModule.swift +250 -0
  17. package/ios/MRZValidation.m +39 -0
  18. package/ios/MRZValidation.swift +802 -0
  19. package/ios/MRZValidator.swift +466 -0
  20. package/ios/OpenCV/OpenCVModule.h +4 -0
  21. package/ios/OpenCV/OpenCVModule.mm +810 -0
  22. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +2 -3
  23. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +1 -2
  24. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +418 -193
  25. package/lib/module/Screens/Static/OTPVerificationScreen.js +11 -11
  26. package/lib/module/Screens/Static/QrCodeScanningScreen.js +5 -1
  27. package/lib/module/Screens/Static/ResultScreen.js +25 -2
  28. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +25 -7
  29. package/lib/module/Shared/Components/DebugNavigationPanel.js +234 -24
  30. package/lib/module/Shared/Components/EIDScanner.js +99 -9
  31. package/lib/module/Shared/Components/FaceCamera.js +170 -179
  32. package/lib/module/Shared/Components/IdentityDocumentCamera.js +2151 -771
  33. package/lib/module/Shared/Components/QrCodeScannerCamera.js +109 -107
  34. package/lib/module/Shared/Components/TrustchexCamera.js +122 -0
  35. package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +91 -0
  36. package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +2 -124
  37. package/lib/module/Shared/EIDReader/tlv/tlvInputStream.js +4 -4
  38. package/lib/module/Shared/EIDReader/tlv/tlvOutputState.js +4 -4
  39. package/lib/module/Shared/EIDReader/tlv/tlvOutputStream.js +4 -4
  40. package/lib/module/Shared/Libs/analytics.utils.js +2 -2
  41. package/lib/module/Shared/Libs/debug.utils.js +132 -0
  42. package/lib/module/Shared/Libs/deeplink.utils.js +6 -5
  43. package/lib/module/Shared/Libs/demo.utils.js +13 -3
  44. package/lib/module/Shared/Libs/mrz.utils.js +1 -175
  45. package/lib/module/Shared/Libs/native-device-info.utils.js +12 -6
  46. package/lib/module/Shared/Libs/tts.utils.js +40 -6
  47. package/lib/module/Shared/Services/AnalyticsService.js +9 -8
  48. package/lib/module/Shared/Types/mrzFields.js +1 -0
  49. package/lib/module/Translation/Resources/en.js +87 -88
  50. package/lib/module/Translation/Resources/tr.js +84 -85
  51. package/lib/module/Trustchex.js +9 -2
  52. package/lib/module/index.js +1 -0
  53. package/lib/module/version.js +1 -1
  54. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  55. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  56. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  57. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
  58. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  59. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  60. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  61. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  62. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts +2 -2
  63. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  64. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +18 -4
  65. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  66. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -4
  67. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  68. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts +2 -1
  69. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  70. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +124 -0
  71. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -0
  72. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts +11 -0
  73. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts.map +1 -0
  74. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts +2 -39
  75. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
  76. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
  77. package/lib/typescript/src/Shared/Libs/debug.utils.d.ts +42 -0
  78. package/lib/typescript/src/Shared/Libs/debug.utils.d.ts.map +1 -0
  79. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  80. package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
  81. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  82. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +0 -4
  83. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  84. package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts.map +1 -1
  85. package/lib/typescript/src/Shared/Libs/tts.utils.d.ts +4 -3
  86. package/lib/typescript/src/Shared/Libs/tts.utils.d.ts.map +1 -1
  87. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -1
  88. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +2 -2
  89. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  90. package/lib/typescript/src/Shared/Types/mrzFields.d.ts +11 -0
  91. package/lib/typescript/src/Shared/Types/mrzFields.d.ts.map +1 -0
  92. package/lib/typescript/src/Translation/Resources/en.d.ts +4 -5
  93. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  94. package/lib/typescript/src/Translation/Resources/tr.d.ts +4 -5
  95. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  96. package/lib/typescript/src/Trustchex.d.ts +2 -0
  97. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  98. package/lib/typescript/src/index.d.ts +1 -0
  99. package/lib/typescript/src/index.d.ts.map +1 -1
  100. package/lib/typescript/src/version.d.ts +1 -1
  101. package/package.json +4 -35
  102. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +1 -1
  103. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +7 -5
  104. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +2 -3
  105. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +498 -216
  106. package/src/Screens/Static/OTPVerificationScreen.tsx +37 -31
  107. package/src/Screens/Static/QrCodeScanningScreen.tsx +8 -1
  108. package/src/Screens/Static/ResultScreen.tsx +136 -88
  109. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +46 -13
  110. package/src/Shared/Components/DebugNavigationPanel.tsx +290 -34
  111. package/src/Shared/Components/EIDScanner.tsx +94 -16
  112. package/src/Shared/Components/FaceCamera.tsx +236 -203
  113. package/src/Shared/Components/IdentityDocumentCamera.tsx +3073 -1030
  114. package/src/Shared/Components/QrCodeScannerCamera.tsx +133 -127
  115. package/src/Shared/Components/TrustchexCamera.tsx +289 -0
  116. package/src/Shared/Config/camera-enhancement.config.ts +2 -2
  117. package/src/Shared/EIDReader/tlv/tlv.helpers.ts +96 -0
  118. package/src/Shared/EIDReader/tlv/tlv.utils.ts +2 -125
  119. package/src/Shared/EIDReader/tlv/tlvInputStream.ts +4 -4
  120. package/src/Shared/EIDReader/tlv/tlvOutputState.ts +4 -4
  121. package/src/Shared/EIDReader/tlv/tlvOutputStream.ts +4 -4
  122. package/src/Shared/Libs/analytics.utils.ts +48 -20
  123. package/src/Shared/Libs/debug.utils.ts +149 -0
  124. package/src/Shared/Libs/deeplink.utils.ts +7 -5
  125. package/src/Shared/Libs/demo.utils.ts +4 -0
  126. package/src/Shared/Libs/http-client.ts +12 -8
  127. package/src/Shared/Libs/mrz.utils.ts +1 -163
  128. package/src/Shared/Libs/native-device-info.utils.ts +12 -6
  129. package/src/Shared/Libs/tts.utils.ts +48 -6
  130. package/src/Shared/Services/AnalyticsService.ts +69 -24
  131. package/src/Shared/Types/identificationInfo.ts +2 -2
  132. package/src/Shared/Types/mrzFields.ts +29 -0
  133. package/src/Translation/Resources/en.ts +90 -100
  134. package/src/Translation/Resources/tr.ts +89 -97
  135. package/src/Translation/index.ts +1 -1
  136. package/src/Trustchex.tsx +21 -4
  137. package/src/index.tsx +14 -0
  138. package/src/version.ts +1 -1
  139. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/barcodescanner/BarcodeScannerFrameProcessorPlugin.kt +0 -301
  140. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/BitmapUtils.kt +0 -205
  141. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/CropperPlugin.kt +0 -72
  142. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/FrameMetadata.kt +0 -4
  143. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/facedetector/FaceDetectorFrameProcessorPlugin.kt +0 -303
  144. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/textrecognition/TextRecognitionFrameProcessorPlugin.kt +0 -115
  145. package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin-Bridging-Header.h +0 -9
  146. package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.mm +0 -22
  147. package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.swift +0 -188
  148. package/ios/VisionCameraPlugins/Cropper/Cropper-Bridging-Header.h +0 -13
  149. package/ios/VisionCameraPlugins/Cropper/Cropper.h +0 -20
  150. package/ios/VisionCameraPlugins/Cropper/Cropper.mm +0 -22
  151. package/ios/VisionCameraPlugins/Cropper/Cropper.swift +0 -145
  152. package/ios/VisionCameraPlugins/Cropper/CropperUtils.swift +0 -49
  153. package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin-Bridging-Header.h +0 -4
  154. package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.mm +0 -22
  155. package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.swift +0 -320
  156. package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin-Bridging-Header.h +0 -4
  157. package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.mm +0 -27
  158. package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.swift +0 -144
  159. package/lib/module/Shared/Libs/camera.utils.js +0 -308
  160. package/lib/module/Shared/Libs/frame-enhancement.utils.js +0 -133
  161. package/lib/module/Shared/Libs/opencv.utils.js +0 -21
  162. package/lib/module/Shared/Libs/worklet.utils.js +0 -68
  163. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.js +0 -46
  164. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +0 -35
  165. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/index.js +0 -19
  166. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.js +0 -26
  167. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/types.js +0 -3
  168. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.js +0 -197
  169. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.js +0 -101
  170. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.js +0 -60
  171. package/lib/module/Shared/VisionCameraPlugins/Cropper/index.js +0 -47
  172. package/lib/module/Shared/VisionCameraPlugins/FaceDetector/Camera.js +0 -42
  173. package/lib/module/Shared/VisionCameraPlugins/FaceDetector/detectFaces.js +0 -35
  174. package/lib/module/Shared/VisionCameraPlugins/FaceDetector/index.js +0 -4
  175. package/lib/module/Shared/VisionCameraPlugins/FaceDetector/types.js +0 -3
  176. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/Camera.js +0 -56
  177. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.js +0 -20
  178. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.js +0 -9
  179. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/index.js +0 -6
  180. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/scanText.js +0 -20
  181. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/translateText.js +0 -19
  182. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/types.js +0 -3
  183. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +0 -87
  184. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +0 -1
  185. package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts +0 -25
  186. package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts.map +0 -1
  187. package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts +0 -3
  188. package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts.map +0 -1
  189. package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts +0 -9
  190. package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts.map +0 -1
  191. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts +0 -13
  192. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts.map +0 -1
  193. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts +0 -6
  194. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +0 -1
  195. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts +0 -12
  196. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts.map +0 -1
  197. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts +0 -3
  198. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts.map +0 -1
  199. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts +0 -52
  200. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts.map +0 -1
  201. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts +0 -62
  202. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts.map +0 -1
  203. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts +0 -34
  204. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts.map +0 -1
  205. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts +0 -32
  206. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts.map +0 -1
  207. package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts +0 -23
  208. package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts.map +0 -1
  209. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts +0 -9
  210. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts.map +0 -1
  211. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts +0 -3
  212. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts.map +0 -1
  213. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts +0 -3
  214. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts.map +0 -1
  215. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts +0 -79
  216. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts.map +0 -1
  217. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts +0 -6
  218. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts.map +0 -1
  219. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts +0 -3
  220. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts.map +0 -1
  221. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts +0 -3
  222. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts.map +0 -1
  223. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts +0 -5
  224. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts.map +0 -1
  225. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts +0 -3
  226. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts.map +0 -1
  227. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts +0 -3
  228. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts.map +0 -1
  229. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts +0 -67
  230. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts.map +0 -1
  231. package/src/Shared/Libs/camera.utils.ts +0 -345
  232. package/src/Shared/Libs/frame-enhancement.utils.ts +0 -217
  233. package/src/Shared/Libs/opencv.utils.ts +0 -40
  234. package/src/Shared/Libs/worklet.utils.ts +0 -72
  235. package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.ts +0 -79
  236. package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +0 -46
  237. package/src/Shared/VisionCameraPlugins/BarcodeScanner/index.ts +0 -60
  238. package/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.ts +0 -32
  239. package/src/Shared/VisionCameraPlugins/BarcodeScanner/types.ts +0 -82
  240. package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.ts +0 -195
  241. package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.ts +0 -135
  242. package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.ts +0 -84
  243. package/src/Shared/VisionCameraPlugins/Cropper/index.ts +0 -78
  244. package/src/Shared/VisionCameraPlugins/FaceDetector/Camera.tsx +0 -63
  245. package/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.ts +0 -44
  246. package/src/Shared/VisionCameraPlugins/FaceDetector/index.ts +0 -3
  247. package/src/Shared/VisionCameraPlugins/FaceDetector/types.ts +0 -99
  248. package/src/Shared/VisionCameraPlugins/TextRecognition/Camera.tsx +0 -76
  249. package/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.ts +0 -18
  250. package/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.ts +0 -7
  251. package/src/Shared/VisionCameraPlugins/TextRecognition/index.ts +0 -7
  252. package/src/Shared/VisionCameraPlugins/TextRecognition/scanText.ts +0 -27
  253. package/src/Shared/VisionCameraPlugins/TextRecognition/translateText.ts +0 -21
  254. package/src/Shared/VisionCameraPlugins/TextRecognition/types.ts +0 -141
@@ -2,10 +2,9 @@
2
2
 
3
3
  import Svg, { Rect as SvgRect, Circle, Mask } from 'react-native-svg';
4
4
  import { useNavigation } from '@react-navigation/native';
5
- import React, { useState, useReducer, useContext, useEffect, useCallback } from 'react';
6
- import { Dimensions, Text, View, StyleSheet, Platform, Vibration } from 'react-native';
7
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
8
- import { SafeAreaView } from 'react-native-safe-area-context';
5
+ import React, { useState, useReducer, useContext, useEffect, useCallback, useRef } from 'react';
6
+ import { StyleSheet, Text, View, Dimensions, Vibration } from 'react-native';
7
+ import { useSafeAreaInsets, SafeAreaView } from 'react-native-safe-area-context';
9
8
  import NativeCircularProgress from "../../Shared/Components/NativeCircularProgress.js";
10
9
  import FaceCamera from "../../Shared/Components/FaceCamera.js";
11
10
  import NavigationManager from "../../Shared/Components/NavigationManager.js";
@@ -14,7 +13,7 @@ import { contains } from "../../Shared/Libs/contains.js";
14
13
  import { useTranslation } from 'react-i18next';
15
14
  import StyledButton from "../../Shared/Components/StyledButton.js";
16
15
  import LottieView from 'lottie-react-native';
17
- import { speakWithDebounce } from "../../Shared/Libs/tts.utils.js";
16
+ import { speak, resetLastMessage } from "../../Shared/Libs/tts.utils.js";
18
17
  import { trackFunnelStep, useScreenTracking, trackVerificationStart, trackVerificationComplete } from "../../Shared/Libs/analytics.utils.js";
19
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
20
19
  const {
@@ -41,6 +40,7 @@ const LivenessDetectionScreen = () => {
41
40
  } = useTranslation();
42
41
  const [isRecording, setIsRecording] = useState(false);
43
42
  const insets = useSafeAreaInsets();
43
+ const referenceFaceTrackingId = useRef(null);
44
44
 
45
45
  // Track screen view and exit
46
46
  useScreenTracking('liveness_detection');
@@ -56,13 +56,16 @@ const LivenessDetectionScreen = () => {
56
56
  processComplete: false,
57
57
  videoPath: ''
58
58
  });
59
- const [instructions, _setInstructions] = useState({
59
+ const hasNavigatedRef = useRef(false);
60
+ const [instructions] = useState({
60
61
  START: {
61
62
  instruction: t('livenessDetectionScreen.start')
62
63
  },
63
64
  SMILE: {
64
65
  instruction: t('livenessDetectionScreen.smile'),
65
- minProbability: 0.7
66
+ minProbability: 0.7,
67
+ maxAngle: LOOK_STRAIGHT_ANGLE_LIMIT,
68
+ minAngle: -LOOK_STRAIGHT_ANGLE_LIMIT
66
69
  },
67
70
  LOOK_STRAIGHT_AND_BLINK: {
68
71
  instruction: t('livenessDetectionScreen.lookStraightAndBlink'),
@@ -91,6 +94,8 @@ const LivenessDetectionScreen = () => {
91
94
  });
92
95
  const [instructionList, setInstructionList] = useState([]);
93
96
  const [hasGuideShown, setHasGuideShown] = useState(false);
97
+ const stoppingRecordingRef = useRef(false); // Track if we're already stopping to prevent multiple calls
98
+ const lastVoiceGuidanceMessage = useRef('');
94
99
  useEffect(() => {
95
100
  const il = Object.keys(instructions).filter(instruction => !['START', 'FINISH'].includes(instruction) && (
96
101
  // Look straight and blink is always included
@@ -110,55 +115,73 @@ const LivenessDetectionScreen = () => {
110
115
  processComplete: false,
111
116
  videoPath: ''
112
117
  });
118
+ lastVoiceGuidanceMessage.current = '';
119
+ resetLastMessage();
113
120
  }, [instructions, appContext.currentWorkflowStep?.data?.allowedLivenessInstructionTypes]);
121
+ const isCommandInProgress = useRef(false);
114
122
  const startRecording = useCallback(async () => {
115
- if (isRecording) {
116
- try {
123
+ console.log('[LivenessDetection] startRecording called, current state isRecording:', isRecording);
124
+ if (isCommandInProgress.current) {
125
+ console.log('[LivenessDetection] Skipping startRecording: Command already in progress');
126
+ return;
127
+ }
128
+ isCommandInProgress.current = true;
129
+ try {
130
+ if (isRecording) {
131
+ console.log('[LivenessDetection] isRecording is true, cancelling existing recording first');
117
132
  await camera?.cancelRecording();
118
133
  setIsRecording(false);
119
- } catch (error) {
120
- // User cancelled recording - expected behavior, no need to track
121
134
  }
122
- }
123
- setTimeout(() => {
124
- // Track liveness check started
135
+ console.log('[LivenessDetection] Starting new recording');
125
136
  trackVerificationStart('LIVENESS_CHECK');
126
137
  camera?.startRecording({
127
138
  fileType: 'mp4',
128
- videoCodec: 'h265',
129
- onRecordingError() {
130
- // Recording errors are retried automatically, no need to track them
139
+ onRecordingError(error) {
140
+ console.error('[LivenessDetection] *** Recording error callback ***:', error);
131
141
  setIsRecording(false);
142
+ isCommandInProgress.current = false;
132
143
  },
133
144
  onRecordingFinished(video) {
134
- setTimeout(() => {
135
- dispatch({
136
- type: 'VIDEO_RECORDED',
137
- payload: video.path
138
- });
139
-
140
- // Track liveness check completion
141
- trackVerificationComplete('LIVENESS_CHECK', true, 1);
142
- trackFunnelStep('Liveness Check Completed', 3, 5, 'document_scanning', true);
143
- setIsRecording(false);
144
- }, 500);
145
+ // Only process if we actually called stopRecording (i.e., reached FINISH)
146
+ if (!stoppingRecordingRef.current) {
147
+ return;
148
+ }
149
+ dispatch({
150
+ type: 'VIDEO_RECORDED',
151
+ payload: video.path
152
+ });
153
+ trackVerificationComplete('LIVENESS_CHECK', true, 1);
154
+ trackFunnelStep('Liveness Check Completed', 3, 5, 'document_scanning', true);
155
+ setIsRecording(false);
156
+ isCommandInProgress.current = false;
145
157
  }
146
158
  });
147
- }, 500);
148
- setIsRecording(true);
159
+ setIsRecording(true);
160
+ console.log('[LivenessDetection] startRecording command sent to camera');
161
+ } catch (error) {
162
+ console.error('[LivenessDetection] Error in startRecording:', error);
163
+ isCommandInProgress.current = false;
164
+ }
149
165
  }, [camera, isRecording]);
150
166
  const stopRecording = useCallback(async () => {
151
167
  try {
152
168
  await camera?.stopRecording();
153
- setIsRecording(false);
154
169
  } catch (error) {
155
- // Stop recording can fail due to race conditions when user navigates away
156
- // This is expected behavior and not actionable
170
+ console.error('[LivenessDetection] Error in stopRecording:', error);
157
171
  }
158
172
  }, [camera]);
159
- const areEyesOpen = face => face.leftEyeOpenProbability >= 0.8 && face.rightEyeOpenProbability >= 0.8;
173
+ const areEyesOpen = face => {
174
+ // Handle undefined probabilities (when ML Kit can't detect eyes)
175
+ const leftOpen = face.leftEyeOpenProbability ?? 0;
176
+ const rightOpen = face.rightEyeOpenProbability ?? 0;
177
+ return leftOpen >= 0.8 && rightOpen >= 0.8;
178
+ };
160
179
  const instructionReducer = (state, action) => {
161
180
  switch (action.type) {
181
+ case 'RESET':
182
+ console.log('[LivenessDetection] Resetting to initial state');
183
+ referenceFaceTrackingId.current = null;
184
+ return initialState;
162
185
  case 'BRIGHTNESS_LOW':
163
186
  if (action.payload) {
164
187
  return {
@@ -181,7 +204,8 @@ const LivenessDetectionScreen = () => {
181
204
  if (action.payload) {
182
205
  return {
183
206
  ...state,
184
- faceTooBig: action.payload
207
+ faceTooBig: action.payload,
208
+ progressFill: 0
185
209
  };
186
210
  }
187
211
  return initialState;
@@ -190,39 +214,170 @@ const LivenessDetectionScreen = () => {
190
214
  return {
191
215
  ...state,
192
216
  faceDetected: action.payload,
193
- progressFill: 100 / (state.instructionList.length + 1)
217
+ progressFill: state.currentInstruction === 'START' ? 0 // No actions completed yet
218
+ : state.progressFill // Keep progress if we're past START
194
219
  };
195
220
  }
196
- return initialState;
221
+ // Face lost - reset to START if we haven't begun the flow yet
222
+ console.log('[LivenessDetection] Face lost');
223
+ if (state.currentInstruction === 'START') {
224
+ return {
225
+ ...state,
226
+ faceDetected: false,
227
+ progressFill: 0
228
+ };
229
+ }
230
+ // If past START, this should have been caught by onFacesDetected
231
+ // but just in case, keep state stable
232
+ return {
233
+ ...state,
234
+ faceDetected: false
235
+ };
197
236
  case 'VIDEO_RECORDED':
237
+ // Only finalize if we're at FINISH instruction - prevents premature completion from stale callbacks
238
+ if (state.currentInstruction !== 'FINISH') {
239
+ return state;
240
+ }
198
241
  return {
199
242
  ...state,
200
- videoPath: action.payload
243
+ videoPath: action.payload,
244
+ processComplete: true,
245
+ progressFill: 100
201
246
  };
202
247
  case 'NEXT_INSTRUCTION':
203
- if (action.payload === 'FINISH') {
248
+ {
249
+ const currentInstructionIndex = state.instructionList.indexOf(action.payload);
250
+ const nextInstructionIndex = currentInstructionIndex + 1;
251
+ const nextInstruction = state.instructionList[nextInstructionIndex];
252
+
253
+ // Reset TTS state when moving to any new instruction to ensure it speaks
254
+ lastVoiceGuidanceMessage.current = '';
255
+ resetLastMessage();
256
+
257
+ // Calculate progress based on actual action steps (excluding START and FINISH)
258
+ // When last action is completed (moving to FINISH), show 100%
259
+ const totalActionSteps = state.instructionList.length - 2; // Exclude START and FINISH
260
+ const completedActionSteps = currentInstructionIndex; // Steps completed before current (START is index 0)
261
+
262
+ const newProgressFill = nextInstruction === 'FINISH' ? 100 // Last action completed - show 100%
263
+ : completedActionSteps / totalActionSteps * 100;
204
264
  return {
205
265
  ...state,
206
- processComplete: true,
207
- progressFill: 100
266
+ currentInstruction: nextInstruction,
267
+ progressFill: newProgressFill
208
268
  };
209
269
  }
210
- const nextInstructionIndex = instructionList.findIndex(instruction => instruction === action.payload) + 1;
211
- const nextInstruction = state.instructionList[nextInstructionIndex];
212
- const progressMultiplier = nextInstructionIndex + 1;
213
- const newProgressFill = 100 / (state.instructionList.length + 1) * progressMultiplier;
214
- return {
215
- ...state,
216
- currentInstruction: nextInstruction,
217
- progressFill: newProgressFill
218
- };
219
270
  default:
220
271
  throw new Error('Unexpected action type.');
221
272
  }
222
273
  };
223
274
  const [state, dispatch] = useReducer(instructionReducer, initialState);
224
- const onFacesDetected = useCallback(async (faces, image, isImageBright) => {
275
+ useEffect(() => {
276
+ if (!appContext.currentWorkflowStep?.data?.voiceGuidanceActive || !hasGuideShown) return;
277
+ let text = '';
278
+
279
+ // Priority order: errors first, then face placement, then instructions
280
+ if (state.brightnessLow) {
281
+ text = t('livenessDetectionScreen.brightnessLow');
282
+ } else if (state.multipleFacesDetected) {
283
+ text = t('livenessDetectionScreen.multipleFacesDetected');
284
+ } else if (state.faceTooBig) {
285
+ text = t('livenessDetectionScreen.faceTooBig');
286
+ } else if (!state.faceDetected) {
287
+ // Only speak "place face" message when face is not detected
288
+ text = t('livenessDetectionScreen.placeFaceInsideCircle');
289
+ } else if (state.faceDetected && state.currentInstruction !== 'START') {
290
+ // Face is detected and we've moved past START - speak the actual instruction
291
+ // Don't speak START instruction, wait for first actual liveness instruction
292
+ text = instructions[state.currentInstruction]?.instruction ?? '';
293
+ }
294
+ // If currentInstruction is 'START' and face is detected, don't speak anything
295
+ // Let the instruction advance first, then speak the next instruction
296
+
297
+ // Only speak if message changed and is not empty
298
+ if (text && text !== lastVoiceGuidanceMessage.current) {
299
+ lastVoiceGuidanceMessage.current = text;
300
+ // Bypass interval for liveness instructions to ensure all instructions are spoken
301
+ speak(text, true);
302
+ }
303
+ }, [appContext.currentWorkflowStep?.data?.voiceGuidanceActive, hasGuideShown, state.brightnessLow, state.multipleFacesDetected, state.faceTooBig, state.faceDetected, state.currentInstruction, instructions, t]);
304
+ const onFacesDetected = useCallback(async (faces, image, isImageBright, frameWidth, frameHeight) => {
305
+ // Skip processing if recording is being finalized or process is already complete
306
+ if (stoppingRecordingRef.current || state.processComplete) {
307
+ return;
308
+ }
309
+
310
+ // Check if no faces detected
311
+ if (faces.length === 0) {
312
+ // Face not detected - reset progress if we've started the flow
313
+ if (state.currentInstruction === 'START') {
314
+ // Just mark face as not detected
315
+ dispatch({
316
+ type: 'FACE_DETECTED',
317
+ payload: false
318
+ });
319
+ } else {
320
+ console.log('[LivenessDetection] No face detected after START, resetting to beginning');
321
+ if (isRecording) {
322
+ await camera?.cancelRecording();
323
+ setIsRecording(false);
324
+ }
325
+ isCommandInProgress.current = false;
326
+ stoppingRecordingRef.current = false;
327
+ dispatch({
328
+ type: 'RESET',
329
+ payload: undefined
330
+ });
331
+ }
332
+ return;
333
+ }
225
334
  const face = faces[0];
335
+
336
+ // Track face identity - ensure same person throughout liveness check
337
+ if (face.trackingId !== undefined) {
338
+ if (referenceFaceTrackingId.current === null) {
339
+ // First face detected - store tracking ID
340
+ referenceFaceTrackingId.current = face.trackingId;
341
+ console.log('[LivenessDetection] Stored reference face tracking ID:', face.trackingId);
342
+ } else if (referenceFaceTrackingId.current !== face.trackingId) {
343
+ // Different person detected - reset
344
+ console.log('[LivenessDetection] Different person detected (tracking ID changed from', referenceFaceTrackingId.current, 'to', face.trackingId, '), resetting');
345
+ if (isRecording) {
346
+ await camera?.cancelRecording();
347
+ setIsRecording(false);
348
+ }
349
+ isCommandInProgress.current = false;
350
+ stoppingRecordingRef.current = false;
351
+ referenceFaceTrackingId.current = null;
352
+ dispatch({
353
+ type: 'RESET',
354
+ payload: undefined
355
+ });
356
+ return;
357
+ }
358
+ }
359
+
360
+ // Check if frame orientation is correct (should be portrait: width < height)
361
+ if (frameWidth > frameHeight) {
362
+ console.warn('[LivenessDetection] WARNING: Frame is landscape but expected portrait!', {
363
+ frameWidth,
364
+ frameHeight,
365
+ faceX: face.bounds.x,
366
+ faceY: face.bounds.y,
367
+ faceWidth: face.bounds.width,
368
+ faceHeight: face.bounds.height
369
+ });
370
+ }
371
+
372
+ // Calculate preview rect in frame coordinates (not screen coordinates)
373
+ // Preview circle is 80% of frame width, centered
374
+ const previewSizeInFrame = frameWidth * 0.8;
375
+ const previewRectInFrame = {
376
+ minX: (frameWidth - previewSizeInFrame) / 2,
377
+ minY: (frameHeight - previewSizeInFrame) / 2,
378
+ width: previewSizeInFrame,
379
+ height: previewSizeInFrame
380
+ };
226
381
  const faceRectSmaller = {
227
382
  width: face.bounds.width - PREVIEW_EDGE_OFFSET,
228
383
  height: face.bounds.height - PREVIEW_EDGE_OFFSET,
@@ -230,22 +385,52 @@ const LivenessDetectionScreen = () => {
230
385
  minX: face.bounds.x + PREVIEW_EDGE_OFFSET / 2
231
386
  };
232
387
  const previewContainsFace = contains({
233
- outside: PREVIEW_RECT,
388
+ outside: previewRectInFrame,
234
389
  inside: faceRectSmaller
235
390
  });
236
391
  const multipleFacesDetected = faces.length > 1;
237
392
  if (!isImageBright) {
238
- dispatch({
239
- type: 'BRIGHTNESS_LOW',
240
- payload: true
241
- });
393
+ // Brightness too low - reset progress if we've started the flow
394
+ if (state.currentInstruction === 'START') {
395
+ dispatch({
396
+ type: 'BRIGHTNESS_LOW',
397
+ payload: true
398
+ });
399
+ } else {
400
+ console.log('[LivenessDetection] Brightness low after START, resetting to beginning');
401
+ if (isRecording) {
402
+ await camera?.cancelRecording();
403
+ setIsRecording(false);
404
+ }
405
+ isCommandInProgress.current = false;
406
+ stoppingRecordingRef.current = false;
407
+ dispatch({
408
+ type: 'RESET',
409
+ payload: undefined
410
+ });
411
+ }
242
412
  return;
243
413
  }
244
414
  if (!previewContainsFace) {
245
- dispatch({
246
- type: 'FACE_DETECTED',
247
- payload: false
248
- });
415
+ // Face went outside circle - reset progress if we've started the flow
416
+ if (state.currentInstruction === 'START') {
417
+ dispatch({
418
+ type: 'FACE_DETECTED',
419
+ payload: false
420
+ });
421
+ } else {
422
+ console.log('[LivenessDetection] Face outside circle after START, resetting to beginning');
423
+ if (isRecording) {
424
+ await camera?.cancelRecording();
425
+ setIsRecording(false);
426
+ }
427
+ isCommandInProgress.current = false;
428
+ stoppingRecordingRef.current = false;
429
+ dispatch({
430
+ type: 'RESET',
431
+ payload: undefined
432
+ });
433
+ }
249
434
  return;
250
435
  }
251
436
  if (state.brightnessLow) {
@@ -255,10 +440,25 @@ const LivenessDetectionScreen = () => {
255
440
  });
256
441
  }
257
442
  if (!state.multipleFacesDetected && multipleFacesDetected) {
258
- dispatch({
259
- type: 'MULTIPLE_FACES_DETECTED',
260
- payload: true
261
- });
443
+ // Multiple faces detected - reset progress if we've started the flow
444
+ if (state.currentInstruction === 'START') {
445
+ dispatch({
446
+ type: 'MULTIPLE_FACES_DETECTED',
447
+ payload: true
448
+ });
449
+ } else {
450
+ console.log('[LivenessDetection] Multiple faces after START, resetting to beginning');
451
+ if (isRecording) {
452
+ await camera?.cancelRecording();
453
+ setIsRecording(false);
454
+ }
455
+ isCommandInProgress.current = false;
456
+ stoppingRecordingRef.current = false;
457
+ dispatch({
458
+ type: 'RESET',
459
+ payload: undefined
460
+ });
461
+ }
262
462
  return;
263
463
  }
264
464
  if (state.multipleFacesDetected && !multipleFacesDetected) {
@@ -267,22 +467,41 @@ const LivenessDetectionScreen = () => {
267
467
  payload: false
268
468
  });
269
469
  }
270
- if (!state.faceDetected) {
271
- if (face.bounds.width >= PREVIEW_SIZE && face.bounds.height >= PREVIEW_SIZE) {
470
+
471
+ // Check if face is too big
472
+ const faceTooBig = face.bounds.width >= previewSizeInFrame && face.bounds.height >= previewSizeInFrame;
473
+ if (faceTooBig) {
474
+ // Face too big - reset progress if we've started the flow
475
+ if (state.currentInstruction !== 'START') {
476
+ console.log('[LivenessDetection] Face too big after START, resetting to beginning');
477
+ if (isRecording) {
478
+ await camera?.cancelRecording();
479
+ setIsRecording(false);
480
+ }
481
+ isCommandInProgress.current = false;
482
+ stoppingRecordingRef.current = false;
272
483
  dispatch({
273
- type: 'FACE_TOO_BIG',
274
- payload: true
484
+ type: 'RESET',
485
+ payload: undefined
275
486
  });
276
- return;
277
- }
278
- if (state.faceTooBig) {
487
+ } else if (!state.faceTooBig) {
279
488
  dispatch({
280
489
  type: 'FACE_TOO_BIG',
281
- payload: false
490
+ payload: true
282
491
  });
283
- return;
284
492
  }
493
+ return;
494
+ }
495
+
496
+ // Face is good size now, clear too big flag if set
497
+ if (state.faceTooBig) {
498
+ dispatch({
499
+ type: 'FACE_TOO_BIG',
500
+ payload: false
501
+ });
285
502
  }
503
+
504
+ // Face is detected and good size
286
505
  if (!state.faceDetected) {
287
506
  dispatch({
288
507
  type: 'FACE_DETECTED',
@@ -292,13 +511,11 @@ const LivenessDetectionScreen = () => {
292
511
  if (state.currentInstruction !== state.previousInstruction) {
293
512
  state.previousInstruction = state.currentInstruction;
294
513
  Vibration.vibrate(100);
295
- if (appContext.currentWorkflowStep?.data?.voiceGuidanceActive) {
296
- speakWithDebounce(instructions[state.currentInstruction]?.instruction ?? '');
297
- }
298
514
  }
299
515
  switch (state.currentInstruction) {
300
516
  case 'START':
301
517
  if (state.faceDetected) {
518
+ console.log('[LivenessDetection] Starting flow...');
302
519
  await startRecording();
303
520
  dispatch({
304
521
  type: 'NEXT_INSTRUCTION',
@@ -310,6 +527,7 @@ const LivenessDetectionScreen = () => {
310
527
  if (instructions.LOOK_STRAIGHT_AND_BLINK.minAngle < face.yawAngle && face.yawAngle < instructions.LOOK_STRAIGHT_AND_BLINK.maxAngle && instructions.LOOK_STRAIGHT_AND_BLINK.minAngle < face.pitchAngle && face.pitchAngle < instructions.LOOK_STRAIGHT_AND_BLINK.maxAngle) {
311
528
  if (instructions.LOOK_STRAIGHT_AND_BLINK.eyesClosed) {
312
529
  if (areEyesOpen(face)) {
530
+ console.log('[LivenessDetection] LOOK_STRAIGHT_AND_BLINK: Eyes opened, completing instruction');
313
531
  instructions.LOOK_STRAIGHT_AND_BLINK.eyesClosed = false;
314
532
  instructions.LOOK_STRAIGHT_AND_BLINK.photo = image;
315
533
  dispatch({
@@ -323,14 +541,22 @@ const LivenessDetectionScreen = () => {
323
541
  }
324
542
  return;
325
543
  case 'SMILE':
326
- if (face.smilingProbability >= instructions.SMILE.minProbability) {
327
- instructions.SMILE.photo = image;
328
- dispatch({
329
- type: 'NEXT_INSTRUCTION',
330
- payload: 'SMILE'
331
- });
544
+ {
545
+ // Handle undefined smilingProbability (when ML Kit can't detect smile)
546
+ const smilingProb = face.smilingProbability ?? 0;
547
+ // Ensure user is looking at camera (face direction check)
548
+ const isFacingCamera = instructions.SMILE.minAngle < face.yawAngle && face.yawAngle < instructions.SMILE.maxAngle && instructions.SMILE.minAngle < face.pitchAngle && face.pitchAngle < instructions.SMILE.maxAngle;
549
+
550
+ // Check if smiling with sufficient probability AND looking at camera AND eyes open
551
+ if (smilingProb >= instructions.SMILE.minProbability && isFacingCamera && areEyesOpen(face)) {
552
+ instructions.SMILE.photo = image;
553
+ dispatch({
554
+ type: 'NEXT_INSTRUCTION',
555
+ payload: 'SMILE'
556
+ });
557
+ }
558
+ return;
332
559
  }
333
- return;
334
560
  case 'LOOK_UP':
335
561
  if (face.pitchAngle >= instructions.LOOK_UP.minAngle && areEyesOpen(face)) {
336
562
  instructions.LOOK_UP.photo = image;
@@ -341,108 +567,93 @@ const LivenessDetectionScreen = () => {
341
567
  }
342
568
  return;
343
569
  case 'TURN_HEAD_LEFT':
344
- if (Platform.OS === 'ios') {
345
- if (face.yawAngle <= instructions.TURN_HEAD_LEFT.maxAngle && areEyesOpen(face)) {
346
- instructions.TURN_HEAD_LEFT.photo = image;
347
- dispatch({
348
- type: 'NEXT_INSTRUCTION',
349
- payload: 'TURN_HEAD_LEFT'
350
- });
351
- }
352
- } else {
353
- if (face.yawAngle >= instructions.TURN_HEAD_LEFT.minAngle && areEyesOpen(face)) {
570
+ {
571
+ const isLeftTurn = face.yawAngle >= TURN_ANGLE_LIMIT;
572
+ if (isLeftTurn && areEyesOpen(face)) {
354
573
  instructions.TURN_HEAD_LEFT.photo = image;
355
574
  dispatch({
356
575
  type: 'NEXT_INSTRUCTION',
357
576
  payload: 'TURN_HEAD_LEFT'
358
577
  });
359
578
  }
579
+ return;
360
580
  }
361
- return;
362
581
  case 'TURN_HEAD_RIGHT':
363
- if (Platform.OS === 'ios') {
364
- if (face.yawAngle >= instructions.TURN_HEAD_RIGHT.maxAngle && areEyesOpen(face)) {
365
- instructions.TURN_HEAD_RIGHT.photo = image;
366
- dispatch({
367
- type: 'NEXT_INSTRUCTION',
368
- payload: 'TURN_HEAD_RIGHT'
369
- });
370
- }
371
- } else {
372
- if (face.yawAngle <= instructions.TURN_HEAD_RIGHT.minAngle && areEyesOpen(face)) {
582
+ {
583
+ const isRightTurn = face.yawAngle <= -TURN_ANGLE_LIMIT;
584
+ if (isRightTurn && areEyesOpen(face)) {
373
585
  instructions.TURN_HEAD_RIGHT.photo = image;
374
586
  dispatch({
375
587
  type: 'NEXT_INSTRUCTION',
376
588
  payload: 'TURN_HEAD_RIGHT'
377
589
  });
378
590
  }
591
+ return;
379
592
  }
380
- return;
381
593
  case 'FINISH':
382
- await stopRecording();
383
- dispatch({
384
- type: 'NEXT_INSTRUCTION',
385
- payload: 'FINISH'
594
+ // Prevent multiple calls to stopRecording
595
+ if (stoppingRecordingRef.current) {
596
+ return;
597
+ }
598
+ stoppingRecordingRef.current = true;
599
+ stopRecording().catch(error => {
600
+ console.error('[LivenessDetection] Error stopping recording:', error);
386
601
  });
387
602
  return;
388
603
  }
389
604
  },
390
605
  // eslint-disable-next-line react-hooks/exhaustive-deps
391
- [state.brightnessLow, state.faceDetected, state.multipleFacesDetected, state.faceTooBig, state.currentInstruction, instructions.LOOK_STRAIGHT_AND_BLINK, instructions.SMILE, instructions.LOOK_UP, instructions.TURN_HEAD_LEFT, instructions.TURN_HEAD_RIGHT, stopRecording, startRecording]);
606
+ [state.brightnessLow, state.faceDetected, state.multipleFacesDetected, state.faceTooBig, state.currentInstruction, instructions.LOOK_STRAIGHT_AND_BLINK, instructions.SMILE, instructions.LOOK_UP, instructions.TURN_HEAD_LEFT, instructions.TURN_HEAD_RIGHT, stopRecording, startRecording, isRecording, camera, state.processComplete]);
392
607
  useEffect(() => {
393
- if (state.processComplete && !!state.videoPath && !!state.instructionList && !!appContext.identificationInfo && !!navigation && !!instructions) {
608
+ console.log('[LivenessDetection] Navigation check:', {
609
+ processComplete: state.processComplete,
610
+ hasVideoPath: !!state.videoPath,
611
+ videoPath: state.videoPath,
612
+ hasInstructionList: !!state.instructionList,
613
+ hasIdentificationInfo: !!appContext.identificationInfo,
614
+ hasNavigation: !!navigation,
615
+ hasNavigated: hasNavigatedRef.current
616
+ });
617
+ if (state.processComplete && state.videoPath && !!state.instructionList && !!appContext.identificationInfo && !!navigation && !!instructions && !hasNavigatedRef.current) {
618
+ console.log('[LivenessDetection] All conditions met, finalizing liveness data');
619
+ hasNavigatedRef.current = true;
394
620
  appContext.identificationInfo.livenessDetection = {
395
- instructions: [...state.instructionList.map(instruction => ({
621
+ instructions: state.instructionList.map(instruction => ({
396
622
  instruction: instruction,
397
623
  photo: instructions[instruction].photo ?? ''
398
- }))],
624
+ })),
399
625
  videoPath: state.videoPath
400
626
  };
627
+ console.log('[LivenessDetection] Navigating to next step');
401
628
  navigationManagerRef.current?.navigateToNextStep();
402
629
  }
403
630
  }, [state.processComplete, state.videoPath, state.instructionList, appContext.identificationInfo, navigation, instructions]);
631
+
632
+ // Cleanup: Cancel recording when component unmounts
633
+ // IMPORTANT: Empty dependency array ensures this only runs on actual unmount
634
+ useEffect(() => {
635
+ // Capture the ref itself (not its value) so we can read .current at cleanup time
636
+ const stoppingRef = stoppingRecordingRef;
637
+ const cameraRef = camera;
638
+ return () => {
639
+ console.log('[LivenessDetection] Component unmounting, checking if should cancel recording');
640
+ console.log('[LivenessDetection] stoppingRecordingRef.current:', stoppingRef.current);
641
+ // Don't cancel if we're already stopping/finishing - let the recording finalize
642
+ if (stoppingRef.current) {
643
+ console.log('[LivenessDetection] Recording is finishing, not cancelling');
644
+ return;
645
+ }
646
+ console.log('[LivenessDetection] Cancelling incomplete recording');
647
+ // Cancel any ongoing recording when component unmounts
648
+ cameraRef?.cancelRecording().catch(() => {
649
+ // Ignore errors during cleanup
650
+ });
651
+ };
652
+ // eslint-disable-next-line react-hooks/exhaustive-deps
653
+ }, []); // Empty array = only run on mount/unmount
654
+
404
655
  return /*#__PURE__*/_jsxs(_Fragment, {
405
- children: [!hasGuideShown ? /*#__PURE__*/_jsxs(SafeAreaView, {
406
- style: styles.guide,
407
- children: [/*#__PURE__*/_jsx(LottieView, {
408
- source: require('../../Shared/Animations/face-scan.json'),
409
- style: styles.guideAnimation,
410
- loop: true,
411
- autoPlay: true
412
- }), /*#__PURE__*/_jsx(Text, {
413
- style: styles.guideHeader,
414
- children: t('livenessDetectionScreen.guideHeader')
415
- }), /*#__PURE__*/_jsxs(View, {
416
- style: styles.guidePoints,
417
- children: [/*#__PURE__*/_jsx(Text, {
418
- style: styles.guideText,
419
- children: t('livenessDetectionScreen.guideText')
420
- }), /*#__PURE__*/_jsxs(Text, {
421
- style: styles.guideText,
422
- children: ["\u2022 ", t('livenessDetectionScreen.guidePoint1')]
423
- }), /*#__PURE__*/_jsxs(Text, {
424
- style: styles.guideText,
425
- children: ["\u2022 ", t('livenessDetectionScreen.guidePoint2')]
426
- }), /*#__PURE__*/_jsxs(Text, {
427
- style: styles.guideText,
428
- children: ["\u2022 ", t('livenessDetectionScreen.guidePoint3')]
429
- }), /*#__PURE__*/_jsxs(Text, {
430
- style: styles.guideText,
431
- children: ["\u2022 ", t('livenessDetectionScreen.guidePoint4')]
432
- })]
433
- }), /*#__PURE__*/_jsx(View, {
434
- style: {
435
- paddingBottom: insets.bottom
436
- },
437
- children: /*#__PURE__*/_jsx(StyledButton, {
438
- mode: "contained",
439
- onPress: () => {
440
- setHasGuideShown(true);
441
- },
442
- children: t('general.letsGo')
443
- })
444
- })]
445
- }) : /*#__PURE__*/_jsxs(_Fragment, {
656
+ children: [hasGuideShown ? /*#__PURE__*/_jsxs(_Fragment, {
446
657
  children: [/*#__PURE__*/_jsx(FaceCamera, {
447
658
  onFacesDetected: onFacesDetected,
448
659
  onCameraInitialized: setCamera,
@@ -481,11 +692,13 @@ const LivenessDetectionScreen = () => {
481
692
  mask: "url(#hole-mask)"
482
693
  })]
483
694
  }), /*#__PURE__*/_jsx(View, {
484
- style: [styles.instructionsContainerTop, {
485
- paddingTop: insets.top
695
+ style: [styles.instructionsContainerBottom, {
696
+ top: PREVIEW_RECT.minY + PREVIEW_SIZE + 20
486
697
  }],
487
698
  children: /*#__PURE__*/_jsx(Text, {
488
- style: styles.instructions,
699
+ style: styles.action,
700
+ numberOfLines: 3,
701
+ adjustsFontSizeToFit: true,
489
702
  children: (() => {
490
703
  if (state.brightnessLow) {
491
704
  return t('livenessDetectionScreen.brightnessLow');
@@ -493,20 +706,52 @@ const LivenessDetectionScreen = () => {
493
706
  return t('livenessDetectionScreen.multipleFacesDetected');
494
707
  } else if (state.faceTooBig) {
495
708
  return t('livenessDetectionScreen.faceTooBig');
496
- } else if (state.faceDetected) {
497
- return t('livenessDetectionScreen.followInstructions');
498
- } else {
709
+ } else if (!state.faceDetected) {
499
710
  return t('livenessDetectionScreen.placeFaceInsideCircle');
711
+ } else {
712
+ return instructions[state.currentInstruction]?.instruction ?? '';
500
713
  }
501
714
  })()
502
715
  })
716
+ })]
717
+ }) : /*#__PURE__*/_jsxs(SafeAreaView, {
718
+ style: styles.guide,
719
+ children: [/*#__PURE__*/_jsx(LottieView, {
720
+ source: require('../../Shared/Animations/face-scan.json'),
721
+ style: styles.guideAnimation,
722
+ loop: true,
723
+ autoPlay: true
724
+ }), /*#__PURE__*/_jsx(Text, {
725
+ style: styles.guideHeader,
726
+ children: t('livenessDetectionScreen.guideHeader')
727
+ }), /*#__PURE__*/_jsxs(View, {
728
+ style: styles.guidePoints,
729
+ children: [/*#__PURE__*/_jsx(Text, {
730
+ style: styles.guideText,
731
+ children: t('livenessDetectionScreen.guideText')
732
+ }), /*#__PURE__*/_jsxs(Text, {
733
+ style: styles.guideText,
734
+ children: ["\u2022 ", t('livenessDetectionScreen.guidePoint1')]
735
+ }), /*#__PURE__*/_jsxs(Text, {
736
+ style: styles.guideText,
737
+ children: ["\u2022 ", t('livenessDetectionScreen.guidePoint2')]
738
+ }), /*#__PURE__*/_jsxs(Text, {
739
+ style: styles.guideText,
740
+ children: ["\u2022 ", t('livenessDetectionScreen.guidePoint3')]
741
+ }), /*#__PURE__*/_jsxs(Text, {
742
+ style: styles.guideText,
743
+ children: ["\u2022 ", t('livenessDetectionScreen.guidePoint4')]
744
+ })]
503
745
  }), /*#__PURE__*/_jsx(View, {
504
- style: [styles.instructionsContainerBottom, {
746
+ style: {
505
747
  paddingBottom: insets.bottom
506
- }],
507
- children: /*#__PURE__*/_jsx(Text, {
508
- style: styles.action,
509
- children: state.faceDetected && !state.faceTooBig && !state.multipleFacesDetected && !state.brightnessLow && instructions[state.currentInstruction]?.instruction
748
+ },
749
+ children: /*#__PURE__*/_jsx(StyledButton, {
750
+ mode: "contained",
751
+ onPress: () => {
752
+ setHasGuideShown(true);
753
+ },
754
+ children: t('general.letsGo')
510
755
  })
511
756
  })]
512
757
  }), /*#__PURE__*/_jsx(View, {
@@ -546,43 +791,23 @@ const styles = StyleSheet.create({
546
791
  left: 0,
547
792
  zIndex: 1
548
793
  },
549
- instructionsContainerTop: {
550
- flex: 1,
551
- position: 'absolute',
552
- top: 0,
553
- width: '100%',
554
- height: windowHeight / 4,
555
- justifyContent: 'flex-end',
556
- alignItems: 'center',
557
- zIndex: 1,
558
- padding: 20
559
- },
560
794
  instructionsContainerBottom: {
561
- flex: 1,
562
795
  position: 'absolute',
563
- bottom: 0,
564
796
  width: '100%',
565
- height: windowHeight / 4,
797
+ maxHeight: windowHeight / 4,
566
798
  justifyContent: 'flex-start',
567
799
  alignItems: 'center',
568
800
  zIndex: 1,
569
- padding: 20,
570
- gap: 20
571
- },
572
- instructions: {
573
- display: 'flex',
574
- alignItems: 'center',
575
- justifyContent: 'center',
576
- fontSize: 20,
577
- color: 'black'
801
+ paddingHorizontal: 20,
802
+ paddingVertical: 10
578
803
  },
579
804
  action: {
580
- display: 'flex',
581
- alignItems: 'center',
582
- justifyContent: 'center',
583
- fontSize: 24,
805
+ fontSize: 22,
584
806
  fontWeight: 'bold',
585
- color: 'black'
807
+ color: 'black',
808
+ textAlign: 'center',
809
+ lineHeight: 30,
810
+ paddingHorizontal: 10
586
811
  },
587
812
  footer: {
588
813
  position: 'absolute',