react-native-rectangle-doc-scanner 15.1.0 → 15.3.0

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.
@@ -33,6 +33,11 @@ export type DocScannerCapture = {
33
33
  width: number;
34
34
  height: number;
35
35
  origin: 'auto' | 'manual';
36
+ pages?: Array<{
37
+ path: string;
38
+ width: number;
39
+ height: number;
40
+ }> | null;
36
41
  };
37
42
  export interface DetectionConfig {
38
43
  processingWidth?: number;
@@ -121,6 +121,7 @@ const ActivityScanner = (0, react_1.forwardRef)(({ onCapture, children, showManu
121
121
  width: event.width ?? 0,
122
122
  height: event.height ?? 0,
123
123
  origin,
124
+ pages: event.pages ?? null,
124
125
  });
125
126
  }
126
127
  if (captureResolvers.current) {
@@ -321,6 +322,7 @@ const VisionCameraScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor =
321
322
  width: event.width ?? 0,
322
323
  height: event.height ?? 0,
323
324
  origin,
325
+ pages: event.pages ?? null,
324
326
  });
325
327
  }
326
328
  setDetectedRectangle(null);
@@ -207,7 +207,12 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
207
207
  secondPrompt: strings?.secondPrompt ?? strings?.secondBtn ?? 'Capture Back Side?',
208
208
  originalBtn: strings?.originalBtn ?? 'Use Original',
209
209
  }), [strings]);
210
- const autoEnhancementEnabled = (0, react_1.useMemo)(() => typeof pdfScannerManager?.applyColorControls === 'function', [pdfScannerManager]);
210
+ const autoEnhancementEnabled = (0, react_1.useMemo)(() => {
211
+ if (usesAndroidScannerActivity) {
212
+ return false;
213
+ }
214
+ return typeof pdfScannerManager?.applyColorControls === 'function';
215
+ }, [pdfScannerManager, usesAndroidScannerActivity]);
211
216
  const ensureBase64ForImage = (0, react_1.useCallback)(async (image) => {
212
217
  if (image.base64) {
213
218
  return image;
@@ -420,6 +425,18 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
420
425
  console.warn('[FullDocScanner] Missing capture mode for capture result, ignoring');
421
426
  return;
422
427
  }
428
+ if (usesAndroidScannerActivity) {
429
+ const pages = document.pages?.length
430
+ ? document.pages
431
+ : [{ path: document.path, width: document.width, height: document.height }];
432
+ const results = pages.map((page) => ({
433
+ path: stripFileUri(page.path),
434
+ rotationDegrees: 0,
435
+ }));
436
+ onResult(results);
437
+ resetScannerView({ remount: true });
438
+ return;
439
+ }
423
440
  const normalizedDoc = normalizeCapturedDocument(document);
424
441
  const shouldOpenAndroidCropEditor = isAndroidCropEditorAvailable &&
425
442
  captureMode === 'grid' &&
@@ -459,11 +476,42 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
459
476
  }, [
460
477
  emitError,
461
478
  isAndroidCropEditorAvailable,
479
+ onResult,
462
480
  openAndroidCropEditor,
463
481
  openCropper,
464
482
  preparePreviewImage,
465
483
  resetScannerView,
484
+ usesAndroidScannerActivity,
466
485
  ]);
