@hexar/biometric-identity-sdk-react-native 1.1.24 → 1.1.26
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/BiometricIdentityFlow.d.ts.map +1 -1
- package/dist/components/BiometricIdentityFlow.js +1 -0
- package/dist/components/ProfilePictureCapture.d.ts.map +1 -1
- package/dist/components/ProfilePictureCapture.js +126 -4
- package/dist/components/VideoRecorder.d.ts.map +1 -1
- package/dist/components/VideoRecorder.js +1 -0
- package/package.json +1 -1
- package/src/components/BiometricIdentityFlow.tsx +1 -0
- package/src/components/ProfilePictureCapture.tsx +147 -6
- package/src/components/VideoRecorder.tsx +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,EAOL,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,cAAc,EAKd,iBAAiB,EAElB,MAAM,oCAAoC,CAAC;AA8B5C,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,OAAO,CAAC,EAAE,SAAS,CAAC;KACrB,CAAC;CACH;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAuatE,CAAC;
|
|
1
|
+
{"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,EAOL,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,cAAc,EAKd,iBAAiB,EAElB,MAAM,oCAAoC,CAAC;AA8B5C,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,OAAO,CAAC,EAAE,SAAS,CAAC;KACrB,CAAC;CACH;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAuatE,CAAC;AA+NF,eAAe,qBAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfilePictureCapture.d.ts","sourceRoot":"","sources":["../../src/components/ProfilePictureCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ProfilePictureCapture.d.ts","sourceRoot":"","sources":["../../src/components/ProfilePictureCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAUxE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAmC,cAAc,EAAsB,MAAM,oCAAoC,CAAC;AAGzJ,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,CAAC,MAAM,EAAE,8BAA8B,KAAK,IAAI,CAAC;IAC7D,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAkUtE,CAAC;AAsEF,eAAe,qBAAqB,CAAC"}
|
|
@@ -44,6 +44,10 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
44
44
|
const [isValidating, setIsValidating] = (0, react_1.useState)(false);
|
|
45
45
|
const [currentChallenges, setCurrentChallenges] = (0, react_1.useState)([]);
|
|
46
46
|
const [isLoadingChallenges, setIsLoadingChallenges] = (0, react_1.useState)(false);
|
|
47
|
+
const animatedProgress = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
48
|
+
const [displayProgress, setDisplayProgress] = (0, react_1.useState)(0);
|
|
49
|
+
const animationRef = (0, react_1.useRef)(null);
|
|
50
|
+
const hasStartedAnimation = (0, react_1.useRef)(false);
|
|
47
51
|
const handleFetchChallenges = (0, react_1.useCallback)(async () => {
|
|
48
52
|
return await fetchChallenges('active');
|
|
49
53
|
}, [fetchChallenges]);
|
|
@@ -139,6 +143,40 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
139
143
|
throw error;
|
|
140
144
|
}
|
|
141
145
|
}, [isInitialized, isUsingBackend, sdk]);
|
|
146
|
+
// Handle progress animation during validation
|
|
147
|
+
(0, react_1.useEffect)(() => {
|
|
148
|
+
if (isValidating && !hasStartedAnimation.current) {
|
|
149
|
+
hasStartedAnimation.current = true;
|
|
150
|
+
animatedProgress.setValue(0);
|
|
151
|
+
setDisplayProgress(0);
|
|
152
|
+
// Start animation to 90% over 60 seconds
|
|
153
|
+
animationRef.current = react_native_1.Animated.timing(animatedProgress, {
|
|
154
|
+
toValue: 90,
|
|
155
|
+
duration: 60000, // 60 seconds
|
|
156
|
+
useNativeDriver: false,
|
|
157
|
+
});
|
|
158
|
+
animationRef.current.start();
|
|
159
|
+
// Update display value
|
|
160
|
+
const listener = animatedProgress.addListener(({ value }) => {
|
|
161
|
+
setDisplayProgress(Math.round(value));
|
|
162
|
+
});
|
|
163
|
+
return () => {
|
|
164
|
+
animatedProgress.removeListener(listener);
|
|
165
|
+
if (animationRef.current) {
|
|
166
|
+
animationRef.current.stop();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
else if (!isValidating && hasStartedAnimation.current) {
|
|
171
|
+
// Reset when validation ends
|
|
172
|
+
hasStartedAnimation.current = false;
|
|
173
|
+
if (animationRef.current) {
|
|
174
|
+
animationRef.current.stop();
|
|
175
|
+
}
|
|
176
|
+
animatedProgress.setValue(0);
|
|
177
|
+
setDisplayProgress(0);
|
|
178
|
+
}
|
|
179
|
+
}, [isValidating, animatedProgress]);
|
|
142
180
|
const handleVideoComplete = (0, react_1.useCallback)(async (videoResult) => {
|
|
143
181
|
biometric_identity_sdk_core_1.logger.info('ProfilePictureCapture: Video recording completed', {
|
|
144
182
|
framesCount: videoResult.frames?.length || 0,
|
|
@@ -150,7 +188,22 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
150
188
|
setIsValidating(true);
|
|
151
189
|
try {
|
|
152
190
|
const result = await validateWithBackend(videoResult);
|
|
191
|
+
if (animationRef.current) {
|
|
192
|
+
animationRef.current.stop();
|
|
193
|
+
}
|
|
194
|
+
react_native_1.Animated.timing(animatedProgress, {
|
|
195
|
+
toValue: 100,
|
|
196
|
+
duration: 500,
|
|
197
|
+
useNativeDriver: false,
|
|
198
|
+
}).start(() => {
|
|
199
|
+
setDisplayProgress(100);
|
|
200
|
+
});
|
|
201
|
+
// Small delay to show 100% before completing
|
|
202
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
153
203
|
if (!result.isValid) {
|
|
204
|
+
if (animationRef.current) {
|
|
205
|
+
animationRef.current.stop();
|
|
206
|
+
}
|
|
154
207
|
setIsValidating(false);
|
|
155
208
|
const livenessError = {
|
|
156
209
|
name: 'BiometricError',
|
|
@@ -164,6 +217,10 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
164
217
|
onComplete(result);
|
|
165
218
|
}
|
|
166
219
|
catch (error) {
|
|
220
|
+
// Stop animation on error
|
|
221
|
+
if (animationRef.current) {
|
|
222
|
+
animationRef.current.stop();
|
|
223
|
+
}
|
|
167
224
|
setIsValidating(false);
|
|
168
225
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
169
226
|
onError(error);
|
|
@@ -190,18 +247,36 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
|
|
|
190
247
|
};
|
|
191
248
|
onError(biometricError);
|
|
192
249
|
}
|
|
193
|
-
}, [validateWithBackend, onComplete, onError, strings, language, currentChallenges]);
|
|
250
|
+
}, [validateWithBackend, onComplete, onError, strings, language, currentChallenges, animatedProgress]);
|
|
194
251
|
const handleVideoCancel = (0, react_1.useCallback)(() => {
|
|
195
252
|
if (onCancel) {
|
|
196
253
|
onCancel();
|
|
197
254
|
}
|
|
198
255
|
}, [onCancel]);
|
|
199
256
|
if (isValidating) {
|
|
257
|
+
const progressWidth = animatedProgress.interpolate({
|
|
258
|
+
inputRange: [0, 100],
|
|
259
|
+
outputRange: ['0%', '100%'],
|
|
260
|
+
extrapolate: 'clamp',
|
|
261
|
+
});
|
|
200
262
|
return (react_1.default.createElement(react_native_1.SafeAreaView, { style: [styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }] },
|
|
201
|
-
react_1.default.createElement(react_native_1.View, { style: styles.
|
|
263
|
+
react_1.default.createElement(react_native_1.View, { style: styles.progressContainer },
|
|
202
264
|
react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: theme?.primaryColor || '#4f46e5' }),
|
|
203
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.
|
|
204
|
-
react_1.default.createElement(react_native_1.Text, { style: [styles.
|
|
265
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.progressTitle, { color: theme?.textColor || '#1e1b4b' }] }, strings.validation.title || strings.liveness.processing || strings.validation.checkingLiveness || 'Validating Profile Picture'),
|
|
266
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.progressStatusText, { color: theme?.secondaryTextColor || '#64748b' }] }, strings.validation.validatingDocumentAndFace || strings.liveness.processing || 'Validando imagen de perfil...'),
|
|
267
|
+
react_1.default.createElement(react_native_1.View, { style: styles.progressBarContainer },
|
|
268
|
+
react_1.default.createElement(react_native_1.View, { style: styles.progressBar },
|
|
269
|
+
react_1.default.createElement(react_native_1.Animated.View, { style: [
|
|
270
|
+
styles.progressFill,
|
|
271
|
+
{
|
|
272
|
+
width: progressWidth,
|
|
273
|
+
backgroundColor: theme?.primaryColor || '#4f46e5',
|
|
274
|
+
},
|
|
275
|
+
] })),
|
|
276
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.progressText, { color: theme?.textColor || '#1e1b4b' }] },
|
|
277
|
+
displayProgress,
|
|
278
|
+
"%")),
|
|
279
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.progressBottomText, { color: theme?.secondaryTextColor || '#64748b' }] }, strings.validation.almostDone || strings.common.loading || 'Esto puede tardar unos segundos...'))));
|
|
205
280
|
}
|
|
206
281
|
// Wait for initialization and challenge loading before showing VideoRecorder
|
|
207
282
|
if (!isInitialized || (isUsingBackend && isLoadingChallenges)) {
|
|
@@ -216,6 +291,7 @@ exports.ProfilePictureCapture = ProfilePictureCapture;
|
|
|
216
291
|
const styles = react_native_1.StyleSheet.create({
|
|
217
292
|
container: {
|
|
218
293
|
flex: 1,
|
|
294
|
+
zIndex: 9999,
|
|
219
295
|
},
|
|
220
296
|
loadingContainer: {
|
|
221
297
|
flex: 1,
|
|
@@ -232,5 +308,51 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
232
308
|
fontSize: 14,
|
|
233
309
|
marginTop: 8,
|
|
234
310
|
},
|
|
311
|
+
progressContainer: {
|
|
312
|
+
flex: 1,
|
|
313
|
+
justifyContent: 'center',
|
|
314
|
+
alignItems: 'center',
|
|
315
|
+
padding: 24,
|
|
316
|
+
width: '85%',
|
|
317
|
+
alignSelf: 'center',
|
|
318
|
+
},
|
|
319
|
+
progressTitle: {
|
|
320
|
+
fontSize: 24,
|
|
321
|
+
fontWeight: 'bold',
|
|
322
|
+
marginTop: 24,
|
|
323
|
+
marginBottom: 8,
|
|
324
|
+
textAlign: 'center',
|
|
325
|
+
},
|
|
326
|
+
progressStatusText: {
|
|
327
|
+
fontSize: 16,
|
|
328
|
+
marginBottom: 32,
|
|
329
|
+
textAlign: 'center',
|
|
330
|
+
},
|
|
331
|
+
progressBarContainer: {
|
|
332
|
+
width: '100%',
|
|
333
|
+
marginBottom: 24,
|
|
334
|
+
},
|
|
335
|
+
progressBar: {
|
|
336
|
+
width: '100%',
|
|
337
|
+
height: 8,
|
|
338
|
+
backgroundColor: '#E5E7EB',
|
|
339
|
+
borderRadius: 4,
|
|
340
|
+
overflow: 'hidden',
|
|
341
|
+
marginBottom: 8,
|
|
342
|
+
},
|
|
343
|
+
progressFill: {
|
|
344
|
+
height: '100%',
|
|
345
|
+
borderRadius: 4,
|
|
346
|
+
},
|
|
347
|
+
progressText: {
|
|
348
|
+
fontSize: 14,
|
|
349
|
+
fontWeight: '600',
|
|
350
|
+
textAlign: 'center',
|
|
351
|
+
},
|
|
352
|
+
progressBottomText: {
|
|
353
|
+
fontSize: 14,
|
|
354
|
+
textAlign: 'center',
|
|
355
|
+
marginTop: 16,
|
|
356
|
+
},
|
|
235
357
|
});
|
|
236
358
|
exports.default = exports.ProfilePictureCapture;
|
|
@@ -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;AAaxE,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;AAiDD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA45BtD,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAaxE,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;AAiDD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA45BtD,CAAC;AA6OF,eAAe,aAAa,CAAC"}
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
StyleSheet,
|
|
6
6
|
ActivityIndicator,
|
|
7
7
|
SafeAreaView,
|
|
8
|
+
Animated,
|
|
8
9
|
} from 'react-native';
|
|
9
10
|
import { VideoRecorder, VideoRecordingResult } from './VideoRecorder';
|
|
10
11
|
import { ThemeConfig, SupportedLanguage, getStrings, setLanguage, logger, BiometricError, BiometricErrorCode } from '@hexar/biometric-identity-sdk-core';
|
|
@@ -44,6 +45,10 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
44
45
|
const [isValidating, setIsValidating] = useState(false);
|
|
45
46
|
const [currentChallenges, setCurrentChallenges] = useState<any[]>([]);
|
|
46
47
|
const [isLoadingChallenges, setIsLoadingChallenges] = useState(false);
|
|
48
|
+
const animatedProgress = useRef(new Animated.Value(0)).current;
|
|
49
|
+
const [displayProgress, setDisplayProgress] = useState(0);
|
|
50
|
+
const animationRef = useRef<Animated.CompositeAnimation | null>(null);
|
|
51
|
+
const hasStartedAnimation = useRef(false);
|
|
47
52
|
|
|
48
53
|
const handleFetchChallenges = useCallback(async () => {
|
|
49
54
|
return await fetchChallenges('active');
|
|
@@ -149,6 +154,44 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
149
154
|
}
|
|
150
155
|
}, [isInitialized, isUsingBackend, sdk]);
|
|
151
156
|
|
|
157
|
+
// Handle progress animation during validation
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
if (isValidating && !hasStartedAnimation.current) {
|
|
160
|
+
hasStartedAnimation.current = true;
|
|
161
|
+
animatedProgress.setValue(0);
|
|
162
|
+
setDisplayProgress(0);
|
|
163
|
+
|
|
164
|
+
// Start animation to 90% over 60 seconds
|
|
165
|
+
animationRef.current = Animated.timing(animatedProgress, {
|
|
166
|
+
toValue: 90,
|
|
167
|
+
duration: 60000, // 60 seconds
|
|
168
|
+
useNativeDriver: false,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
animationRef.current.start();
|
|
172
|
+
|
|
173
|
+
// Update display value
|
|
174
|
+
const listener = animatedProgress.addListener(({ value }) => {
|
|
175
|
+
setDisplayProgress(Math.round(value));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return () => {
|
|
179
|
+
animatedProgress.removeListener(listener);
|
|
180
|
+
if (animationRef.current) {
|
|
181
|
+
animationRef.current.stop();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
} else if (!isValidating && hasStartedAnimation.current) {
|
|
185
|
+
// Reset when validation ends
|
|
186
|
+
hasStartedAnimation.current = false;
|
|
187
|
+
if (animationRef.current) {
|
|
188
|
+
animationRef.current.stop();
|
|
189
|
+
}
|
|
190
|
+
animatedProgress.setValue(0);
|
|
191
|
+
setDisplayProgress(0);
|
|
192
|
+
}
|
|
193
|
+
}, [isValidating, animatedProgress]);
|
|
194
|
+
|
|
152
195
|
const handleVideoComplete = useCallback(async (videoResult: VideoRecordingResult) => {
|
|
153
196
|
logger.info('ProfilePictureCapture: Video recording completed', {
|
|
154
197
|
framesCount: videoResult.frames?.length || 0,
|
|
@@ -163,7 +206,24 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
163
206
|
try {
|
|
164
207
|
const result = await validateWithBackend(videoResult);
|
|
165
208
|
|
|
209
|
+
if (animationRef.current) {
|
|
210
|
+
animationRef.current.stop();
|
|
211
|
+
}
|
|
212
|
+
Animated.timing(animatedProgress, {
|
|
213
|
+
toValue: 100,
|
|
214
|
+
duration: 500,
|
|
215
|
+
useNativeDriver: false,
|
|
216
|
+
}).start(() => {
|
|
217
|
+
setDisplayProgress(100);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Small delay to show 100% before completing
|
|
221
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
222
|
+
|
|
166
223
|
if (!result.isValid) {
|
|
224
|
+
if (animationRef.current) {
|
|
225
|
+
animationRef.current.stop();
|
|
226
|
+
}
|
|
167
227
|
setIsValidating(false);
|
|
168
228
|
const livenessError: BiometricError = {
|
|
169
229
|
name: 'BiometricError',
|
|
@@ -177,6 +237,10 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
177
237
|
setIsValidating(false);
|
|
178
238
|
onComplete(result);
|
|
179
239
|
} catch (error: any) {
|
|
240
|
+
// Stop animation on error
|
|
241
|
+
if (animationRef.current) {
|
|
242
|
+
animationRef.current.stop();
|
|
243
|
+
}
|
|
180
244
|
setIsValidating(false);
|
|
181
245
|
|
|
182
246
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
@@ -205,7 +269,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
205
269
|
} as BiometricError;
|
|
206
270
|
onError(biometricError);
|
|
207
271
|
}
|
|
208
|
-
}, [validateWithBackend, onComplete, onError, strings, language, currentChallenges]);
|
|
272
|
+
}, [validateWithBackend, onComplete, onError, strings, language, currentChallenges, animatedProgress]);
|
|
209
273
|
|
|
210
274
|
const handleVideoCancel = useCallback(() => {
|
|
211
275
|
if (onCancel) {
|
|
@@ -214,15 +278,45 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
214
278
|
}, [onCancel]);
|
|
215
279
|
|
|
216
280
|
if (isValidating) {
|
|
281
|
+
const progressWidth = animatedProgress.interpolate({
|
|
282
|
+
inputRange: [0, 100],
|
|
283
|
+
outputRange: ['0%', '100%'],
|
|
284
|
+
extrapolate: 'clamp',
|
|
285
|
+
});
|
|
286
|
+
|
|
217
287
|
return (
|
|
218
288
|
<SafeAreaView style={[styles.container, { backgroundColor: theme?.backgroundColor || '#FFFFFF' }]}>
|
|
219
|
-
<View style={styles.
|
|
289
|
+
<View style={styles.progressContainer}>
|
|
220
290
|
<ActivityIndicator size="large" color={theme?.primaryColor || '#4f46e5'} />
|
|
221
|
-
|
|
222
|
-
|
|
291
|
+
|
|
292
|
+
<Text style={[styles.progressTitle, { color: theme?.textColor || '#1e1b4b' }]}>
|
|
293
|
+
{strings.validation.title || strings.liveness.processing || strings.validation.checkingLiveness || 'Validating Profile Picture'}
|
|
294
|
+
</Text>
|
|
295
|
+
|
|
296
|
+
<Text style={[styles.progressStatusText, { color: theme?.secondaryTextColor || '#64748b' }]}>
|
|
297
|
+
{strings.validation.validatingDocumentAndFace || strings.liveness.processing || 'Validando imagen de perfil...'}
|
|
223
298
|
</Text>
|
|
224
|
-
|
|
225
|
-
|
|
299
|
+
|
|
300
|
+
{/* Progress Bar */}
|
|
301
|
+
<View style={styles.progressBarContainer}>
|
|
302
|
+
<View style={styles.progressBar}>
|
|
303
|
+
<Animated.View
|
|
304
|
+
style={[
|
|
305
|
+
styles.progressFill,
|
|
306
|
+
{
|
|
307
|
+
width: progressWidth,
|
|
308
|
+
backgroundColor: theme?.primaryColor || '#4f46e5',
|
|
309
|
+
},
|
|
310
|
+
]}
|
|
311
|
+
/>
|
|
312
|
+
</View>
|
|
313
|
+
<Text style={[styles.progressText, { color: theme?.textColor || '#1e1b4b' }]}>
|
|
314
|
+
{displayProgress}%
|
|
315
|
+
</Text>
|
|
316
|
+
</View>
|
|
317
|
+
|
|
318
|
+
<Text style={[styles.progressBottomText, { color: theme?.secondaryTextColor || '#64748b' }]}>
|
|
319
|
+
{strings.validation.almostDone || strings.common.loading || 'Esto puede tardar unos segundos...'}
|
|
226
320
|
</Text>
|
|
227
321
|
</View>
|
|
228
322
|
</SafeAreaView>
|
|
@@ -260,6 +354,7 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
|
|
|
260
354
|
const styles = StyleSheet.create({
|
|
261
355
|
container: {
|
|
262
356
|
flex: 1,
|
|
357
|
+
zIndex: 9999,
|
|
263
358
|
},
|
|
264
359
|
loadingContainer: {
|
|
265
360
|
flex: 1,
|
|
@@ -276,6 +371,52 @@ const styles = StyleSheet.create({
|
|
|
276
371
|
fontSize: 14,
|
|
277
372
|
marginTop: 8,
|
|
278
373
|
},
|
|
374
|
+
progressContainer: {
|
|
375
|
+
flex: 1,
|
|
376
|
+
justifyContent: 'center',
|
|
377
|
+
alignItems: 'center',
|
|
378
|
+
padding: 24,
|
|
379
|
+
width: '85%',
|
|
380
|
+
alignSelf: 'center',
|
|
381
|
+
},
|
|
382
|
+
progressTitle: {
|
|
383
|
+
fontSize: 24,
|
|
384
|
+
fontWeight: 'bold',
|
|
385
|
+
marginTop: 24,
|
|
386
|
+
marginBottom: 8,
|
|
387
|
+
textAlign: 'center',
|
|
388
|
+
},
|
|
389
|
+
progressStatusText: {
|
|
390
|
+
fontSize: 16,
|
|
391
|
+
marginBottom: 32,
|
|
392
|
+
textAlign: 'center',
|
|
393
|
+
},
|
|
394
|
+
progressBarContainer: {
|
|
395
|
+
width: '100%',
|
|
396
|
+
marginBottom: 24,
|
|
397
|
+
},
|
|
398
|
+
progressBar: {
|
|
399
|
+
width: '100%',
|
|
400
|
+
height: 8,
|
|
401
|
+
backgroundColor: '#E5E7EB',
|
|
402
|
+
borderRadius: 4,
|
|
403
|
+
overflow: 'hidden',
|
|
404
|
+
marginBottom: 8,
|
|
405
|
+
},
|
|
406
|
+
progressFill: {
|
|
407
|
+
height: '100%',
|
|
408
|
+
borderRadius: 4,
|
|
409
|
+
},
|
|
410
|
+
progressText: {
|
|
411
|
+
fontSize: 14,
|
|
412
|
+
fontWeight: '600',
|
|
413
|
+
textAlign: 'center',
|
|
414
|
+
},
|
|
415
|
+
progressBottomText: {
|
|
416
|
+
fontSize: 14,
|
|
417
|
+
textAlign: 'center',
|
|
418
|
+
marginTop: 16,
|
|
419
|
+
},
|
|
279
420
|
});
|
|
280
421
|
|
|
281
422
|
export default ProfilePictureCapture;
|