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.
Files changed (135) hide show
  1. package/README.md +93 -43
  2. package/android/build.gradle +16 -15
  3. package/android/src/main/AndroidManifest.xml +1 -1
  4. package/android/src/main/java/com/omniretail/omnipay/OmnipayActivityPackage.java +2 -2
  5. package/android/src/main/java/com/omniretail/omnipay/OmnipayLivenessCameraView.java +153 -0
  6. package/android/src/main/java/com/omniretail/omnipay/OmnipayLivenessCameraViewManager.java +49 -0
  7. package/android/src/main/java/com/omniretail/omnipay/OmnipayLivenessModule.java +524 -0
  8. package/ios/OmnipayLivenessCameraView.h +15 -0
  9. package/ios/OmnipayLivenessCameraView.m +80 -0
  10. package/ios/OmnipayLivenessCameraViewManager.m +19 -0
  11. package/ios/OmnipayLivenessModule.h +38 -0
  12. package/ios/OmnipayLivenessModule.m +554 -0
  13. package/lib/commonjs/components/OmnipayProvider.js +2 -66
  14. package/lib/commonjs/components/OmnipayProvider.js.map +1 -1
  15. package/lib/commonjs/components/OmnipayView.js.map +1 -1
  16. package/lib/commonjs/components/biometrics/FaceVerification.js +252 -345
  17. package/lib/commonjs/components/biometrics/FaceVerification.js.map +1 -1
  18. package/lib/commonjs/components/biometrics/LivenessDetection.js +90 -198
  19. package/lib/commonjs/components/biometrics/LivenessDetection.js.map +1 -1
  20. package/lib/commonjs/components/biometrics/OmnipayLivenessCameraView.js +15 -0
  21. package/lib/commonjs/components/biometrics/OmnipayLivenessCameraView.js.map +1 -0
  22. package/lib/commonjs/components/biometrics/PermissionManager.js +279 -0
  23. package/lib/commonjs/components/biometrics/PermissionManager.js.map +1 -0
  24. package/lib/commonjs/components/biometrics/index.js +45 -0
  25. package/lib/commonjs/components/biometrics/index.js.map +1 -0
  26. package/lib/commonjs/components/biometrics/types.js +17 -0
  27. package/lib/commonjs/components/biometrics/types.js.map +1 -0
  28. package/lib/commonjs/components/views/BvnVerification.js.map +1 -1
  29. package/lib/commonjs/components/views/PaylaterAgreement.js.map +1 -1
  30. package/lib/commonjs/components/views/Registration.js.map +1 -1
  31. package/lib/commonjs/index.js +23 -18
  32. package/lib/commonjs/index.js.map +1 -1
  33. package/lib/module/components/OmnipayProvider.js +3 -67
  34. package/lib/module/components/OmnipayProvider.js.map +1 -1
  35. package/lib/module/components/OmnipayView.js.map +1 -1
  36. package/lib/module/components/biometrics/FaceVerification.js +254 -346
  37. package/lib/module/components/biometrics/FaceVerification.js.map +1 -1
  38. package/lib/module/components/biometrics/LivenessDetection.js +75 -197
  39. package/lib/module/components/biometrics/LivenessDetection.js.map +1 -1
  40. package/lib/module/components/biometrics/OmnipayLivenessCameraView.js +7 -0
  41. package/lib/module/components/biometrics/OmnipayLivenessCameraView.js.map +1 -0
  42. package/lib/module/components/biometrics/PermissionManager.js +272 -0
  43. package/lib/module/components/biometrics/PermissionManager.js.map +1 -0
  44. package/lib/module/components/biometrics/index.js +12 -0
  45. package/lib/module/components/biometrics/index.js.map +1 -0
  46. package/lib/module/components/biometrics/types.js +16 -0
  47. package/lib/module/components/biometrics/types.js.map +1 -0
  48. package/lib/module/components/views/BvnVerification.js.map +1 -1
  49. package/lib/module/components/views/PaylaterAgreement.js.map +1 -1
  50. package/lib/module/components/views/Registration.js.map +1 -1
  51. package/lib/module/index.js +5 -4
  52. package/lib/module/index.js.map +1 -1
  53. package/lib/typescript/{src/components → components}/OmnipayProvider.d.ts +1 -1
  54. package/lib/typescript/components/OmnipayProvider.d.ts.map +1 -0
  55. package/lib/typescript/{src/components → components}/OmnipayView.d.ts +21 -20
  56. package/lib/typescript/components/OmnipayView.d.ts.map +1 -0
  57. package/lib/typescript/components/biometrics/FaceVerification.d.ts +11 -0
  58. package/lib/typescript/components/biometrics/FaceVerification.d.ts.map +1 -0
  59. package/lib/typescript/components/biometrics/LivenessDetection.d.ts +33 -0
  60. package/lib/typescript/components/biometrics/LivenessDetection.d.ts.map +1 -0
  61. package/lib/typescript/components/biometrics/OmnipayLivenessCameraView.d.ts +18 -0
  62. package/lib/typescript/components/biometrics/OmnipayLivenessCameraView.d.ts.map +1 -0
  63. package/lib/typescript/components/biometrics/PermissionManager.d.ts +58 -0
  64. package/lib/typescript/components/biometrics/PermissionManager.d.ts.map +1 -0
  65. package/lib/typescript/components/biometrics/index.d.ts +5 -0
  66. package/lib/typescript/components/biometrics/index.d.ts.map +1 -0
  67. package/lib/typescript/components/biometrics/types.d.ts +73 -0
  68. package/lib/typescript/components/biometrics/types.d.ts.map +1 -0
  69. package/lib/typescript/{src/components → components}/views/BvnVerification.d.ts +2 -1
  70. package/lib/typescript/components/views/BvnVerification.d.ts.map +1 -0
  71. package/lib/typescript/{src/components → components}/views/PaylaterAgreement.d.ts +2 -1
  72. package/lib/typescript/components/views/PaylaterAgreement.d.ts.map +1 -0
  73. package/lib/typescript/{src/components → components}/views/Registration.d.ts +2 -1
  74. package/lib/typescript/components/views/Registration.d.ts.map +1 -0
  75. package/lib/typescript/functions.d.ts.map +1 -0
  76. package/lib/typescript/hooks/useOmnipay.d.ts +28 -0
  77. package/lib/typescript/hooks/useOmnipay.d.ts.map +1 -0
  78. package/lib/typescript/index.d.ts +7 -0
  79. package/lib/typescript/index.d.ts.map +1 -0
  80. package/lib/typescript/lib/colors.d.ts.map +1 -0
  81. package/lib/typescript/lib/config.d.ts.map +1 -0
  82. package/omnipay-reactnative-sdk.podspec +32 -29
  83. package/package.json +16 -11
  84. package/src/components/OmnipayProvider.tsx +3 -106
  85. package/src/components/OmnipayView.tsx +1 -1
  86. package/src/components/biometrics/FaceVerification.tsx +291 -368
  87. package/src/components/biometrics/LivenessDetection.ts +113 -250
  88. package/src/components/biometrics/OmnipayLivenessCameraView.tsx +19 -0
  89. package/src/components/biometrics/PermissionManager.ts +317 -0
  90. package/src/components/biometrics/index.ts +11 -0
  91. package/src/components/biometrics/types.ts +86 -0
  92. package/src/components/views/BvnVerification.tsx +1 -1
  93. package/src/components/views/PaylaterAgreement.tsx +1 -1
  94. package/src/components/views/Registration.tsx +1 -1
  95. package/src/index.tsx +4 -15
  96. package/android/src/main/java/com/omniretail/omnipay/LivenessCameraViewManager.java +0 -116
  97. package/android/src/main/java/com/omniretail/omnipay/LivenessDetectionModule.java +0 -588
  98. package/ios/LivenessCameraView.h +0 -22
  99. package/ios/LivenessCameraView.m +0 -135
  100. package/ios/LivenessCameraViewManager.h +0 -12
  101. package/ios/LivenessCameraViewManager.m +0 -24
  102. package/ios/LivenessDetectionModule.h +0 -46
  103. package/ios/LivenessDetectionModule.m +0 -603
  104. package/lib/commonjs/components/biometrics/LivenessCameraView.js +0 -45
  105. package/lib/commonjs/components/biometrics/LivenessCameraView.js.map +0 -1
  106. package/lib/module/components/biometrics/LivenessCameraView.js +0 -39
  107. package/lib/module/components/biometrics/LivenessCameraView.js.map +0 -1
  108. package/lib/typescript/demo/src/App.d.ts +0 -3
  109. package/lib/typescript/demo/src/App.d.ts.map +0 -1
  110. package/lib/typescript/demo/src/Body.d.ts +0 -3
  111. package/lib/typescript/demo/src/Body.d.ts.map +0 -1
  112. package/lib/typescript/demo/src/NotificationsExample.d.ts +0 -4
  113. package/lib/typescript/demo/src/NotificationsExample.d.ts.map +0 -1
  114. package/lib/typescript/src/components/OmnipayProvider.d.ts.map +0 -1
  115. package/lib/typescript/src/components/OmnipayView.d.ts.map +0 -1
  116. package/lib/typescript/src/components/biometrics/FaceVerification.d.ts +0 -12
  117. package/lib/typescript/src/components/biometrics/FaceVerification.d.ts.map +0 -1
  118. package/lib/typescript/src/components/biometrics/LivenessCameraView.d.ts +0 -22
  119. package/lib/typescript/src/components/biometrics/LivenessCameraView.d.ts.map +0 -1
  120. package/lib/typescript/src/components/biometrics/LivenessDetection.d.ts +0 -73
  121. package/lib/typescript/src/components/biometrics/LivenessDetection.d.ts.map +0 -1
  122. package/lib/typescript/src/components/views/BvnVerification.d.ts.map +0 -1
  123. package/lib/typescript/src/components/views/PaylaterAgreement.d.ts.map +0 -1
  124. package/lib/typescript/src/components/views/Registration.d.ts.map +0 -1
  125. package/lib/typescript/src/functions.d.ts.map +0 -1
  126. package/lib/typescript/src/hooks/useOmnipay.d.ts +0 -28
  127. package/lib/typescript/src/hooks/useOmnipay.d.ts.map +0 -1
  128. package/lib/typescript/src/index.d.ts +0 -8
  129. package/lib/typescript/src/index.d.ts.map +0 -1
  130. package/lib/typescript/src/lib/colors.d.ts.map +0 -1
  131. package/lib/typescript/src/lib/config.d.ts.map +0 -1
  132. package/src/components/biometrics/LivenessCameraView.tsx +0 -61
  133. /package/lib/typescript/{src/functions.d.ts → functions.d.ts} +0 -0
  134. /package/lib/typescript/{src/lib → lib}/colors.d.ts +0 -0
  135. /package/lib/typescript/{src/lib → lib}/config.d.ts +0 -0
