omnipay-reactnative-sdk 1.2.2-beta.0 → 1.2.2-beta.2
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 +45 -136
- package/android/build.gradle +13 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/com/omniretail/omnipay/LivenessCameraViewManager.java +116 -0
- package/android/src/main/java/com/omniretail/omnipay/LivenessDetectionModule.java +588 -0
- package/android/src/main/java/com/omniretail/omnipay/OmnipayActivityPackage.java +4 -1
- package/ios/LivenessCameraView.h +22 -0
- package/ios/LivenessCameraView.m +135 -0
- package/ios/LivenessCameraViewManager.h +12 -0
- package/ios/LivenessCameraViewManager.m +24 -0
- package/ios/LivenessDetectionModule.h +46 -0
- package/ios/LivenessDetectionModule.m +603 -0
- package/lib/commonjs/components/OmnipayProvider.js +6 -56
- package/lib/commonjs/components/OmnipayProvider.js.map +1 -1
- package/lib/commonjs/components/biometrics/FaceVerification.js +439 -0
- package/lib/commonjs/components/biometrics/FaceVerification.js.map +1 -0
- package/lib/commonjs/components/biometrics/LivenessCameraView.js +43 -0
- package/lib/commonjs/components/biometrics/LivenessCameraView.js.map +1 -0
- package/lib/commonjs/components/biometrics/LivenessDetection.js +252 -0
- package/lib/commonjs/components/biometrics/LivenessDetection.js.map +1 -0
- package/lib/commonjs/index.js +28 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/components/OmnipayProvider.js +6 -56
- package/lib/module/components/OmnipayProvider.js.map +1 -1
- package/lib/module/components/biometrics/FaceVerification.js +429 -0
- package/lib/module/components/biometrics/FaceVerification.js.map +1 -0
- package/lib/module/components/biometrics/LivenessCameraView.js +38 -0
- package/lib/module/components/biometrics/LivenessCameraView.js.map +1 -0
- package/lib/module/components/biometrics/LivenessDetection.js +244 -0
- package/lib/module/components/biometrics/LivenessDetection.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/components/OmnipayProvider.d.ts.map +1 -1
- package/lib/typescript/components/biometrics/FaceVerification.d.ts +12 -0
- package/lib/typescript/components/biometrics/FaceVerification.d.ts.map +1 -0
- package/lib/typescript/components/biometrics/LivenessCameraView.d.ts +22 -0
- package/lib/typescript/components/biometrics/LivenessCameraView.d.ts.map +1 -0
- package/lib/typescript/components/biometrics/LivenessDetection.d.ts +73 -0
- package/lib/typescript/components/biometrics/LivenessDetection.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +3 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/omnipay-reactnative-sdk.podspec +47 -0
- package/package.json +6 -11
- package/src/components/OmnipayProvider.tsx +8 -65
- package/src/components/biometrics/FaceVerification.tsx +484 -0
- package/src/components/biometrics/LivenessCameraView.tsx +61 -0
- package/src/components/biometrics/LivenessDetection.ts +305 -0
- package/src/index.tsx +18 -0
- package/lib/commonjs/components/FaceVerification.js +0 -755
- package/lib/commonjs/components/FaceVerification.js.map +0 -1
- package/lib/commonjs/types/faceVerification.js +0 -2
- package/lib/commonjs/types/faceVerification.js.map +0 -1
- package/lib/commonjs/types/index.js +0 -17
- package/lib/commonjs/types/index.js.map +0 -1
- package/lib/module/components/FaceVerification.js +0 -746
- package/lib/module/components/FaceVerification.js.map +0 -1
- package/lib/module/types/faceVerification.js +0 -2
- package/lib/module/types/faceVerification.js.map +0 -1
- package/lib/module/types/index.js +0 -2
- package/lib/module/types/index.js.map +0 -1
- package/lib/typescript/components/FaceVerification.d.ts +0 -10
- package/lib/typescript/components/FaceVerification.d.ts.map +0 -1
- package/lib/typescript/types/faceVerification.d.ts +0 -18
- package/lib/typescript/types/faceVerification.d.ts.map +0 -1
- package/lib/typescript/types/index.d.ts +0 -2
- package/lib/typescript/types/index.d.ts.map +0 -1
- package/src/components/FaceVerification.tsx +0 -884
- package/src/types/faceVerification.ts +0 -27
- package/src/types/index.ts +0 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
Modal,
|
|
6
|
+
Dimensions,
|
|
7
|
+
Platform,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
TouchableOpacity,
|
|
10
|
+
ActivityIndicator,
|
|
11
|
+
} from 'react-native';
|
|
12
|
+
import {
|
|
13
|
+
LivenessDetection,
|
|
14
|
+
type LivenessDetectionConfig,
|
|
15
|
+
type LivenessResult,
|
|
16
|
+
} from './LivenessDetection';
|
|
17
|
+
import LivenessCameraView from './LivenessCameraView';
|
|
18
|
+
|
|
19
|
+
type FaceVerificationProps = {
|
|
20
|
+
onClose: () => void;
|
|
21
|
+
onSuccess: (result: LivenessResult) => void;
|
|
22
|
+
primaryColor: string;
|
|
23
|
+
challenges?: ('smile' | 'blink' | 'turn_left' | 'turn_right')[];
|
|
24
|
+
timeout?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type DetectionState = 'idle' | 'starting' | 'active' | 'completed' | 'error';
|
|
28
|
+
|
|
29
|
+
const FaceVerification: React.FC<FaceVerificationProps> = ({
|
|
30
|
+
onClose,
|
|
31
|
+
onSuccess,
|
|
32
|
+
primaryColor,
|
|
33
|
+
challenges = ['smile', 'blink', 'turn_left'],
|
|
34
|
+
timeout = 300000, // 5 minutes default
|
|
35
|
+
}) => {
|
|
36
|
+
const [detectionState, setDetectionState] = useState<DetectionState>('idle');
|
|
37
|
+
const [currentChallenge, setCurrentChallenge] = useState<string>('');
|
|
38
|
+
const [completedChallenges, setCompletedChallenges] = useState<string[]>([]);
|
|
39
|
+
const [error, setError] = useState<string>('');
|
|
40
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
41
|
+
|
|
42
|
+
// Challenge instruction messages
|
|
43
|
+
const getChallengeInstruction = (challenge: string): string => {
|
|
44
|
+
switch (challenge) {
|
|
45
|
+
case 'smile':
|
|
46
|
+
return 'Please smile naturally';
|
|
47
|
+
case 'blink':
|
|
48
|
+
return 'Please blink your eyes';
|
|
49
|
+
case 'turn_left':
|
|
50
|
+
return 'Please turn your head to the left';
|
|
51
|
+
case 'turn_right':
|
|
52
|
+
return 'Please turn your head to the right';
|
|
53
|
+
default:
|
|
54
|
+
return 'Please follow the instruction';
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const startLivenessDetection = useCallback(async () => {
|
|
59
|
+
try {
|
|
60
|
+
setDetectionState('starting');
|
|
61
|
+
setIsLoading(true);
|
|
62
|
+
setError('');
|
|
63
|
+
|
|
64
|
+
// Check and request camera permission first
|
|
65
|
+
const hasPermission = await LivenessDetection.checkCameraPermission();
|
|
66
|
+
if (!hasPermission) {
|
|
67
|
+
const granted = await LivenessDetection.requestCameraPermission();
|
|
68
|
+
if (!granted) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'Camera permission is required for face verification'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const config: LivenessDetectionConfig = {
|
|
76
|
+
challenges,
|
|
77
|
+
timeout,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
await LivenessDetection.startLivenessDetection(config, {
|
|
81
|
+
onChallengeStart: (challenge) => {
|
|
82
|
+
setCurrentChallenge(challenge);
|
|
83
|
+
setDetectionState('active');
|
|
84
|
+
setIsLoading(false);
|
|
85
|
+
},
|
|
86
|
+
onChallengeSuccess: (challenge) => {
|
|
87
|
+
setCompletedChallenges((prev) => [...prev, challenge]);
|
|
88
|
+
},
|
|
89
|
+
onChallengeFailure: (challenge, reason) => {
|
|
90
|
+
console.warn(`Challenge ${challenge} failed: ${reason}`);
|
|
91
|
+
// Continue with next challenge - the native module handles this
|
|
92
|
+
},
|
|
93
|
+
onAllChallengesComplete: (result) => {
|
|
94
|
+
setDetectionState('completed');
|
|
95
|
+
setCurrentChallenge('');
|
|
96
|
+
onSuccess(result);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error('Liveness detection error:', err);
|
|
101
|
+
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
|
+
}
|
|
111
|
+
onClose();
|
|
112
|
+
}, [detectionState, onClose]);
|
|
113
|
+
|
|
114
|
+
const handleRetry = useCallback(() => {
|
|
115
|
+
setDetectionState('idle');
|
|
116
|
+
setError('');
|
|
117
|
+
setCompletedChallenges([]);
|
|
118
|
+
setCurrentChallenge('');
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
// Auto-start detection when component mounts
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (detectionState === 'idle') {
|
|
124
|
+
// Small delay to ensure UI is ready
|
|
125
|
+
const timer = setTimeout(() => {
|
|
126
|
+
startLivenessDetection();
|
|
127
|
+
}, 500);
|
|
128
|
+
return () => clearTimeout(timer);
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}, [detectionState, startLivenessDetection]);
|
|
132
|
+
|
|
133
|
+
// Cleanup on unmount
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
return () => {
|
|
136
|
+
LivenessDetection.cleanup();
|
|
137
|
+
};
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
140
|
+
const renderContent = () => {
|
|
141
|
+
if (detectionState === 'error') {
|
|
142
|
+
return (
|
|
143
|
+
<View style={styles.errorContainer}>
|
|
144
|
+
<Text style={styles.errorTitle}>Detection Failed</Text>
|
|
145
|
+
<Text style={styles.errorSubtitle}>{error}</Text>
|
|
146
|
+
<TouchableOpacity
|
|
147
|
+
style={[styles.button, { backgroundColor: primaryColor }]}
|
|
148
|
+
onPress={handleRetry}
|
|
149
|
+
>
|
|
150
|
+
<Text style={styles.buttonText}>Try Again</Text>
|
|
151
|
+
</TouchableOpacity>
|
|
152
|
+
</View>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (detectionState === 'completed') {
|
|
157
|
+
return (
|
|
158
|
+
<View style={styles.successContainer}>
|
|
159
|
+
<Text style={styles.successTitle}>Verification Complete!</Text>
|
|
160
|
+
<Text style={styles.successSubtitle}>
|
|
161
|
+
All challenges completed successfully
|
|
162
|
+
</Text>
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<View style={styles.contentContainer}>
|
|
169
|
+
{/* Camera View */}
|
|
170
|
+
<View style={styles.cameraContainer}>
|
|
171
|
+
<LivenessCameraView
|
|
172
|
+
style={styles.camera}
|
|
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
|
+
);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<Modal
|
|
230
|
+
visible={true}
|
|
231
|
+
transparent={true}
|
|
232
|
+
onRequestClose={handleClose}
|
|
233
|
+
style={styles.modal}
|
|
234
|
+
animationType="slide"
|
|
235
|
+
>
|
|
236
|
+
<View style={styles.backdrop}>
|
|
237
|
+
<View style={styles.container}>
|
|
238
|
+
{/* Header */}
|
|
239
|
+
<View style={styles.header}>
|
|
240
|
+
<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
|
+
|
|
246
|
+
{renderContent()}
|
|
247
|
+
</View>
|
|
248
|
+
</View>
|
|
249
|
+
</Modal>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export default FaceVerification;
|
|
254
|
+
|
|
255
|
+
const styles = StyleSheet.create({
|
|
256
|
+
hide: {
|
|
257
|
+
display: 'none',
|
|
258
|
+
},
|
|
259
|
+
full: {
|
|
260
|
+
flex: 1,
|
|
261
|
+
width: '100%',
|
|
262
|
+
height: '100%',
|
|
263
|
+
},
|
|
264
|
+
webview: {
|
|
265
|
+
flex: 1,
|
|
266
|
+
width: '100%',
|
|
267
|
+
height: Dimensions.get('window').height - 40,
|
|
268
|
+
backgroundColor: 'white',
|
|
269
|
+
borderTopRightRadius: 20,
|
|
270
|
+
borderTopLeftRadius: 20,
|
|
271
|
+
paddingTop: 150,
|
|
272
|
+
},
|
|
273
|
+
webviewLoader: {
|
|
274
|
+
zIndex: 3,
|
|
275
|
+
backgroundColor: 'white',
|
|
276
|
+
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
|
+
},
|
|
287
|
+
backdrop: {
|
|
288
|
+
backgroundColor: 'rgba(0,0,0,0.48)',
|
|
289
|
+
flex: 1,
|
|
290
|
+
justifyContent: 'flex-end',
|
|
291
|
+
position: 'relative',
|
|
292
|
+
height: '100%',
|
|
293
|
+
},
|
|
294
|
+
container: {
|
|
295
|
+
backgroundColor: 'white',
|
|
296
|
+
borderTopRightRadius: 20,
|
|
297
|
+
borderTopLeftRadius: 20,
|
|
298
|
+
maxHeight: Dimensions.get('window').height - 40,
|
|
299
|
+
flex: 1,
|
|
300
|
+
position: 'relative',
|
|
301
|
+
...(Platform.OS === 'android' && { overflow: 'hidden' }),
|
|
302
|
+
},
|
|
303
|
+
modal: {
|
|
304
|
+
flex: 1,
|
|
305
|
+
backgroundColor: 'rgba(0,0,0,0.48)',
|
|
306
|
+
height: '100%',
|
|
307
|
+
width: '100%',
|
|
308
|
+
},
|
|
309
|
+
header: {
|
|
310
|
+
flexDirection: 'row',
|
|
311
|
+
justifyContent: 'space-between',
|
|
312
|
+
alignItems: 'center',
|
|
313
|
+
padding: 16,
|
|
314
|
+
borderBottomWidth: 1,
|
|
315
|
+
borderBottomColor: '#f0f0f0',
|
|
316
|
+
},
|
|
317
|
+
title: {
|
|
318
|
+
fontSize: 18,
|
|
319
|
+
fontWeight: '600',
|
|
320
|
+
color: '#333',
|
|
321
|
+
},
|
|
322
|
+
closeButton: {
|
|
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,
|
|
336
|
+
},
|
|
337
|
+
cameraContainer: {
|
|
338
|
+
flex: 1,
|
|
339
|
+
position: 'relative',
|
|
340
|
+
},
|
|
341
|
+
camera: {
|
|
342
|
+
flex: 1,
|
|
343
|
+
width: '100%',
|
|
344
|
+
height: '100%',
|
|
345
|
+
},
|
|
346
|
+
overlay: {
|
|
347
|
+
position: 'absolute',
|
|
348
|
+
top: 0,
|
|
349
|
+
left: 0,
|
|
350
|
+
right: 0,
|
|
351
|
+
bottom: 0,
|
|
352
|
+
justifyContent: 'space-between',
|
|
353
|
+
alignItems: 'center',
|
|
354
|
+
paddingVertical: 40,
|
|
355
|
+
paddingHorizontal: 20,
|
|
356
|
+
},
|
|
357
|
+
loadingContainer: {
|
|
358
|
+
flex: 1,
|
|
359
|
+
justifyContent: 'center',
|
|
360
|
+
alignItems: 'center',
|
|
361
|
+
},
|
|
362
|
+
loadingText: {
|
|
363
|
+
color: 'white',
|
|
364
|
+
fontSize: 16,
|
|
365
|
+
marginTop: 12,
|
|
366
|
+
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
|
+
color: 'white',
|
|
377
|
+
fontSize: 16,
|
|
378
|
+
fontWeight: '500',
|
|
379
|
+
textAlign: 'center',
|
|
380
|
+
},
|
|
381
|
+
progressContainer: {
|
|
382
|
+
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
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',
|
|
407
|
+
alignItems: 'center',
|
|
408
|
+
paddingHorizontal: 20,
|
|
409
|
+
},
|
|
410
|
+
errorTitle: {
|
|
411
|
+
fontSize: 20,
|
|
412
|
+
fontWeight: '600',
|
|
413
|
+
color: '#d32f2f',
|
|
414
|
+
marginBottom: 8,
|
|
415
|
+
textAlign: 'center',
|
|
416
|
+
},
|
|
417
|
+
errorSubtitle: {
|
|
418
|
+
textAlign: 'center',
|
|
419
|
+
fontSize: 14,
|
|
420
|
+
color: '#5e7079',
|
|
421
|
+
marginBottom: 20,
|
|
422
|
+
paddingHorizontal: 8,
|
|
423
|
+
},
|
|
424
|
+
successContainer: {
|
|
425
|
+
flex: 1,
|
|
426
|
+
justifyContent: 'center',
|
|
427
|
+
alignItems: 'center',
|
|
428
|
+
paddingHorizontal: 20,
|
|
429
|
+
},
|
|
430
|
+
successTitle: {
|
|
431
|
+
fontSize: 20,
|
|
432
|
+
fontWeight: '600',
|
|
433
|
+
color: '#2e7d32',
|
|
434
|
+
marginBottom: 8,
|
|
435
|
+
textAlign: 'center',
|
|
436
|
+
},
|
|
437
|
+
successSubtitle: {
|
|
438
|
+
textAlign: 'center',
|
|
439
|
+
fontSize: 14,
|
|
440
|
+
color: '#5e7079',
|
|
441
|
+
paddingHorizontal: 8,
|
|
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',
|
|
452
|
+
justifyContent: 'center',
|
|
453
|
+
zIndex: 2,
|
|
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,
|
|
466
|
+
},
|
|
467
|
+
retryButton: {
|
|
468
|
+
minWidth: 160,
|
|
469
|
+
marginHorizontal: 'auto',
|
|
470
|
+
},
|
|
471
|
+
button: {
|
|
472
|
+
borderRadius: 6,
|
|
473
|
+
paddingHorizontal: 20,
|
|
474
|
+
paddingVertical: 14,
|
|
475
|
+
alignItems: 'center',
|
|
476
|
+
justifyContent: 'center',
|
|
477
|
+
minWidth: 120,
|
|
478
|
+
},
|
|
479
|
+
buttonText: {
|
|
480
|
+
color: 'white',
|
|
481
|
+
fontSize: 16,
|
|
482
|
+
fontWeight: '600',
|
|
483
|
+
},
|
|
484
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { requireNativeComponent, Platform, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
// Props for the native camera view
|
|
5
|
+
interface LivenessCameraViewProps {
|
|
6
|
+
style?: ViewStyle;
|
|
7
|
+
scaleType?:
|
|
8
|
+
| 'fillCenter'
|
|
9
|
+
| 'fillStart'
|
|
10
|
+
| 'fillEnd'
|
|
11
|
+
| 'fitCenter'
|
|
12
|
+
| 'fitStart'
|
|
13
|
+
| 'fitEnd';
|
|
14
|
+
onCameraReady?: () => void;
|
|
15
|
+
onCameraError?: (error: { nativeEvent: { error: string } }) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Native component interface
|
|
19
|
+
interface NativeLivenessCameraViewProps extends LivenessCameraViewProps {
|
|
20
|
+
style?: ViewStyle;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Import native component based on platform
|
|
24
|
+
const NativeLivenessCameraView = Platform.select({
|
|
25
|
+
android:
|
|
26
|
+
requireNativeComponent<NativeLivenessCameraViewProps>('LivenessCameraView'),
|
|
27
|
+
ios: requireNativeComponent<NativeLivenessCameraViewProps>(
|
|
28
|
+
'LivenessCameraView'
|
|
29
|
+
),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* LivenessCameraView - Native camera component for liveness detection
|
|
34
|
+
*
|
|
35
|
+
* This component provides a camera preview that integrates with the
|
|
36
|
+
* liveness detection native module for real-time face analysis.
|
|
37
|
+
*/
|
|
38
|
+
const LivenessCameraView: React.FC<LivenessCameraViewProps> = ({
|
|
39
|
+
style,
|
|
40
|
+
scaleType = 'fillCenter',
|
|
41
|
+
onCameraReady,
|
|
42
|
+
onCameraError,
|
|
43
|
+
}) => {
|
|
44
|
+
// Platform check
|
|
45
|
+
if (!NativeLivenessCameraView) {
|
|
46
|
+
console.warn('LivenessCameraView is not available on this platform');
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<NativeLivenessCameraView
|
|
52
|
+
style={style}
|
|
53
|
+
scaleType={scaleType}
|
|
54
|
+
onCameraReady={onCameraReady}
|
|
55
|
+
onCameraError={onCameraError}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default LivenessCameraView;
|
|
61
|
+
export type { LivenessCameraViewProps };
|