@rick427/react-native-liveness 0.1.9 → 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,25 +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
- const DEFAULT_FONT = 'Baloo-Medium';
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);
10
14
 
11
- // Circle diameter = 82 % of container width — large enough to fit any face
12
- // comfortably without the user needing to fiddle with distance.
15
+ // ─── Constants ────────────────────────────────────────────────────────────────
16
+ const DEFAULT_FONT = 'Baloo-Medium';
13
17
  const CIRCLE_DIAMETER_RATIO = 0.82;
14
18
  const STROKE_WIDTH = 3;
15
- // Cubic bezier approximation constant for a smooth circle path
16
- 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;
17
23
 
24
+ // ─── Colour helper ────────────────────────────────────────────────────────────
18
25
  /**
19
- * Returns the stroke colour for the circle guide.
20
- *
21
26
  * ● White – no face / scanning (score < 0.4)
22
- * ● Yellow – face detected, confidence building (0.4 ≤ score < threshold)
27
+ * ● Yellow – face detected, confidence building
23
28
  * ● Green – liveness confirmed / countdown / capture
24
29
  * ● Red – error
25
30
  */
@@ -37,44 +42,210 @@ function getCircleColor(state, score) {
37
42
  }
38
43
  }
39
44
 
40
- /**
41
- * Returns an SVG path string tracing a circle at (cx, cy) with radius r
42
- * using cubic bezier curves. Used inside a compound path with
43
- * fillRule="evenodd" to punch a transparent hole through the dark scrim.
44
- */
45
+ // ─── SVG path helpers ─────────────────────────────────────────────────────────
46
+ /** Cubic bezier circle path used inside compound evenodd scrim. */
45
47
  function circlePath(cx, cy, r) {
46
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(' ');
47
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 ────────────────────────────────────────────────────────────
48
64
  function CircleOverlay({
49
65
  width,
50
66
  height,
51
67
  state,
52
- score
68
+ score,
69
+ livenessThreshold
53
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 ─────────────────────
54
133
  if (width === 0 || height === 0) return null;
134
+
135
+ // ── Geometry ──────────────────────────────────────────────────────────────
55
136
  const cx = width / 2;
56
137
  const cy = height * 0.42;
57
138
  const r = width * CIRCLE_DIAMETER_RATIO / 2;
139
+ const circumference = 2 * Math.PI * r;
58
140
  const color = getCircleColor(state, score);
141
+
142
+ // Scrim: full-screen dark rect with transparent circle hole
59
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(' ');
60
166
  return /*#__PURE__*/_jsxs(Svg, {
61
167
  style: StyleSheet.absoluteFill,
62
168
  width: width,
63
169
  height: height,
64
- 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, {
65
203
  d: scrimD,
66
204
  fill: "rgba(0,0,0,0.55)",
67
205
  fillRule: "evenodd"
68
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, {
69
234
  cx: cx,
70
235
  cy: cy,
71
236
  r: r,
72
237
  fill: "none",
73
238
  stroke: color,
74
- strokeWidth: STROKE_WIDTH
239
+ strokeWidth: STROKE_WIDTH,
240
+ strokeDasharray: circumference,
241
+ strokeDashoffset: strokeDashoffset,
242
+ strokeLinecap: "round",
243
+ transform: `rotate(-90, ${cx}, ${cy})`
75
244
  })]
76
245
  });
77
246
  }
247
+
248
+ // ─── CountdownBubble ──────────────────────────────────────────────────────────
78
249
  function CountdownBubble({
79
250
  value,
80
251
  fontFamily
@@ -121,6 +292,8 @@ function CountdownBubble({
121
292
  })
122
293
  });
123
294
  }