486
+ const startAndroidScan = (0, react_1.useCallback)(async () => {
487
+ if (!usesAndroidScannerActivity || !pdfScannerManager?.startDocumentScanner) {
488
+ throw new Error('document_scanner_not_available');
489
+ }
490
+ if (captureInProgressRef.current) {
491
+ throw new Error('capture_in_progress');
492
+ }
493
+ captureInProgressRef.current = true;
494
+ captureModeRef.current = 'grid';
495
+ try {
496
+ const payload = await pdfScannerManager.startDocumentScanner({ pageLimit: 2 });
497
+ const normalizedPath = stripFileUri(payload?.initialImage ?? payload?.croppedImage ?? '');
498
+ const capturePayload = {
499
+ path: normalizedPath,
500
+ initialPath: payload?.initialImage ? stripFileUri(payload.initialImage) : normalizedPath,
501
+ croppedPath: payload?.croppedImage ? stripFileUri(payload.croppedImage) : normalizedPath,
502
+ quad: null,
503
+ rectangle: null,
504
+ width: payload?.width ?? 0,
505
+ height: payload?.height ?? 0,
506
+ origin: 'manual',
507
+ pages: payload?.pages ?? null,
508
+ };
509
+ await handleCapture(capturePayload);
510
+ }
511
+ finally {
512
+ captureInProgressRef.current = false;
513
+ }
514
+ }, [handleCapture, pdfScannerManager, usesAndroidScannerActivity]);
467
515
  const triggerManualCapture = (0, react_1.useCallback)(() => {
468
516
  const scannerInstance = docScannerRef.current;
469
517
  const hasScanner = !!scannerInstance;
@@ -489,6 +537,19 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
489
537
  return;
490
538
  }
491
539
  if (!hasScanner) {
540
+ if (usesAndroidScannerActivity) {
541
+ startAndroidScan().catch((error) => {
542
+ const errorMessage = error instanceof Error ? error.message : String(error);
543
+ console.error('[FullDocScanner] Android scan failed:', errorMessage, error);
544
+ if (errorMessage.includes('SCAN_CANCELLED')) {
545
+ resetScannerView({ remount: true });
546
+ onClose?.();
547
+ return;
548
+ }
549
+ emitError(error instanceof Error ? error : new Error(String(error)), 'Failed to capture image. Please try again.');
550
+ });
551
+ return;
552
+ }
492
553
  console.error('[FullDocScanner] DocScanner ref not available');
493
554
  return;
494
555
  }
@@ -522,13 +583,24 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
522
583
  captureModeRef.current = null;
523
584
  captureInProgressRef.current = false;
524
585
  if (errorMessage.includes('SCAN_CANCELLED')) {
586
+ resetScannerView({ remount: true });
587
+ onClose?.();
525
588
  return;
526
589
  }
527
590
  if (error instanceof Error && error.message !== 'capture_in_progress') {
528
591
  emitError(error, 'Failed to capture image. Please try again.');
529
592
  }
530
593
  });
531
- }, [processing, rectangleDetected, rectangleHint, captureReady, emitError, usesAndroidScannerActivity]);
594
+ }, [
595
+ emitError,
596
+ onClose,
597
+ processing,
598
+ rectangleDetected,
599
+ rectangleHint,
600
+ captureReady,
601
+ resetScannerView,
602
+ usesAndroidScannerActivity,
603
+ ]);
532
604
  const handleGalleryPick = (0, react_1.useCallback)(async () => {
533
605
  console.log('[FullDocScanner] handleGalleryPick called');
534
606
  if (processing || isGalleryOpen) {
@@ -642,7 +714,18 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
642
714
  setCurrentPhotoIndex(0);
643
715
  }
644
716
  resetScannerView({ remount: true });
645
- }, [capturedPhotos.length, isBusinessMode, resetScannerView]);
717
+ if (usesAndroidScannerActivity) {
718
+ requestAnimationFrame(() => {
719
+ triggerManualCapture();
720
+ });
721
+ }
722
+ }, [
723
+ capturedPhotos.length,
724
+ isBusinessMode,
725
+ resetScannerView,
726
+ triggerManualCapture,
727
+ usesAndroidScannerActivity,
728
+ ]);
646
729
  const handleRectangleDetect = (0, react_1.useCallback)((event) => {
647
730
  const stableCounter = event.stableCounter ?? 0;
648
731
  const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
@@ -785,7 +868,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
785
868
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.retakeButton], onPress: handleCropEditorCancel, accessibilityLabel: mergedStrings.retake, accessibilityRole: "button", disabled: processing },
786
869
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
787
870
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleCropEditorConfirm, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button", disabled: processing },
788
- react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
871
+ react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm))))) : usesAndroidScannerActivity ? (react_1.default.createElement(react_native_1.View, { style: styles.flex })) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
789
872
  react_1.default.createElement(DocScanner_1.DocScanner, { key: scannerSession, ref: docScannerRef, autoCapture: false, overlayColor: overlayColor, showGrid: showGrid, gridColor: resolvedGridColor, gridLineWidth: gridLineWidth, minStableFrames: minStableFrames ?? 6, detectionConfig: detectionConfig, onCapture: handleCapture, onRectangleDetect: handleRectangleDetect, showManualCaptureButton: false, enableTorch: flashEnabled },
790
873
  react_1.default.createElement(react_native_1.View, { style: styles.overlayTop, pointerEvents: "box-none" },
791
874
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "15.1.0",
3
+ "version": "15.3.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -48,6 +48,7 @@ export type DocScannerCapture = {
48
48
  width: number;
49
49
  height: number;
50
50
  origin: 'auto' | 'manual';
51
+ pages?: Array<{ path: string; width: number; height: number }> | null;
51
52
  };
52
53
 
53
54
  const isFiniteNumber = (value: unknown): value is number =>
@@ -192,6 +193,7 @@ const ActivityScanner = forwardRef<DocScannerHandle, Props>(
192
193
  width: event.width ?? 0,
193
194
  height: event.height ?? 0,
194
195
  origin,
196
+ pages: event.pages ?? null,
195
197
  });
