@switchlabs/verify-ai-react-native 2.4.5 → 2.4.8

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.
@@ -93,6 +93,24 @@ export function VerifyAIScanner({ onCapture, onResult, onError, overlay, style,
93
93
  const { width: windowWidth, height: windowHeight } = useWindowDimensions();
94
94
  const isLandscape = windowWidth > windowHeight;
95
95
  const prevDimensionsRef = useRef({ width: windowWidth, height: windowHeight });
96
+ // Track physical device orientation independently of interface orientation.
97
+ // When the host app is orientation-locked, window dimensions don't change
98
+ // on rotation — this fires via expo-camera's responsive-orientation callback
99
+ // so we can rotate the overlay UI to stay readable from the user's viewpoint.
100
+ const [physicalOrientation, setPhysicalOrientation] = useState('portrait');
101
+ const overlayRotationDeg = (() => {
102
+ switch (physicalOrientation) {
103
+ case 'landscapeLeft':
104
+ return 90;
105
+ case 'landscapeRight':
106
+ return -90;
107
+ case 'portraitUpsideDown':
108
+ return 180;
109
+ case 'portrait':
110
+ default:
111
+ return 0;
112
+ }
113
+ })();
96
114
  // Detect orientation changes and remount camera after rotation settles.
97
115
  // On iOS, AVCaptureVideoPreviewLayer distorts if remounted during the rotation
98
116
  // animation — the native preview layer initializes with transitional bounds.
@@ -369,13 +387,21 @@ export function VerifyAIScanner({ onCapture, onResult, onError, overlay, style,
369
387
  return (_jsxs(View, { style: [styles.container, styles.permissionContainer, style], children: [_jsx(Text, { style: styles.permissionText, children: "Camera access is required for photo verification" }), _jsx(TouchableOpacity, { style: styles.permissionButton, onPress: requestPermission, children: _jsx(Text, { style: styles.permissionButtonText, children: "Grant Camera Access" }) })] }));
370
388
  }
371
389
  const showBottomCard = status === 'success' || status === 'error';
372
- return (_jsx(View, { style: [styles.container, style], children: _jsx(CameraView, { ref: cameraRef, style: styles.camera, facing: "back", enableTorch: !terminated && enableTorch, onCameraReady: onCameraReady, onMountError: onMountError, children: _jsxs(View, { style: styles.overlay, children: [overlay?.title && (_jsx(View, { style: [styles.topBar, isLandscape && styles.topBarLandscape], children: _jsx(Text, { style: styles.titleText, children: overlay.title }) })), overlay?.showGuideFrame && (_jsxs(View, { style: [styles.guideContainer, isLandscape && styles.guideContainerLandscape], children: [_jsxs(View, { style: [
390
+ return (_jsx(View, { style: [styles.container, style], children: _jsx(CameraView, { ref: cameraRef, style: styles.camera, facing: "back", enableTorch: !terminated && enableTorch, onCameraReady: onCameraReady, onMountError: onMountError, responsiveOrientationWhenOrientationLocked: true, onResponsiveOrientationChanged: (event) => {
391
+ setPhysicalOrientation(event.orientation);
392
+ }, children: _jsxs(View, { style: styles.overlay, children: [overlay?.title && (_jsx(View, { style: [styles.topBar, isLandscape && styles.topBarLandscape], children: _jsx(Text, { style: [
393
+ styles.titleText,
394
+ { transform: [{ rotate: `${overlayRotationDeg}deg` }] },
395
+ ], children: overlay.title }) })), overlay?.showGuideFrame && (_jsxs(View, { style: [styles.guideContainer, isLandscape && styles.guideContainerLandscape], children: [_jsxs(View, { style: [
373
396
  styles.guideFrame,
374
397
  isLandscape && styles.guideFrameLandscape,
375
398
  overlay.guideFrameAspectRatio
376
399
  ? { aspectRatio: overlay.guideFrameAspectRatio }
377
400
  : undefined,
378
- ], children: [overlay.guideOverlayContent && (_jsx(View, { style: [StyleSheet.absoluteFill, { opacity: overlay.guideOverlayOpacity ?? 0.3 }], children: overlay.guideOverlayContent })), _jsx(View, { style: [styles.corner, styles.cornerTopLeft, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined] }), _jsx(View, { style: [styles.corner, styles.cornerTopRight, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined] }), _jsx(View, { style: [styles.corner, styles.cornerBottomLeft, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined] }), _jsx(View, { style: [styles.corner, styles.cornerBottomRight, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined] })] }), overlay.guideCaption && (_jsx(Text, { style: styles.guideCaptionText, children: overlay.guideCaption }))] })), status === 'processing' && (_jsxs(View, { style: styles.processingOverlay, children: [_jsx(ActivityIndicator, { size: "large", color: "#fff" }), _jsx(Text, { style: styles.statusText, children: overlay?.processingMessage || 'Analyzing photo...' })] })), showBottomCard && _jsx(View, { style: styles.cardBackdrop }), _jsxs(View, { style: [styles.bottomArea, isLandscape && styles.bottomAreaLandscape], children: [status === 'success' && result && (_jsxs(View, { style: styles.resultCard, children: [_jsxs(View, { style: styles.resultCardHeader, children: [_jsx(View, { style: [
401
+ ], children: [overlay.guideOverlayContent && (_jsx(View, { style: [StyleSheet.absoluteFill, { opacity: overlay.guideOverlayOpacity ?? 0.3 }], children: overlay.guideOverlayContent })), _jsx(View, { style: [styles.corner, styles.cornerTopLeft, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined] }), _jsx(View, { style: [styles.corner, styles.cornerTopRight, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined] }), _jsx(View, { style: [styles.corner, styles.cornerBottomLeft, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined] }), _jsx(View, { style: [styles.corner, styles.cornerBottomRight, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined] })] }), overlay.guideCaption && (_jsx(Text, { style: [
402
+ styles.guideCaptionText,
403
+ { transform: [{ rotate: `${overlayRotationDeg}deg` }] },
404
+ ], children: overlay.guideCaption }))] })), status === 'processing' && (_jsxs(View, { style: styles.processingOverlay, children: [_jsx(ActivityIndicator, { size: "large", color: "#fff" }), _jsx(Text, { style: styles.statusText, children: overlay?.processingMessage || 'Analyzing photo...' })] })), showBottomCard && _jsx(View, { style: styles.cardBackdrop }), _jsxs(View, { style: [styles.bottomArea, isLandscape && styles.bottomAreaLandscape], children: [status === 'success' && result && (_jsxs(View, { style: styles.resultCard, children: [_jsxs(View, { style: styles.resultCardHeader, children: [_jsx(View, { style: [
379
405
  styles.resultIconCircle,
380
406
  exhausted
381
407
  ? styles.resultIconExhausted
@@ -419,7 +445,10 @@ export function VerifyAIScanner({ onCapture, onResult, onError, overlay, style,
419
445
  errorMessage = display.message;
420
446
  }
421
447
  return (_jsxs(View, { style: styles.resultCard, children: [_jsxs(View, { style: styles.resultCardHeader, children: [_jsx(View, { style: [styles.resultIconCircle, styles.resultIconError, overlay?.theme?.failureColor ? { backgroundColor: overlay.theme.failureColor } : undefined], children: _jsx(Text, { style: styles.resultIcon, children: "!" }) }), _jsx(Text, { style: [styles.resultLabel, styles.resultLabelError, overlay?.theme?.failureColor ? { color: overlay.theme.failureColor } : undefined], children: errorTitle })] }), _jsx(Text, { style: styles.feedbackText, children: errorMessage })] }));
422
- })(), !showBottomCard && (_jsxs(_Fragment, { children: [overlay?.instructions && status === 'idle' && (_jsx(Text, { style: styles.instructionsText, children: overlay.instructions })), showCaptureButton && (_jsx(View, { style: styles.captureButtonRow, children: _jsx(TouchableOpacity, { style: [
448
+ })(), !showBottomCard && (_jsxs(_Fragment, { children: [overlay?.instructions && status === 'idle' && (_jsx(Text, { style: [
449
+ styles.instructionsText,
450
+ { transform: [{ rotate: `${overlayRotationDeg}deg` }] },
451
+ ], children: overlay.instructions })), showCaptureButton && (_jsx(View, { style: styles.captureButtonRow, children: _jsx(TouchableOpacity, { style: [
423
452
  styles.captureButton,
424
453
  overlay?.theme?.captureButtonColor ? { borderColor: overlay.theme.captureButtonColor } : undefined,
425
454
  (!cameraReady || status === 'capturing' || status === 'processing') &&
package/lib/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.4.5";
1
+ export declare const SDK_VERSION = "2.4.8";
package/lib/version.js CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '2.4.5';
1
+ export const SDK_VERSION = '2.4.8';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@switchlabs/verify-ai-react-native",
3
- "version": "2.4.5",
3
+ "version": "2.4.8",
4
4
  "description": "React Native SDK for Verify AI - photo verification with AI vision processing",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -159,6 +159,28 @@ export function VerifyAIScanner({
159
159
  const isLandscape = windowWidth > windowHeight;
160
160
  const prevDimensionsRef = useRef({ width: windowWidth, height: windowHeight });
161
161
 
162
+ // Track physical device orientation independently of interface orientation.
163
+ // When the host app is orientation-locked, window dimensions don't change
164
+ // on rotation — this fires via expo-camera's responsive-orientation callback
165
+ // so we can rotate the overlay UI to stay readable from the user's viewpoint.
166
+ const [physicalOrientation, setPhysicalOrientation] = useState<
167
+ 'portrait' | 'portraitUpsideDown' | 'landscapeLeft' | 'landscapeRight'
168
+ >('portrait');
169
+
170
+ const overlayRotationDeg = (() => {
171
+ switch (physicalOrientation) {
172
+ case 'landscapeLeft':
173
+ return 90;
174
+ case 'landscapeRight':
175
+ return -90;
176
+ case 'portraitUpsideDown':
177
+ return 180;
178
+ case 'portrait':
179
+ default:
180
+ return 0;
181
+ }
182
+ })();
183
+
162
184
  // Detect orientation changes and remount camera after rotation settles.
163
185
  // On iOS, AVCaptureVideoPreviewLayer distorts if remounted during the rotation
164
186
  // animation — the native preview layer initializes with transitional bounds.
@@ -486,12 +508,31 @@ export function VerifyAIScanner({
486
508
 
487
509
  return (
488
510
  <View style={[styles.container, style]}>
489
- <CameraView key={cameraKey} ref={cameraRef} style={styles.camera} facing="back" enableTorch={!terminated && enableTorch} onCameraReady={onCameraReady} onMountError={onMountError}>
511
+ <CameraView
512
+ key={cameraKey}
513
+ ref={cameraRef}
514
+ style={styles.camera}
515
+ facing="back"
516
+ enableTorch={!terminated && enableTorch}
517
+ onCameraReady={onCameraReady}
518
+ onMountError={onMountError}
519
+ responsiveOrientationWhenOrientationLocked
520
+ onResponsiveOrientationChanged={(event) => {
521
+ setPhysicalOrientation(event.orientation);
522
+ }}
523
+ >
490
524
  {/* Overlay */}
491
525
  <View style={styles.overlay}>
492
526
  {overlay?.title && (
493
527
  <View style={[styles.topBar, isLandscape && styles.topBarLandscape]}>
494
- <Text style={styles.titleText}>{overlay.title}</Text>
528
+ <Text
529
+ style={[
530
+ styles.titleText,
531
+ { transform: [{ rotate: `${overlayRotationDeg}deg` }] },
532
+ ]}
533
+ >
534
+ {overlay.title}
535
+ </Text>
495
536
  </View>
496
537
  )}
497
538
 
@@ -519,7 +560,14 @@ export function VerifyAIScanner({
519
560
  <View style={[styles.corner, styles.cornerBottomRight, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined]} />
520
561
  </View>
521
562
  {overlay.guideCaption && (
522
- <Text style={styles.guideCaptionText}>{overlay.guideCaption}</Text>
563
+ <Text
564
+ style={[
565
+ styles.guideCaptionText,
566
+ { transform: [{ rotate: `${overlayRotationDeg}deg` }] },
567
+ ]}
568
+ >
569
+ {overlay.guideCaption}
570
+ </Text>
523
571
  )}
524
572
  </View>
525
573
  )}
@@ -621,7 +669,14 @@ export function VerifyAIScanner({
621
669
  {!showBottomCard && (
622
670
  <>
623
671
  {overlay?.instructions && status === 'idle' && (
624
- <Text style={styles.instructionsText}>{overlay.instructions}</Text>
672
+ <Text
673
+ style={[
674
+ styles.instructionsText,
675
+ { transform: [{ rotate: `${overlayRotationDeg}deg` }] },
676
+ ]}
677
+ >
678
+ {overlay.instructions}
679
+ </Text>
625
680
  )}
626
681
  {showCaptureButton && (
627
682
  <View style={styles.captureButtonRow}>
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '2.4.5';
1
+ export const SDK_VERSION = '2.4.8';