omnipay-reactnative-sdk 1.2.3-beta.9 → 1.2.3
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 +42 -102
- package/android/build.gradle +4 -15
- package/android/src/main/java/com/omniretail/omnipay/OmnipayActivityPackage.java +0 -5
- package/lib/commonjs/components/OmnipayProvider.js +91 -18
- package/lib/commonjs/components/OmnipayProvider.js.map +1 -1
- package/lib/module/components/OmnipayProvider.js +92 -19
- package/lib/module/components/OmnipayProvider.js.map +1 -1
- package/lib/module/components/OmnipayView.js.map +1 -1
- 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/typescript/components/OmnipayProvider.d.ts +3 -2
- package/lib/typescript/components/OmnipayProvider.d.ts.map +1 -1
- package/lib/typescript/hooks/useOmnipay.d.ts +2 -1
- package/lib/typescript/hooks/useOmnipay.d.ts.map +1 -1
- package/package.json +12 -29
- package/src/components/OmnipayProvider.tsx +133 -21
- package/src/components/OmnipayView.tsx +1 -1
- 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/hooks/useOmnipay.tsx +1 -1
- package/android/src/main/java/com/omniretail/omnipay/FaceVerificationFrameProcessor.kt +0 -111
- package/ios/FaceVerificationFrameProcessor.swift +0 -138
- package/ios/FaceVerificationFrameProcessorPlugin.m +0 -4
- package/ios/OmnipayReactnativeSdk.m +0 -5
- package/ios/OmnipayReactnativeSdk.swift +0 -10
- package/ios/omnipay-reactnative-sdk-Bridging-Header.h +0 -8
- package/ios/omnipay_reactnative_sdk.h +0 -6
- package/lib/commonjs/components/Button.js +0 -68
- package/lib/commonjs/components/Button.js.map +0 -1
- package/lib/commonjs/components/biometrics/FaceVerification.js +0 -380
- package/lib/commonjs/components/biometrics/FaceVerification.js.map +0 -1
- package/lib/commonjs/components/biometrics/useFaceVerification.js +0 -85
- package/lib/commonjs/components/biometrics/useFaceVerification.js.map +0 -1
- package/lib/commonjs/components/biometrics/useFaceVerificationFlow.js +0 -157
- package/lib/commonjs/components/biometrics/useFaceVerificationFlow.js.map +0 -1
- package/lib/module/components/Button.js +0 -61
- package/lib/module/components/Button.js.map +0 -1
- package/lib/module/components/biometrics/FaceVerification.js +0 -372
- package/lib/module/components/biometrics/FaceVerification.js.map +0 -1
- package/lib/module/components/biometrics/useFaceVerification.js +0 -78
- package/lib/module/components/biometrics/useFaceVerification.js.map +0 -1
- package/lib/module/components/biometrics/useFaceVerificationFlow.js +0 -150
- package/lib/module/components/biometrics/useFaceVerificationFlow.js.map +0 -1
- package/lib/typescript/components/Button.d.ts +0 -17
- package/lib/typescript/components/Button.d.ts.map +0 -1
- package/lib/typescript/components/biometrics/FaceVerification.d.ts +0 -9
- package/lib/typescript/components/biometrics/FaceVerification.d.ts.map +0 -1
- package/lib/typescript/components/biometrics/useFaceVerification.d.ts +0 -38
- package/lib/typescript/components/biometrics/useFaceVerification.d.ts.map +0 -1
- package/lib/typescript/components/biometrics/useFaceVerificationFlow.d.ts +0 -29
- package/lib/typescript/components/biometrics/useFaceVerificationFlow.d.ts.map +0 -1
- package/omnipay_reactnative_sdk.podspec +0 -52
- package/src/components/Button.tsx +0 -86
- package/src/components/biometrics/FaceVerification.tsx +0 -429
- package/src/components/biometrics/useFaceVerification.ts +0 -120
- package/src/components/biometrics/useFaceVerificationFlow.ts +0 -224
|
@@ -12,12 +12,15 @@ import {
|
|
|
12
12
|
Image,
|
|
13
13
|
TouchableWithoutFeedback,
|
|
14
14
|
Text,
|
|
15
|
+
NativeModules,
|
|
16
|
+
NativeEventEmitter,
|
|
15
17
|
} from 'react-native';
|
|
16
|
-
import WebView, { WebViewMessageEvent } from 'react-native-webview';
|
|
18
|
+
import WebView, { type WebViewMessageEvent } from 'react-native-webview';
|
|
17
19
|
import { getContact } from '../functions';
|
|
18
20
|
import Share from 'react-native-share';
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
22
|
+
|
|
23
|
+
const OmnipayActivity = NativeModules.OmnipayActivity || {};
|
|
21
24
|
|
|
22
25
|
type OmnipayProviderProps = {
|
|
23
26
|
publicKey: string;
|
|
@@ -26,6 +29,16 @@ type OmnipayProviderProps = {
|
|
|
26
29
|
children: React.ReactElement | React.ReactElement[];
|
|
27
30
|
};
|
|
28
31
|
|
|
32
|
+
type PosTransactionType = {
|
|
33
|
+
amount: number;
|
|
34
|
+
purchaseType: 'PURCHASE' | 'KEY EXCHANGE';
|
|
35
|
+
color: string;
|
|
36
|
+
print: boolean;
|
|
37
|
+
rrn: string;
|
|
38
|
+
stan: string;
|
|
39
|
+
terminalId: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
29
42
|
type PostMessage = {
|
|
30
43
|
[key: string]: unknown;
|
|
31
44
|
};
|
|
@@ -57,6 +70,7 @@ type InitiateWalletType = {
|
|
|
57
70
|
sessionId?: string;
|
|
58
71
|
kycStatus?: 'verified' | 'unverified';
|
|
59
72
|
launchPage?: string;
|
|
73
|
+
promoName?: string;
|
|
60
74
|
};
|
|
61
75
|
|
|
62
76
|
export type OmnipayContextType = {
|
|
@@ -81,6 +95,7 @@ export type OmnipayContextType = {
|
|
|
81
95
|
sessionId,
|
|
82
96
|
launchPage,
|
|
83
97
|
kycStatus,
|
|
98
|
+
promoName,
|
|
84
99
|
}: InitiateWalletType) => void;
|
|
85
100
|
};
|
|
86
101
|
|
|
@@ -110,18 +125,99 @@ export const OmnipayProvider = ({
|
|
|
110
125
|
const isValidEnv = ['prod', 'dev'].includes(env);
|
|
111
126
|
const isValidColor = color.length > 2;
|
|
112
127
|
const onCloseRef = useRef<(() => void) | undefined>(undefined);
|
|
113
|
-
const [
|
|
128
|
+
const [canUsePos, setCanUsePos] = useState(false);
|
|
114
129
|
|
|
115
130
|
useEffect(() => {
|
|
116
|
-
|
|
117
|
-
setShowFaceVerification(true);
|
|
118
|
-
}, 1000);
|
|
131
|
+
checkPaymentApp();
|
|
119
132
|
}, []);
|
|
120
133
|
|
|
121
134
|
useEffect(() => {
|
|
122
135
|
visibilityRef.current = isVisible;
|
|
123
136
|
}, [isVisible]);
|
|
124
137
|
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (canUsePos) {
|
|
140
|
+
const eventEmitter = new NativeEventEmitter(OmnipayActivity);
|
|
141
|
+
eventEmitter.addListener('OmnipayEvent', (event) => {
|
|
142
|
+
console.log('native event', event);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}, [canUsePos]);
|
|
146
|
+
|
|
147
|
+
async function checkPaymentApp() {
|
|
148
|
+
try {
|
|
149
|
+
if (Platform.OS === 'android') {
|
|
150
|
+
const isInstalled = await OmnipayActivity.isPackageInstalled(
|
|
151
|
+
'com.horizonpay.sample'
|
|
152
|
+
);
|
|
153
|
+
if (isInstalled) {
|
|
154
|
+
setCanUsePos(true);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function startPosTransaction({
|
|
161
|
+
amount,
|
|
162
|
+
purchaseType,
|
|
163
|
+
print,
|
|
164
|
+
rrn,
|
|
165
|
+
stan,
|
|
166
|
+
terminalId,
|
|
167
|
+
}: PosTransactionType) {
|
|
168
|
+
try {
|
|
169
|
+
if (Platform.OS === 'android') {
|
|
170
|
+
let result = '';
|
|
171
|
+
|
|
172
|
+
if (purchaseType === 'KEY EXCHANGE') {
|
|
173
|
+
const isKeyExchanged = await AsyncStorage.getItem('isKeyExchanged');
|
|
174
|
+
if (!isKeyExchanged) {
|
|
175
|
+
result = await OmnipayActivity.initiateHorizonTransaction(
|
|
176
|
+
amount,
|
|
177
|
+
purchaseType,
|
|
178
|
+
color,
|
|
179
|
+
print,
|
|
180
|
+
rrn,
|
|
181
|
+
stan,
|
|
182
|
+
terminalId
|
|
183
|
+
);
|
|
184
|
+
if (
|
|
185
|
+
terminalId &&
|
|
186
|
+
result &&
|
|
187
|
+
result.toLowerCase().includes('-message-success')
|
|
188
|
+
) {
|
|
189
|
+
await AsyncStorage.setItem('isKeyExchanged', terminalId);
|
|
190
|
+
}
|
|
191
|
+
postMessage({
|
|
192
|
+
dataKey: 'onPosKeyExchanged',
|
|
193
|
+
dataValue: result,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
result = await OmnipayActivity.initiateHorizonTransaction(
|
|
198
|
+
amount,
|
|
199
|
+
purchaseType,
|
|
200
|
+
color,
|
|
201
|
+
print,
|
|
202
|
+
rrn,
|
|
203
|
+
stan,
|
|
204
|
+
terminalId
|
|
205
|
+
);
|
|
206
|
+
postMessage({
|
|
207
|
+
dataKey: 'onPosTransactionSuccess',
|
|
208
|
+
dataValue: result,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.log(error);
|
|
214
|
+
postMessage({
|
|
215
|
+
dataKey: 'onPosTransactionFailure',
|
|
216
|
+
dataValue: '',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
125
221
|
function getWebviewStyle() {
|
|
126
222
|
if (!showWebview) {
|
|
127
223
|
return { opacity: 0, height: 0, width: 0, flex: 0 };
|
|
@@ -181,6 +277,9 @@ export const OmnipayProvider = ({
|
|
|
181
277
|
if (dataKey === 'shareReceipt') {
|
|
182
278
|
shareReceipt(dataValue);
|
|
183
279
|
}
|
|
280
|
+
if (dataKey === 'startPosTransaction') {
|
|
281
|
+
startPosTransaction(JSON.parse(dataValue));
|
|
282
|
+
}
|
|
184
283
|
}
|
|
185
284
|
} catch (error) {}
|
|
186
285
|
}
|
|
@@ -228,6 +327,7 @@ export const OmnipayProvider = ({
|
|
|
228
327
|
sessionId = '',
|
|
229
328
|
kycStatus,
|
|
230
329
|
launchPage = 'wallet',
|
|
330
|
+
promoName = '',
|
|
231
331
|
}: InitiateWalletType) => {
|
|
232
332
|
//prevent opening if it's already open
|
|
233
333
|
if (visibilityRef.current) {
|
|
@@ -239,9 +339,9 @@ export const OmnipayProvider = ({
|
|
|
239
339
|
const usesNativeShare = true;
|
|
240
340
|
|
|
241
341
|
if (isPhoneNumberValid || isValidCustomerRef || isValidUserRef) {
|
|
242
|
-
const webUrl = `${webHost}?theme=${color}&view=wallet&publicKey=${publicKey}&phoneNumber=${phoneNumber}&usesPaylater=${usesPaylater}&usesPromo=${usesPromo}&usesAirtimeData=${usesAirtimeData}&usesTransfer=${usesTransfer}&usesBills=${usesBills}&usesPos=${usesPos}&customerRef=${customerRef}&userRef=${userRef}&promoBalanceOffset=${promoBalanceOffset}&deviceId=${deviceId}&deviceName=${deviceName}&hideWalletTransfer=${hideWalletTransfer}&bvnRequired=${isBvnValidationRequired}&usesNativeShare=${usesNativeShare}&walletTab=${walletTab}&sessionId=${sessionId}&kycStatus=${
|
|
342
|
+
const webUrl = `${webHost}?theme=${color}&view=wallet&publicKey=${publicKey}&phoneNumber=${phoneNumber}&usesPaylater=${usesPaylater}&usesPromo=${usesPromo}&usesAirtimeData=${usesAirtimeData}&usesTransfer=${usesTransfer}&usesBills=${usesBills}&usesPos=${usesPos}&customerRef=${customerRef}&userRef=${userRef}&promoBalanceOffset=${promoBalanceOffset}&deviceId=${deviceId}&deviceName=${deviceName}&hideWalletTransfer=${hideWalletTransfer}&bvnRequired=${isBvnValidationRequired}&usesNativeShare=${usesNativeShare}&isPosEnabled=${canUsePos}&walletTab=${walletTab}&sessionId=${sessionId}&kycStatus=${
|
|
243
343
|
kycStatus || ''
|
|
244
|
-
}&launchPage=${launchPage}`;
|
|
344
|
+
}&launchPage=${launchPage}&promoName=${promoName}`;
|
|
245
345
|
setWebviewUrl(webUrl);
|
|
246
346
|
setIsVisible(true);
|
|
247
347
|
onCloseRef.current = onClose;
|
|
@@ -317,6 +417,7 @@ export const OmnipayProvider = ({
|
|
|
317
417
|
}}
|
|
318
418
|
domStorageEnabled={true}
|
|
319
419
|
originWhitelist={['*']}
|
|
420
|
+
allowsInlineMediaPlayback={true}
|
|
320
421
|
onLoadEnd={() => setWebviewStatus('success')}
|
|
321
422
|
renderError={() => (
|
|
322
423
|
<View style={StyleSheet.absoluteFillObject}>
|
|
@@ -324,12 +425,21 @@ export const OmnipayProvider = ({
|
|
|
324
425
|
<Text style={styles.errorSubtitle}>
|
|
325
426
|
Unable to open your wallet. Please try again
|
|
326
427
|
</Text>
|
|
327
|
-
<
|
|
328
|
-
|
|
428
|
+
<TouchableOpacity
|
|
429
|
+
activeOpacity={0.8}
|
|
329
430
|
onPress={reloadWebview}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
431
|
+
style={[
|
|
432
|
+
styles.button,
|
|
433
|
+
{
|
|
434
|
+
backgroundColor: color,
|
|
435
|
+
borderColor: color,
|
|
436
|
+
},
|
|
437
|
+
]}
|
|
438
|
+
>
|
|
439
|
+
<>
|
|
440
|
+
<Text style={styles.buttonText}>Retry</Text>
|
|
441
|
+
</>
|
|
442
|
+
</TouchableOpacity>
|
|
333
443
|
</View>
|
|
334
444
|
</View>
|
|
335
445
|
)}
|
|
@@ -350,13 +460,6 @@ export const OmnipayProvider = ({
|
|
|
350
460
|
</>
|
|
351
461
|
)}
|
|
352
462
|
{children}
|
|
353
|
-
{showFaceVerification && (
|
|
354
|
-
<FaceVerification
|
|
355
|
-
onClose={() => setShowFaceVerification(false)}
|
|
356
|
-
onSuccess={() => setShowFaceVerification(false)}
|
|
357
|
-
primaryColor={color}
|
|
358
|
-
/>
|
|
359
|
-
)}
|
|
360
463
|
</OmnipayContext.Provider>
|
|
361
464
|
);
|
|
362
465
|
};
|
|
@@ -467,4 +570,13 @@ const styles = StyleSheet.create({
|
|
|
467
570
|
minWidth: 160,
|
|
468
571
|
marginHorizontal: 'auto',
|
|
469
572
|
},
|
|
573
|
+
button: {
|
|
574
|
+
borderRadius: 6,
|
|
575
|
+
paddingHorizontal: 12,
|
|
576
|
+
paddingVertical: 14,
|
|
577
|
+
borderWidth: 1,
|
|
578
|
+
alignItems: 'center',
|
|
579
|
+
justifyContent: 'center',
|
|
580
|
+
},
|
|
581
|
+
buttonText: { color: 'white', fontSize: 16, paddingHorizontal: 30 },
|
|
470
582
|
});
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
Text,
|
|
8
8
|
Linking,
|
|
9
9
|
} from 'react-native';
|
|
10
|
-
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
10
|
+
import { WebView, type WebViewMessageEvent } from 'react-native-webview';
|
|
11
11
|
import { clientSdkBaseUrl, serverSdkBaseUrl } from '../lib/config';
|
|
12
12
|
import { getContact } from '../functions';
|
|
13
13
|
import { Registration } from './views/Registration';
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
Text,
|
|
7
7
|
View,
|
|
8
8
|
} from 'react-native';
|
|
9
|
-
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
9
|
+
import { WebView, type WebViewMessageEvent } from 'react-native-webview';
|
|
10
10
|
import { serverSdkBaseUrl } from '../../lib/config';
|
|
11
11
|
|
|
12
12
|
type OmnipayProps = {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
Text,
|
|
7
7
|
View,
|
|
8
8
|
} from 'react-native';
|
|
9
|
-
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
9
|
+
import { WebView, type WebViewMessageEvent } from 'react-native-webview';
|
|
10
10
|
import { serverSdkBaseUrl } from '../../lib/config';
|
|
11
11
|
|
|
12
12
|
type AgreementSubmittedType = {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
Text,
|
|
7
7
|
View,
|
|
8
8
|
} from 'react-native';
|
|
9
|
-
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
9
|
+
import { WebView, type WebViewMessageEvent } from 'react-native-webview';
|
|
10
10
|
import { serverSdkBaseUrl } from '../../lib/config';
|
|
11
11
|
|
|
12
12
|
type RegisterSuccessType = {
|
package/src/hooks/useOmnipay.tsx
CHANGED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
package com.omniretail.omnipay
|
|
2
|
-
|
|
3
|
-
import android.util.Log
|
|
4
|
-
import com.google.mlkit.vision.common.InputImage
|
|
5
|
-
import com.google.mlkit.vision.face.FaceDetection
|
|
6
|
-
import com.google.mlkit.vision.face.FaceDetectorOptions
|
|
7
|
-
import com.google.mlkit.vision.face.Face
|
|
8
|
-
import com.mrousavy.camera.core.FrameInvalidError
|
|
9
|
-
import com.mrousavy.camera.frameprocessor.Frame
|
|
10
|
-
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin
|
|
11
|
-
import com.mrousavy.camera.frameprocessor.VisionCameraProxy
|
|
12
|
-
|
|
13
|
-
class FaceVerificationFrameProcessor(proxy: VisionCameraProxy, options: Map<String, Any>?) : FrameProcessorPlugin() {
|
|
14
|
-
|
|
15
|
-
private val faceDetector = FaceDetection.getClient(
|
|
16
|
-
FaceDetectorOptions.Builder()
|
|
17
|
-
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
|
|
18
|
-
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
|
|
19
|
-
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
|
|
20
|
-
.setMinFaceSize(0.15f)
|
|
21
|
-
.enableTracking()
|
|
22
|
-
.build()
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
override fun callback(frame: Frame, arguments: Map<String, Any>?): Any {
|
|
26
|
-
return try {
|
|
27
|
-
val results = mutableMapOf<String, Any>()
|
|
28
|
-
|
|
29
|
-
// Convert frame to InputImage
|
|
30
|
-
val image = InputImage.fromMediaImage(frame.image, frame.imageRotationDegrees)
|
|
31
|
-
|
|
32
|
-
// Process face detection synchronously
|
|
33
|
-
val task = faceDetector.process(image)
|
|
34
|
-
|
|
35
|
-
// Wait for result (this blocks the thread but that's OK for frame processors)
|
|
36
|
-
val faces = try {
|
|
37
|
-
// For simplicity, we'll use a blocking approach
|
|
38
|
-
// In production, you might want to handle this differently
|
|
39
|
-
while (!task.isComplete && !task.isCanceled) {
|
|
40
|
-
Thread.sleep(1)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (task.isSuccessful) {
|
|
44
|
-
task.result
|
|
45
|
-
} else {
|
|
46
|
-
emptyList()
|
|
47
|
-
}
|
|
48
|
-
} catch (e: Exception) {
|
|
49
|
-
Log.e("FaceVerification", "Face detection failed: ${e.message}")
|
|
50
|
-
results["error"] = e.message ?: "Face detection failed"
|
|
51
|
-
return results
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (faces.isEmpty()) {
|
|
55
|
-
results["faceDetected"] = false
|
|
56
|
-
return results
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Process first detected face
|
|
60
|
-
val face = faces[0]
|
|
61
|
-
results["faceDetected"] = true
|
|
62
|
-
|
|
63
|
-
// Face bounding box
|
|
64
|
-
results["boundingBox"] = mapOf(
|
|
65
|
-
"left" to face.boundingBox.left,
|
|
66
|
-
"top" to face.boundingBox.top,
|
|
67
|
-
"right" to face.boundingBox.right,
|
|
68
|
-
"bottom" to face.boundingBox.bottom
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
// Smile detection
|
|
72
|
-
face.smilingProbability?.let { smilingProbability ->
|
|
73
|
-
results["isSmiling"] = smilingProbability > 0.7f
|
|
74
|
-
results["smileProbability"] = smilingProbability
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Eye detection (for blinking)
|
|
78
|
-
val leftEyeOpenProbability = face.leftEyeOpenProbability
|
|
79
|
-
val rightEyeOpenProbability = face.rightEyeOpenProbability
|
|
80
|
-
|
|
81
|
-
if (leftEyeOpenProbability != null && rightEyeOpenProbability != null) {
|
|
82
|
-
val leftEyeClosed = leftEyeOpenProbability < 0.3f
|
|
83
|
-
val rightEyeClosed = rightEyeOpenProbability < 0.3f
|
|
84
|
-
|
|
85
|
-
results["leftEyeClosed"] = leftEyeClosed
|
|
86
|
-
results["rightEyeClosed"] = rightEyeClosed
|
|
87
|
-
results["isBlinking"] = leftEyeClosed && rightEyeClosed
|
|
88
|
-
results["leftEyeOpenProbability"] = leftEyeOpenProbability
|
|
89
|
-
results["rightEyeOpenProbability"] = rightEyeOpenProbability
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Head pose detection
|
|
93
|
-
results["headPose"] = mapOf(
|
|
94
|
-
"yaw" to face.headEulerAngleY, // Left-right movement
|
|
95
|
-
"pitch" to face.headEulerAngleX, // Up-down movement
|
|
96
|
-
"roll" to face.headEulerAngleZ // Tilt movement
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
// Face tracking ID (useful for consistency across frames)
|
|
100
|
-
face.trackingId?.let { trackingId ->
|
|
101
|
-
results["trackingId"] = trackingId
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
results
|
|
105
|
-
|
|
106
|
-
} catch (e: Exception) {
|
|
107
|
-
Log.e("FaceVerification", "Unexpected error: ${e.message}")
|
|
108
|
-
mapOf("error" to (e.message ?: "Unknown error occurred"))
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import VisionCamera
|
|
2
|
-
import Vision
|
|
3
|
-
import AVFoundation
|
|
4
|
-
|
|
5
|
-
@objc(FaceVerificationFrameProcessor)
|
|
6
|
-
public class FaceVerificationFrameProcessor: FrameProcessorPlugin {
|
|
7
|
-
|
|
8
|
-
public override init(proxy: VisionCameraProxyHolder, options: [AnyHashable : Any]! = [:]) {
|
|
9
|
-
super.init(proxy: proxy, options: options)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
public override func callback(_ frame: Frame, withArguments arguments: [AnyHashable : Any]?) -> Any {
|
|
13
|
-
let buffer = frame.buffer
|
|
14
|
-
guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else {
|
|
15
|
-
return ["error": "Failed to get image buffer"]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return performFaceDetection(on: imageBuffer)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
private func performFaceDetection(on imageBuffer: CVImageBuffer) -> [String: Any] {
|
|
22
|
-
let request = VNDetectFaceLandmarksRequest()
|
|
23
|
-
let handler = VNImageRequestHandler(cvPixelBuffer: imageBuffer, options: [:])
|
|
24
|
-
|
|
25
|
-
do {
|
|
26
|
-
try handler.perform([request])
|
|
27
|
-
|
|
28
|
-
guard let observations = request.results as? [VNFaceObservation],
|
|
29
|
-
let face = observations.first else {
|
|
30
|
-
return ["faceDetected": false]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
var results: [String: Any] = [
|
|
34
|
-
"faceDetected": true,
|
|
35
|
-
"boundingBox": [
|
|
36
|
-
"x": face.boundingBox.origin.x,
|
|
37
|
-
"y": face.boundingBox.origin.y,
|
|
38
|
-
"width": face.boundingBox.size.width,
|
|
39
|
-
"height": face.boundingBox.size.height
|
|
40
|
-
]
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
// Analyze facial features
|
|
44
|
-
if let landmarks = face.landmarks {
|
|
45
|
-
results = analyzeFacialFeatures(landmarks: landmarks, results: results)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Calculate head pose
|
|
49
|
-
if let pose = calculateHeadPose(face: face) {
|
|
50
|
-
results["headPose"] = pose
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return results
|
|
54
|
-
|
|
55
|
-
} catch {
|
|
56
|
-
return ["error": error.localizedDescription]
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private func analyzeFacialFeatures(landmarks: VNFaceLandmarks2D, results: [String: Any]) -> [String: Any] {
|
|
61
|
-
var updatedResults = results
|
|
62
|
-
|
|
63
|
-
// Detect smile
|
|
64
|
-
if let mouth = landmarks.outerLips {
|
|
65
|
-
let isSmiling = detectSmile(mouthPoints: mouth.normalizedPoints)
|
|
66
|
-
updatedResults["isSmiling"] = isSmiling
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Detect blinks
|
|
70
|
-
if let leftEye = landmarks.leftEye, let rightEye = landmarks.rightEye {
|
|
71
|
-
let leftEyeClosed = detectEyeClosure(eyePoints: leftEye.normalizedPoints)
|
|
72
|
-
let rightEyeClosed = detectEyeClosure(eyePoints: rightEye.normalizedPoints)
|
|
73
|
-
|
|
74
|
-
updatedResults["leftEyeClosed"] = leftEyeClosed
|
|
75
|
-
updatedResults["rightEyeClosed"] = rightEyeClosed
|
|
76
|
-
updatedResults["isBlinking"] = leftEyeClosed && rightEyeClosed
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return updatedResults
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private func detectSmile(mouthPoints: [CGPoint]) -> Bool {
|
|
83
|
-
guard mouthPoints.count >= 6 else { return false }
|
|
84
|
-
|
|
85
|
-
// Calculate mouth corner heights vs center
|
|
86
|
-
let leftCorner = mouthPoints[0]
|
|
87
|
-
let rightCorner = mouthPoints[3]
|
|
88
|
-
let topCenter = mouthPoints[1]
|
|
89
|
-
let bottomCenter = mouthPoints[4]
|
|
90
|
-
|
|
91
|
-
let cornerHeight = (leftCorner.y + rightCorner.y) / 2
|
|
92
|
-
let centerHeight = (topCenter.y + bottomCenter.y) / 2
|
|
93
|
-
|
|
94
|
-
// Smile detection: corners higher than center
|
|
95
|
-
return cornerHeight < centerHeight - 0.01
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private func detectEyeClosure(eyePoints: [CGPoint]) -> Bool {
|
|
99
|
-
guard eyePoints.count >= 6 else { return false }
|
|
100
|
-
|
|
101
|
-
// Calculate eye aspect ratio
|
|
102
|
-
let topPoints = Array(eyePoints[1...2])
|
|
103
|
-
let bottomPoints = Array(eyePoints[4...5])
|
|
104
|
-
let leftPoint = eyePoints[0]
|
|
105
|
-
let rightPoint = eyePoints[3]
|
|
106
|
-
|
|
107
|
-
let verticalDist1 = distance(topPoints[0], bottomPoints[0])
|
|
108
|
-
let verticalDist2 = distance(topPoints[1], bottomPoints[1])
|
|
109
|
-
let horizontalDist = distance(leftPoint, rightPoint)
|
|
110
|
-
|
|
111
|
-
let eyeAspectRatio = (verticalDist1 + verticalDist2) / (2.0 * horizontalDist)
|
|
112
|
-
|
|
113
|
-
// Eye is closed if aspect ratio is below threshold
|
|
114
|
-
return eyeAspectRatio < 0.2
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private func calculateHeadPose(face: VNFaceObservation) -> [String: Double]? {
|
|
118
|
-
guard let yaw = face.yaw, let pitch = face.pitch, let roll = face.roll else {
|
|
119
|
-
return nil
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
let yawDegrees = Double(truncating: yaw) * 180.0 / Double.pi
|
|
123
|
-
let pitchDegrees = Double(truncating: pitch) * 180.0 / Double.pi
|
|
124
|
-
let rollDegrees = Double(truncating: roll) * 180.0 / Double.pi
|
|
125
|
-
|
|
126
|
-
return [
|
|
127
|
-
"yaw": yawDegrees, // Left-right head movement
|
|
128
|
-
"pitch": pitchDegrees, // Up-down head movement
|
|
129
|
-
"roll": rollDegrees // Head tilt
|
|
130
|
-
]
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private func distance(_ point1: CGPoint, _ point2: CGPoint) -> Double {
|
|
134
|
-
let dx = point1.x - point2.x
|
|
135
|
-
let dy = point1.y - point2.y
|
|
136
|
-
return sqrt(Double(dx * dx + dy * dy))
|
|
137
|
-
}
|
|
138
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Use this file to import your target's public headers that you would like to expose to Swift.
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
#import <React/RCTBridgeModule.h>
|
|
6
|
-
#import <VisionCamera/FrameProcessorPlugin.h>
|
|
7
|
-
#import <VisionCamera/FrameProcessorPluginRegistry.h>
|
|
8
|
-
#import <VisionCamera/Frame.h>
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _react = _interopRequireDefault(require("react"));
|
|
8
|
-
var _reactNative = require("react-native");
|
|
9
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
-
const Button = ({
|
|
11
|
-
title,
|
|
12
|
-
onPress,
|
|
13
|
-
backgroundColor = '#007AFF',
|
|
14
|
-
borderColor,
|
|
15
|
-
textColor = 'white',
|
|
16
|
-
disabled = false,
|
|
17
|
-
loading = false,
|
|
18
|
-
style,
|
|
19
|
-
textStyle,
|
|
20
|
-
activeOpacity = 0.8
|
|
21
|
-
}) => {
|
|
22
|
-
const buttonStyle = {
|
|
23
|
-
...styles.button,
|
|
24
|
-
backgroundColor: disabled ? '#cccccc' : backgroundColor,
|
|
25
|
-
borderColor: disabled ? '#cccccc' : borderColor || backgroundColor,
|
|
26
|
-
...style
|
|
27
|
-
};
|
|
28
|
-
const finalTextStyle = {
|
|
29
|
-
...styles.buttonText,
|
|
30
|
-
color: disabled ? '#666666' : textColor,
|
|
31
|
-
...textStyle
|
|
32
|
-
};
|
|
33
|
-
return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableOpacity, {
|
|
34
|
-
style: buttonStyle,
|
|
35
|
-
onPress: onPress,
|
|
36
|
-
disabled: disabled || loading,
|
|
37
|
-
activeOpacity: activeOpacity,
|
|
38
|
-
accessibilityRole: "button",
|
|
39
|
-
accessibilityLabel: title,
|
|
40
|
-
accessibilityState: {
|
|
41
|
-
disabled: disabled || loading
|
|
42
|
-
}
|
|
43
|
-
}, loading ? /*#__PURE__*/_react.default.createElement(_reactNative.ActivityIndicator, {
|
|
44
|
-
color: textColor,
|
|
45
|
-
size: "small"
|
|
46
|
-
}) : /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
|
|
47
|
-
style: finalTextStyle
|
|
48
|
-
}, title));
|
|
49
|
-
};
|
|
50
|
-
const styles = _reactNative.StyleSheet.create({
|
|
51
|
-
button: {
|
|
52
|
-
borderRadius: 6,
|
|
53
|
-
paddingHorizontal: 12,
|
|
54
|
-
paddingVertical: 14,
|
|
55
|
-
borderWidth: 1,
|
|
56
|
-
alignItems: 'center',
|
|
57
|
-
justifyContent: 'center',
|
|
58
|
-
minHeight: 48
|
|
59
|
-
},
|
|
60
|
-
buttonText: {
|
|
61
|
-
color: 'white',
|
|
62
|
-
fontSize: 16,
|
|
63
|
-
fontWeight: '600',
|
|
64
|
-
paddingHorizontal: 30
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
var _default = exports.default = Button;
|
|
68
|
-
//# sourceMappingURL=Button.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireDefault","require","_reactNative","e","__esModule","default","Button","title","onPress","backgroundColor","borderColor","textColor","disabled","loading","style","textStyle","activeOpacity","buttonStyle","styles","button","finalTextStyle","buttonText","color","createElement","TouchableOpacity","accessibilityRole","accessibilityLabel","accessibilityState","ActivityIndicator","size","Text","StyleSheet","create","borderRadius","paddingHorizontal","paddingVertical","borderWidth","alignItems","justifyContent","minHeight","fontSize","fontWeight","_default","exports"],"sourceRoot":"../../../src","sources":["components/Button.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AAOsB,SAAAD,uBAAAG,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAetB,MAAMG,MAA6B,GAAGA,CAAC;EACrCC,KAAK;EACLC,OAAO;EACPC,eAAe,GAAG,SAAS;EAC3BC,WAAW;EACXC,SAAS,GAAG,OAAO;EACnBC,QAAQ,GAAG,KAAK;EAChBC,OAAO,GAAG,KAAK;EACfC,KAAK;EACLC,SAAS;EACTC,aAAa,GAAG;AAClB,CAAC,KAAK;EACJ,MAAMC,WAAsB,GAAG;IAC7B,GAAGC,MAAM,CAACC,MAAM;IAChBV,eAAe,EAAEG,QAAQ,GAAG,SAAS,GAAGH,eAAe;IACvDC,WAAW,EAAEE,QAAQ,GAAG,SAAS,GAAGF,WAAW,IAAID,eAAe;IAClE,GAAGK;EACL,CAAC;EAED,MAAMM,cAAyB,GAAG;IAChC,GAAGF,MAAM,CAACG,UAAU;IACpBC,KAAK,EAAEV,QAAQ,GAAG,SAAS,GAAGD,SAAS;IACvC,GAAGI;EACL,CAAC;EAED,oBACEhB,MAAA,CAAAM,OAAA,CAAAkB,aAAA,CAACrB,YAAA,CAAAsB,gBAAgB;IACfV,KAAK,EAAEG,WAAY;IACnBT,OAAO,EAAEA,OAAQ;IACjBI,QAAQ,EAAEA,QAAQ,IAAIC,OAAQ;IAC9BG,aAAa,EAAEA,aAAc;IAC7BS,iBAAiB,EAAC,QAAQ;IAC1BC,kBAAkB,EAAEnB,KAAM;IAC1BoB,kBAAkB,EAAE;MAAEf,QAAQ,EAAEA,QAAQ,IAAIC;IAAQ;EAAE,GAErDA,OAAO,gBACNd,MAAA,CAAAM,OAAA,CAAAkB,aAAA,CAACrB,YAAA,CAAA0B,iBAAiB;IAACN,KAAK,EAAEX,SAAU;IAACkB,IAAI,EAAC;EAAO,CAAE,CAAC,gBAEpD9B,MAAA,CAAAM,OAAA,CAAAkB,aAAA,CAACrB,YAAA,CAAA4B,IAAI;IAAChB,KAAK,EAAEM;EAAe,GAAEb,KAAY,CAE5B,CAAC;AAEvB,CAAC;AAED,MAAMW,MAAM,GAAGa,uBAAU,CAACC,MAAM,CAAC;EAC/Bb,MAAM,EAAE;IACNc,YAAY,EAAE,CAAC;IACfC,iBAAiB,EAAE,EAAE;IACrBC,eAAe,EAAE,EAAE;IACnBC,WAAW,EAAE,CAAC;IACdC,UAAU,EAAE,QAAQ;IACpBC,cAAc,EAAE,QAAQ;IACxBC,SAAS,EAAE;EACb,CAAC;EACDlB,UAAU,EAAE;IACVC,KAAK,EAAE,OAAO;IACdkB,QAAQ,EAAE,EAAE;IACZC,UAAU,EAAE,KAAK;IACjBP,iBAAiB,EAAE;EACrB;AACF,CAAC,CAAC;AAAC,IAAAQ,QAAA,GAAAC,OAAA,CAAAtC,OAAA,GAEYC,MAAM","ignoreList":[]}
|