@switchlabs/verify-ai-react-native 2.4.7 → 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, responsiveOrientationWhenOrientationLocked: true, 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.7";
1
+ export declare const SDK_VERSION = "2.4.8";
package/lib/version.js CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '2.4.7';
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.7",
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.
@@ -495,12 +517,22 @@ export function VerifyAIScanner({
495
517
  onCameraReady={onCameraReady}
496
518
  onMountError={onMountError}
497
519
  responsiveOrientationWhenOrientationLocked
520
+ onResponsiveOrientationChanged={(event) => {
521
+ setPhysicalOrientation(event.orientation);
522
+ }}
498
523
  >
499
524
  {/* Overlay */}
500
525
  <View style={styles.overlay}>
501
526
  {overlay?.title && (
502
527
  <View style={[styles.topBar, isLandscape && styles.topBarLandscape]}>
503
- <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>
504
536
  </View>
505
537
  )}
506
538
 
@@ -528,7 +560,14 @@ export function VerifyAIScanner({
528
560
  <View style={[styles.corner, styles.cornerBottomRight, overlay.theme?.cornerColor ? { borderColor: overlay.theme.cornerColor } : undefined]} />
529
561
  </View>
530
562
  {overlay.guideCaption && (
531
- <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>
532
571
  )}
533
572
  </View>
534
573
  )}
@@ -630,7 +669,14 @@ export function VerifyAIScanner({
630
669
  {!showBottomCard && (
631
670
  <>
632
671
  {overlay?.instructions && status === 'idle' && (
633
- <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>
634
680
  )}
635
681
  {showCaptureButton && (
636
682
  <View style={styles.captureButtonRow}>
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '2.4.7';
1
+ export const SDK_VERSION = '2.4.8';