@@ -1,484 +1,407 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
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
- LivenessDetection,
14
- type LivenessDetectionConfig,
15
- type LivenessResult,
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: LivenessResult) => void;
19
+ onSuccess: (result?: any) => void;
22
20
  primaryColor: string;
23
- challenges?: ('smile' | 'blink' | 'turn_left' | 'turn_right')[];
24
- timeout?: number;
21
+ config?: Partial<LivenessConfig>;
25
22
  };
26
23
 
27
- type DetectionState = 'idle' | 'starting' | 'active' | 'completed' | 'error';
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
- challenges = ['smile', 'blink', 'turn_left'],
34
- timeout = 300000, // 5 minutes default
35
+ config,
35
36
  }) => {
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);
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
- // 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';
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 startLivenessDetection = useCallback(async () => {
59
- try {
60
- setDetectionState('starting');
61
- setIsLoading(true);
62
- setError('');
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 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
- }
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 config: LivenessDetectionConfig = {
76
- challenges,
77
- timeout,
88
+ const livenessConfig = {
89
+ ...LivenessDetection.getDefaultConfig(),
90
+ ...config,
78
91
  };
79
92
 
80
- await LivenessDetection.startLivenessDetection(config, {
93
+ const result = await LivenessDetection.startDetection(livenessConfig, {
81
94
  onChallengeStart: (challenge) => {
82
95
  setCurrentChallenge(challenge);
83
- setDetectionState('active');
84
- setIsLoading(false);
96
+ setState('running');
97
+ setChallengeProgress(getChallengeInstruction(challenge));
85
98
  },
86
- onChallengeSuccess: (challenge) => {
87
- setCompletedChallenges((prev) => [...prev, challenge]);
99
+ onChallengeSuccess: (challenge, _challengeResult) => {
100
+ setChallengeProgress(
101
+ `✓ ${getChallengeInstruction(challenge)} completed`
102
+ );
88
103
  },
89
104
  onChallengeFailure: (challenge, reason) => {
90
- console.warn(`Challenge ${challenge} failed: ${reason}`);
91
- // Continue with next challenge - the native module handles this
105
+ setError(`Failed: ${reason}, ${challenge}`);
92
106
  },
93
- onAllChallengesComplete: (result) => {
94
- setDetectionState('completed');
95
- setCurrentChallenge('');
96
- onSuccess(result);
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
- console.error('Liveness detection error:', err);
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
- onClose();
112
- }, [detectionState, onClose]);
113
-
114
- const handleRetry = useCallback(() => {
115
- setDetectionState('idle');
116
- setError('');
117
- setCompletedChallenges([]);
118
- setCurrentChallenge('');
119
- }, []);
134
+ };
120
135
 
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);
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
- return undefined;
131
- }, [detectionState, startLivenessDetection]);
132
-
133
- // Cleanup on unmount
134
- useEffect(() => {
135
- return () => {
136
- LivenessDetection.cleanup();
137
- };
138
- }, []);
149
+ };
139
150
 
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
- );
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
- 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
- );
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
- 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
- );
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={handleClose}
198
+ onRequestClose={onClose}
233
199
  style={styles.modal}
