@rick427/react-native-liveness 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/module/LivenessCamera.js +212 -33
- package/lib/module/LivenessCamera.js.map +1 -1
- package/lib/module/LivenessCameraModal.js +117 -0
- package/lib/module/LivenessCameraModal.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/LivenessCamera.d.ts +1 -1
- package/lib/typescript/src/LivenessCamera.d.ts.map +1 -1
- package/lib/typescript/src/LivenessCameraModal.d.ts +15 -0
- package/lib/typescript/src/LivenessCameraModal.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +35 -3
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/LivenessCamera.tsx +230 -29
- package/src/LivenessCameraModal.tsx +116 -0
- package/src/index.ts +2 -0
- package/src/types.ts +42 -3
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { Animated, StyleSheet, Text, View } from 'react-native';
|
|
4
|
+
import { Animated, Easing, StyleSheet, Text, View } from 'react-native';
|
|
5
5
|
import { Camera, useCameraDevice, useCameraFormat, useCameraPermission } from 'react-native-vision-camera';
|
|
6
|
-
import { Circle, Path, Svg } from 'react-native-svg';
|
|
6
|
+
import { Circle, ClipPath, Defs, G, LinearGradient, Path, Rect, Stop, Svg } from 'react-native-svg';
|
|
7
7
|
import { useLivenessCamera } from "./useLivenessCamera.js";
|
|
8
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// ─── Animated SVG components ─────────────────────────────────────────────────
|
|
10
|
+
// Created once at module level so React never recreates the component class.
|
|
11
|
+
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
|
12
|
+
const AnimatedG = Animated.createAnimatedComponent(G);
|
|
13
|
+
const AnimatedRect = Animated.createAnimatedComponent(Rect);
|
|
14
|
+
|
|
15
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
16
|
+
const DEFAULT_FONT = 'Baloo-Medium';
|
|
11
17
|
const CIRCLE_DIAMETER_RATIO = 0.82;
|
|
12
18
|
const STROKE_WIDTH = 3;
|
|
13
|
-
|
|
14
|
-
const
|
|
19
|
+
const K = 0.5523; // cubic bezier ellipse approximation
|
|
20
|
+
const SCAN_LINE_HEIGHT = 44; // px — height of the sweep bar
|
|
21
|
+
const BRACKET_SPAN_DEG = 44; // degrees each corner bracket spans
|
|
22
|
+
const BRACKET_STROKE = STROKE_WIDTH + 1;
|
|
15
23
|
|
|
24
|
+
// ─── Colour helper ────────────────────────────────────────────────────────────
|
|
16
25
|
/**
|
|
17
|
-
* Returns the stroke colour for the circle guide.
|
|
18
|
-
*
|
|
19
26
|
* ● White – no face / scanning (score < 0.4)
|
|
20
|
-
* ● Yellow – face detected, confidence building
|
|
27
|
+
* ● Yellow – face detected, confidence building
|
|
21
28
|
* ● Green – liveness confirmed / countdown / capture
|
|
22
29
|
* ● Red – error
|
|
23
30
|
*/
|
|
@@ -35,53 +42,214 @@ function getCircleColor(state, score) {
|
|
|
35
42
|
}
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* using cubic bezier curves. Used inside a compound path with
|
|
41
|
-
* fillRule="evenodd" to punch a transparent hole through the dark scrim.
|
|
42
|
-
*/
|
|
45
|
+
// ─── SVG path helpers ─────────────────────────────────────────────────────────
|
|
46
|
+
/** Cubic bezier circle path — used inside compound evenodd scrim. */
|
|
43
47
|
function circlePath(cx, cy, r) {
|
|
44
48
|
return [`M ${cx + r} ${cy}`, `C ${cx + r} ${cy - r * K} ${cx + r * K} ${cy - r} ${cx} ${cy - r}`, `C ${cx - r * K} ${cy - r} ${cx - r} ${cy - r * K} ${cx - r} ${cy}`, `C ${cx - r} ${cy + r * K} ${cx - r * K} ${cy + r} ${cx} ${cy + r}`, `C ${cx + r * K} ${cy + r} ${cx + r} ${cy + r * K} ${cx + r} ${cy}`, 'Z'].join(' ');
|
|
45
49
|
}
|
|
50
|
+
|
|
51
|
+
/** One corner bracket arc centred at `centerDeg`, spanning `spanDeg`. */
|
|
52
|
+
function bracketArcPath(cx, cy, r, centerDeg, spanDeg) {
|
|
53
|
+
const toRad = d => d * Math.PI / 180;
|
|
54
|
+
const a1 = toRad(centerDeg - spanDeg / 2);
|
|
55
|
+
const a2 = toRad(centerDeg + spanDeg / 2);
|
|
56
|
+
const x1 = cx + r * Math.cos(a1);
|
|
57
|
+
const y1 = cy + r * Math.sin(a1);
|
|
58
|
+
const x2 = cx + r * Math.cos(a2);
|
|
59
|
+
const y2 = cy + r * Math.sin(a2);
|
|
60
|
+
return `M ${x1} ${y1} A ${r} ${r} 0 0 1 ${x2} ${y2}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── CircleOverlay ────────────────────────────────────────────────────────────
|
|
46
64
|
function CircleOverlay({
|
|
47
65
|
width,
|
|
48
66
|
height,
|
|
49
67
|
state,
|
|
50
|
-
score
|
|
68
|
+
score,
|
|
69
|
+
livenessThreshold
|
|
51
70
|
}) {
|
|
71
|
+
// ── Animation values (must be declared before any early return) ────────────
|
|
72
|
+
const scanAnim = useRef(new Animated.Value(0)).current;
|
|
73
|
+
const scanOpacity = useRef(new Animated.Value(1)).current;
|
|
74
|
+
const progressAnim = useRef(new Animated.Value(0)).current;
|
|
75
|
+
const bracketAnim = useRef(new Animated.Value(0)).current;
|
|
76
|
+
const scanLoopRef = useRef(null);
|
|
77
|
+
const bracketLoopRef = useRef(null);
|
|
78
|
+
|
|
79
|
+
// ── Start scan line + bracket rotation on mount ───────────────────────────
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
// Scan line: ping-pong top → bottom → top, 1.8 s each leg
|
|
82
|
+
scanLoopRef.current = Animated.loop(Animated.sequence([Animated.timing(scanAnim, {
|
|
83
|
+
toValue: 1,
|
|
84
|
+
duration: 1800,
|
|
85
|
+
easing: Easing.linear,
|
|
86
|
+
useNativeDriver: false
|
|
87
|
+
}), Animated.timing(scanAnim, {
|
|
88
|
+
toValue: 0,
|
|
89
|
+
duration: 1800,
|
|
90
|
+
easing: Easing.linear,
|
|
91
|
+
useNativeDriver: false
|
|
92
|
+
})]));
|
|
93
|
+
scanLoopRef.current.start();
|
|
94
|
+
|
|
95
|
+
// Brackets: one full rotation every 6 s — slow, atmospheric
|
|
96
|
+
bracketLoopRef.current = Animated.loop(Animated.timing(bracketAnim, {
|
|
97
|
+
toValue: 360,
|
|
98
|
+
duration: 6000,
|
|
99
|
+
easing: Easing.linear,
|
|
100
|
+
useNativeDriver: false
|
|
101
|
+
}));
|
|
102
|
+
bracketLoopRef.current.start();
|
|
103
|
+
return () => {
|
|
104
|
+
scanLoopRef.current?.stop();
|
|
105
|
+
bracketLoopRef.current?.stop();
|
|
106
|
+
};
|
|
107
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
108
|
+
|
|
109
|
+
// ── On confirmed: stop scan + brackets, fade out scan line ────────────────
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
const isActive = state === 'scanning';
|
|
112
|
+
if (!isActive) {
|
|
113
|
+
scanLoopRef.current?.stop();
|
|
114
|
+
bracketLoopRef.current?.stop();
|
|
115
|
+
Animated.timing(scanOpacity, {
|
|
116
|
+
toValue: 0,
|
|
117
|
+
duration: 350,
|
|
118
|
+
useNativeDriver: false
|
|
119
|
+
}).start();
|
|
120
|
+
}
|
|
121
|
+
}, [state, scanOpacity]);
|
|
122
|
+
|
|
123
|
+
// ── Drive progress arc from live score ────────────────────────────────────
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
Animated.timing(progressAnim, {
|
|
126
|
+
toValue: Math.min(score, livenessThreshold),
|
|
127
|
+
duration: 180,
|
|
128
|
+
useNativeDriver: false
|
|
129
|
+
}).start();
|
|
130
|
+
}, [score, livenessThreshold, progressAnim]);
|
|
131
|
+
|
|
132
|
+
// ── Guard — render nothing until dimensions are known ─────────────────────
|
|
52
133
|
if (width === 0 || height === 0) return null;
|
|
134
|
+
|
|
135
|
+
// ── Geometry ──────────────────────────────────────────────────────────────
|
|
53
136
|
const cx = width / 2;
|
|
54
|
-
// Centre the circle slightly above midpoint so the face sits naturally
|
|
55
137
|
const cy = height * 0.42;
|
|
56
138
|
const r = width * CIRCLE_DIAMETER_RATIO / 2;
|
|
139
|
+
const circumference = 2 * Math.PI * r;
|
|
57
140
|
const color = getCircleColor(state, score);
|
|
58
141
|
|
|
59
|
-
//
|
|
60
|
-
// evenodd fill rule makes the circle area transparent.
|
|
142
|
+
// Scrim: full-screen dark rect with transparent circle hole
|
|
61
143
|
const scrimD = `M0 0H${width}V${height}H0Z ${circlePath(cx, cy, r)}`;
|
|
144
|
+
|
|
145
|
+
// Scan line Y: sweeps from just inside top to just inside bottom of circle
|
|
146
|
+
const scanLineY = scanAnim.interpolate({
|
|
147
|
+
inputRange: [0, 1],
|
|
148
|
+
outputRange: [cy - r + 2, cy + r - SCAN_LINE_HEIGHT - 2]
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Progress arc: strokeDashoffset goes from full (no arc) → 0 (full arc)
|
|
152
|
+
const strokeDashoffset = progressAnim.interpolate({
|
|
153
|
+
inputRange: [0, livenessThreshold],
|
|
154
|
+
outputRange: [circumference, 0],
|
|
155
|
+
extrapolate: 'clamp'
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Bracket group rotation — centred on the circle
|
|
159
|
+
const bracketTransform = bracketAnim.interpolate({
|
|
160
|
+
inputRange: [0, 360],
|
|
161
|
+
outputRange: [`rotate(0, ${cx}, ${cy})`, `rotate(360, ${cx}, ${cy})`]
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// 4 corner brackets at NE / SE / SW / NW diagonal positions
|
|
165
|
+
const bracketD = [45, 135, 225, 315].map(deg => bracketArcPath(cx, cy, r, deg, BRACKET_SPAN_DEG)).join(' ');
|
|
62
166
|
return /*#__PURE__*/_jsxs(Svg, {
|
|
63
167
|
style: StyleSheet.absoluteFill,
|
|
64
168
|
width: width,
|
|
65
169
|
height: height,
|
|
66
|
-
children: [/*#__PURE__*/
|
|
170
|
+
children: [/*#__PURE__*/_jsxs(Defs, {
|
|
171
|
+
children: [/*#__PURE__*/_jsx(ClipPath, {
|
|
172
|
+
id: "liveness-circle-clip",
|
|
173
|
+
children: /*#__PURE__*/_jsx(Circle, {
|
|
174
|
+
cx: cx,
|
|
175
|
+
cy: cy,
|
|
176
|
+
r: r
|
|
177
|
+
})
|
|
178
|
+
}), /*#__PURE__*/_jsxs(LinearGradient, {
|
|
179
|
+
id: "scan-gradient",
|
|
180
|
+
x1: "0",
|
|
181
|
+
y1: "0",
|
|
182
|
+
x2: "0",
|
|
183
|
+
y2: "1",
|
|
184
|
+
children: [/*#__PURE__*/_jsx(Stop, {
|
|
185
|
+
offset: "0",
|
|
186
|
+
stopColor: "#fff",
|
|
187
|
+
stopOpacity: "0"
|
|
188
|
+
}), /*#__PURE__*/_jsx(Stop, {
|
|
189
|
+
offset: "0.25",
|
|
190
|
+
stopColor: "#fff",
|
|
191
|
+
stopOpacity: "0.65"
|
|
192
|
+
}), /*#__PURE__*/_jsx(Stop, {
|
|
193
|
+
offset: "0.75",
|
|
194
|
+
stopColor: "#fff",
|
|
195
|
+
stopOpacity: "0.65"
|
|
196
|
+
}), /*#__PURE__*/_jsx(Stop, {
|
|
197
|
+
offset: "1",
|
|
198
|
+
stopColor: "#fff",
|
|
199
|
+
stopOpacity: "0"
|
|
200
|
+
})]
|
|
201
|
+
})]
|
|
202
|
+
}), /*#__PURE__*/_jsx(Path, {
|
|
67
203
|
d: scrimD,
|
|
68
204
|
fill: "rgba(0,0,0,0.55)",
|
|
69
205
|
fillRule: "evenodd"
|
|
70
206
|
}), /*#__PURE__*/_jsx(Circle, {
|
|
207
|
+
cx: cx,
|
|
208
|
+
cy: cy,
|
|
209
|
+
r: r,
|
|
210
|
+
fill: "none",
|
|
211
|
+
stroke: "rgba(255,255,255,0.18)",
|
|
212
|
+
strokeWidth: 1
|
|
213
|
+
}), /*#__PURE__*/_jsx(G, {
|
|
214
|
+
clipPath: "url(#liveness-circle-clip)",
|
|
215
|
+
children: /*#__PURE__*/_jsx(AnimatedRect, {
|
|
216
|
+
x: cx - r,
|
|
217
|
+
y: scanLineY,
|
|
218
|
+
width: r * 2,
|
|
219
|
+
height: SCAN_LINE_HEIGHT,
|
|
220
|
+
fill: "url(#scan-gradient)",
|
|
221
|
+
opacity: scanOpacity
|
|
222
|
+
})
|
|
223
|
+
}), /*#__PURE__*/_jsx(AnimatedG, {
|
|
224
|
+
transform: bracketTransform,
|
|
225
|
+
children: /*#__PURE__*/_jsx(Path, {
|
|
226
|
+
d: bracketD,
|
|
227
|
+
fill: "none",
|
|
228
|
+
stroke: color,
|
|
229
|
+
strokeWidth: BRACKET_STROKE,
|
|
230
|
+
strokeLinecap: "round",
|
|
231
|
+
opacity: 0.85
|
|
232
|
+
})
|
|
233
|
+
}), /*#__PURE__*/_jsx(AnimatedCircle, {
|
|
71
234
|
cx: cx,
|
|
72
235
|
cy: cy,
|
|
73
236
|
r: r,
|
|
74
237
|
fill: "none",
|
|
75
238
|
stroke: color,
|
|
76
|
-
strokeWidth: STROKE_WIDTH
|
|
239
|
+
strokeWidth: STROKE_WIDTH,
|
|
240
|
+
strokeDasharray: circumference,
|
|
241
|
+
strokeDashoffset: strokeDashoffset,
|
|
242
|
+
strokeLinecap: "round",
|
|
243
|
+
transform: `rotate(-90, ${cx}, ${cy})`
|
|
77
244
|
})]
|
|
78
245
|
});
|
|
79
246
|
}
|
|
247
|
+
|
|
248
|
+
// ─── CountdownBubble ──────────────────────────────────────────────────────────
|
|
80
249
|
function CountdownBubble({
|
|
81
|
-
value
|
|
250
|
+
value,
|
|
251
|
+
fontFamily
|
|
82
252
|
}) {
|
|
83
|
-
// key={countdown} in the parent remounts this component on every tick,
|
|
84
|
-
// so [] deps are correct — each mount runs a fresh animation.
|
|
85
253
|
const scale = useRef(new Animated.Value(0)).current;
|
|
86
254
|
const opacity = useRef(new Animated.Value(0)).current;
|
|
87
255
|
useEffect(() => {
|
|
@@ -117,11 +285,15 @@ function CountdownBubble({
|
|
|
117
285
|
}]
|
|
118
286
|
}],
|
|
119
287
|
children: /*#__PURE__*/_jsx(Text, {
|
|
120
|
-
style: styles.countdownText,
|
|
288
|
+
style: [styles.countdownText, {
|
|
289
|
+
fontFamily
|
|
290
|
+
}],
|
|
121
291
|
children: value
|
|
122
292
|
})
|
|
123
293
|
});
|
|
124
294
|
}
|
|
295
|
+
|
|
296
|
+
// ─── LivenessCamera ───────────────────────────────────────────────────────────
|
|
125
297
|
export function LivenessCamera({
|
|
126
298
|
onCapture,
|
|
127
299
|
onLivenessConfirmed,
|
|
@@ -130,6 +302,7 @@ export function LivenessCamera({
|
|
|
130
302
|
livenessThreshold = 0.75,
|
|
131
303
|
confirmFrames = 10,
|
|
132
304
|
soundEnabled = true,
|
|
305
|
+
fontFamily = DEFAULT_FONT,
|
|
133
306
|
style
|
|
134
307
|
}) {
|
|
135
308
|
const {
|
|
@@ -137,8 +310,6 @@ export function LivenessCamera({
|
|
|
137
310
|
requestPermission
|
|
138
311
|
} = useCameraPermission();
|
|
139
312
|
const device = useCameraDevice('front');
|
|
140
|
-
// Pick the best format that supports up to 60 fps. Falls back gracefully
|
|
141
|
-
// to whatever the device offers if 60 fps isn't available.
|
|
142
313
|
const format = useCameraFormat(device, [{
|
|
143
314
|
fps: 60
|
|
144
315
|
}]);
|
|
@@ -185,7 +356,9 @@ export function LivenessCamera({
|
|
|
185
356
|
return /*#__PURE__*/_jsx(View, {
|
|
186
357
|
style: [styles.root, style, styles.centered],
|
|
187
358
|
children: /*#__PURE__*/_jsx(Text, {
|
|
188
|
-
style: styles.permissionText,
|
|
359
|
+
style: [styles.permissionText, {
|
|
360
|
+
fontFamily
|
|
361
|
+
}],
|
|
189
362
|
children: "Camera permission required"
|
|
190
363
|
})
|
|
191
364
|
});
|
|
@@ -194,7 +367,9 @@ export function LivenessCamera({
|
|
|
194
367
|
return /*#__PURE__*/_jsx(View, {
|
|
195
368
|
style: [styles.root, style, styles.centered],
|
|
196
369
|
children: /*#__PURE__*/_jsx(Text, {
|
|
197
|
-
style: styles.permissionText,
|
|
370
|
+
style: [styles.permissionText, {
|
|
371
|
+
fontFamily
|
|
372
|
+
}],
|
|
198
373
|
children: "No front camera found"
|
|
199
374
|
})
|
|
200
375
|
});
|
|
@@ -216,17 +391,21 @@ export function LivenessCamera({
|
|
|
216
391
|
width: containerSize.width,
|
|
217
392
|
height: containerSize.height,
|
|
218
393
|
state: livenessState,
|
|
219
|
-
score: livenessScore
|
|
394
|
+
score: livenessScore,
|
|
395
|
+
livenessThreshold: livenessThreshold
|
|
220
396
|
}), livenessState !== 'done' && /*#__PURE__*/_jsx(View, {
|
|
221
397
|
style: styles.feedbackContainer,
|
|
222
398
|
children: /*#__PURE__*/_jsx(Text, {
|
|
223
|
-
style: styles.feedbackText,
|
|
399
|
+
style: [styles.feedbackText, {
|
|
400
|
+
fontFamily
|
|
401
|
+
}],
|
|
224
402
|
children: feedback
|
|
225
403
|
})
|
|
226
404
|
}), livenessState === 'countdown' && countdown !== null && /*#__PURE__*/_jsx(View, {
|
|
227
405
|
style: styles.countdownContainer,
|
|
228
406
|
children: /*#__PURE__*/_jsx(CountdownBubble, {
|
|
229
|
-
value: countdown
|
|
407
|
+
value: countdown,
|
|
408
|
+
fontFamily: fontFamily
|
|
230
409
|
}, countdown)
|
|
231
410
|
}), livenessState === 'capturing' && /*#__PURE__*/_jsx(View, {
|
|
232
411
|
style: styles.captureFlash,
|
|
@@ -234,6 +413,8 @@ export function LivenessCamera({
|
|
|
234
413
|
})]
|
|
235
414
|
});
|
|
236
415
|
}
|
|
416
|
+
|
|
417
|
+
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
237
418
|
const styles = StyleSheet.create({
|
|
238
419
|
root: {
|
|
239
420
|
flex: 1,
|
|
@@ -261,7 +442,6 @@ const styles = StyleSheet.create({
|
|
|
261
442
|
feedbackText: {
|
|
262
443
|
color: '#fff',
|
|
263
444
|
fontSize: 16,
|
|
264
|
-
fontWeight: '600',
|
|
265
445
|
textAlign: 'center',
|
|
266
446
|
textShadowColor: 'rgba(0,0,0,0.8)',
|
|
267
447
|
textShadowOffset: {
|
|
@@ -288,7 +468,6 @@ const styles = StyleSheet.create({
|
|
|
288
468
|
countdownText: {
|
|
289
469
|
color: '#fff',
|
|
290
470
|
fontSize: 52,
|
|
291
|
-
fontWeight: '700',
|
|
292
471
|
lineHeight: 60
|
|
293
472
|
},
|
|
294
473
|
captureFlash: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useRef","useState","Animated","StyleSheet","Text","View","Camera","useCameraDevice","useCameraFormat","useCameraPermission","Circle","Path","Svg","useLivenessCamera","jsx","_jsx","jsxs","_jsxs","CIRCLE_DIAMETER_RATIO","STROKE_WIDTH","K","getCircleColor","state","score","circlePath","cx","cy","r","join","CircleOverlay","width","height","color","scrimD","style","absoluteFill","children","d","fill","fillRule","stroke","strokeWidth","CountdownBubble","value","scale","Value","current","opacity","parallel","sequence","spring","toValue","stiffness","damping","useNativeDriver","timing","duration","start","styles","countdownBubble","transform","countdownText","LivenessCamera","onCapture","onLivenessConfirmed","onError","countdownFrom","livenessThreshold","confirmFrames","soundEnabled","hasPermission","requestPermission","device","format","fps","Math","min","maxFps","cameraRef","containerSize","setContainerSize","frameProcessor","livenessState","livenessScore","countdown","feedback","handleLayout","e","nativeEvent","layout","catch","Error","root","centered","permissionText","onLayout","ref","isActive","photo","pixelFormat","feedbackContainer","feedbackText","countdownContainer","captureFlash","pointerEvents","create","flex","backgroundColor","overflow","justifyContent","alignItems","fontSize","textAlign","paddingHorizontal","position","bottom","left","right","fontWeight","textShadowColor","textShadowOffset","textShadowRadius","absoluteFillObject","borderRadius","borderWidth","borderColor","lineHeight"],"sourceRoot":"../../src","sources":["LivenessCamera.tsx"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useRef","useState","Animated","Easing","StyleSheet","Text","View","Camera","useCameraDevice","useCameraFormat","useCameraPermission","Circle","ClipPath","Defs","G","LinearGradient","Path","Rect","Stop","Svg","useLivenessCamera","jsx","_jsx","jsxs","_jsxs","AnimatedCircle","createAnimatedComponent","AnimatedG","AnimatedRect","DEFAULT_FONT","CIRCLE_DIAMETER_RATIO","STROKE_WIDTH","K","SCAN_LINE_HEIGHT","BRACKET_SPAN_DEG","BRACKET_STROKE","getCircleColor","state","score","circlePath","cx","cy","r","join","bracketArcPath","centerDeg","spanDeg","toRad","d","Math","PI","a1","a2","x1","cos","y1","sin","x2","y2","CircleOverlay","width","height","livenessThreshold","scanAnim","Value","current","scanOpacity","progressAnim","bracketAnim","scanLoopRef","bracketLoopRef","loop","sequence","timing","toValue","duration","easing","linear","useNativeDriver","start","stop","isActive","min","circumference","color","scrimD","scanLineY","interpolate","inputRange","outputRange","strokeDashoffset","extrapolate","bracketTransform","bracketD","map","deg","style","absoluteFill","children","id","offset","stopColor","stopOpacity","fill","fillRule","stroke","strokeWidth","clipPath","x","y","opacity","transform","strokeLinecap","strokeDasharray","CountdownBubble","value","fontFamily","scale","parallel","spring","stiffness","damping","styles","countdownBubble","countdownText","LivenessCamera","onCapture","onLivenessConfirmed","onError","countdownFrom","confirmFrames","soundEnabled","hasPermission","requestPermission","device","format","fps","maxFps","cameraRef","containerSize","setContainerSize","frameProcessor","livenessState","livenessScore","countdown","feedback","handleLayout","e","nativeEvent","layout","catch","Error","root","centered","permissionText","onLayout","ref","photo","pixelFormat","feedbackContainer","feedbackText","countdownContainer","captureFlash","pointerEvents","create","flex","backgroundColor","overflow","justifyContent","alignItems","fontSize","textAlign","paddingHorizontal","position","bottom","left","right","textShadowColor","textShadowOffset","textShadowRadius","absoluteFillObject","borderRadius","borderWidth","borderColor","lineHeight"],"sourceRoot":"../../src","sources":["LivenessCamera.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { Modal, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
4
|
+
import { Path, Svg } from 'react-native-svg';
|
|
5
|
+
import { LivenessCamera } from "./LivenessCamera.js";
|
|
6
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
|
+
/** × icon drawn with SVG — no external icon library required. */
|
|
8
|
+
function CloseIcon({
|
|
9
|
+
color = '#fff',
|
|
10
|
+
size = 18
|
|
11
|
+
}) {
|
|
12
|
+
const pad = size * 0.1;
|
|
13
|
+
const end = size - pad;
|
|
14
|
+
return /*#__PURE__*/_jsx(Svg, {
|
|
15
|
+
width: size,
|
|
16
|
+
height: size,
|
|
17
|
+
viewBox: `0 0 ${size} ${size}`,
|
|
18
|
+
children: /*#__PURE__*/_jsx(Path, {
|
|
19
|
+
d: `M${pad} ${pad} L${end} ${end} M${end} ${pad} L${pad} ${end}`,
|
|
20
|
+
stroke: color,
|
|
21
|
+
strokeWidth: size * 0.14,
|
|
22
|
+
strokeLinecap: "round"
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Drop-in liveness detection modal.
|
|
29
|
+
*
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <LivenessCameraModal
|
|
32
|
+
* visible={showLiveness}
|
|
33
|
+
* onClose={() => setShowLiveness(false)}
|
|
34
|
+
* onCapture={(result) => console.log(result.photo.path)}
|
|
35
|
+
* animationType="slide"
|
|
36
|
+
* />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function LivenessCameraModal({
|
|
40
|
+
visible,
|
|
41
|
+
onClose,
|
|
42
|
+
animationType = 'slide',
|
|
43
|
+
closeButtonStyle,
|
|
44
|
+
closeButtonIconColor = '#fff',
|
|
45
|
+
closeButtonIconSize = 18,
|
|
46
|
+
// forward all LivenessCamera props
|
|
47
|
+
onCapture,
|
|
48
|
+
onLivenessConfirmed,
|
|
49
|
+
onError,
|
|
50
|
+
countdownFrom,
|
|
51
|
+
livenessThreshold,
|
|
52
|
+
confirmFrames,
|
|
53
|
+
soundEnabled,
|
|
54
|
+
fontFamily
|
|
55
|
+
}) {
|
|
56
|
+
const handleCapture = result => {
|
|
57
|
+
onCapture(result);
|
|
58
|
+
// Modal stays open — consumer decides when to close via onCapture callback
|
|
59
|
+
};
|
|
60
|
+
return /*#__PURE__*/_jsx(Modal, {
|
|
61
|
+
visible: visible,
|
|
62
|
+
animationType: animationType,
|
|
63
|
+
statusBarTranslucent: true,
|
|
64
|
+
onRequestClose: onClose,
|
|
65
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
66
|
+
style: styles.container,
|
|
67
|
+
children: [/*#__PURE__*/_jsx(LivenessCamera, {
|
|
68
|
+
style: styles.camera,
|
|
69
|
+
onCapture: handleCapture,
|
|
70
|
+
onLivenessConfirmed: onLivenessConfirmed,
|
|
71
|
+
onError: onError,
|
|
72
|
+
countdownFrom: countdownFrom,
|
|
73
|
+
livenessThreshold: livenessThreshold,
|
|
74
|
+
confirmFrames: confirmFrames,
|
|
75
|
+
soundEnabled: soundEnabled,
|
|
76
|
+
fontFamily: fontFamily
|
|
77
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
78
|
+
style: [styles.closeButton, closeButtonStyle],
|
|
79
|
+
onPress: onClose,
|
|
80
|
+
activeOpacity: 0.7,
|
|
81
|
+
hitSlop: {
|
|
82
|
+
top: 8,
|
|
83
|
+
bottom: 8,
|
|
84
|
+
left: 8,
|
|
85
|
+
right: 8
|
|
86
|
+
},
|
|
87
|
+
children: /*#__PURE__*/_jsx(CloseIcon, {
|
|
88
|
+
color: closeButtonIconColor,
|
|
89
|
+
size: closeButtonIconSize
|
|
90
|
+
})
|
|
91
|
+
})]
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const styles = StyleSheet.create({
|
|
96
|
+
container: {
|
|
97
|
+
flex: 1,
|
|
98
|
+
backgroundColor: '#000'
|
|
99
|
+
},
|
|
100
|
+
camera: {
|
|
101
|
+
flex: 1
|
|
102
|
+
},
|
|
103
|
+
closeButton: {
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
top: 52,
|
|
106
|
+
left: 16,
|
|
107
|
+
width: 48,
|
|
108
|
+
height: 48,
|
|
109
|
+
borderRadius: 24,
|
|
110
|
+
justifyContent: 'center',
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
113
|
+
borderWidth: 1,
|
|
114
|
+
borderColor: 'rgba(255,255,255,0.3)'
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=LivenessCameraModal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["Modal","StyleSheet","TouchableOpacity","View","Path","Svg","LivenessCamera","jsx","_jsx","jsxs","_jsxs","CloseIcon","color","size","pad","end","width","height","viewBox","children","d","stroke","strokeWidth","strokeLinecap","LivenessCameraModal","visible","onClose","animationType","closeButtonStyle","closeButtonIconColor","closeButtonIconSize","onCapture","onLivenessConfirmed","onError","countdownFrom","livenessThreshold","confirmFrames","soundEnabled","fontFamily","handleCapture","result","statusBarTranslucent","onRequestClose","style","styles","container","camera","closeButton","onPress","activeOpacity","hitSlop","top","bottom","left","right","create","flex","backgroundColor","position","borderRadius","justifyContent","alignItems","borderWidth","borderColor"],"sourceRoot":"../../src","sources":["LivenessCameraModal.tsx"],"mappings":"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","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["LivenessCamera","useLivenessCamera"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,cAAc,QAAQ,qBAAkB;AACjD,SAASC,iBAAiB,QAAQ,wBAAqB","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["LivenessCamera","LivenessCameraModal","useLivenessCamera"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,cAAc,QAAQ,qBAAkB;AACjD,SAASC,mBAAmB,QAAQ,0BAAuB;AAC3D,SAASC,iBAAiB,QAAQ,wBAAqB","ignoreList":[]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { LivenessCameraProps } from './types';
|
|
2
|
-
export declare function LivenessCamera({ onCapture, onLivenessConfirmed, onError, countdownFrom, livenessThreshold, confirmFrames, soundEnabled, style, }: LivenessCameraProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function LivenessCamera({ onCapture, onLivenessConfirmed, onError, countdownFrom, livenessThreshold, confirmFrames, soundEnabled, fontFamily, style, }: LivenessCameraProps): import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=LivenessCamera.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/LivenessCamera.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/LivenessCamera.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AA4TlE,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAiB,EACjB,iBAAwB,EACxB,aAAkB,EAClB,YAAmB,EACnB,UAAyB,EACzB,KAAK,GACN,EAAE,mBAAmB,2CA+FrB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LivenessCameraModalProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Drop-in liveness detection modal.
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* <LivenessCameraModal
|
|
7
|
+
* visible={showLiveness}
|
|
8
|
+
* onClose={() => setShowLiveness(false)}
|
|
9
|
+
* onCapture={(result) => console.log(result.photo.path)}
|
|
10
|
+
* animationType="slide"
|
|
11
|
+
* />
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function LivenessCameraModal({ visible, onClose, animationType, closeButtonStyle, closeButtonIconColor, closeButtonIconSize, onCapture, onLivenessConfirmed, onError, countdownFrom, livenessThreshold, confirmFrames, soundEnabled, fontFamily, }: LivenessCameraModalProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=LivenessCameraModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LivenessCameraModal.d.ts","sourceRoot":"","sources":["../../../src/LivenessCameraModal.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAwBxD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,OAAO,EACP,aAAuB,EACvB,gBAAgB,EAChB,oBAA6B,EAC7B,mBAAwB,EAExB,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,UAAU,GACX,EAAE,wBAAwB,2CAqC1B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { LivenessCamera } from './LivenessCamera';
|
|
2
|
+
export { LivenessCameraModal } from './LivenessCameraModal';
|
|
2
3
|
export { useLivenessCamera } from './useLivenessCamera';
|
|
3
|
-
export type { CaptureResult, FaceData, FeedbackMessage, LivenessCameraProps, LivenessState, } from './types';
|
|
4
|
+
export type { CaptureResult, FaceData, FeedbackMessage, LivenessCameraModalProps, LivenessCameraProps, LivenessState, } from './types';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,aAAa,GACd,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,QAAQ,EACR,eAAe,EACf,wBAAwB,EACxB,mBAAmB,EACnB,aAAa,GACd,MAAM,SAAS,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ViewStyle } from 'react-native';
|
|
1
|
+
import type { ModalProps, ViewStyle } from 'react-native';
|
|
2
2
|
import type { PhotoFile } from 'react-native-vision-camera';
|
|
3
3
|
export type FaceData = {
|
|
4
4
|
detected: boolean;
|
|
@@ -53,9 +53,41 @@ export type LivenessCameraProps = {
|
|
|
53
53
|
*/
|
|
54
54
|
style?: ViewStyle;
|
|
55
55
|
/**
|
|
56
|
-
* Whether to play a shutter sound on capture.
|
|
57
|
-
* to be installed. Defaults to true.
|
|
56
|
+
* Whether to play a shutter sound on capture. Defaults to true.
|
|
58
57
|
*/
|
|
59
58
|
soundEnabled?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Font family applied to all text inside the component.
|
|
61
|
+
* Defaults to 'Baloo-Medium'. Set to undefined to use the system font.
|
|
62
|
+
*/
|
|
63
|
+
fontFamily?: string;
|
|
64
|
+
};
|
|
65
|
+
export type LivenessCameraModalProps = Omit<LivenessCameraProps, 'style'> & {
|
|
66
|
+
/**
|
|
67
|
+
* Controls modal visibility — pass your own boolean state.
|
|
68
|
+
*/
|
|
69
|
+
visible: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Called when the close button is pressed or the Android back button fires.
|
|
72
|
+
* Use this to set your visible state to false.
|
|
73
|
+
*/
|
|
74
|
+
onClose: () => void;
|
|
75
|
+
/**
|
|
76
|
+
* Modal entrance/exit animation. Defaults to 'slide'.
|
|
77
|
+
*/
|
|
78
|
+
animationType?: ModalProps['animationType'];
|
|
79
|
+
/**
|
|
80
|
+
* Override styles on the close button container.
|
|
81
|
+
* Useful for adjusting position or size to match your design system.
|
|
82
|
+
*/
|
|
83
|
+
closeButtonStyle?: ViewStyle;
|
|
84
|
+
/**
|
|
85
|
+
* Colour of the × icon inside the close button. Defaults to '#fff'.
|
|
86
|
+
*/
|
|
87
|
+
closeButtonIconColor?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Size of the × icon in dp. Defaults to 18.
|
|
90
|
+
*/
|
|
91
|
+
closeButtonIconSize?: number;
|
|
60
92
|
};
|
|
61
93
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE;QACN,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;IAChC,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,UAAU,GACV,WAAW,GACX,WAAW,GACX,WAAW,GACX,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,MAAM,eAAe,GACvB,kCAAkC,GAClC,aAAa,GACb,mBAAmB,GACnB,qBAAqB,GACrB,eAAe,GACf,YAAY,GACZ,gBAAgB,GAChB,oBAAoB,GACpB,EAAE,CAAC;AAEP,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC;;OAEG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAE3C;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IAEjC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,mBAAmB,EAAE,OAAO,CAAC,GAAG;IAC1E;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;IAE5C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAE7B;;OAEG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rick427/react-native-liveness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Liveness detection library for React Native using Vision Camera v4 and ML Kit",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/LivenessCamera.tsx
CHANGED
|
@@ -1,27 +1,44 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { Animated, StyleSheet, Text, View } from 'react-native';
|
|
2
|
+
import { Animated, Easing, StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import {
|
|
4
4
|
Camera,
|
|
5
5
|
useCameraDevice,
|
|
6
6
|
useCameraFormat,
|
|
7
7
|
useCameraPermission,
|
|
8
8
|
} from 'react-native-vision-camera';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Circle,
|
|
11
|
+
ClipPath,
|
|
12
|
+
Defs,
|
|
13
|
+
G,
|
|
14
|
+
LinearGradient,
|
|
15
|
+
Path,
|
|
16
|
+
Rect,
|
|
17
|
+
Stop,
|
|
18
|
+
Svg,
|
|
19
|
+
} from 'react-native-svg';
|
|
10
20
|
import { useLivenessCamera } from './useLivenessCamera';
|
|
11
21
|
import type { LivenessCameraProps, LivenessState } from './types';
|
|
12
22
|
|
|
13
|
-
//
|
|
14
|
-
//
|
|
23
|
+
// ─── Animated SVG components ─────────────────────────────────────────────────
|
|
24
|
+
// Created once at module level so React never recreates the component class.
|
|
25
|
+
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
|
26
|
+
const AnimatedG = Animated.createAnimatedComponent(G);
|
|
27
|
+
const AnimatedRect = Animated.createAnimatedComponent(Rect);
|
|
28
|
+
|
|
29
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
30
|
+
const DEFAULT_FONT = 'Baloo-Medium';
|
|
15
31
|
const CIRCLE_DIAMETER_RATIO = 0.82;
|
|
16
32
|
const STROKE_WIDTH = 3;
|
|
17
|
-
|
|
18
|
-
const
|
|
33
|
+
const K = 0.5523; // cubic bezier ellipse approximation
|
|
34
|
+
const SCAN_LINE_HEIGHT = 44; // px — height of the sweep bar
|
|
35
|
+
const BRACKET_SPAN_DEG = 44; // degrees each corner bracket spans
|
|
36
|
+
const BRACKET_STROKE = STROKE_WIDTH + 1;
|
|
19
37
|
|
|
38
|
+
// ─── Colour helper ────────────────────────────────────────────────────────────
|
|
20
39
|
/**
|
|
21
|
-
* Returns the stroke colour for the circle guide.
|
|
22
|
-
*
|
|
23
40
|
* ● White – no face / scanning (score < 0.4)
|
|
24
|
-
* ● Yellow – face detected, confidence building
|
|
41
|
+
* ● Yellow – face detected, confidence building
|
|
25
42
|
* ● Green – liveness confirmed / countdown / capture
|
|
26
43
|
* ● Red – error
|
|
27
44
|
*/
|
|
@@ -39,11 +56,8 @@ function getCircleColor(state: LivenessState, score: number): string {
|
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* using cubic bezier curves. Used inside a compound path with
|
|
45
|
-
* fillRule="evenodd" to punch a transparent hole through the dark scrim.
|
|
46
|
-
*/
|
|
59
|
+
// ─── SVG path helpers ─────────────────────────────────────────────────────────
|
|
60
|
+
/** Cubic bezier circle path — used inside compound evenodd scrim. */
|
|
47
61
|
function circlePath(cx: number, cy: number, r: number): string {
|
|
48
62
|
return [
|
|
49
63
|
`M ${cx + r} ${cy}`,
|
|
@@ -55,47 +69,226 @@ function circlePath(cx: number, cy: number, r: number): string {
|
|
|
55
69
|
].join(' ');
|
|
56
70
|
}
|
|
57
71
|
|
|
72
|
+
/** One corner bracket arc centred at `centerDeg`, spanning `spanDeg`. */
|
|
73
|
+
function bracketArcPath(
|
|
74
|
+
cx: number,
|
|
75
|
+
cy: number,
|
|
76
|
+
r: number,
|
|
77
|
+
centerDeg: number,
|
|
78
|
+
spanDeg: number
|
|
79
|
+
): string {
|
|
80
|
+
const toRad = (d: number) => (d * Math.PI) / 180;
|
|
81
|
+
const a1 = toRad(centerDeg - spanDeg / 2);
|
|
82
|
+
const a2 = toRad(centerDeg + spanDeg / 2);
|
|
83
|
+
const x1 = cx + r * Math.cos(a1);
|
|
84
|
+
const y1 = cy + r * Math.sin(a1);
|
|
85
|
+
const x2 = cx + r * Math.cos(a2);
|
|
86
|
+
const y2 = cy + r * Math.sin(a2);
|
|
87
|
+
return `M ${x1} ${y1} A ${r} ${r} 0 0 1 ${x2} ${y2}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── CircleOverlay ────────────────────────────────────────────────────────────
|
|
58
91
|
function CircleOverlay({
|
|
59
92
|
width,
|
|
60
93
|
height,
|
|
61
94
|
state,
|
|
62
95
|
score,
|
|
96
|
+
livenessThreshold,
|
|
63
97
|
}: {
|
|
64
98
|
width: number;
|
|
65
99
|
height: number;
|
|
66
100
|
state: LivenessState;
|
|
67
101
|
score: number;
|
|
102
|
+
livenessThreshold: number;
|
|
68
103
|
}) {
|
|
104
|
+
// ── Animation values (must be declared before any early return) ────────────
|
|
105
|
+
const scanAnim = useRef(new Animated.Value(0)).current;
|
|
106
|
+
const scanOpacity = useRef(new Animated.Value(1)).current;
|
|
107
|
+
const progressAnim = useRef(new Animated.Value(0)).current;
|
|
108
|
+
const bracketAnim = useRef(new Animated.Value(0)).current;
|
|
109
|
+
|
|
110
|
+
const scanLoopRef = useRef<Animated.CompositeAnimation | null>(null);
|
|
111
|
+
const bracketLoopRef = useRef<Animated.CompositeAnimation | null>(null);
|
|
112
|
+
|
|
113
|
+
// ── Start scan line + bracket rotation on mount ───────────────────────────
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
// Scan line: ping-pong top → bottom → top, 1.8 s each leg
|
|
116
|
+
scanLoopRef.current = Animated.loop(
|
|
117
|
+
Animated.sequence([
|
|
118
|
+
Animated.timing(scanAnim, {
|
|
119
|
+
toValue: 1,
|
|
120
|
+
duration: 1800,
|
|
121
|
+
easing: Easing.linear,
|
|
122
|
+
useNativeDriver: false,
|
|
123
|
+
}),
|
|
124
|
+
Animated.timing(scanAnim, {
|
|
125
|
+
toValue: 0,
|
|
126
|
+
duration: 1800,
|
|
127
|
+
easing: Easing.linear,
|
|
128
|
+
useNativeDriver: false,
|
|
129
|
+
}),
|
|
130
|
+
])
|
|
131
|
+
);
|
|
132
|
+
scanLoopRef.current.start();
|
|
133
|
+
|
|
134
|
+
// Brackets: one full rotation every 6 s — slow, atmospheric
|
|
135
|
+
bracketLoopRef.current = Animated.loop(
|
|
136
|
+
Animated.timing(bracketAnim, {
|
|
137
|
+
toValue: 360,
|
|
138
|
+
duration: 6000,
|
|
139
|
+
easing: Easing.linear,
|
|
140
|
+
useNativeDriver: false,
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
bracketLoopRef.current.start();
|
|
144
|
+
|
|
145
|
+
return () => {
|
|
146
|
+
scanLoopRef.current?.stop();
|
|
147
|
+
bracketLoopRef.current?.stop();
|
|
148
|
+
};
|
|
149
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
150
|
+
|
|
151
|
+
// ── On confirmed: stop scan + brackets, fade out scan line ────────────────
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const isActive = state === 'scanning';
|
|
154
|
+
if (!isActive) {
|
|
155
|
+
scanLoopRef.current?.stop();
|
|
156
|
+
bracketLoopRef.current?.stop();
|
|
157
|
+
Animated.timing(scanOpacity, {
|
|
158
|
+
toValue: 0,
|
|
159
|
+
duration: 350,
|
|
160
|
+
useNativeDriver: false,
|
|
161
|
+
}).start();
|
|
162
|
+
}
|
|
163
|
+
}, [state, scanOpacity]);
|
|
164
|
+
|
|
165
|
+
// ── Drive progress arc from live score ────────────────────────────────────
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
Animated.timing(progressAnim, {
|
|
168
|
+
toValue: Math.min(score, livenessThreshold),
|
|
169
|
+
duration: 180,
|
|
170
|
+
useNativeDriver: false,
|
|
171
|
+
}).start();
|
|
172
|
+
}, [score, livenessThreshold, progressAnim]);
|
|
173
|
+
|
|
174
|
+
// ── Guard — render nothing until dimensions are known ─────────────────────
|
|
69
175
|
if (width === 0 || height === 0) return null;
|
|
70
176
|
|
|
177
|
+
// ── Geometry ──────────────────────────────────────────────────────────────
|
|
71
178
|
const cx = width / 2;
|
|
72
|
-
// Centre the circle slightly above midpoint so the face sits naturally
|
|
73
179
|
const cy = height * 0.42;
|
|
74
180
|
const r = (width * CIRCLE_DIAMETER_RATIO) / 2;
|
|
181
|
+
const circumference = 2 * Math.PI * r;
|
|
75
182
|
const color = getCircleColor(state, score);
|
|
76
183
|
|
|
77
|
-
//
|
|
78
|
-
// evenodd fill rule makes the circle area transparent.
|
|
184
|
+
// Scrim: full-screen dark rect with transparent circle hole
|
|
79
185
|
const scrimD = `M0 0H${width}V${height}H0Z ${circlePath(cx, cy, r)}`;
|
|
80
186
|
|
|
187
|
+
// Scan line Y: sweeps from just inside top to just inside bottom of circle
|
|
188
|
+
const scanLineY = scanAnim.interpolate({
|
|
189
|
+
inputRange: [0, 1],
|
|
190
|
+
outputRange: [cy - r + 2, cy + r - SCAN_LINE_HEIGHT - 2],
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Progress arc: strokeDashoffset goes from full (no arc) → 0 (full arc)
|
|
194
|
+
const strokeDashoffset = progressAnim.interpolate({
|
|
195
|
+
inputRange: [0, livenessThreshold],
|
|
196
|
+
outputRange: [circumference, 0],
|
|
197
|
+
extrapolate: 'clamp',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Bracket group rotation — centred on the circle
|
|
201
|
+
const bracketTransform = bracketAnim.interpolate({
|
|
202
|
+
inputRange: [0, 360],
|
|
203
|
+
outputRange: [`rotate(0, ${cx}, ${cy})`, `rotate(360, ${cx}, ${cy})`],
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// 4 corner brackets at NE / SE / SW / NW diagonal positions
|
|
207
|
+
const bracketD = [45, 135, 225, 315]
|
|
208
|
+
.map((deg) => bracketArcPath(cx, cy, r, deg, BRACKET_SPAN_DEG))
|
|
209
|
+
.join(' ');
|
|
210
|
+
|
|
81
211
|
return (
|
|
82
212
|
<Svg style={StyleSheet.absoluteFill} width={width} height={height}>
|
|
213
|
+
{/* ── Definitions ─────────────────────────────────────────────────── */}
|
|
214
|
+
<Defs>
|
|
215
|
+
{/* Clip path: restrict scan line to circle area */}
|
|
216
|
+
<ClipPath id="liveness-circle-clip">
|
|
217
|
+
<Circle cx={cx} cy={cy} r={r} />
|
|
218
|
+
</ClipPath>
|
|
219
|
+
|
|
220
|
+
{/* Vertical gradient for the scan bar — fades at top and bottom */}
|
|
221
|
+
<LinearGradient id="scan-gradient" x1="0" y1="0" x2="0" y2="1">
|
|
222
|
+
<Stop offset="0" stopColor="#fff" stopOpacity="0" />
|
|
223
|
+
<Stop offset="0.25" stopColor="#fff" stopOpacity="0.65" />
|
|
224
|
+
<Stop offset="0.75" stopColor="#fff" stopOpacity="0.65" />
|
|
225
|
+
<Stop offset="1" stopColor="#fff" stopOpacity="0" />
|
|
226
|
+
</LinearGradient>
|
|
227
|
+
</Defs>
|
|
228
|
+
|
|
229
|
+
{/* ── Dark scrim with transparent circle cutout ────────────────────── */}
|
|
83
230
|
<Path d={scrimD} fill="rgba(0,0,0,0.55)" fillRule="evenodd" />
|
|
231
|
+
|
|
232
|
+
{/* ── Dim base ring — always shows the circle boundary ─────────────── */}
|
|
84
233
|
<Circle
|
|
234
|
+
cx={cx}
|
|
235
|
+
cy={cy}
|
|
236
|
+
r={r}
|
|
237
|
+
fill="none"
|
|
238
|
+
stroke="rgba(255,255,255,0.18)"
|
|
239
|
+
strokeWidth={1}
|
|
240
|
+
/>
|
|
241
|
+
|
|
242
|
+
{/* ── Scan line — sweeps top → bottom, clipped to circle ───────────── */}
|
|
243
|
+
<G clipPath="url(#liveness-circle-clip)">
|
|
244
|
+
<AnimatedRect
|
|
245
|
+
x={cx - r}
|
|
246
|
+
y={scanLineY}
|
|
247
|
+
width={r * 2}
|
|
248
|
+
height={SCAN_LINE_HEIGHT}
|
|
249
|
+
fill="url(#scan-gradient)"
|
|
250
|
+
opacity={scanOpacity}
|
|
251
|
+
/>
|
|
252
|
+
</G>
|
|
253
|
+
|
|
254
|
+
{/* ── Rotating corner brackets ─────────────────────────────────────── */}
|
|
255
|
+
<AnimatedG transform={bracketTransform}>
|
|
256
|
+
<Path
|
|
257
|
+
d={bracketD}
|
|
258
|
+
fill="none"
|
|
259
|
+
stroke={color}
|
|
260
|
+
strokeWidth={BRACKET_STROKE}
|
|
261
|
+
strokeLinecap="round"
|
|
262
|
+
opacity={0.85}
|
|
263
|
+
/>
|
|
264
|
+
</AnimatedG>
|
|
265
|
+
|
|
266
|
+
{/* ── Progress arc — draws in as liveness confidence builds ────────── */}
|
|
267
|
+
{/* Rotated -90° so it starts at 12 o'clock and goes clockwise */}
|
|
268
|
+
<AnimatedCircle
|
|
85
269
|
cx={cx}
|
|
86
270
|
cy={cy}
|
|
87
271
|
r={r}
|
|
88
272
|
fill="none"
|
|
89
273
|
stroke={color}
|
|
90
274
|
strokeWidth={STROKE_WIDTH}
|
|
275
|
+
strokeDasharray={circumference}
|
|
276
|
+
strokeDashoffset={strokeDashoffset}
|
|
277
|
+
strokeLinecap="round"
|
|
278
|
+
transform={`rotate(-90, ${cx}, ${cy})`}
|
|
91
279
|
/>
|
|
92
280
|
</Svg>
|
|
93
281
|
);
|
|
94
282
|
}
|
|
95
283
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
284
|
+
// ─── CountdownBubble ──────────────────────────────────────────────────────────
|
|
285
|
+
function CountdownBubble({
|
|
286
|
+
value,
|
|
287
|
+
fontFamily,
|
|
288
|
+
}: {
|
|
289
|
+
value: number;
|
|
290
|
+
fontFamily: string;
|
|
291
|
+
}) {
|
|
99
292
|
const scale = useRef(new Animated.Value(0)).current;
|
|
100
293
|
const opacity = useRef(new Animated.Value(0)).current;
|
|
101
294
|
|
|
@@ -135,11 +328,12 @@ function CountdownBubble({ value }: { value: number }) {
|
|
|
135
328
|
<Animated.View
|
|
136
329
|
style={[styles.countdownBubble, { opacity, transform: [{ scale }] }]}
|
|
137
330
|
>
|
|
138
|
-
<Text style={styles.countdownText}>{value}</Text>
|
|
331
|
+
<Text style={[styles.countdownText, { fontFamily }]}>{value}</Text>
|
|
139
332
|
</Animated.View>
|
|
140
333
|
);
|
|
141
334
|
}
|
|
142
335
|
|
|
336
|
+
// ─── LivenessCamera ───────────────────────────────────────────────────────────
|
|
143
337
|
export function LivenessCamera({
|
|
144
338
|
onCapture,
|
|
145
339
|
onLivenessConfirmed,
|
|
@@ -148,12 +342,11 @@ export function LivenessCamera({
|
|
|
148
342
|
livenessThreshold = 0.75,
|
|
149
343
|
confirmFrames = 10,
|
|
150
344
|
soundEnabled = true,
|
|
345
|
+
fontFamily = DEFAULT_FONT,
|
|
151
346
|
style,
|
|
152
347
|
}: LivenessCameraProps) {
|
|
153
348
|
const { hasPermission, requestPermission } = useCameraPermission();
|
|
154
349
|
const device = useCameraDevice('front');
|
|
155
|
-
// Pick the best format that supports up to 60 fps. Falls back gracefully
|
|
156
|
-
// to whatever the device offers if 60 fps isn't available.
|
|
157
350
|
const format = useCameraFormat(device, [{ fps: 60 }]);
|
|
158
351
|
const fps = Math.min(format?.maxFps ?? 30, 60);
|
|
159
352
|
const cameraRef = useRef<Camera>(null);
|
|
@@ -190,7 +383,9 @@ export function LivenessCamera({
|
|
|
190
383
|
if (!hasPermission) {
|
|
191
384
|
return (
|
|
192
385
|
<View style={[styles.root, style, styles.centered]}>
|
|
193
|
-
<Text style={styles.permissionText
|
|
386
|
+
<Text style={[styles.permissionText, { fontFamily }]}>
|
|
387
|
+
Camera permission required
|
|
388
|
+
</Text>
|
|
194
389
|
</View>
|
|
195
390
|
);
|
|
196
391
|
}
|
|
@@ -198,7 +393,9 @@ export function LivenessCamera({
|
|
|
198
393
|
if (!device) {
|
|
199
394
|
return (
|
|
200
395
|
<View style={[styles.root, style, styles.centered]}>
|
|
201
|
-
<Text style={styles.permissionText
|
|
396
|
+
<Text style={[styles.permissionText, { fontFamily }]}>
|
|
397
|
+
No front camera found
|
|
398
|
+
</Text>
|
|
202
399
|
</View>
|
|
203
400
|
);
|
|
204
401
|
}
|
|
@@ -221,15 +418,20 @@ export function LivenessCamera({
|
|
|
221
418
|
height={containerSize.height}
|
|
222
419
|
state={livenessState}
|
|
223
420
|
score={livenessScore}
|
|
421
|
+
livenessThreshold={livenessThreshold}
|
|
224
422
|
/>
|
|
225
423
|
{livenessState !== 'done' && (
|
|
226
424
|
<View style={styles.feedbackContainer}>
|
|
227
|
-
<Text style={styles.feedbackText}>{feedback}</Text>
|
|
425
|
+
<Text style={[styles.feedbackText, { fontFamily }]}>{feedback}</Text>
|
|
228
426
|
</View>
|
|
229
427
|
)}
|
|
230
428
|
{livenessState === 'countdown' && countdown !== null && (
|
|
231
429
|
<View style={styles.countdownContainer}>
|
|
232
|
-
<CountdownBubble
|
|
430
|
+
<CountdownBubble
|
|
431
|
+
key={countdown}
|
|
432
|
+
value={countdown}
|
|
433
|
+
fontFamily={fontFamily}
|
|
434
|
+
/>
|
|
233
435
|
</View>
|
|
234
436
|
)}
|
|
235
437
|
{livenessState === 'capturing' && (
|
|
@@ -239,6 +441,7 @@ export function LivenessCamera({
|
|
|
239
441
|
);
|
|
240
442
|
}
|
|
241
443
|
|
|
444
|
+
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
242
445
|
const styles = StyleSheet.create({
|
|
243
446
|
root: {
|
|
244
447
|
flex: 1,
|
|
@@ -266,7 +469,6 @@ const styles = StyleSheet.create({
|
|
|
266
469
|
feedbackText: {
|
|
267
470
|
color: '#fff',
|
|
268
471
|
fontSize: 16,
|
|
269
|
-
fontWeight: '600',
|
|
270
472
|
textAlign: 'center',
|
|
271
473
|
textShadowColor: 'rgba(0,0,0,0.8)',
|
|
272
474
|
textShadowOffset: { width: 0, height: 1 },
|
|
@@ -290,7 +492,6 @@ const styles = StyleSheet.create({
|
|
|
290
492
|
countdownText: {
|
|
291
493
|
color: '#fff',
|
|
292
494
|
fontSize: 52,
|
|
293
|
-
fontWeight: '700',
|
|
294
495
|
lineHeight: 60,
|
|
295
496
|
},
|
|
296
497
|
captureFlash: {
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Modal, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
2
|
+
import { Path, Svg } from 'react-native-svg';
|
|
3
|
+
import { LivenessCamera } from './LivenessCamera';
|
|
4
|
+
import type { LivenessCameraModalProps } from './types';
|
|
5
|
+
|
|
6
|
+
/** × icon drawn with SVG — no external icon library required. */
|
|
7
|
+
function CloseIcon({
|
|
8
|
+
color = '#fff',
|
|
9
|
+
size = 18,
|
|
10
|
+
}: {
|
|
11
|
+
color?: string;
|
|
12
|
+
size?: number;
|
|
13
|
+
}) {
|
|
14
|
+
const pad = size * 0.1;
|
|
15
|
+
const end = size - pad;
|
|
16
|
+
return (
|
|
17
|
+
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
18
|
+
<Path
|
|
19
|
+
d={`M${pad} ${pad} L${end} ${end} M${end} ${pad} L${pad} ${end}`}
|
|
20
|
+
stroke={color}
|
|
21
|
+
strokeWidth={size * 0.14}
|
|
22
|
+
strokeLinecap="round"
|
|
23
|
+
/>
|
|
24
|
+
</Svg>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Drop-in liveness detection modal.
|
|
30
|
+
*
|
|
31
|
+
* ```tsx
|
|
32
|
+
* <LivenessCameraModal
|
|
33
|
+
* visible={showLiveness}
|
|
34
|
+
* onClose={() => setShowLiveness(false)}
|
|
35
|
+
* onCapture={(result) => console.log(result.photo.path)}
|
|
36
|
+
* animationType="slide"
|
|
37
|
+
* />
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function LivenessCameraModal({
|
|
41
|
+
visible,
|
|
42
|
+
onClose,
|
|
43
|
+
animationType = 'slide',
|
|
44
|
+
closeButtonStyle,
|
|
45
|
+
closeButtonIconColor = '#fff',
|
|
46
|
+
closeButtonIconSize = 18,
|
|
47
|
+
// forward all LivenessCamera props
|
|
48
|
+
onCapture,
|
|
49
|
+
onLivenessConfirmed,
|
|
50
|
+
onError,
|
|
51
|
+
countdownFrom,
|
|
52
|
+
livenessThreshold,
|
|
53
|
+
confirmFrames,
|
|
54
|
+
soundEnabled,
|
|
55
|
+
fontFamily,
|
|
56
|
+
}: LivenessCameraModalProps) {
|
|
57
|
+
const handleCapture: LivenessCameraModalProps['onCapture'] = (result) => {
|
|
58
|
+
onCapture(result);
|
|
59
|
+
// Modal stays open — consumer decides when to close via onCapture callback
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Modal
|
|
64
|
+
visible={visible}
|
|
65
|
+
animationType={animationType}
|
|
66
|
+
statusBarTranslucent
|
|
67
|
+
onRequestClose={onClose}
|
|
68
|
+
>
|
|
69
|
+
<View style={styles.container}>
|
|
70
|
+
<LivenessCamera
|
|
71
|
+
style={styles.camera}
|
|
72
|
+
onCapture={handleCapture}
|
|
73
|
+
onLivenessConfirmed={onLivenessConfirmed}
|
|
74
|
+
onError={onError}
|
|
75
|
+
countdownFrom={countdownFrom}
|
|
76
|
+
livenessThreshold={livenessThreshold}
|
|
77
|
+
confirmFrames={confirmFrames}
|
|
78
|
+
soundEnabled={soundEnabled}
|
|
79
|
+
fontFamily={fontFamily}
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
<TouchableOpacity
|
|
83
|
+
style={[styles.closeButton, closeButtonStyle]}
|
|
84
|
+
onPress={onClose}
|
|
85
|
+
activeOpacity={0.7}
|
|
86
|
+
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
87
|
+
>
|
|
88
|
+
<CloseIcon color={closeButtonIconColor} size={closeButtonIconSize} />
|
|
89
|
+
</TouchableOpacity>
|
|
90
|
+
</View>
|
|
91
|
+
</Modal>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const styles = StyleSheet.create({
|
|
96
|
+
container: {
|
|
97
|
+
flex: 1,
|
|
98
|
+
backgroundColor: '#000',
|
|
99
|
+
},
|
|
100
|
+
camera: {
|
|
101
|
+
flex: 1,
|
|
102
|
+
},
|
|
103
|
+
closeButton: {
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
top: 52,
|
|
106
|
+
left: 16,
|
|
107
|
+
width: 48,
|
|
108
|
+
height: 48,
|
|
109
|
+
borderRadius: 24,
|
|
110
|
+
justifyContent: 'center',
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
113
|
+
borderWidth: 1,
|
|
114
|
+
borderColor: 'rgba(255,255,255,0.3)',
|
|
115
|
+
},
|
|
116
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { LivenessCamera } from './LivenessCamera';
|
|
2
|
+
export { LivenessCameraModal } from './LivenessCameraModal';
|
|
2
3
|
export { useLivenessCamera } from './useLivenessCamera';
|
|
3
4
|
export type {
|
|
4
5
|
CaptureResult,
|
|
5
6
|
FaceData,
|
|
6
7
|
FeedbackMessage,
|
|
8
|
+
LivenessCameraModalProps,
|
|
7
9
|
LivenessCameraProps,
|
|
8
10
|
LivenessState,
|
|
9
11
|
} from './types';
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ViewStyle } from 'react-native';
|
|
1
|
+
import type { ModalProps, ViewStyle } from 'react-native';
|
|
2
2
|
import type { PhotoFile } from 'react-native-vision-camera';
|
|
3
3
|
|
|
4
4
|
export type FaceData = {
|
|
@@ -81,8 +81,47 @@ export type LivenessCameraProps = {
|
|
|
81
81
|
style?: ViewStyle;
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
* Whether to play a shutter sound on capture.
|
|
85
|
-
* to be installed. Defaults to true.
|
|
84
|
+
* Whether to play a shutter sound on capture. Defaults to true.
|
|
86
85
|
*/
|
|
87
86
|
soundEnabled?: boolean;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Font family applied to all text inside the component.
|
|
90
|
+
* Defaults to 'Baloo-Medium'. Set to undefined to use the system font.
|
|
91
|
+
*/
|
|
92
|
+
fontFamily?: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type LivenessCameraModalProps = Omit<LivenessCameraProps, 'style'> & {
|
|
96
|
+
/**
|
|
97
|
+
* Controls modal visibility — pass your own boolean state.
|
|
98
|
+
*/
|
|
99
|
+
visible: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Called when the close button is pressed or the Android back button fires.
|
|
103
|
+
* Use this to set your visible state to false.
|
|
104
|
+
*/
|
|
105
|
+
onClose: () => void;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Modal entrance/exit animation. Defaults to 'slide'.
|
|
109
|
+
*/
|
|
110
|
+
animationType?: ModalProps['animationType'];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Override styles on the close button container.
|
|
114
|
+
* Useful for adjusting position or size to match your design system.
|
|
115
|
+
*/
|
|
116
|
+
closeButtonStyle?: ViewStyle;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Colour of the × icon inside the close button. Defaults to '#fff'.
|
|
120
|
+
*/
|
|
121
|
+
closeButtonIconColor?: string;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Size of the × icon in dp. Defaults to 18.
|
|
125
|
+
*/
|
|
126
|
+
closeButtonIconSize?: number;
|
|
88
127
|
};
|