@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.
- package/README.md +2 -9
- package/TrustchexSDK.podspec +5 -4
- package/android/build.gradle +6 -4
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +45 -25
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraManager.kt +168 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +871 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +245 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidationModule.kt +785 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidator.kt +419 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +818 -0
- package/ios/Camera/TrustchexCameraManager.m +37 -0
- package/ios/Camera/TrustchexCameraManager.swift +125 -0
- package/ios/Camera/TrustchexCameraView.swift +1176 -0
- package/ios/MLKit/MLKitModule.m +23 -0
- package/ios/MLKit/MLKitModule.swift +250 -0
- package/ios/MRZValidation.m +39 -0
- package/ios/MRZValidation.swift +802 -0
- package/ios/MRZValidator.swift +466 -0
- package/ios/OpenCV/OpenCVModule.h +4 -0
- package/ios/OpenCV/OpenCVModule.mm +810 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +2 -3
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +1 -2
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +418 -193
- package/lib/module/Screens/Static/OTPVerificationScreen.js +11 -11
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +5 -1
- package/lib/module/Screens/Static/ResultScreen.js +25 -2
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +25 -7
- package/lib/module/Shared/Components/DebugNavigationPanel.js +234 -24
- package/lib/module/Shared/Components/EIDScanner.js +99 -9
- package/lib/module/Shared/Components/FaceCamera.js +170 -179
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +2151 -771
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +109 -107
- package/lib/module/Shared/Components/TrustchexCamera.js +122 -0
- package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +91 -0
- package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +2 -124
- package/lib/module/Shared/EIDReader/tlv/tlvInputStream.js +4 -4
- package/lib/module/Shared/EIDReader/tlv/tlvOutputState.js +4 -4
- package/lib/module/Shared/EIDReader/tlv/tlvOutputStream.js +4 -4
- package/lib/module/Shared/Libs/analytics.utils.js +2 -2
- package/lib/module/Shared/Libs/debug.utils.js +132 -0
- package/lib/module/Shared/Libs/deeplink.utils.js +6 -5
- package/lib/module/Shared/Libs/demo.utils.js +13 -3
- package/lib/module/Shared/Libs/mrz.utils.js +1 -175
- package/lib/module/Shared/Libs/native-device-info.utils.js +12 -6
- package/lib/module/Shared/Libs/tts.utils.js +40 -6
- package/lib/module/Shared/Services/AnalyticsService.js +9 -8
- package/lib/module/Shared/Types/mrzFields.js +1 -0
- package/lib/module/Translation/Resources/en.js +87 -88
- package/lib/module/Translation/Resources/tr.js +84 -85
- package/lib/module/Trustchex.js +9 -2
- package/lib/module/index.js +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts +2 -2
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +18 -4
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -4
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts +2 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +124 -0
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts +11 -0
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts +2 -39
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/debug.utils.d.ts +42 -0
- package/lib/typescript/src/Shared/Libs/debug.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +0 -4
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/tts.utils.d.ts +4 -3
- package/lib/typescript/src/Shared/Libs/tts.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +2 -2
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts +11 -0
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts.map +1 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts +4 -5
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +4 -5
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts +2 -0
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +4 -35
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +1 -1
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +7 -5
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +2 -3
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +498 -216
- package/src/Screens/Static/OTPVerificationScreen.tsx +37 -31
- package/src/Screens/Static/QrCodeScanningScreen.tsx +8 -1
- package/src/Screens/Static/ResultScreen.tsx +136 -88
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +46 -13
- package/src/Shared/Components/DebugNavigationPanel.tsx +290 -34
- package/src/Shared/Components/EIDScanner.tsx +94 -16
- package/src/Shared/Components/FaceCamera.tsx +236 -203
- package/src/Shared/Components/IdentityDocumentCamera.tsx +3073 -1030
- package/src/Shared/Components/QrCodeScannerCamera.tsx +133 -127
- package/src/Shared/Components/TrustchexCamera.tsx +289 -0
- package/src/Shared/Config/camera-enhancement.config.ts +2 -2
- package/src/Shared/EIDReader/tlv/tlv.helpers.ts +96 -0
- package/src/Shared/EIDReader/tlv/tlv.utils.ts +2 -125
- package/src/Shared/EIDReader/tlv/tlvInputStream.ts +4 -4
- package/src/Shared/EIDReader/tlv/tlvOutputState.ts +4 -4
- package/src/Shared/EIDReader/tlv/tlvOutputStream.ts +4 -4
- package/src/Shared/Libs/analytics.utils.ts +48 -20
- package/src/Shared/Libs/debug.utils.ts +149 -0
- package/src/Shared/Libs/deeplink.utils.ts +7 -5
- package/src/Shared/Libs/demo.utils.ts +4 -0
- package/src/Shared/Libs/http-client.ts +12 -8
- package/src/Shared/Libs/mrz.utils.ts +1 -163
- package/src/Shared/Libs/native-device-info.utils.ts +12 -6
- package/src/Shared/Libs/tts.utils.ts +48 -6
- package/src/Shared/Services/AnalyticsService.ts +69 -24
- package/src/Shared/Types/identificationInfo.ts +2 -2
- package/src/Shared/Types/mrzFields.ts +29 -0
- package/src/Translation/Resources/en.ts +90 -100
- package/src/Translation/Resources/tr.ts +89 -97
- package/src/Translation/index.ts +1 -1
- package/src/Trustchex.tsx +21 -4
- package/src/index.tsx +14 -0
- package/src/version.ts +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/barcodescanner/BarcodeScannerFrameProcessorPlugin.kt +0 -301
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/BitmapUtils.kt +0 -205
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/CropperPlugin.kt +0 -72
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/FrameMetadata.kt +0 -4
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/facedetector/FaceDetectorFrameProcessorPlugin.kt +0 -303
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/textrecognition/TextRecognitionFrameProcessorPlugin.kt +0 -115
- package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin-Bridging-Header.h +0 -9
- package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.mm +0 -22
- package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.swift +0 -188
- package/ios/VisionCameraPlugins/Cropper/Cropper-Bridging-Header.h +0 -13
- package/ios/VisionCameraPlugins/Cropper/Cropper.h +0 -20
- package/ios/VisionCameraPlugins/Cropper/Cropper.mm +0 -22
- package/ios/VisionCameraPlugins/Cropper/Cropper.swift +0 -145
- package/ios/VisionCameraPlugins/Cropper/CropperUtils.swift +0 -49
- package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin-Bridging-Header.h +0 -4
- package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.mm +0 -22
- package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.swift +0 -320
- package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin-Bridging-Header.h +0 -4
- package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.mm +0 -27
- package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.swift +0 -144
- package/lib/module/Shared/Libs/camera.utils.js +0 -308
- package/lib/module/Shared/Libs/frame-enhancement.utils.js +0 -133
- package/lib/module/Shared/Libs/opencv.utils.js +0 -21
- package/lib/module/Shared/Libs/worklet.utils.js +0 -68
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.js +0 -46
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +0 -35
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/index.js +0 -19
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.js +0 -26
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/types.js +0 -3
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.js +0 -197
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.js +0 -101
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.js +0 -60
- package/lib/module/Shared/VisionCameraPlugins/Cropper/index.js +0 -47
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/Camera.js +0 -42
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/detectFaces.js +0 -35
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/index.js +0 -4
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/types.js +0 -3
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/Camera.js +0 -56
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.js +0 -20
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.js +0 -9
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/index.js +0 -6
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/scanText.js +0 -20
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/translateText.js +0 -19
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/types.js +0 -3
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +0 -87
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts +0 -25
- package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts +0 -3
- package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts +0 -9
- package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts +0 -13
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts +0 -6
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts +0 -12
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts +0 -52
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts +0 -62
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts +0 -34
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts +0 -32
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts +0 -23
- package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts +0 -9
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts +0 -79
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts +0 -6
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts +0 -5
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts +0 -67
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts.map +0 -1
- package/src/Shared/Libs/camera.utils.ts +0 -345
- package/src/Shared/Libs/frame-enhancement.utils.ts +0 -217
- package/src/Shared/Libs/opencv.utils.ts +0 -40
- package/src/Shared/Libs/worklet.utils.ts +0 -72
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.ts +0 -79
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +0 -46
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/index.ts +0 -60
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.ts +0 -32
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/types.ts +0 -82
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.ts +0 -195
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.ts +0 -135
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.ts +0 -84
- package/src/Shared/VisionCameraPlugins/Cropper/index.ts +0 -78
- package/src/Shared/VisionCameraPlugins/FaceDetector/Camera.tsx +0 -63
- package/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.ts +0 -44
- package/src/Shared/VisionCameraPlugins/FaceDetector/index.ts +0 -3
- package/src/Shared/VisionCameraPlugins/FaceDetector/types.ts +0 -99
- package/src/Shared/VisionCameraPlugins/TextRecognition/Camera.tsx +0 -76
- package/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.ts +0 -18
- package/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.ts +0 -7
- package/src/Shared/VisionCameraPlugins/TextRecognition/index.ts +0 -7
- package/src/Shared/VisionCameraPlugins/TextRecognition/scanText.ts +0 -27
- package/src/Shared/VisionCameraPlugins/TextRecognition/translateText.ts +0 -21
- 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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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 =>
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
progressFill:
|
|
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
|
-
|
|
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:
|
|
388
|
+
outside: previewRectInFrame,
|
|
234
389
|
inside: faceRectSmaller
|
|
235
390
|
});
|
|
236
391
|
const multipleFacesDetected = faces.length > 1;
|
|
237
392
|
if (!isImageBright) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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: '
|
|
274
|
-
payload:
|
|
484
|
+
type: 'RESET',
|
|
485
|
+
payload: undefined
|
|
275
486
|
});
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
if (state.faceTooBig) {
|
|
487
|
+
} else if (!state.faceTooBig) {
|
|
279
488
|
dispatch({
|
|
280
489
|
type: 'FACE_TOO_BIG',
|
|
281
|
-
payload:
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
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:
|
|
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: [
|
|
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.
|
|
485
|
-
|
|
695
|
+
style: [styles.instructionsContainerBottom, {
|
|
696
|
+
top: PREVIEW_RECT.minY + PREVIEW_SIZE + 20
|
|
486
697
|
}],
|
|
487
698
|
children: /*#__PURE__*/_jsx(Text, {
|
|
488
|
-
style: styles.
|
|
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:
|
|
746
|
+
style: {
|
|
505
747
|
paddingBottom: insets.bottom
|
|
506
|
-
}
|
|
507
|
-
children: /*#__PURE__*/_jsx(
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
797
|
+
maxHeight: windowHeight / 4,
|
|
566
798
|
justifyContent: 'flex-start',
|
|
567
799
|
alignItems: 'center',
|
|
568
800
|
zIndex: 1,
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
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',
|