295
+
296
+ // ─── LivenessCamera ───────────────────────────────────────────────────────────
124
297
  export function LivenessCamera({
125
298
  onCapture,
126
299
  onLivenessConfirmed,
@@ -218,7 +391,8 @@ export function LivenessCamera({
218
391
  width: containerSize.width,
219
392
  height: containerSize.height,
220
393
  state: livenessState,
221
- score: livenessScore
394
+ score: livenessScore,
395
+ livenessThreshold: livenessThreshold
222
396
  }), livenessState !== 'done' && /*#__PURE__*/_jsx(View, {
223
397
  style: styles.feedbackContainer,
224
398
  children: /*#__PURE__*/_jsx(Text, {
@@ -239,6 +413,8 @@ export function LivenessCamera({
239
413
  })]
240
414
  });
241
415
  }
416
+
417
+ // ─── Styles ───────────────────────────────────────────────────────────────────
242
418
  const styles = StyleSheet.create({
243
419
  root: {
244
420
  flex: 1,
@@ -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","DEFAULT_FONT","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","fontFamily","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","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":[]}
@@ -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;AAuIlE,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAiB,EACjB,iBAAwB,EACxB,aAAkB,EAClB,YAAmB,EACnB,UAAyB,EACzB,KAAK,GACN,EAAE,mBAAmB,2CA8FrB"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rick427/react-native-liveness",
3
- "version": "0.1.9",
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,29 +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
- const DEFAULT_FONT = 'Baloo-Medium';
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);
14
28
 
15
- // Circle diameter = 82 % of container width — large enough to fit any face
16
- // comfortably without the user needing to fiddle with distance.
29
+ // ─── Constants ────────────────────────────────────────────────────────────────
30
+ const DEFAULT_FONT = 'Baloo-Medium';
17
31
  const CIRCLE_DIAMETER_RATIO = 0.82;
18
32
  const STROKE_WIDTH = 3;
19
- // Cubic bezier approximation constant for a smooth circle path
20
- 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;
21
37
 
38
+ // ─── Colour helper ────────────────────────────────────────────────────────────
22
39
  /**
23
- * Returns the stroke colour for the circle guide.
24
- *
25
40
  * ● White – no face / scanning (score < 0.4)
26
- * ● Yellow – face detected, confidence building (0.4 ≤ score < threshold)
41
+ * ● Yellow – face detected, confidence building
27
42
  * ● Green – liveness confirmed / countdown / capture
28
43
  * ● Red – error
29
44
  */
@@ -41,11 +56,8 @@ function getCircleColor(state: LivenessState, score: number): string {
41
56
  }
42
57
  }
43
58
 
44
- /**
45
- * Returns an SVG path string tracing a circle at (cx, cy) with radius r
46
- * using cubic bezier curves. Used inside a compound path with
47
- * fillRule="evenodd" to punch a transparent hole through the dark scrim.
48
- */
59
+ // ─── SVG path helpers ─────────────────────────────────────────────────────────
60
+ /** Cubic bezier circle path used inside compound evenodd scrim. */
49
61
  function circlePath(cx: number, cy: number, r: number): string {
50
62
  return [
51
63
  `M ${cx + r} ${cy}`,
@@ -57,41 +69,219 @@ function circlePath(cx: number, cy: number, r: number): string {
57
69
  ].join(' ');
58
70
  }
59
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 ────────────────────────────────────────────────────────────
60
91
  function CircleOverlay({
61
92
  width,
62
93
  height,
63
94
  state,
64
95
  score,
96
+ livenessThreshold,
65
97
  }: {
66
98
  width: number;
67
99
  height: number;
68
100
  state: LivenessState;
69
101
  score: number;
102
+ livenessThreshold: number;
70
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 ─────────────────────
71
175
  if (width === 0 || height === 0) return null;
72
176
 
177
+ // ── Geometry ──────────────────────────────────────────────────────────────
73
178
  const cx = width / 2;
74
179
  const cy = height * 0.42;
75
180
  const r = (width * CIRCLE_DIAMETER_RATIO) / 2;
181
+ const circumference = 2 * Math.PI * r;
76
182
  const color = getCircleColor(state, score);
77
183
 
184
+ // Scrim: full-screen dark rect with transparent circle hole
78
185
  const scrimD = `M0 0H${width}V${height}H0Z ${circlePath(cx, cy, r)}`;
79
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
+
80
211
  return (
81
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 ────────────────────── */}
82
230
  <Path d={scrimD} fill="rgba(0,0,0,0.55)" fillRule="evenodd" />
231
+
232
+ {/* ── Dim base ring — always shows the circle boundary ─────────────── */}
83
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
84
269
  cx={cx}
85
270
  cy={cy}
86
271
  r={r}
87
272
  fill="none"
88
273
  stroke={color}
89
274
  strokeWidth={STROKE_WIDTH}
275
+ strokeDasharray={circumference}
276
+ strokeDashoffset={strokeDashoffset}
277
+ strokeLinecap="round"
278
+ transform={`rotate(-90, ${cx}, ${cy})`}
90
279
  />
91
280
  </Svg>
92
281
  );
93
282
  }
94
283
 
284
+ // ─── CountdownBubble ──────────────────────────────────────────────────────────
95
285
  function CountdownBubble({
96
286
  value,
97
287
  fontFamily,
@@ -143,6 +333,7 @@ function CountdownBubble({
143
333
  );
144
334
  }
145
335
 
336
+ // ─── LivenessCamera ───────────────────────────────────────────────────────────
146
337
  export function LivenessCamera({
147
338
  onCapture,
148
339
  onLivenessConfirmed,
@@ -227,6 +418,7 @@ export function LivenessCamera({
227
418
  height={containerSize.height}
228
419
  state={livenessState}
229
420
  score={livenessScore}
421
+ livenessThreshold={livenessThreshold}
230
422
  />
231
423
  {livenessState !== 'done' && (
232
424
  <View style={styles.feedbackContainer}>
@@ -249,6 +441,7 @@ export function LivenessCamera({
249
441
  );
250
442
  }
251
443
 
444
+ // ─── Styles ───────────────────────────────────────────────────────────────────
252
445
  const styles = StyleSheet.create({
253
446
  root: {
254
447
  flex: 1,