@hexar/biometric-identity-sdk-react-native 1.17.0 → 1.18.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/dist/components/CameraCapture.d.ts.map +1 -1
- package/dist/components/CameraCapture.js +31 -2
- package/dist/components/VideoRecorder.d.ts.map +1 -1
- package/dist/components/VideoRecorder.js +16 -0
- package/package.json +1 -1
- package/src/components/CameraCapture.tsx +36 -5
- package/src/components/VideoRecorder.tsx +17 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CameraCapture.d.ts","sourceRoot":"","sources":["../../src/components/CameraCapture.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"CameraCapture.d.ts","sourceRoot":"","sources":["../../src/components/CameraCapture.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAaxE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAmC,MAAM,oCAAoC,CAAC;AAIrH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAoStD,CAAC;AA0JF,eAAe,aAAa,CAAC"}
|
|
@@ -68,6 +68,24 @@ const CameraCapture = ({ mode, theme, language, onCapture, onCancel, }) => {
|
|
|
68
68
|
const timeout = setTimeout(() => setDeviceReady(true), 5000);
|
|
69
69
|
return () => clearTimeout(timeout);
|
|
70
70
|
}, [device]);
|
|
71
|
+
// Trigger autofocus on the center of the viewfinder.
|
|
72
|
+
// Some Android devices (e.g. Ulefone Armor series) don't auto-focus
|
|
73
|
+
// without an explicit programmatic trigger.
|
|
74
|
+
const triggerFocus = (0, react_1.useCallback)(() => {
|
|
75
|
+
try {
|
|
76
|
+
cameraRef.current?.focus({ x: width / 2, y: height / 2 });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// focus() not supported or camera not ready — safe to ignore
|
|
80
|
+
}
|
|
81
|
+
}, []);
|
|
82
|
+
// Trigger focus once camera is ready
|
|
83
|
+
(0, react_1.useEffect)(() => {
|
|
84
|
+
if (device && hasPermission) {
|
|
85
|
+
const timer = setTimeout(triggerFocus, 500);
|
|
86
|
+
return () => clearTimeout(timer);
|
|
87
|
+
}
|
|
88
|
+
}, [device, hasPermission, triggerFocus]);
|
|
71
89
|
const checkPermissions = async () => {
|
|
72
90
|
try {
|
|
73
91
|
// First check if we already have permission from the hook
|
|
@@ -99,11 +117,22 @@ const CameraCapture = ({ mode, theme, language, onCapture, onCancel, }) => {
|
|
|
99
117
|
react_native_1.Alert.alert('Camera Permission Required', errorMsg);
|
|
100
118
|
}
|
|
101
119
|
};
|
|
120
|
+
const handleTapToFocus = (0, react_1.useCallback)((evt) => {
|
|
121
|
+
try {
|
|
122
|
+
cameraRef.current?.focus({ x: evt.nativeEvent.locationX, y: evt.nativeEvent.locationY });
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// ignore — device may not support tap-to-focus
|
|
126
|
+
}
|
|
127
|
+
}, []);
|
|
102
128
|
const handleCapture = async () => {
|
|
103
129
|
if (isCapturing || !cameraRef.current || !device)
|
|
104
130
|
return;
|
|
105
131
|
try {
|
|
106
132
|
setIsCapturing(true);
|
|
133
|
+
// Trigger focus before capture to ensure sharpness
|
|
134
|
+
triggerFocus();
|
|
135
|
+
await new Promise(r => setTimeout(r, 300));
|
|
107
136
|
// Capture photo using react-native-vision-camera
|
|
108
137
|
const photo = await cameraRef.current.takePhoto({
|
|
109
138
|
flash: 'off',
|
|
@@ -195,9 +224,9 @@ const CameraCapture = ({ mode, theme, language, onCapture, onCancel, }) => {
|
|
|
195
224
|
react_1.default.createElement(react_native_1.Text, { style: styles.buttonText }, strings.common.cancel || 'Cancel')))));
|
|
196
225
|
}
|
|
197
226
|
return (react_1.default.createElement(react_native_1.View, { style: styles.container },
|
|
198
|
-
react_1.default.createElement(react_native_1.
|
|
227
|
+
react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.cameraContainer, activeOpacity: 1, onPress: handleTapToFocus },
|
|
199
228
|
react_1.default.createElement(react_native_vision_camera_1.Camera, { ref: cameraRef, style: react_native_1.StyleSheet.absoluteFill, device: device, isActive: true, photo: true }),
|
|
200
|
-
react_1.default.createElement(react_native_1.View, { style: styles.overlay },
|
|
229
|
+
react_1.default.createElement(react_native_1.View, { style: styles.overlay, pointerEvents: "none" },
|
|
201
230
|
react_1.default.createElement(react_native_1.View, { style: styles.frameContainer },
|
|
202
231
|
react_1.default.createElement(react_native_1.View, { style: styles.frame },
|
|
203
232
|
react_1.default.createElement(react_native_1.View, { style: [styles.corner, styles.cornerTopLeft] }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAexE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAAmC,MAAM,oCAAoC,CAAC;AAE1I,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,iCAAiC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAq/BtD,CAAC;AA4PF,eAAe,aAAa,CAAC"}
|
|
@@ -116,6 +116,17 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
116
116
|
const videoRecordingRef = (0, react_1.useRef)(null);
|
|
117
117
|
const isRecordingRef = (0, react_1.useRef)(false);
|
|
118
118
|
const recordingTimeoutRef = (0, react_1.useRef)(null);
|
|
119
|
+
// Trigger autofocus on center — some Android devices (e.g. Ulefone Armor)
|
|
120
|
+
// don't auto-focus without an explicit programmatic trigger.
|
|
121
|
+
const triggerFocus = (0, react_1.useCallback)(() => {
|
|
122
|
+
try {
|
|
123
|
+
const { width: w, height: h } = react_native_1.Dimensions.get('window');
|
|
124
|
+
cameraRef.current?.focus({ x: w / 2, y: h / 2 });
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// focus() not supported or camera not ready — safe to ignore
|
|
128
|
+
}
|
|
129
|
+
}, []);
|
|
119
130
|
const minDurationMs = 8000;
|
|
120
131
|
const totalDuration = duration || Math.max(minDurationMs, challenges.reduce((sum, c) => sum + c.duration_ms, 0) + 2000);
|
|
121
132
|
(0, react_1.useEffect)(() => {
|
|
@@ -220,6 +231,9 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
220
231
|
(0, react_1.useEffect)(() => {
|
|
221
232
|
if (phase !== 'countdown')
|
|
222
233
|
return;
|
|
234
|
+
// Trigger focus at the start of countdown so the camera has ~3s to lock focus
|
|
235
|
+
if (countdown === 3)
|
|
236
|
+
triggerFocus();
|
|
223
237
|
if (countdown > 0) {
|
|
224
238
|
react_native_1.Animated.sequence([
|
|
225
239
|
react_native_1.Animated.timing(scaleAnim, {
|
|
@@ -639,6 +653,8 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
639
653
|
setPhase('recording');
|
|
640
654
|
recordingStartTime.current = Date.now();
|
|
641
655
|
isRecordingRef.current = true;
|
|
656
|
+
// Ensure camera is focused before recording
|
|
657
|
+
triggerFocus();
|
|
642
658
|
// Reset completed challenges ref when starting new recording
|
|
643
659
|
completedChallengesRef.current = [];
|
|
644
660
|
setCompletedChallenges([]);
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles ID document photo capture
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
6
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
7
7
|
import {
|
|
8
8
|
View,
|
|
9
9
|
Text,
|
|
@@ -63,6 +63,25 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
|
|
|
63
63
|
return () => clearTimeout(timeout);
|
|
64
64
|
}, [device]);
|
|
65
65
|
|
|
66
|
+
// Trigger autofocus on the center of the viewfinder.
|
|
67
|
+
// Some Android devices (e.g. Ulefone Armor series) don't auto-focus
|
|
68
|
+
// without an explicit programmatic trigger.
|
|
69
|
+
const triggerFocus = useCallback(() => {
|
|
70
|
+
try {
|
|
71
|
+
cameraRef.current?.focus({ x: width / 2, y: height / 2 });
|
|
72
|
+
} catch {
|
|
73
|
+
// focus() not supported or camera not ready — safe to ignore
|
|
74
|
+
}
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
// Trigger focus once camera is ready
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (device && hasPermission) {
|
|
80
|
+
const timer = setTimeout(triggerFocus, 500);
|
|
81
|
+
return () => clearTimeout(timer);
|
|
82
|
+
}
|
|
83
|
+
}, [device, hasPermission, triggerFocus]);
|
|
84
|
+
|
|
66
85
|
const checkPermissions = async () => {
|
|
67
86
|
try {
|
|
68
87
|
// First check if we already have permission from the hook
|
|
@@ -98,12 +117,24 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
|
|
|
98
117
|
}
|
|
99
118
|
};
|
|
100
119
|
|
|
120
|
+
const handleTapToFocus = useCallback((evt: any) => {
|
|
121
|
+
try {
|
|
122
|
+
cameraRef.current?.focus({ x: evt.nativeEvent.locationX, y: evt.nativeEvent.locationY });
|
|
123
|
+
} catch {
|
|
124
|
+
// ignore — device may not support tap-to-focus
|
|
125
|
+
}
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
101
128
|
const handleCapture = async () => {
|
|
102
129
|
if (isCapturing || !cameraRef.current || !device) return;
|
|
103
130
|
|
|
104
131
|
try {
|
|
105
132
|
setIsCapturing(true);
|
|
106
133
|
|
|
134
|
+
// Trigger focus before capture to ensure sharpness
|
|
135
|
+
triggerFocus();
|
|
136
|
+
await new Promise(r => setTimeout(r, 300));
|
|
137
|
+
|
|
107
138
|
// Capture photo using react-native-vision-camera
|
|
108
139
|
const photo = await cameraRef.current.takePhoto({
|
|
109
140
|
flash: 'off',
|
|
@@ -231,8 +262,8 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
|
|
|
231
262
|
|
|
232
263
|
return (
|
|
233
264
|
<View style={styles.container}>
|
|
234
|
-
{/* Camera View */}
|
|
235
|
-
<
|
|
265
|
+
{/* Camera View — tap anywhere to trigger focus */}
|
|
266
|
+
<TouchableOpacity style={styles.cameraContainer} activeOpacity={1} onPress={handleTapToFocus}>
|
|
236
267
|
<Camera
|
|
237
268
|
ref={cameraRef}
|
|
238
269
|
style={StyleSheet.absoluteFill}
|
|
@@ -242,7 +273,7 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
|
|
|
242
273
|
/>
|
|
243
274
|
|
|
244
275
|
{/* Document Frame Overlay */}
|
|
245
|
-
<View style={styles.overlay}>
|
|
276
|
+
<View style={styles.overlay} pointerEvents="none">
|
|
246
277
|
<View style={styles.frameContainer}>
|
|
247
278
|
<View style={styles.frame}>
|
|
248
279
|
<View style={[styles.corner, styles.cornerTopLeft]} />
|
|
@@ -252,7 +283,7 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
|
|
|
252
283
|
</View>
|
|
253
284
|
</View>
|
|
254
285
|
</View>
|
|
255
|
-
</
|
|
286
|
+
</TouchableOpacity>
|
|
256
287
|
|
|
257
288
|
{/* Instructions */}
|
|
258
289
|
<View style={styles.instructionsContainer}>
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
Platform,
|
|
15
15
|
Alert,
|
|
16
16
|
ActivityIndicator,
|
|
17
|
+
Dimensions,
|
|
17
18
|
} from 'react-native';
|
|
18
19
|
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
|
|
19
20
|
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
|
@@ -146,6 +147,17 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
146
147
|
const videoRecordingRef = useRef<any>(null);
|
|
147
148
|
const isRecordingRef = useRef<boolean>(false);
|
|
148
149
|
const recordingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
150
|
+
// Trigger autofocus on center — some Android devices (e.g. Ulefone Armor)
|
|
151
|
+
// don't auto-focus without an explicit programmatic trigger.
|
|
152
|
+
const triggerFocus = useCallback(() => {
|
|
153
|
+
try {
|
|
154
|
+
const { width: w, height: h } = Dimensions.get('window');
|
|
155
|
+
cameraRef.current?.focus({ x: w / 2, y: h / 2 });
|
|
156
|
+
} catch {
|
|
157
|
+
// focus() not supported or camera not ready — safe to ignore
|
|
158
|
+
}
|
|
159
|
+
}, []);
|
|
160
|
+
|
|
149
161
|
const minDurationMs = 8000;
|
|
150
162
|
const totalDuration = duration || Math.max(
|
|
151
163
|
minDurationMs,
|
|
@@ -260,6 +272,9 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
260
272
|
useEffect(() => {
|
|
261
273
|
if (phase !== 'countdown') return;
|
|
262
274
|
|
|
275
|
+
// Trigger focus at the start of countdown so the camera has ~3s to lock focus
|
|
276
|
+
if (countdown === 3) triggerFocus();
|
|
277
|
+
|
|
263
278
|
if (countdown > 0) {
|
|
264
279
|
Animated.sequence([
|
|
265
280
|
Animated.timing(scaleAnim, {
|
|
@@ -745,6 +760,8 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
745
760
|
setPhase('recording');
|
|
746
761
|
recordingStartTime.current = Date.now();
|
|
747
762
|
isRecordingRef.current = true;
|
|
763
|
+
// Ensure camera is focused before recording
|
|
764
|
+
triggerFocus();
|
|
748
765
|
// Reset completed challenges ref when starting new recording
|
|
749
766
|
completedChallengesRef.current = [];
|
|
750
767
|
setCompletedChallenges([]);
|