@hexar/biometric-identity-sdk-react-native 1.1.25 → 1.1.27

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":"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;AAwBF,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.validatingFace || strings.liveness.processing || 'Validando rostro...'),
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)) {
@@ -233,5 +308,51 @@ const styles = react_native_1.StyleSheet.create({
233
308
  fontSize: 14,
234
309
  marginTop: 8,
235
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
+ },
236
357
  });
237
358
  exports.default = exports.ProfilePictureCapture;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.1.25",
3
+ "version": "1.1.27",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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 as any).validatingFace || strings.liveness.processing || 'Validando rostro...'}
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>
@@ -277,6 +371,52 @@ const styles = StyleSheet.create({
277
371
  fontSize: 14,
278
372
  marginTop: 8,
279
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
+ },
280
420
  });
281
421
 
282
422
  export default ProfilePictureCapture;