@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
@@ -0,0 +1,871 @@
1
+ package com.trustchex.reactnativesdk.camera
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.graphics.BitmapFactory
5
+ import android.graphics.ImageFormat
6
+ import android.graphics.Matrix
7
+ import android.graphics.Rect
8
+ import android.graphics.YuvImage
9
+ import android.util.Size
10
+ import android.widget.FrameLayout
11
+ import androidx.annotation.OptIn
12
+ import androidx.camera.core.*
13
+ import androidx.camera.lifecycle.ProcessCameraProvider
14
+ import androidx.camera.video.*
15
+ import androidx.camera.view.PreviewView
16
+ import androidx.core.content.ContextCompat
17
+ import androidx.lifecycle.LifecycleOwner
18
+ import com.facebook.react.bridge.Arguments
19
+ import com.facebook.react.bridge.ReactContext
20
+ import com.facebook.react.bridge.WritableMap
21
+ import com.facebook.react.uimanager.ThemedReactContext
22
+ import com.facebook.react.uimanager.events.RCTEventEmitter
23
+ import com.google.android.gms.tasks.Tasks
24
+ import com.google.mlkit.vision.barcode.BarcodeScanning
25
+ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
26
+ import com.google.mlkit.vision.barcode.common.Barcode
27
+ import com.google.mlkit.vision.common.InputImage
28
+ import com.trustchex.reactnativesdk.mrz.MRZValidator
29
+ import com.google.mlkit.vision.face.FaceDetection
30
+ import com.google.mlkit.vision.face.FaceDetectorOptions
31
+ import com.google.mlkit.vision.text.TextRecognition
32
+ import com.google.mlkit.vision.text.latin.TextRecognizerOptions
33
+ import java.io.ByteArrayOutputStream
34
+ import kotlin.math.max
35
+ import java.io.File
36
+ import java.util.concurrent.ExecutorService
37
+ import java.util.concurrent.Executors
38
+ import java.util.concurrent.atomic.AtomicBoolean
39
+
40
+ @SuppressLint("ViewConstructor")
41
+ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
42
+
43
+ // ==================================================================================
44
+ // PORTRAIT-ONLY CAMERA VIEW
45
+ // All camera use cases are locked to ROTATION_0 (portrait) orientation.
46
+ // Preview: 720x1280 (Portrait HD)
47
+ // ImageAnalysis: 720x1280 (Portrait HD for ML Kit)
48
+ // ImageCapture: 720x1280 (Portrait HD for frame capture during recording)
49
+ // VideoCapture: Quality.HD (CameraX auto-selects portrait HD resolution)
50
+ // Activity must be locked to portrait in AndroidManifest.xml
51
+ // ==================================================================================
52
+
53
+ private val reactContext: ReactContext = context
54
+ private val previewView: PreviewView = PreviewView(context).apply {
55
+ implementationMode = PreviewView.ImplementationMode.COMPATIBLE
56
+ scaleType = PreviewView.ScaleType.FILL_CENTER
57
+ }
58
+ private var cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
59
+ private var cameraProvider: ProcessCameraProvider? = null
60
+ private var camera: Camera? = null
61
+ private var preview: Preview? = null
62
+ private var imageAnalyzer: ImageAnalysis? = null
63
+ private var videoCapture: VideoCapture<Recorder>? = null
64
+ private var activeRecording: Recording? = null
65
+ private var currentRecordingFile: File? = null
66
+
67
+ private var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
68
+ private var torchEnabled = false
69
+ private var frameProcessingEnabled = false
70
+ private var targetFps = 6
71
+ private var isCameraInitialized = false
72
+ private var isStoppingRecording = false // Track if stopRecording was called to prevent cancelRecording from deleting the file
73
+
74
+ // ML Kit detection modes — controlled via props from JS
75
+ private var faceDetectionEnabled = false
76
+ private var textRecognitionEnabled = false
77
+ private var barcodeScanningEnabled = false
78
+ private var mrzValidationEnabled = false
79
+ private var includeBase64 = false
80
+
81
+ // ML Kit detector instances (lazy, created once)
82
+ private val textRecognizer by lazy {
83
+ TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
84
+ }
85
+ private val faceDetector by lazy {
86
+ FaceDetection.getClient(
87
+ FaceDetectorOptions.Builder()
88
+ .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
89
+ .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_NONE)
90
+ .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
91
+ .setMinFaceSize(0.1f) // 10% - small enough to detect faces reliably
92
+ .build()
93
+ )
94
+ }
95
+ private val barcodeScanner by lazy {
96
+ BarcodeScanning.getClient(
97
+ BarcodeScannerOptions.Builder()
98
+ .setBarcodeFormats(
99
+ Barcode.FORMAT_PDF417,
100
+ Barcode.FORMAT_QR_CODE,
101
+ Barcode.FORMAT_CODE_128,
102
+ Barcode.FORMAT_CODE_39,
103
+ Barcode.FORMAT_EAN_13,
104
+ Barcode.FORMAT_EAN_8,
105
+ Barcode.FORMAT_AZTEC,
106
+ Barcode.FORMAT_DATA_MATRIX
107
+ )
108
+ .build()
109
+ )
110
+ }
111
+
112
+ // Prevent concurrent ML Kit processing (drop frames while busy)
113
+ private val isProcessing = AtomicBoolean(false)
114
+
115
+ private var lastFrameTime = 0L
116
+ private val minFrameInterval get() = 1000L / targetFps
117
+
118
+ // React Native suppresses layout passes for custom views.
119
+ // Without this override, PreviewView's internal SurfaceView/TextureView
120
+ // stays at 0x0 dimensions and the preview is black.
121
+ private val measureAndLayout = Runnable {
122
+ measure(
123
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
124
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
125
+ )
126
+ layout(left, top, right, bottom)
127
+ }
128
+
129
+ override fun requestLayout() {
130
+ super.requestLayout()
131
+ // Post a forced layout pass so the PreviewView gets measured
132
+ post(measureAndLayout)
133
+ }
134
+
135
+ init {
136
+ addView(previewView, LayoutParams(
137
+ LayoutParams.MATCH_PARENT,
138
+ LayoutParams.MATCH_PARENT
139
+ ))
140
+
141
+ // Wait for view to be laid out before initializing camera
142
+ post {
143
+ initializeCamera()
144
+ }
145
+ }
146
+
147
+ fun setCameraType(type: String) {
148
+ val newSelector = selectBestCamera(type)
149
+ // Only reinitialize if the selector actually changed and camera is already running
150
+ if (newSelector != cameraSelector || !isCameraInitialized) {
151
+ cameraSelector = newSelector
152
+ if (isCameraInitialized) {
153
+ initializeCamera()
154
+ }
155
+ }
156
+ }
157
+
158
+ private fun selectBestCamera(type: String): CameraSelector {
159
+ if (type == "front") {
160
+ return CameraSelector.DEFAULT_FRONT_CAMERA
161
+ }
162
+
163
+ // For back camera, try to select Ultra Wide camera for document scanning
164
+ val cameraProvider = cameraProvider ?: return CameraSelector.DEFAULT_BACK_CAMERA
165
+
166
+ try {
167
+ // Check for Ultra Wide camera
168
+ val ultraWideSelector = CameraSelector.Builder()
169
+ .requireLensFacing(CameraSelector.LENS_FACING_BACK)
170
+ .build()
171
+
172
+ val cameras = cameraProvider.availableCameraInfos.filter {
173
+ ultraWideSelector.filter(listOf(it)).isNotEmpty()
174
+ }
175
+
176
+ // Heuristic: If we have 3+ back cameras, index 2 is often Ultra Wide
177
+ if (cameras.size >= 3) {
178
+ // Heuristic: If we have 3+ back cameras, index 2 is often Ultra Wide
179
+ }
180
+ } catch (e: Exception) {
181
+ android.util.Log.e("TrustchexCamera", "Failed to enumerate cameras: ${e.message}")
182
+ }
183
+
184
+ return CameraSelector.DEFAULT_BACK_CAMERA
185
+ }
186
+
187
+ fun setTorchEnabled(enabled: Boolean) {
188
+ torchEnabled = enabled
189
+ camera?.cameraControl?.enableTorch(enabled)
190
+ }
191
+
192
+ fun setFrameProcessingEnabled(enabled: Boolean) {
193
+ frameProcessingEnabled = enabled
194
+ }
195
+
196
+ fun setTargetFps(fps: Int) {
197
+ targetFps = fps.coerceIn(1, 30)
198
+ }
199
+
200
+ fun setFaceDetectionEnabled(enabled: Boolean) {
201
+ faceDetectionEnabled = enabled
202
+ }
203
+
204
+ fun setTextRecognitionEnabled(enabled: Boolean) {
205
+ textRecognitionEnabled = enabled
206
+ }
207
+
208
+ fun setBarcodeScanningEnabled(enabled: Boolean) {
209
+ barcodeScanningEnabled = enabled
210
+ }
211
+
212
+ fun setMrzValidationEnabled(enabled: Boolean) {
213
+ mrzValidationEnabled = enabled
214
+ }
215
+
216
+ fun setIncludeBase64(enabled: Boolean) {
217
+ includeBase64 = enabled
218
+ }
219
+
220
+ private fun initializeCamera() {
221
+ val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
222
+
223
+ cameraProviderFuture.addListener({
224
+ try {
225
+ cameraProvider = cameraProviderFuture.get()
226
+ bindCamera()
227
+ } catch (e: Exception) {
228
+ sendErrorEvent("Camera initialization failed: ${e.message}")
229
+ }
230
+ }, ContextCompat.getMainExecutor(context))
231
+ }
232
+
233
+ private fun bindCamera() {
234
+ val provider = cameraProvider ?: return
235
+
236
+ // Unbind previous use cases
237
+ provider.unbindAll()
238
+
239
+ try {
240
+ // Determine resolution based on camera type
241
+ // Front camera (liveness): HD (720x1280) for optimal performance
242
+ // Back camera (documents): Full HD (1080x1920) for sharp document capture
243
+ val isFrontCamera = cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA
244
+ val targetResolution = if (isFrontCamera) {
245
+ Size(720, 1280) // Portrait HD for front camera
246
+ } else {
247
+ Size(1080, 1920) // Portrait Full HD for back camera
248
+ }
249
+
250
+ // Preview use case
251
+ preview = Preview.Builder()
252
+ .setTargetResolution(targetResolution)
253
+ .setTargetRotation(android.view.Surface.ROTATION_0) // Portrait
254
+ .build()
255
+ .also {
256
+ it.setSurfaceProvider(previewView.surfaceProvider)
257
+ }
258
+
259
+ // Image analysis use case
260
+ imageAnalyzer = ImageAnalysis.Builder()
261
+ .setTargetResolution(targetResolution)
262
+ .setTargetRotation(android.view.Surface.ROTATION_0) // Portrait
263
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
264
+ .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
265
+ .build()
266
+ .also {
267
+ it.setAnalyzer(cameraExecutor, FrameAnalyzer())
268
+ }
269
+
270
+ // Image capture use case is NOT used in the current version of the SDK
271
+ // (Liveness detection uses ImageAnalyzer and VideoCapture)
272
+ // Removing it allows us to bind VideoCapture + Analyzer + Preview simultaneously
273
+ // within the 3-use-case limit of many Android devices.
274
+
275
+ // Video capture use case — PORTRAIT HD
276
+ val recorder = Recorder.Builder()
277
+ .setQualitySelector(QualitySelector.from(Quality.HD))
278
+ .build()
279
+ videoCapture = VideoCapture.withOutput(recorder)
280
+
281
+ // Bind all 3 essential use cases (Preview + Analyzer + Video)
282
+ // This ensures instant recording start without camera restart/jump
283
+ val useCases = mutableListOf<androidx.camera.core.UseCase>()
284
+ preview?.let { useCases.add(it) }
285
+ imageAnalyzer?.let { useCases.add(it) }
286
+ videoCapture?.let { useCases.add(it) }
287
+
288
+ val lifecycleOwner = (reactContext.currentActivity as? LifecycleOwner)
289
+ ?: run {
290
+ sendErrorEvent("Activity not available")
291
+ return
292
+ }
293
+
294
+ camera = provider.bindToLifecycle(
295
+ lifecycleOwner,
296
+ cameraSelector,
297
+ *useCases.toTypedArray()
298
+ )
299
+
300
+ isCameraInitialized = true
301
+
302
+ // Apply torch setting
303
+ if (torchEnabled) {
304
+ camera?.cameraControl?.enableTorch(true)
305
+ }
306
+
307
+ // Apply stable exposure and focus settings
308
+ applyCameraSettings()
309
+
310
+ // Force a layout pass so the preview surface gets sized correctly
311
+ post(measureAndLayout)
312
+
313
+ // Get exposure and focus capabilities
314
+ val cameraInfo = camera?.cameraInfo
315
+ val exposureState = cameraInfo?.exposureState
316
+ val minExposure = exposureState?.exposureCompensationRange?.lower ?: -2
317
+ val maxExposure = exposureState?.exposureCompensationRange?.upper ?: 2
318
+
319
+ sendReadyEvent(minExposure.toDouble(), maxExposure.toDouble())
320
+
321
+ } catch (e: Exception) {
322
+ sendErrorEvent("Camera binding failed: ${e.message}")
323
+ }
324
+ }
325
+
326
+ private inner class FrameAnalyzer : ImageAnalysis.Analyzer {
327
+
328
+ @OptIn(ExperimentalGetImage::class)
329
+ override fun analyze(imageProxy: ImageProxy) {
330
+ val currentTime = System.currentTimeMillis()
331
+
332
+ // Throttle to target FPS (6 frames per second = ~167ms per frame)
333
+ val timeSinceLastFrame = currentTime - lastFrameTime
334
+ if (timeSinceLastFrame < minFrameInterval) {
335
+ imageProxy.close()
336
+ return
337
+ }
338
+ lastFrameTime = currentTime
339
+
340
+ // Prevent concurrent ML Kit processing
341
+ val canProcess = isProcessing.compareAndSet(false, true)
342
+ if (!canProcess) {
343
+ imageProxy.close()
344
+ return
345
+ }
346
+
347
+ val mediaImage = imageProxy.image ?: run {
348
+ isProcessing.set(false)
349
+ imageProxy.close()
350
+ return
351
+ }
352
+
353
+ val hasAnyDetection = frameProcessingEnabled && (faceDetectionEnabled || textRecognitionEnabled || barcodeScanningEnabled)
354
+ if (!hasAnyDetection) {
355
+ isProcessing.set(false)
356
+ imageProxy.close()
357
+ return
358
+ }
359
+
360
+ // Create InputImage directly from camera frame — zero-copy, no base64 for ML Kit
361
+ val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
362
+
363
+ val rotationDegrees = imageProxy.imageInfo.rotationDegrees
364
+
365
+ // Calculate portrait-oriented dimensions early (JPEG is already rotated)
366
+ val isRotated = rotationDegrees == 90 || rotationDegrees == 270
367
+ val reportedWidth = if (isRotated) imageProxy.height else imageProxy.width
368
+ val reportedHeight = if (isRotated) imageProxy.width else imageProxy.height
369
+
370
+ // Compute brightness natively from Y plane (no base64 or bitmap needed)
371
+ // Brightness calculation restricted to scanning frame area (between 36% from top and 36% from bottom, 5% margins on sides)
372
+ val averageBrightness = computeYPlaneBrightness(imageProxy, reportedWidth, reportedHeight)
373
+
374
+ // Generate JPEG base64 only when JS side explicitly needs the image
375
+ // NOTE: Do NOT auto-generate for face detection - too expensive, causes frame drops
376
+ val jpegBase64: String? = if (includeBase64) {
377
+ yuvImageProxyToJpegBase64(imageProxy, rotationDegrees)
378
+ } else null
379
+
380
+ // Launch all enabled detectors in parallel using Google Tasks API
381
+ val tasks = mutableListOf<com.google.android.gms.tasks.Task<*>>()
382
+
383
+ val faceTask = if (faceDetectionEnabled) {
384
+ faceDetector.process(inputImage).also { tasks.add(it) }
385
+ } else null
386
+
387
+ val textTask = if (textRecognitionEnabled) {
388
+ textRecognizer.process(inputImage).also { tasks.add(it) }
389
+ } else null
390
+
391
+ val barcodeTask = if (barcodeScanningEnabled) {
392
+ barcodeScanner.process(inputImage).also { tasks.add(it) }
393
+ } else null
394
+
395
+ // Wait for all detectors, then build + send a single event to JS
396
+ Tasks.whenAllComplete(tasks).addOnCompleteListener { _ ->
397
+ try {
398
+ val frameData = Arguments.createMap()
399
+ frameData.putInt("width", reportedWidth)
400
+ frameData.putInt("height", reportedHeight)
401
+ frameData.putInt("orientation", 0) // Already corrected
402
+ frameData.putDouble("timestamp", currentTime.toDouble())
403
+ frameData.putDouble("brightness", averageBrightness)
404
+
405
+ if (jpegBase64 != null) {
406
+ frameData.putString("base64Image", jpegBase64)
407
+ }
408
+
409
+ // Face detection results
410
+ // ML Kit with InputImage.fromMediaImage(img, rotationDegrees) returns
411
+ // coordinates in the rotated (portrait) coordinate space — no transform needed.
412
+ if (faceTask != null) {
413
+ val facesArray = Arguments.createArray()
414
+ if (faceTask.isSuccessful) {
415
+ for (face in faceTask.result) {
416
+ val faceMap = Arguments.createMap()
417
+ val bounds = Arguments.createMap()
418
+ bounds.putInt("x", face.boundingBox.left)
419
+ bounds.putInt("y", face.boundingBox.top)
420
+ bounds.putInt("width", face.boundingBox.width())
421
+ bounds.putInt("height", face.boundingBox.height())
422
+ faceMap.putMap("bounds", bounds)
423
+ faceMap.putDouble("yawAngle", face.headEulerAngleY.toDouble())
424
+ faceMap.putDouble("pitchAngle", face.headEulerAngleX.toDouble())
425
+ faceMap.putDouble("rollAngle", face.headEulerAngleZ.toDouble())
426
+ if (face.trackingId != null) {
427
+ faceMap.putInt("trackingId", face.trackingId!!)
428
+ }
429
+ if (face.smilingProbability != null) {
430
+ faceMap.putDouble("smilingProbability", face.smilingProbability!!.toDouble())
431
+ }
432
+ if (face.leftEyeOpenProbability != null) {
433
+ faceMap.putDouble("leftEyeOpenProbability", face.leftEyeOpenProbability!!.toDouble())
434
+ }
435
+ if (face.rightEyeOpenProbability != null) {
436
+ faceMap.putDouble("rightEyeOpenProbability", face.rightEyeOpenProbability!!.toDouble())
437
+ }
438
+ facesArray.pushMap(faceMap)
439
+ }
440
+ }
441
+ frameData.putArray("faces", facesArray)
442
+ }
443
+
444
+ // Text recognition results
445
+ if (textTask != null) {
446
+ if (textTask.isSuccessful) {
447
+ val result = textTask.result
448
+ frameData.putString("resultText", result.text)
449
+
450
+ val blocksArray = Arguments.createArray()
451
+ for (block in result.textBlocks) {
452
+ val blockMap = Arguments.createMap()
453
+ blockMap.putString("text", block.text)
454
+ val bb = block.boundingBox
455
+ if (bb != null) {
456
+ val boxMap = Arguments.createMap()
457
+ boxMap.putInt("x", bb.left)
458
+ boxMap.putInt("y", bb.top)
459
+ boxMap.putInt("width", bb.width())
460
+ boxMap.putInt("height", bb.height())
461
+ boxMap.putInt("boundingCenterX", bb.centerX())
462
+ boxMap.putInt("boundingCenterY", bb.centerY())
463
+ blockMap.putMap("blockFrame", boxMap)
464
+ }
465
+ blocksArray.pushMap(blockMap)
466
+ }
467
+ frameData.putArray("textBlocks", blocksArray)
468
+ } else {
469
+ frameData.putString("resultText", "")
470
+ frameData.putArray("textBlocks", Arguments.createArray())
471
+ }
472
+ }
473
+
474
+ // MRZ validation (if enabled and text was recognized)
475
+ if (mrzValidationEnabled && textTask != null && textTask.isSuccessful) {
476
+ val rawText = textTask.result.text
477
+ val mrzResult = MRZValidator.validateWithCorrections(rawText)
478
+ val mrzMap = Arguments.createMap()
479
+ mrzMap.putBoolean("valid", mrzResult.valid)
480
+ mrzMap.putString("format", mrzResult.format)
481
+ mrzResult.error?.let { mrzMap.putString("error", it) }
482
+ mrzResult.documentCode?.let { mrzMap.putString("documentCode", it) }
483
+ mrzResult.issuingState?.let { mrzMap.putString("issuingState", it) }
484
+ mrzResult.documentNumber?.let { mrzMap.putString("documentNumber", it) }
485
+ mrzResult.lastName?.let { mrzMap.putString("lastName", it) }
486
+ mrzResult.firstName?.let { mrzMap.putString("firstName", it) }
487
+ mrzResult.birthDate?.let { mrzMap.putString("birthDate", it) }
488
+ mrzResult.sex?.let { mrzMap.putString("sex", it) }
489
+ mrzResult.expirationDate?.let { mrzMap.putString("expirationDate", it) }
490
+ mrzResult.nationality?.let { mrzMap.putString("nationality", it) }
491
+ mrzResult.optional1?.let { mrzMap.putString("optional1", it) }
492
+ mrzResult.optional2?.let { mrzMap.putString("optional2", it) }
493
+ mrzResult.rawLines?.let { mrzMap.putString("rawLines", it) }
494
+ frameData.putMap("mrzResult", mrzMap)
495
+ }
496
+
497
+ // Barcode scanning results
498
+ if (barcodeTask != null) {
499
+ val barcodesArray = Arguments.createArray()
500
+ if (barcodeTask.isSuccessful) {
501
+ for (barcode in barcodeTask.result) {
502
+ val barcodeMap = Arguments.createMap()
503
+ barcodeMap.putString("rawValue", barcode.rawValue ?: "")
504
+ barcodeMap.putString("displayValue", barcode.displayValue ?: "")
505
+ barcodeMap.putInt("format", barcode.format)
506
+ val bb = barcode.boundingBox
507
+ if (bb != null) {
508
+ val boxMap = Arguments.createMap()
509
+ boxMap.putInt("left", bb.left)
510
+ boxMap.putInt("top", bb.top)
511
+ boxMap.putInt("right", bb.right)
512
+ boxMap.putInt("bottom", bb.bottom)
513
+ barcodeMap.putMap("boundingBox", boxMap)
514
+ }
515
+ val cornerPoints = Arguments.createArray()
516
+ barcode.cornerPoints?.forEach { point ->
517
+ val pointMap = Arguments.createMap()
518
+ pointMap.putInt("x", point.x)
519
+ pointMap.putInt("y", point.y)
520
+ cornerPoints.pushMap(pointMap)
521
+ }
522
+ barcodeMap.putArray("cornerPoints", cornerPoints)
523
+ barcodesArray.pushMap(barcodeMap)
524
+ }
525
+ }
526
+ frameData.putArray("barcodes", barcodesArray)
527
+ }
528
+
529
+ sendFrameEvent(frameData)
530
+ } finally {
531
+ imageProxy.close()
532
+ isProcessing.set(false)
533
+ }
534
+ }
535
+ }
536
+
537
+ }
538
+
539
+ /**
540
+ * Fast brightness calculation directly from the Y plane — no allocation, no base64.
541
+ * Samples within the scanning frame area only (~100 points).
542
+ * Scanning frame: top 36%, bottom 36%, left 5%, right 5% of portrait dimensions.
543
+ * Returns average luma value (0-255).
544
+ */
545
+ private fun computeYPlaneBrightness(imageProxy: ImageProxy, portraitWidth: Int, portraitHeight: Int): Double {
546
+ val yPlane = imageProxy.planes[0]
547
+ val yBuffer = yPlane.buffer
548
+ val rowStride = yPlane.rowStride
549
+ val bufferWidth = imageProxy.width
550
+ val bufferHeight = imageProxy.height
551
+
552
+ // Define scanning frame area (from IdentityDocumentCamera.tsx styles)
553
+ // top: 36%, left: 5%, right: 5%, bottom: 36%
554
+ val scanTopPercent = 0.36
555
+ val scanBottomPercent = 0.36
556
+ val scanLeftPercent = 0.05
557
+ val scanRightPercent = 0.05
558
+
559
+ // Convert percentages to pixel coordinates in the portrait frame
560
+ val scanTop = (portraitHeight * scanTopPercent).toInt()
561
+ val scanBottom = (portraitHeight * (1.0 - scanBottomPercent)).toInt()
562
+ val scanLeft = (portraitWidth * scanLeftPercent).toInt()
563
+ val scanRight = (portraitWidth * (1.0 - scanRightPercent)).toInt()
564
+
565
+ // Map from portrait coordinates to buffer coordinates
566
+ // Portrait frame dimensions and buffer dimensions may differ in rotation
567
+ // Sample points proportionally within the scanning frame
568
+ val scanWidth = scanRight - scanLeft
569
+ val scanHeight = scanBottom - scanTop
570
+
571
+ var sum = 0L
572
+ var count = 0
573
+ val sampleCount = 100
574
+
575
+ for (i in 0..<sampleCount) {
576
+ // Distribute samples evenly within the scanning frame
577
+ val sampleOffsetX = (i * 17) % sampleCount
578
+ val sampleOffsetY = (i * 23) % sampleCount
579
+
580
+ val portraitX = scanLeft + (sampleOffsetX * scanWidth) / max(1, sampleCount - 1)
581
+ val portraitY = scanTop + (sampleOffsetY * scanHeight) / max(1, sampleCount - 1)
582
+
583
+ // Map portrait coordinates to buffer coordinates (accounting for rotation)
584
+ val bufferX = (portraitX * bufferWidth) / portraitWidth
585
+ val bufferY = (portraitY * bufferHeight) / portraitHeight
586
+
587
+ val clampedX = bufferX.coerceIn(0, bufferWidth - 1)
588
+ val clampedY = bufferY.coerceIn(0, bufferHeight - 1)
589
+
590
+ val bufferIndex = clampedY * rowStride + clampedX
591
+ if (bufferIndex < yBuffer.capacity()) {
592
+ sum += (yBuffer.get(bufferIndex).toInt() and 0xFF)
593
+ count++
594
+ }
595
+ }
596
+
597
+ return if (count > 0) sum.toDouble() / count else 0.0
598
+ }
599
+
600
+ private fun yuvImageProxyToJpegBase64(imageProxy: ImageProxy, rotationDegrees: Int): String? {
601
+ try {
602
+ val width = imageProxy.width
603
+ val height = imageProxy.height
604
+
605
+ // Check plane count
606
+ val planeCount = imageProxy.planes.size
607
+ if (planeCount < 3) {
608
+ android.util.Log.e("TrustchexCamera", "[yuvImageProxyToJpegBase64] ERROR: Expected 3 planes (YUV), got $planeCount")
609
+ return null
610
+ }
611
+
612
+ val yPlane = imageProxy.planes[0]
613
+ val uPlane = imageProxy.planes[1]
614
+ val vPlane = imageProxy.planes[2]
615
+
616
+ val yRowStride = yPlane.rowStride
617
+ val uvRowStride = uPlane.rowStride
618
+ val uvPixelStride = uPlane.pixelStride
619
+ val nv21 = ByteArray(width * height + width * (height / 2))
620
+
621
+ // Copy Y plane row-by-row, stripping any row padding
622
+ val yBuffer = yPlane.buffer
623
+ for (row in 0 until height) {
624
+ yBuffer.position(row * yRowStride)
625
+ yBuffer.get(nv21, row * width, width)
626
+ }
627
+
628
+ // Copy UV planes into interleaved VU (NV21) format
629
+ val uBuffer = uPlane.buffer
630
+ val vBuffer = vPlane.buffer
631
+ val uvHeight = height / 2
632
+ val uvWidth = width / 2
633
+ var nv21Offset = width * height
634
+
635
+ // Reset buffer positions to start
636
+ if (uvPixelStride == 2) {
637
+ val actualUvRowStride = if (uvRowStride >= uvWidth * 2) uvRowStride else uvWidth * 2
638
+
639
+ for (row in 0 until uvHeight) {
640
+ val rowOffset = row * actualUvRowStride
641
+ for (col in 0 until uvWidth) {
642
+ val uvIndex = rowOffset + col * uvPixelStride
643
+ if (uvIndex < vBuffer.capacity() && uvIndex < uBuffer.capacity()) {
644
+ nv21[nv21Offset++] = vBuffer.get(uvIndex)
645
+ nv21[nv21Offset++] = uBuffer.get(uvIndex)
646
+ } else {
647
+ android.util.Log.w("TrustchexCamera", "UV index out of bounds: $uvIndex at row=$row, col=$col")
648
+ nv21[nv21Offset++] = 128.toByte()
649
+ nv21[nv21Offset++] = 128.toByte()
650
+ }
651
+ }
652
+ }
653
+ }
654
+
655
+ // Convert YUV to JPEG with optimized quality (75% for faster encoding)
656
+ val strides = intArrayOf(width, width)
657
+ val yuvImage = YuvImage(nv21, ImageFormat.NV21, width, height, strides)
658
+ val jpegStream = ByteArrayOutputStream()
659
+ yuvImage.compressToJpeg(Rect(0, 0, width, height), 75, jpegStream)
660
+
661
+ // Rotate to portrait if needed
662
+ if (rotationDegrees != 0) {
663
+ val jpegBytes = jpegStream.toByteArray()
664
+ val bitmap = BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
665
+ val matrix = Matrix().apply { postRotate(rotationDegrees.toFloat()) }
666
+ val rotated = android.graphics.Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
667
+ bitmap.recycle()
668
+ val rotatedStream = ByteArrayOutputStream()
669
+ rotated.compress(android.graphics.Bitmap.CompressFormat.JPEG, 60, rotatedStream)
670
+ rotated.recycle()
671
+ return android.util.Base64.encodeToString(rotatedStream.toByteArray(), android.util.Base64.NO_WRAP)
672
+ }
673
+
674
+ return android.util.Base64.encodeToString(jpegStream.toByteArray(), android.util.Base64.NO_WRAP)
675
+ } catch (e: Exception) {
676
+ android.util.Log.e("TrustchexCamera", "[yuvImageProxyToJpegBase64] ERROR: ${e.message}", e)
677
+ return null
678
+ }
679
+ }
680
+
681
+ fun setFocusPoint(x: Double, y: Double) {
682
+ val factory = previewView.meteringPointFactory
683
+ val point = factory.createPoint(x.toFloat(), y.toFloat())
684
+ val action = FocusMeteringAction.Builder(
685
+ point,
686
+ FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE
687
+ ).build()
688
+
689
+ camera?.cameraControl?.startFocusAndMetering(action)
690
+ }
691
+
692
+ fun setExposureOffset(offset: Double) {
693
+ val exposureState = camera?.cameraInfo?.exposureState
694
+ val range = exposureState?.exposureCompensationRange
695
+ if (range != null) {
696
+ val index = offset.toInt().coerceIn(range.lower, range.upper)
697
+ camera?.cameraControl?.setExposureCompensationIndex(index)
698
+ }
699
+ }
700
+
701
+ @SuppressLint("MissingPermission")
702
+ fun startRecording() {
703
+ if (activeRecording != null) {
704
+ return
705
+ }
706
+
707
+ val videoCapture = videoCapture ?: run {
708
+ sendRecordingErrorEvent("VideoCapture not initialized")
709
+ return
710
+ }
711
+
712
+ try {
713
+ // Apply torch setting if needed
714
+ if (torchEnabled) {
715
+ camera?.cameraControl?.enableTorch(true)
716
+ }
717
+
718
+ // Apply stable camera settings
719
+ applyCameraSettings()
720
+
721
+ // Create output file in cache directory (not visible in gallery)
722
+ val fileName = "liveness_${System.currentTimeMillis()}.mp4"
723
+ val videoFile = File(context.cacheDir, fileName)
724
+ currentRecordingFile = videoFile
725
+ isStoppingRecording = false // Reset flag for new recording
726
+
727
+ val fileOutputOptions = FileOutputOptions.Builder(videoFile).build()
728
+
729
+ activeRecording = videoCapture.output
730
+ .prepareRecording(reactContext, fileOutputOptions)
731
+ .start(ContextCompat.getMainExecutor(reactContext)) { event ->
732
+ when (event) {
733
+ is VideoRecordEvent.Finalize -> {
734
+ activeRecording = null
735
+
736
+ if (event.hasError()) {
737
+ currentRecordingFile?.delete()
738
+ currentRecordingFile = null
739
+ sendRecordingErrorEvent("Recording error: ${event.cause?.message}")
740
+ } else {
741
+ val filePath = videoFile.absolutePath
742
+ sendRecordingFinishedEvent(filePath)
743
+ }
744
+ }
745
+ else -> {}
746
+ }
747
+ }
748
+ } catch (e: Exception) {
749
+ activeRecording = null
750
+ sendRecordingErrorEvent("Failed to start recording: ${e.message}")
751
+ }
752
+ }
753
+
754
+ fun stopRecording() {
755
+ isStoppingRecording = true // Mark that we're stopping normally
756
+ activeRecording?.stop()
757
+ }
758
+
759
+ fun cancelRecording() {
760
+ // Don't delete the file if stopRecording was already called - let it finalize normally
761
+ if (isStoppingRecording) {
762
+ return
763
+ }
764
+
765
+ activeRecording?.close()
766
+ activeRecording = null
767
+
768
+ // Delete incomplete recording file
769
+ currentRecordingFile?.let { file ->
770
+ try {
771
+ if (file.exists()) {
772
+ file.delete()
773
+ }
774
+ } catch (e: Exception) {
775
+ // Silently handle
776
+ }
777
+ currentRecordingFile = null
778
+ }
779
+ }
780
+
781
+ @OptIn(ExperimentalGetImage::class)
782
+ private fun applyCameraSettings() {
783
+ // CameraX manages continuous autofocus automatically
784
+ // We only need to set initial focus point at center
785
+ val camera = camera ?: return
786
+
787
+ try {
788
+ val centerPoint = previewView.meteringPointFactory.createPoint(0.5f, 0.5f)
789
+ val afAction = FocusMeteringAction.Builder(
790
+ centerPoint,
791
+ FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE
792
+ ).build()
793
+
794
+ camera.cameraControl.startFocusAndMetering(afAction)
795
+ } catch (e: Exception) {
796
+ // Silently handle - not critical
797
+ }
798
+ }
799
+ private fun sendFrameEvent(frameData: WritableMap) {
800
+ val event = Arguments.createMap()
801
+ event.putMap("frame", frameData)
802
+
803
+ reactContext
804
+ .getJSModule(RCTEventEmitter::class.java)
805
+ .receiveEvent(id, TrustchexCameraManager.EVENT_FRAME_AVAILABLE, event)
806
+ }
807
+
808
+ private fun sendReadyEvent(minExposure: Double, maxExposure: Double) {
809
+ val event = Arguments.createMap()
810
+ event.putDouble("minExposureOffset", minExposure)
811
+ event.putDouble("maxExposureOffset", maxExposure)
812
+
813
+ reactContext
814
+ .getJSModule(RCTEventEmitter::class.java)
815
+ .receiveEvent(id, TrustchexCameraManager.EVENT_CAMERA_READY, event)
816
+ }
817
+
818
+ private fun sendErrorEvent(error: String) {
819
+ val event = Arguments.createMap()
820
+ event.putString("error", error)
821
+
822
+ reactContext
823
+ .getJSModule(RCTEventEmitter::class.java)
824
+ .receiveEvent(id, TrustchexCameraManager.EVENT_CAMERA_ERROR, event)
825
+ }
826
+
827
+ private fun sendRecordingFinishedEvent(path: String) {
828
+ val event = Arguments.createMap()
829
+ event.putString("path", path)
830
+
831
+ try {
832
+ reactContext
833
+ .getJSModule(RCTEventEmitter::class.java)
834
+ .receiveEvent(id, TrustchexCameraManager.EVENT_RECORDING_FINISHED, event)
835
+ } catch (e: Exception) {
836
+ android.util.Log.e("TrustchexCamera", "Error sending recording finished event: ${e.message}", e)
837
+ }
838
+ }
839
+
840
+ private fun sendRecordingErrorEvent(error: String) {
841
+ val event = Arguments.createMap()
842
+ event.putString("error", error)
843
+
844
+ reactContext
845
+ .getJSModule(RCTEventEmitter::class.java)
846
+ .receiveEvent(id, TrustchexCameraManager.EVENT_RECORDING_ERROR, event)
847
+ }
848
+
849
+ fun deleteRecording(filePath: String) {
850
+ try {
851
+ val file = File(filePath)
852
+ if (file.exists()) {
853
+ file.delete()
854
+ }
855
+ } catch (e: Exception) {
856
+ // Silently handle
857
+ }
858
+ }
859
+
860
+ fun cleanup() {
861
+ // Cancel any active recording and delete file
862
+ if (activeRecording != null) {
863
+ activeRecording?.close()
864
+ currentRecordingFile?.delete()
865
+ currentRecordingFile = null
866
+ }
867
+
868
+ cameraProvider?.unbindAll()
869
+ cameraExecutor.shutdown()
870
+ }
871
+ }