234
- animationType="slide"
235
200
  >
236
- <View style={styles.backdrop}>
237
- <View style={styles.container}>
238
- {/* Header */}
239
- <View style={styles.header}>
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
- {renderContent()}
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
- </View>
270
+ </TouchableOpacity>
249
271
  </Modal>
250
272
  );
251
273
  };
252
274
 
253
- export default FaceVerification;
275
+ const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
254
276
 
255
277
  const styles = StyleSheet.create({
256
- hide: {
257
- display: 'none',
258
- },
259
- full: {
260
- flex: 1,
261
- width: '100%',
262
- height: '100%',
278
+ modal: {
279
+ margin: 0,
263
280
  },
264
- webview: {
281
+ backdrop: {
265
282
  flex: 1,
266
- width: '100%',
267
- height: Dimensions.get('window').height - 40,
268
- backgroundColor: 'white',
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
- webviewLoader: {
274
- zIndex: 3,
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
- backdrop: {
288
- backgroundColor: 'rgba(0,0,0,0.48)',
289
- flex: 1,
290
- justifyContent: 'flex-end',
291
- position: 'relative',
292
- height: '100%',
295
+ close: {
296
+ position: 'absolute',
297
+ top: 15,
298
+ right: 15,
299
+ zIndex: 10,
300
+ padding: 5,
293
301
  },
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
+ closeIcon: {
303
+ width: 20,
304
+ height: 20,
305
+ tintColor: '#666',
302
306
  },
303
- modal: {
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
- padding: 16,
314
- borderBottomWidth: 1,
315
- borderBottomColor: '#f0f0f0',
310
+ paddingTop: 20,
316
311
  },
317
312
  title: {
318
- fontSize: 18,
319
- fontWeight: '600',
313
+ fontSize: 24,
314
+ fontWeight: 'bold',
320
315
  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,
316
+ marginBottom: 20,
317
+ textAlign: 'center',
336
318
  },
337
319
  cameraContainer: {
338
- flex: 1,
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
- top: 0,
340
+ bottom: 0,
349
341
  left: 0,
350
342
  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',
343
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
344
+ padding: 15,
360
345
  alignItems: 'center',
361
346
  },
362
- loadingText: {
363
- color: 'white',
347
+ instruction: {
364
348
  fontSize: 16,
365
- marginTop: 12,
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
- 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',
354
+ marginBottom: 20,
407
355
  alignItems: 'center',
408
- paddingHorizontal: 20,
409
356
  },
410
- errorTitle: {
411
- fontSize: 20,
357
+ challengeText: {
358
+ fontSize: 18,
412
359
  fontWeight: '600',
413
- color: '#d32f2f',
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
- successContainer: {
425
- flex: 1,
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
- successTitle: {
431
- fontSize: 20,
374
+ retryButtonText: {
375
+ color: 'white',
376
+ fontSize: 16,
432
377
  fontWeight: '600',
433
- color: '#2e7d32',
434
- marginBottom: 8,
435
- textAlign: 'center',
436
378
  },
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',
379
+ successIcon: {
380
+ width: 60,
381
+ height: 60,
382
+ borderRadius: 30,
383
+ backgroundColor: '#4CAF50',
452
384
  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,
385
+ alignItems: 'center',
466
386
  },
467
- retryButton: {
468
- minWidth: 160,
469
- marginHorizontal: 'auto',
387
+ successText: {
388
+ fontSize: 30,
389
+ color: 'white',
390
+ fontWeight: 'bold',
470
391
  },
471
- button: {
472
- borderRadius: 6,
473
- paddingHorizontal: 20,
474
- paddingVertical: 14,
475
- alignItems: 'center',
392
+ errorIcon: {
393
+ width: 60,
394
+ height: 60,
395
+ borderRadius: 30,
396
+ backgroundColor: '#F44336',
476
397
  justifyContent: 'center',
477
- minWidth: 120,
398
+ alignItems: 'center',
478
399
  },
479
- buttonText: {
400
+ errorText: {
401
+ fontSize: 30,
480
402
  color: 'white',
481
- fontSize: 16,
482
- fontWeight: '600',
403
+ fontWeight: 'bold',
483
404
  },
484
405
  });
406
+
407
+ export default FaceVerification;