196
198
  }
197
199
 
@@ -480,6 +482,7 @@ const VisionCameraScanner = forwardRef<DocScannerHandle, Props>(
480
482
  width: event.width ?? 0,
481
483
  height: event.height ?? 0,
482
484
  origin,
485
+ pages: event.pages ?? null,
483
486
  });
484
487
  }
485
488
 
@@ -293,10 +293,12 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
293
293
  [strings],
294
294
  );
295
295
 
296
- const autoEnhancementEnabled = useMemo(
297
- () => typeof pdfScannerManager?.applyColorControls === 'function',
298
- [pdfScannerManager],
299
- );
296
+ const autoEnhancementEnabled = useMemo(() => {
297
+ if (usesAndroidScannerActivity) {
298
+ return false;
299
+ }
300
+ return typeof pdfScannerManager?.applyColorControls === 'function';
301
+ }, [pdfScannerManager, usesAndroidScannerActivity]);
300
302
 
301
303
  const ensureBase64ForImage = useCallback(
302
304
  async (image: PreviewImageInfo): Promise<PreviewImageInfo> => {
@@ -580,6 +582,19 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
580
582
  return;
581
583
  }
582
584
 
585
+ if (usesAndroidScannerActivity) {
586
+ const pages = document.pages?.length
587
+ ? document.pages
588
+ : [{ path: document.path, width: document.width, height: document.height }];
589
+ const results: FullDocScannerResult[] = pages.map((page) => ({
590
+ path: stripFileUri(page.path),
591
+ rotationDegrees: 0,
592
+ }));
593
+ onResult(results);
594
+ resetScannerView({ remount: true });
595
+ return;
596
+ }
597
+
583
598
  const normalizedDoc = normalizeCapturedDocument(document);
584
599
 
585
600
  const shouldOpenAndroidCropEditor =
@@ -630,13 +645,49 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
630
645
  [
631
646
  emitError,
632
647
  isAndroidCropEditorAvailable,
648
+ onResult,
633
649
  openAndroidCropEditor,
634
650
  openCropper,
635
651
  preparePreviewImage,
636
652
  resetScannerView,
653
+ usesAndroidScannerActivity,
637
654
  ],
638
655
  );
639
656
 
