@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.
@@ -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
- // Circle diameter = 82 % of container width — large enough to fit any face
10
- // comfortably without the user needing to fiddle with distance.
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
- // Cubic bezier approximation constant for a smooth circle path
14
- const K = 0.5523;
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 (0.4 ≤ score < threshold)
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
- * Returns an SVG path string tracing a circle at (cx, cy) with radius r
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
- // Compound path: full-screen rect + circle cutout.
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__*/_jsx(Path, {
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":[]}
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
3
  export { LivenessCamera } from "./LivenessCamera.js";
4
+ export { LivenessCameraModal } from "./LivenessCameraModal.js";
4
5
  export { useLivenessCamera } from "./useLivenessCamera.js";
5
6
  //# sourceMappingURL=index.js.map
@@ -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":"AAUA,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAoIlE,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAiB,EACjB,iBAAwB,EACxB,aAAkB,EAClB,YAAmB,EACnB,KAAK,GACN,EAAE,mBAAmB,2CAwFrB"}
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. Requires react-native-sound
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;AAC9C,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;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,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.1.8",
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",
@@ -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 { Circle, Path, Svg } from 'react-native-svg';
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
- // Circle diameter = 82 % of container width — large enough to fit any face
14
- // comfortably without the user needing to fiddle with distance.
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
- // Cubic bezier approximation constant for a smooth circle path
18
- const K = 0.5523;
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 (0.4 ≤ score < threshold)
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
- * Returns an SVG path string tracing a circle at (cx, cy) with radius r
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
- // Compound path: full-screen rect + circle cutout.
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
- function CountdownBubble({ value }: { value: number }) {
97
- // key={countdown} in the parent remounts this component on every tick,
98
- // so [] deps are correct — each mount runs a fresh animation.
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}>Camera permission required</Text>
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}>No front camera found</Text>
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 key={countdown} value={countdown} />
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. Requires react-native-sound
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
  };