omnipay-reactnative-sdk 1.2.2-beta.3 → 1.2.2-beta.6
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 +93 -43
- package/android/build.gradle +16 -15
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/omniretail/omnipay/OmnipayActivityPackage.java +2 -2
- package/android/src/main/java/com/omniretail/omnipay/OmnipayLivenessCameraView.java +153 -0
- package/android/src/main/java/com/omniretail/omnipay/OmnipayLivenessCameraViewManager.java +49 -0
- package/android/src/main/java/com/omniretail/omnipay/OmnipayLivenessModule.java +524 -0
- package/ios/OmnipayLivenessCameraView.h +15 -0
- package/ios/OmnipayLivenessCameraView.m +80 -0
- package/ios/OmnipayLivenessCameraViewManager.m +19 -0
- package/ios/OmnipayLivenessModule.h +38 -0
- package/ios/OmnipayLivenessModule.m +554 -0
- package/lib/commonjs/components/OmnipayProvider.js +2 -66
- package/lib/commonjs/components/OmnipayProvider.js.map +1 -1
- package/lib/commonjs/components/OmnipayView.js.map +1 -1
- package/lib/commonjs/components/biometrics/FaceVerification.js +252 -345
- package/lib/commonjs/components/biometrics/FaceVerification.js.map +1 -1
- package/lib/commonjs/components/biometrics/LivenessDetection.js +90 -198
- package/lib/commonjs/components/biometrics/LivenessDetection.js.map +1 -1
- package/lib/commonjs/components/biometrics/OmnipayLivenessCameraView.js +15 -0
- package/lib/commonjs/components/biometrics/OmnipayLivenessCameraView.js.map +1 -0
- package/lib/commonjs/components/biometrics/PermissionManager.js +279 -0
- package/lib/commonjs/components/biometrics/PermissionManager.js.map +1 -0
- package/lib/commonjs/components/biometrics/index.js +45 -0
- package/lib/commonjs/components/biometrics/index.js.map +1 -0
- package/lib/commonjs/components/biometrics/types.js +17 -0
- package/lib/commonjs/components/biometrics/types.js.map +1 -0
- package/lib/commonjs/components/views/BvnVerification.js.map +1 -1
- package/lib/commonjs/components/views/PaylaterAgreement.js.map +1 -1
- package/lib/commonjs/components/views/Registration.js.map +1 -1
- package/lib/commonjs/index.js +23 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/components/OmnipayProvider.js +3 -67
- package/lib/module/components/OmnipayProvider.js.map +1 -1
- package/lib/module/components/OmnipayView.js.map +1 -1
- package/lib/module/components/biometrics/FaceVerification.js +254 -346
- package/lib/module/components/biometrics/FaceVerification.js.map +1 -1
- package/lib/module/components/biometrics/LivenessDetection.js +75 -197
- package/lib/module/components/biometrics/LivenessDetection.js.map +1 -1
- package/lib/module/components/biometrics/OmnipayLivenessCameraView.js +7 -0
- package/lib/module/components/biometrics/OmnipayLivenessCameraView.js.map +1 -0
- package/lib/module/components/biometrics/PermissionManager.js +272 -0
- package/lib/module/components/biometrics/PermissionManager.js.map +1 -0
- package/lib/module/components/biometrics/index.js +12 -0
- package/lib/module/components/biometrics/index.js.map +1 -0
- package/lib/module/components/biometrics/types.js +16 -0
- package/lib/module/components/biometrics/types.js.map +1 -0
- package/lib/module/components/views/BvnVerification.js.map +1 -1
- package/lib/module/components/views/PaylaterAgreement.js.map +1 -1
- package/lib/module/components/views/Registration.js.map +1 -1
- package/lib/module/index.js +5 -4
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/{src/components → components}/OmnipayProvider.d.ts +1 -1
- package/lib/typescript/components/OmnipayProvider.d.ts.map +1 -0
- package/lib/typescript/{src/components → components}/OmnipayView.d.ts +21 -20
- package/lib/typescript/components/OmnipayView.d.ts.map +1 -0
- package/lib/typescript/components/biometrics/FaceVerification.d.ts +11 -0
- package/lib/typescript/components/biometrics/FaceVerification.d.ts.map +1 -0
- package/lib/typescript/components/biometrics/LivenessDetection.d.ts +33 -0
- package/lib/typescript/components/biometrics/LivenessDetection.d.ts.map +1 -0
- package/lib/typescript/components/biometrics/OmnipayLivenessCameraView.d.ts +18 -0
- package/lib/typescript/components/biometrics/OmnipayLivenessCameraView.d.ts.map +1 -0
- package/lib/typescript/components/biometrics/PermissionManager.d.ts +58 -0
- package/lib/typescript/components/biometrics/PermissionManager.d.ts.map +1 -0
- package/lib/typescript/components/biometrics/index.d.ts +5 -0
- package/lib/typescript/components/biometrics/index.d.ts.map +1 -0
- package/lib/typescript/components/biometrics/types.d.ts +73 -0
- package/lib/typescript/components/biometrics/types.d.ts.map +1 -0
- package/lib/typescript/{src/components → components}/views/BvnVerification.d.ts +2 -1
- package/lib/typescript/components/views/BvnVerification.d.ts.map +1 -0
- package/lib/typescript/{src/components → components}/views/PaylaterAgreement.d.ts +2 -1
- package/lib/typescript/components/views/PaylaterAgreement.d.ts.map +1 -0
- package/lib/typescript/{src/components → components}/views/Registration.d.ts +2 -1
- package/lib/typescript/components/views/Registration.d.ts.map +1 -0
- package/lib/typescript/functions.d.ts.map +1 -0
- package/lib/typescript/hooks/useOmnipay.d.ts +28 -0
- package/lib/typescript/hooks/useOmnipay.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +7 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/lib/colors.d.ts.map +1 -0
- package/lib/typescript/lib/config.d.ts.map +1 -0
- package/omnipay-reactnative-sdk.podspec +32 -29
- package/package.json +16 -11
- package/src/components/OmnipayProvider.tsx +3 -106
- package/src/components/OmnipayView.tsx +1 -1
- package/src/components/biometrics/FaceVerification.tsx +291 -368
- package/src/components/biometrics/LivenessDetection.ts +113 -250
- package/src/components/biometrics/OmnipayLivenessCameraView.tsx +19 -0
- package/src/components/biometrics/PermissionManager.ts +317 -0
- package/src/components/biometrics/index.ts +11 -0
- package/src/components/biometrics/types.ts +86 -0
- package/src/components/views/BvnVerification.tsx +1 -1
- package/src/components/views/PaylaterAgreement.tsx +1 -1
- package/src/components/views/Registration.tsx +1 -1
- package/src/index.tsx +4 -15
- package/android/src/main/java/com/omniretail/omnipay/LivenessCameraViewManager.java +0 -116
- package/android/src/main/java/com/omniretail/omnipay/LivenessDetectionModule.java +0 -588
- package/ios/LivenessCameraView.h +0 -22
- package/ios/LivenessCameraView.m +0 -135
- package/ios/LivenessCameraViewManager.h +0 -12
- package/ios/LivenessCameraViewManager.m +0 -24
- package/ios/LivenessDetectionModule.h +0 -46
- package/ios/LivenessDetectionModule.m +0 -603
- package/lib/commonjs/components/biometrics/LivenessCameraView.js +0 -45
- package/lib/commonjs/components/biometrics/LivenessCameraView.js.map +0 -1
- package/lib/module/components/biometrics/LivenessCameraView.js +0 -39
- package/lib/module/components/biometrics/LivenessCameraView.js.map +0 -1
- package/lib/typescript/demo/src/App.d.ts +0 -3
- package/lib/typescript/demo/src/App.d.ts.map +0 -1
- package/lib/typescript/demo/src/Body.d.ts +0 -3
- package/lib/typescript/demo/src/Body.d.ts.map +0 -1
- package/lib/typescript/demo/src/NotificationsExample.d.ts +0 -4
- package/lib/typescript/demo/src/NotificationsExample.d.ts.map +0 -1
- package/lib/typescript/src/components/OmnipayProvider.d.ts.map +0 -1
- package/lib/typescript/src/components/OmnipayView.d.ts.map +0 -1
- package/lib/typescript/src/components/biometrics/FaceVerification.d.ts +0 -12
- package/lib/typescript/src/components/biometrics/FaceVerification.d.ts.map +0 -1
- package/lib/typescript/src/components/biometrics/LivenessCameraView.d.ts +0 -22
- package/lib/typescript/src/components/biometrics/LivenessCameraView.d.ts.map +0 -1
- package/lib/typescript/src/components/biometrics/LivenessDetection.d.ts +0 -73
- package/lib/typescript/src/components/biometrics/LivenessDetection.d.ts.map +0 -1
- package/lib/typescript/src/components/views/BvnVerification.d.ts.map +0 -1
- package/lib/typescript/src/components/views/PaylaterAgreement.d.ts.map +0 -1
- package/lib/typescript/src/components/views/Registration.d.ts.map +0 -1
- package/lib/typescript/src/functions.d.ts.map +0 -1
- package/lib/typescript/src/hooks/useOmnipay.d.ts +0 -28
- package/lib/typescript/src/hooks/useOmnipay.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts +0 -8
- package/lib/typescript/src/index.d.ts.map +0 -1
- package/lib/typescript/src/lib/colors.d.ts.map +0 -1
- package/lib/typescript/src/lib/config.d.ts.map +0 -1
- package/src/components/biometrics/LivenessCameraView.tsx +0 -61
- /package/lib/typescript/{src/functions.d.ts → functions.d.ts} +0 -0
- /package/lib/typescript/{src/lib → lib}/colors.d.ts +0 -0
- /package/lib/typescript/{src/lib → lib}/config.d.ts +0 -0
|
@@ -1,484 +1,407 @@
|
|
|
1
|
-
import React, { useState, useEffect
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
|
-
Text,
|
|
5
4
|
Modal,
|
|
6
5
|
Dimensions,
|
|
7
|
-
Platform,
|
|
8
6
|
StyleSheet,
|
|
9
7
|
TouchableOpacity,
|
|
8
|
+
Image,
|
|
9
|
+
Text,
|
|
10
10
|
ActivityIndicator,
|
|
11
11
|
} from 'react-native';
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from './LivenessDetection';
|
|
17
|
-
import LivenessCameraView from './LivenessCameraView';
|
|
12
|
+
import { LivenessDetection } from './LivenessDetection';
|
|
13
|
+
import { OmnipayLivenessCameraView } from './OmnipayLivenessCameraView';
|
|
14
|
+
import { CameraPermissionManager, PermissionResult } from './PermissionManager';
|
|
15
|
+
import { LivenessChallenge, type LivenessConfig } from './types';
|
|
18
16
|
|
|
19
17
|
type FaceVerificationProps = {
|
|
20
18
|
onClose: () => void;
|
|
21
|
-
onSuccess: (result
|
|
19
|
+
onSuccess: (result?: any) => void;
|
|
22
20
|
primaryColor: string;
|
|
23
|
-
|
|
24
|
-
timeout?: number;
|
|
21
|
+
config?: Partial<LivenessConfig>;
|
|
25
22
|
};
|
|
26
23
|
|
|
27
|
-
type DetectionState =
|
|
24
|
+
type DetectionState =
|
|
25
|
+
| 'initializing'
|
|
26
|
+
| 'starting'
|
|
27
|
+
| 'running'
|
|
28
|
+
| 'completed'
|
|
29
|
+
| 'failed';
|
|
28
30
|
|
|
29
31
|
const FaceVerification: React.FC<FaceVerificationProps> = ({
|
|
30
32
|
onClose,
|
|
31
33
|
onSuccess,
|
|
32
34
|
primaryColor,
|
|
33
|
-
|
|
34
|
-
timeout = 300000, // 5 minutes default
|
|
35
|
+
config,
|
|
35
36
|
}) => {
|
|
36
|
-
const [
|
|
37
|
-
const [
|
|
38
|
-
const [
|
|
39
|
-
const [
|
|
40
|
-
|
|
37
|
+
const [state, setState] = useState<DetectionState>('initializing');
|
|
38
|
+
const [error, setError] = useState<string | null>(null);
|
|
39
|
+
const [isSupported, setIsSupported] = useState<boolean>(false);
|
|
40
|
+
const [currentChallenge, setCurrentChallenge] =
|
|
41
|
+
useState<LivenessChallenge | null>(null);
|
|
42
|
+
const [challengeProgress, setChallengeProgress] = useState<string>('');
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
checkSupport();
|
|
46
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const checkSupport = async () => {
|
|
50
|
+
try {
|
|
51
|
+
const supported = await LivenessDetection.isSupported();
|
|
52
|
+
setIsSupported(supported);
|
|
53
|
+
if (supported) {
|
|
54
|
+
await startDetection();
|
|
55
|
+
} else {
|
|
56
|
+
setState('failed');
|
|
57
|
+
setError('Liveness detection is not supported on this device');
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
setState('failed');
|
|
61
|
+
setError('Failed to initialize liveness detection');
|
|
55
62
|
}
|
|
56
63
|
};
|
|
57
64
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
const startDetection = async () => {
|
|
66
|
+
if (!isSupported) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setState('starting');
|
|
71
|
+
setError(null);
|
|
63
72
|
|
|
73
|
+
try {
|
|
64
74
|
// Check and request camera permission first
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
const permissionResponse =
|
|
76
|
+
await CameraPermissionManager.requestCameraPermission();
|
|
77
|
+
|
|
78
|
+
if (permissionResponse.result !== PermissionResult.GRANTED) {
|
|
79
|
+
setState('failed');
|
|
80
|
+
setError(
|
|
81
|
+
permissionResponse.result === PermissionResult.BLOCKED
|
|
82
|
+
? 'Camera permission is blocked. Please enable it in device settings.'
|
|
83
|
+
: 'Camera permission is required for face verification.'
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
73
86
|
}
|
|
74
87
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
const livenessConfig = {
|
|
89
|
+
...LivenessDetection.getDefaultConfig(),
|
|
90
|
+
...config,
|
|
78
91
|
};
|
|
79
92
|
|
|
80
|
-
await LivenessDetection.
|
|
93
|
+
const result = await LivenessDetection.startDetection(livenessConfig, {
|
|
81
94
|
onChallengeStart: (challenge) => {
|
|
82
95
|
setCurrentChallenge(challenge);
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
setState('running');
|
|
97
|
+
setChallengeProgress(getChallengeInstruction(challenge));
|
|
85
98
|
},
|
|
86
|
-
onChallengeSuccess: (challenge) => {
|
|
87
|
-
|
|
99
|
+
onChallengeSuccess: (challenge, _challengeResult) => {
|
|
100
|
+
setChallengeProgress(
|
|
101
|
+
`✓ ${getChallengeInstruction(challenge)} completed`
|
|
102
|
+
);
|
|
88
103
|
},
|
|
89
104
|
onChallengeFailure: (challenge, reason) => {
|
|
90
|
-
|
|
91
|
-
// Continue with next challenge - the native module handles this
|
|
105
|
+
setError(`Failed: ${reason}, ${challenge}`);
|
|
92
106
|
},
|
|
93
|
-
onAllChallengesComplete: (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
onAllChallengesComplete: () => {
|
|
108
|
+
setChallengeProgress(
|
|
109
|
+
'All challenges completed! Taking final photo...'
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
onScreenshotCaptured: (_screenshot) => {
|
|
113
|
+
// Screenshot captured, detection will complete soon
|
|
114
|
+
},
|
|
115
|
+
onDetectionFailed: (reason) => {
|
|
116
|
+
setError(reason);
|
|
117
|
+
setState('failed');
|
|
97
118
|
},
|
|
98
119
|
});
|
|
120
|
+
|
|
121
|
+
if (result.success) {
|
|
122
|
+
setState('completed');
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
onSuccess(result);
|
|
125
|
+
}, 1000); // Brief delay to show success state
|
|
126
|
+
} else {
|
|
127
|
+
setState('failed');
|
|
128
|
+
setError(result.failureReason || 'Face verification failed');
|
|
129
|
+
}
|
|
99
130
|
} catch (err) {
|
|
100
|
-
|
|
131
|
+
setState('failed');
|
|
101
132
|
setError(err instanceof Error ? err.message : 'Unknown error occurred');
|
|
102
|
-
setDetectionState('error');
|
|
103
|
-
setIsLoading(false);
|
|
104
|
-
}
|
|
105
|
-
}, [challenges, timeout, onSuccess]);
|
|
106
|
-
|
|
107
|
-
const handleClose = useCallback(() => {
|
|
108
|
-
if (detectionState === 'active' || detectionState === 'starting') {
|
|
109
|
-
LivenessDetection.stopDetection();
|
|
110
133
|
}
|
|
111
|
-
|
|
112
|
-
}, [detectionState, onClose]);
|
|
113
|
-
|
|
114
|
-
const handleRetry = useCallback(() => {
|
|
115
|
-
setDetectionState('idle');
|
|
116
|
-
setError('');
|
|
117
|
-
setCompletedChallenges([]);
|
|
118
|
-
setCurrentChallenge('');
|
|
119
|
-
}, []);
|
|
134
|
+
};
|
|
120
135
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
136
|
+
const getChallengeInstruction = (challenge: LivenessChallenge): string => {
|
|
137
|
+
switch (challenge) {
|
|
138
|
+
case LivenessChallenge.SMILE:
|
|
139
|
+
return 'Please smile';
|
|
140
|
+
case LivenessChallenge.BLINK:
|
|
141
|
+
return 'Please blink';
|
|
142
|
+
case LivenessChallenge.TURN_LEFT:
|
|
143
|
+
return 'Please turn your head left';
|
|
144
|
+
case LivenessChallenge.TURN_RIGHT:
|
|
145
|
+
return 'Please turn your head right';
|
|
146
|
+
default:
|
|
147
|
+
return 'Follow the instruction';
|
|
129
148
|
}
|
|
130
|
-
|
|
131
|
-
}, [detectionState, startLivenessDetection]);
|
|
132
|
-
|
|
133
|
-
// Cleanup on unmount
|
|
134
|
-
useEffect(() => {
|
|
135
|
-
return () => {
|
|
136
|
-
LivenessDetection.cleanup();
|
|
137
|
-
};
|
|
138
|
-
}, []);
|
|
149
|
+
};
|
|
139
150
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
const getStateMessage = (): string => {
|
|
152
|
+
switch (state) {
|
|
153
|
+
case 'initializing':
|
|
154
|
+
return 'Initializing camera...';
|
|
155
|
+
case 'starting':
|
|
156
|
+
return 'Starting face verification...';
|
|
157
|
+
case 'running':
|
|
158
|
+
return challengeProgress || 'Look at the camera';
|
|
159
|
+
case 'completed':
|
|
160
|
+
return 'Face verification completed!';
|
|
161
|
+
case 'failed':
|
|
162
|
+
return error || 'Face verification failed';
|
|
163
|
+
default:
|
|
164
|
+
return 'Please wait...';
|
|
154
165
|
}
|
|
166
|
+
};
|
|
155
167
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
168
|
+
const getStateColor = (): string => {
|
|
169
|
+
switch (state) {
|
|
170
|
+
case 'completed':
|
|
171
|
+
return '#4CAF50';
|
|
172
|
+
case 'failed':
|
|
173
|
+
return '#F44336';
|
|
174
|
+
case 'running':
|
|
175
|
+
return primaryColor;
|
|
176
|
+
default:
|
|
177
|
+
return '#666';
|
|
165
178
|
}
|
|
179
|
+
};
|
|
166
180
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
scaleType="fillCenter"
|
|
174
|
-
onCameraReady={() => console.log('Camera ready')}
|
|
175
|
-
onCameraError={(error) => {
|
|
176
|
-
console.error('Camera error:', error);
|
|
177
|
-
setError('Camera initialization failed');
|
|
178
|
-
setDetectionState('error');
|
|
179
|
-
}}
|
|
180
|
-
/>
|
|
181
|
-
|
|
182
|
-
{/* Overlay with instructions */}
|
|
183
|
-
<View style={styles.overlay}>
|
|
184
|
-
{isLoading ? (
|
|
185
|
-
<View style={styles.loadingContainer}>
|
|
186
|
-
<ActivityIndicator size="large" color={primaryColor} />
|
|
187
|
-
<Text style={styles.loadingText}>Initializing camera...</Text>
|
|
188
|
-
</View>
|
|
189
|
-
) : (
|
|
190
|
-
<>
|
|
191
|
-
{/* Current challenge instruction */}
|
|
192
|
-
{currentChallenge && (
|
|
193
|
-
<View style={styles.instructionContainer}>
|
|
194
|
-
<Text style={styles.instructionText}>
|
|
195
|
-
{getChallengeInstruction(currentChallenge)}
|
|
196
|
-
</Text>
|
|
197
|
-
</View>
|
|
198
|
-
)}
|
|
199
|
-
|
|
200
|
-
{/* Progress indicator */}
|
|
201
|
-
<View style={styles.progressContainer}>
|
|
202
|
-
<Text style={styles.progressText}>
|
|
203
|
-
{completedChallenges.length} / {challenges.length} completed
|
|
204
|
-
</Text>
|
|
205
|
-
<View style={styles.progressBar}>
|
|
206
|
-
<View
|
|
207
|
-
style={[
|
|
208
|
-
styles.progressFill,
|
|
209
|
-
{
|
|
210
|
-
width: `${
|
|
211
|
-
(completedChallenges.length / challenges.length) *
|
|
212
|
-
100
|
|
213
|
-
}%`,
|
|
214
|
-
backgroundColor: primaryColor,
|
|
215
|
-
},
|
|
216
|
-
]}
|
|
217
|
-
/>
|
|
218
|
-
</View>
|
|
219
|
-
</View>
|
|
220
|
-
</>
|
|
221
|
-
)}
|
|
222
|
-
</View>
|
|
223
|
-
</View>
|
|
224
|
-
</View>
|
|
225
|
-
);
|
|
181
|
+
const handleRetry = () => {
|
|
182
|
+
setState('initializing');
|
|
183
|
+
setError(null);
|
|
184
|
+
setCurrentChallenge(null);
|
|
185
|
+
setChallengeProgress('');
|
|
186
|
+
checkSupport();
|
|
226
187
|
};
|
|
227
188
|
|
|
189
|
+
const showRetryButton =
|
|
190
|
+
state === 'failed' &&
|
|
191
|
+
error !==
|
|
192
|
+
'Camera permission is blocked. Please enable it in device settings.';
|
|
193
|
+
|
|
228
194
|
return (
|
|
229
195
|
<Modal
|
|
230
196
|
visible={true}
|
|
231
197
|
transparent={true}
|
|
232
|
-
onRequestClose={
|
|
198
|
+
onRequestClose={onClose}
|
|
233
199
|
style={styles.modal}
|
|
234
|
-
animationType="slide"
|
|
235
200
|
>
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
201
|
+
<TouchableOpacity
|
|
202
|
+
style={styles.backdrop}
|
|
203
|
+
activeOpacity={1}
|
|
204
|
+
onPress={() => {}}
|
|
205
|
+
>
|
|
206
|
+
<View style={[styles.container]}>
|
|
207
|
+
<TouchableOpacity style={styles.close} onPress={onClose}>
|
|
208
|
+
<Image
|
|
209
|
+
source={require('../../assets/cancel.png')}
|
|
210
|
+
style={styles.closeIcon}
|
|
211
|
+
/>
|
|
212
|
+
</TouchableOpacity>
|
|
213
|
+
|
|
214
|
+
<View style={styles.contentContainer}>
|
|
240
215
|
<Text style={styles.title}>Face Verification</Text>
|
|
241
|
-
<TouchableOpacity onPress={handleClose} style={styles.closeButton}>
|
|
242
|
-
<Text style={styles.closeText}>✕</Text>
|
|
243
|
-
</TouchableOpacity>
|
|
244
|
-
</View>
|
|
245
216
|
|
|
246
|
-
|
|
217
|
+
{/* Camera Preview */}
|
|
218
|
+
<View style={styles.cameraContainer}>
|
|
219
|
+
{state === 'running' && isSupported ? (
|
|
220
|
+
<OmnipayLivenessCameraView style={styles.camera} />
|
|
221
|
+
) : (
|
|
222
|
+
<View style={[styles.camera, styles.cameraPlaceholder]}>
|
|
223
|
+
{state === 'initializing' || state === 'starting' ? (
|
|
224
|
+
<ActivityIndicator size="large" color={primaryColor} />
|
|
225
|
+
) : state === 'completed' ? (
|
|
226
|
+
<View style={styles.successIcon}>
|
|
227
|
+
<Text style={styles.successText}>✓</Text>
|
|
228
|
+
</View>
|
|
229
|
+
) : (
|
|
230
|
+
<View style={styles.errorIcon}>
|
|
231
|
+
<Text style={styles.errorText}>!</Text>
|
|
232
|
+
</View>
|
|
233
|
+
)}
|
|
234
|
+
</View>
|
|
235
|
+
)}
|
|
236
|
+
|
|
237
|
+
{/* Overlay with instructions */}
|
|
238
|
+
<View style={styles.overlay}>
|
|
239
|
+
<Text style={[styles.instruction, { color: getStateColor() }]}>
|
|
240
|
+
{getStateMessage()}
|
|
241
|
+
</Text>
|
|
242
|
+
</View>
|
|
243
|
+
</View>
|
|
244
|
+
|
|
245
|
+
{/* Progress indicator for challenges */}
|
|
246
|
+
{state === 'running' && currentChallenge && (
|
|
247
|
+
<View style={styles.progressContainer}>
|
|
248
|
+
<Text style={styles.challengeText}>
|
|
249
|
+
{getChallengeInstruction(currentChallenge)}
|
|
250
|
+
</Text>
|
|
251
|
+
</View>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{/* Action buttons */}
|
|
255
|
+
<View style={styles.buttonContainer}>
|
|
256
|
+
{showRetryButton && (
|
|
257
|
+
<TouchableOpacity
|
|
258
|
+
style={[
|
|
259
|
+
styles.retryButton,
|
|
260
|
+
{ backgroundColor: primaryColor },
|
|
261
|
+
]}
|
|
262
|
+
onPress={handleRetry}
|
|
263
|
+
>
|
|
264
|
+
<Text style={styles.retryButtonText}>Try Again</Text>
|
|
265
|
+
</TouchableOpacity>
|
|
266
|
+
)}
|
|
267
|
+
</View>
|
|
268
|
+
</View>
|
|
247
269
|
</View>
|
|
248
|
-
</
|
|
270
|
+
</TouchableOpacity>
|
|
249
271
|
</Modal>
|
|
250
272
|
);
|
|
251
273
|
};
|
|
252
274
|
|
|
253
|
-
|
|
275
|
+
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
|
|
254
276
|
|
|
255
277
|
const styles = StyleSheet.create({
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
},
|
|
259
|
-
full: {
|
|
260
|
-
flex: 1,
|
|
261
|
-
width: '100%',
|
|
262
|
-
height: '100%',
|
|
278
|
+
modal: {
|
|
279
|
+
margin: 0,
|
|
263
280
|
},
|
|
264
|
-
|
|
281
|
+
backdrop: {
|
|
265
282
|
flex: 1,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
borderTopRightRadius: 20,
|
|
270
|
-
borderTopLeftRadius: 20,
|
|
271
|
-
paddingTop: 150,
|
|
283
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
284
|
+
justifyContent: 'center',
|
|
285
|
+
alignItems: 'center',
|
|
272
286
|
},
|
|
273
|
-
|
|
274
|
-
|
|
287
|
+
container: {
|
|
288
|
+
width: screenWidth * 0.9,
|
|
289
|
+
maxHeight: screenHeight * 0.8,
|
|
275
290
|
backgroundColor: 'white',
|
|
291
|
+
borderRadius: 16,
|
|
292
|
+
padding: 20,
|
|
276
293
|
alignItems: 'center',
|
|
277
|
-
justifyContent: 'center',
|
|
278
|
-
flex: 1,
|
|
279
|
-
width: '100%',
|
|
280
|
-
height: '100%',
|
|
281
|
-
position: 'absolute',
|
|
282
|
-
top: 0,
|
|
283
|
-
left: 0,
|
|
284
|
-
borderTopRightRadius: 20,
|
|
285
|
-
borderTopLeftRadius: 20,
|
|
286
294
|
},
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
295
|
+
close: {
|
|
296
|
+
position: 'absolute',
|
|
297
|
+
top: 15,
|
|
298
|
+
right: 15,
|
|
299
|
+
zIndex: 10,
|
|
300
|
+
padding: 5,
|
|
293
301
|
},
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
maxHeight: Dimensions.get('window').height - 40,
|
|
299
|
-
flex: 1,
|
|
300
|
-
position: 'relative',
|
|
301
|
-
...(Platform.OS === 'android' && { overflow: 'hidden' }),
|
|
302
|
+
closeIcon: {
|
|
303
|
+
width: 20,
|
|
304
|
+
height: 20,
|
|
305
|
+
tintColor: '#666',
|
|
302
306
|
},
|
|
303
|
-
|
|
304
|
-
flex: 1,
|
|
305
|
-
backgroundColor: 'rgba(0,0,0,0.48)',
|
|
306
|
-
height: '100%',
|
|
307
|
+
contentContainer: {
|
|
307
308
|
width: '100%',
|
|
308
|
-
},
|
|
309
|
-
header: {
|
|
310
|
-
flexDirection: 'row',
|
|
311
|
-
justifyContent: 'space-between',
|
|
312
309
|
alignItems: 'center',
|
|
313
|
-
|
|
314
|
-
borderBottomWidth: 1,
|
|
315
|
-
borderBottomColor: '#f0f0f0',
|
|
310
|
+
paddingTop: 20,
|
|
316
311
|
},
|
|
317
312
|
title: {
|
|
318
|
-
fontSize:
|
|
319
|
-
fontWeight: '
|
|
313
|
+
fontSize: 24,
|
|
314
|
+
fontWeight: 'bold',
|
|
320
315
|
color: '#333',
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
width: 30,
|
|
324
|
-
height: 30,
|
|
325
|
-
borderRadius: 15,
|
|
326
|
-
backgroundColor: '#f0f0f0',
|
|
327
|
-
alignItems: 'center',
|
|
328
|
-
justifyContent: 'center',
|
|
329
|
-
},
|
|
330
|
-
closeText: {
|
|
331
|
-
fontSize: 16,
|
|
332
|
-
color: '#666',
|
|
333
|
-
},
|
|
334
|
-
contentContainer: {
|
|
335
|
-
flex: 1,
|
|
316
|
+
marginBottom: 20,
|
|
317
|
+
textAlign: 'center',
|
|
336
318
|
},
|
|
337
319
|
cameraContainer: {
|
|
338
|
-
|
|
320
|
+
width: screenWidth * 0.7,
|
|
321
|
+
height: screenWidth * 0.9,
|
|
322
|
+
borderRadius: 12,
|
|
323
|
+
overflow: 'hidden',
|
|
324
|
+
backgroundColor: '#f0f0f0',
|
|
339
325
|
position: 'relative',
|
|
326
|
+
marginBottom: 20,
|
|
340
327
|
},
|
|
341
328
|
camera: {
|
|
342
329
|
flex: 1,
|
|
343
330
|
width: '100%',
|
|
344
331
|
height: '100%',
|
|
345
332
|
},
|
|
333
|
+
cameraPlaceholder: {
|
|
334
|
+
justifyContent: 'center',
|
|
335
|
+
alignItems: 'center',
|
|
336
|
+
backgroundColor: '#f5f5f5',
|
|
337
|
+
},
|
|
346
338
|
overlay: {
|
|
347
339
|
position: 'absolute',
|
|
348
|
-
|
|
340
|
+
bottom: 0,
|
|
349
341
|
left: 0,
|
|
350
342
|
right: 0,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
alignItems: 'center',
|
|
354
|
-
paddingVertical: 40,
|
|
355
|
-
paddingHorizontal: 20,
|
|
356
|
-
},
|
|
357
|
-
loadingContainer: {
|
|
358
|
-
flex: 1,
|
|
359
|
-
justifyContent: 'center',
|
|
343
|
+
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
344
|
+
padding: 15,
|
|
360
345
|
alignItems: 'center',
|
|
361
346
|
},
|
|
362
|
-
|
|
363
|
-
color: 'white',
|
|
347
|
+
instruction: {
|
|
364
348
|
fontSize: 16,
|
|
365
|
-
|
|
349
|
+
fontWeight: '600',
|
|
366
350
|
textAlign: 'center',
|
|
367
|
-
},
|
|
368
|
-
instructionContainer: {
|
|
369
|
-
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
370
|
-
paddingHorizontal: 20,
|
|
371
|
-
paddingVertical: 12,
|
|
372
|
-
borderRadius: 8,
|
|
373
|
-
marginTop: 20,
|
|
374
|
-
},
|
|
375
|
-
instructionText: {
|
|
376
351
|
color: 'white',
|
|
377
|
-
fontSize: 16,
|
|
378
|
-
fontWeight: '500',
|
|
379
|
-
textAlign: 'center',
|
|
380
352
|
},
|
|
381
353
|
progressContainer: {
|
|
382
|
-
|
|
383
|
-
paddingHorizontal: 20,
|
|
384
|
-
paddingVertical: 12,
|
|
385
|
-
borderRadius: 8,
|
|
386
|
-
minWidth: 200,
|
|
387
|
-
},
|
|
388
|
-
progressText: {
|
|
389
|
-
color: 'white',
|
|
390
|
-
fontSize: 14,
|
|
391
|
-
textAlign: 'center',
|
|
392
|
-
marginBottom: 8,
|
|
393
|
-
},
|
|
394
|
-
progressBar: {
|
|
395
|
-
height: 4,
|
|
396
|
-
backgroundColor: 'rgba(255,255,255,0.3)',
|
|
397
|
-
borderRadius: 2,
|
|
398
|
-
overflow: 'hidden',
|
|
399
|
-
},
|
|
400
|
-
progressFill: {
|
|
401
|
-
height: '100%',
|
|
402
|
-
borderRadius: 2,
|
|
403
|
-
},
|
|
404
|
-
errorContainer: {
|
|
405
|
-
flex: 1,
|
|
406
|
-
justifyContent: 'center',
|
|
354
|
+
marginBottom: 20,
|
|
407
355
|
alignItems: 'center',
|
|
408
|
-
paddingHorizontal: 20,
|
|
409
356
|
},
|
|
410
|
-
|
|
411
|
-
fontSize:
|
|
357
|
+
challengeText: {
|
|
358
|
+
fontSize: 18,
|
|
412
359
|
fontWeight: '600',
|
|
413
|
-
color: '#
|
|
414
|
-
marginBottom: 8,
|
|
415
|
-
textAlign: 'center',
|
|
416
|
-
},
|
|
417
|
-
errorSubtitle: {
|
|
360
|
+
color: '#333',
|
|
418
361
|
textAlign: 'center',
|
|
419
|
-
fontSize: 14,
|
|
420
|
-
color: '#5e7079',
|
|
421
|
-
marginBottom: 20,
|
|
422
|
-
paddingHorizontal: 8,
|
|
423
362
|
},
|
|
424
|
-
|
|
425
|
-
|
|
363
|
+
buttonContainer: {
|
|
364
|
+
flexDirection: 'row',
|
|
426
365
|
justifyContent: 'center',
|
|
366
|
+
gap: 15,
|
|
367
|
+
},
|
|
368
|
+
retryButton: {
|
|
369
|
+
paddingHorizontal: 30,
|
|
370
|
+
paddingVertical: 12,
|
|
371
|
+
borderRadius: 8,
|
|
427
372
|
alignItems: 'center',
|
|
428
|
-
paddingHorizontal: 20,
|
|
429
373
|
},
|
|
430
|
-
|
|
431
|
-
|
|
374
|
+
retryButtonText: {
|
|
375
|
+
color: 'white',
|
|
376
|
+
fontSize: 16,
|
|
432
377
|
fontWeight: '600',
|
|
433
|
-
color: '#2e7d32',
|
|
434
|
-
marginBottom: 8,
|
|
435
|
-
textAlign: 'center',
|
|
436
378
|
},
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
},
|
|
443
|
-
close: {
|
|
444
|
-
position: 'absolute',
|
|
445
|
-
top: 10,
|
|
446
|
-
right: 10,
|
|
447
|
-
backgroundColor: 'white',
|
|
448
|
-
height: 24,
|
|
449
|
-
width: 24,
|
|
450
|
-
borderRadius: 1000,
|
|
451
|
-
alignItems: 'center',
|
|
379
|
+
successIcon: {
|
|
380
|
+
width: 60,
|
|
381
|
+
height: 60,
|
|
382
|
+
borderRadius: 30,
|
|
383
|
+
backgroundColor: '#4CAF50',
|
|
452
384
|
justifyContent: 'center',
|
|
453
|
-
|
|
454
|
-
},
|
|
455
|
-
closeIcon: {
|
|
456
|
-
height: 12,
|
|
457
|
-
width: 12,
|
|
458
|
-
},
|
|
459
|
-
testContent: {
|
|
460
|
-
paddingTop: 30,
|
|
461
|
-
paddingLeft: 16,
|
|
462
|
-
},
|
|
463
|
-
testTwoContent: {
|
|
464
|
-
paddingTop: 10,
|
|
465
|
-
paddingLeft: 16,
|
|
385
|
+
alignItems: 'center',
|
|
466
386
|
},
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
387
|
+
successText: {
|
|
388
|
+
fontSize: 30,
|
|
389
|
+
color: 'white',
|
|
390
|
+
fontWeight: 'bold',
|
|
470
391
|
},
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
392
|
+
errorIcon: {
|
|
393
|
+
width: 60,
|
|
394
|
+
height: 60,
|
|
395
|
+
borderRadius: 30,
|
|
396
|
+
backgroundColor: '#F44336',
|
|
476
397
|
justifyContent: 'center',
|
|
477
|
-
|
|
398
|
+
alignItems: 'center',
|
|
478
399
|
},
|
|
479
|
-
|
|
400
|
+
errorText: {
|
|
401
|
+
fontSize: 30,
|
|
480
402
|
color: 'white',
|
|
481
|
-
|
|
482
|
-
fontWeight: '600',
|
|
403
|
+
fontWeight: 'bold',
|
|
483
404
|
},
|
|
484
405
|
});
|
|
406
|
+
|
|
407
|
+
export default FaceVerification;
|