657
+ const startAndroidScan = useCallback(async () => {
658
+ if (!usesAndroidScannerActivity || !pdfScannerManager?.startDocumentScanner) {
659
+ throw new Error('document_scanner_not_available');
660
+ }
661
+
662
+ if (captureInProgressRef.current) {
663
+ throw new Error('capture_in_progress');
664
+ }
665
+
666
+ captureInProgressRef.current = true;
667
+ captureModeRef.current = 'grid';
668
+
669
+ try {
670
+ const payload = await pdfScannerManager.startDocumentScanner({ pageLimit: 2 });
671
+ const normalizedPath = stripFileUri(payload?.initialImage ?? payload?.croppedImage ?? '');
672
+
673
+ const capturePayload: DocScannerCapture = {
674
+ path: normalizedPath,
675
+ initialPath: payload?.initialImage ? stripFileUri(payload.initialImage) : normalizedPath,
676
+ croppedPath: payload?.croppedImage ? stripFileUri(payload.croppedImage) : normalizedPath,
677
+ quad: null,
678
+ rectangle: null,
679
+ width: payload?.width ?? 0,
680
+ height: payload?.height ?? 0,
681
+ origin: 'manual',
682
+ pages: payload?.pages ?? null,
683
+ };
684
+
685
+ await handleCapture(capturePayload);
686
+ } finally {
687
+ captureInProgressRef.current = false;
688
+ }
689
+ }, [handleCapture, pdfScannerManager, usesAndroidScannerActivity]);
690
+
640
691
  const triggerManualCapture = useCallback(() => {
641
692
  const scannerInstance = docScannerRef.current;
642
693
  const hasScanner = !!scannerInstance;
@@ -666,6 +717,22 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
666
717
  }
667
718
 
668
719
  if (!hasScanner) {
720
+ if (usesAndroidScannerActivity) {
721
+ startAndroidScan().catch((error: unknown) => {
722
+ const errorMessage = error instanceof Error ? error.message : String(error);
723
+ console.error('[FullDocScanner] Android scan failed:', errorMessage, error);
724
+ if (errorMessage.includes('SCAN_CANCELLED')) {
725
+ resetScannerView({ remount: true });
726
+ onClose?.();
727
+ return;
728
+ }
729
+ emitError(
730
+ error instanceof Error ? error : new Error(String(error)),
731
+ 'Failed to capture image. Please try again.',
732
+ );
733
+ });
734
+ return;
735
+ }
669
736
  console.error('[FullDocScanner] DocScanner ref not available');
670
737
  return;
671
738
  }
@@ -707,6 +774,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
707
774
  captureInProgressRef.current = false;
708
775
 
709
776
  if (errorMessage.includes('SCAN_CANCELLED')) {
777
+ resetScannerView({ remount: true });
778
+ onClose?.();
710
779
  return;
711
780
  }
712
781
 
@@ -717,7 +786,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
717
786
  );
718
787
  }
719
788
  });
720
- }, [processing, rectangleDetected, rectangleHint, captureReady, emitError, usesAndroidScannerActivity]);
789
+ }, [
790
+ emitError,
791
+ onClose,
792
+ processing,
793
+ rectangleDetected,
794
+ rectangleHint,
795
+ captureReady,
796
+ resetScannerView,
797
+ usesAndroidScannerActivity,
798
+ ]);
721
799
 
722
800
  const handleGalleryPick = useCallback(async () => {
723
801
  console.log('[FullDocScanner] handleGalleryPick called');
@@ -863,7 +941,19 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
863
941
  }
864
942
 
865
943
  resetScannerView({ remount: true });
866
- }, [capturedPhotos.length, isBusinessMode, resetScannerView]);
944
+
945
+ if (usesAndroidScannerActivity) {
946
+ requestAnimationFrame(() => {
947
+ triggerManualCapture();
948
+ });
949
+ }
950
+ }, [
951
+ capturedPhotos.length,
952
+ isBusinessMode,
953
+ resetScannerView,
954
+ triggerManualCapture,
955
+ usesAndroidScannerActivity,
956
+ ]);
867
957
 
868
958
  const handleRectangleDetect = useCallback((event: RectangleDetectEvent) => {
869
959
  const stableCounter = event.stableCounter ?? 0;
@@ -1142,6 +1232,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
1142
1232
  </TouchableOpacity>
1143
1233
  </View>
1144
1234
  </View>
1235
+ ) : usesAndroidScannerActivity ? (
1236
+ <View style={styles.flex} />
1145
1237
  ) : (
1146
1238
  <View style={styles.flex}>
1147
1239
  <DocScanner