@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.
@@ -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;AA8NF,eAAe,qBAAqB,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"}
@@ -419,6 +419,7 @@ const createStyles = (theme) => {
419
419
  container: {
420
420
  flex: 1,
421
421
  backgroundColor,
422
+ zIndex: 9999,
422
423
  },
423
424
  content: {
424
425
  flex: 1,
@@ -1 +1 @@
1
- {"version":3,"file":"ProfilePictureCapture.d.ts","sourceRoot":"","sources":["../../src/components/ProfilePictureCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AASxE,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,CAqOtE,CAAC;AAuBF,eAAe,qBAAqB,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.loadingContainer },
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.loadingText, { color: theme?.textColor || '#1e1b4b' }] }, strings.liveness.processing || strings.validation.checkingLiveness || 'Processing...'),
204
- react_1.default.createElement(react_native_1.Text, { style: [styles.loadingSubtext, { color: theme?.secondaryTextColor || '#64748b' }] }, strings.validation.almostDone || strings.common.loading || 'This may take a few seconds'))));
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;AA4OF,eAAe,aAAa,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"}
@@ -787,6 +787,7 @@ const styles = react_native_1.StyleSheet.create({
787
787
  container: {
788
788
  flex: 1,
789
789
  backgroundColor: '#000000',
790
+ zIndex: 9999,
790
791
  },
791
792
  cameraContainer: {
792
793
  flex: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -632,6 +632,7 @@ const createStyles = (theme?: ThemeConfig) => {
632
632
  container: {
633
633
  flex: 1,
634
634
  backgroundColor,
635
+ zIndex: 9999,
635
636
  },
636
637
  content: {
637
638
  flex: 1,
@@ -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.loadingContainer}>
289
+ <View style={styles.progressContainer}>
220
290
  <ActivityIndicator size="large" color={theme?.primaryColor || '#4f46e5'} />
221
- <Text style={[styles.loadingText, { color: theme?.textColor || '#1e1b4b' }]}>
222
- {strings.liveness.processing || strings.validation.checkingLiveness || 'Processing...'}
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
- <Text style={[styles.loadingSubtext, { color: theme?.secondaryTextColor || '#64748b' }]}>
225
- {strings.validation.almostDone || strings.common.loading || 'This may take a few seconds'}
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;
@@ -1033,6 +1033,7 @@ const styles = StyleSheet.create({
1033
1033
  container: {
1034
1034
  flex: 1,
1035
1035
  backgroundColor: '#000000',
1036
+ zIndex: 9999,
1036
1037
  },
1037
1038
  cameraContainer: {
1038
1039
  flex: 1,