react-native-rectangle-doc-scanner 15.0.0 → 15.2.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.
@@ -54,7 +54,7 @@ class DocumentScannerModule(reactContext: ReactApplicationContext) :
54
54
  .setScannerMode(GmsDocumentScannerOptions.SCANNER_MODE_FULL)
55
55
  .setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG)
56
56
  .setPageLimit(pageLimit.coerceAtMost(2))
57
- .setGalleryImportAllowed(false)
57
+ .setGalleryImportAllowed(true)
58
58
  .build()
59
59
 
60
60
  val scanner = GmsDocumentScanning.getClient(scannerOptions)
@@ -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);
@@ -149,6 +149,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
149
149
  const [scannerSession, setScannerSession] = (0, react_1.useState)(0);
150
150
  const [cropEditorDocument, setCropEditorDocument] = (0, react_1.useState)(null);
151
151
  const [cropEditorRectangle, setCropEditorRectangle] = (0, react_1.useState)(null);
152
+ const [androidScanAutoRequested, setAndroidScanAutoRequested] = (0, react_1.useState)(false);
152
153
  const resolvedGridColor = gridColor ?? overlayColor;
153
154
  const docScannerRef = (0, react_1.useRef)(null);
154
155
  const captureModeRef = (0, react_1.useRef)(null);
@@ -165,6 +166,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
165
166
  setCroppedImageData(null);
166
167
  setCropEditorDocument(null);
167
168
  setCropEditorRectangle(null);
169
+ setAndroidScanAutoRequested(false);
168
170
  setRotationDegrees(0);
169
171
  setRectangleDetected(false);
170
172
  setRectangleHint(false);
@@ -205,7 +207,12 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
205
207
  secondPrompt: strings?.secondPrompt ?? strings?.secondBtn ?? 'Capture Back Side?',
206
208
  originalBtn: strings?.originalBtn ?? 'Use Original',
207
209
  }), [strings]);
208
- 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]);
209
216
  const ensureBase64ForImage = (0, react_1.useCallback)(async (image) => {
210
217
  if (image.base64) {
211
218
  return image;
@@ -418,6 +425,18 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
418
425
  console.warn('[FullDocScanner] Missing capture mode for capture result, ignoring');
419
426
  return;
420
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
+ }
421
440
  const normalizedDoc = normalizeCapturedDocument(document);
422
441
  const shouldOpenAndroidCropEditor = isAndroidCropEditorAvailable &&
423
442
  captureMode === 'grid' &&
@@ -457,10 +476,12 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
457
476
  }, [
458
477
  emitError,
459
478
  isAndroidCropEditorAvailable,
479
+ onResult,
460
480
  openAndroidCropEditor,
461
481
  openCropper,
462
482
  preparePreviewImage,
463
483
  resetScannerView,
484
+ usesAndroidScannerActivity,
464
485
  ]);
465
486
  const triggerManualCapture = (0, react_1.useCallback)(() => {
466
487
  const scannerInstance = docScannerRef.current;
@@ -519,11 +540,25 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
519
540
  console.error('[FullDocScanner] Manual capture failed:', errorMessage, error);
520
541
  captureModeRef.current = null;
521
542
  captureInProgressRef.current = false;
543
+ if (errorMessage.includes('SCAN_CANCELLED')) {
544
+ resetScannerView({ remount: true });
545
+ onClose?.();
546
+ return;
547
+ }
522
548
  if (error instanceof Error && error.message !== 'capture_in_progress') {
523
549
  emitError(error, 'Failed to capture image. Please try again.');
524
550
  }
525
551
  });
526
- }, [processing, rectangleDetected, rectangleHint, captureReady, emitError, usesAndroidScannerActivity]);
552
+ }, [
553
+ emitError,
554
+ onClose,
555
+ processing,
556
+ rectangleDetected,
557
+ rectangleHint,
558
+ captureReady,
559
+ resetScannerView,
560
+ usesAndroidScannerActivity,
561
+ ]);
527
562
  const handleGalleryPick = (0, react_1.useCallback)(async () => {
528
563
  console.log('[FullDocScanner] handleGalleryPick called');
529
564
  if (processing || isGalleryOpen) {
@@ -637,7 +672,18 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
637
672
  setCurrentPhotoIndex(0);
638
673
  }
639
674
  resetScannerView({ remount: true });
640
- }, [capturedPhotos.length, isBusinessMode, resetScannerView]);
675
+ if (usesAndroidScannerActivity) {
676
+ requestAnimationFrame(() => {
677
+ triggerManualCapture();
678
+ });
679
+ }
680
+ }, [
681
+ capturedPhotos.length,
682
+ isBusinessMode,
683
+ resetScannerView,
684
+ triggerManualCapture,
685
+ usesAndroidScannerActivity,
686
+ ]);
641
687
  const handleRectangleDetect = (0, react_1.useCallback)((event) => {
642
688
  const stableCounter = event.stableCounter ?? 0;
643
689
  const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
@@ -709,6 +755,23 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
709
755
  setCaptureReady(true);
710
756
  }
711
757
  }, [usesAndroidScannerActivity]);
758
+ (0, react_1.useEffect)(() => {
759
+ if (!usesAndroidScannerActivity) {
760
+ return;
761
+ }
762
+ if (androidScanAutoRequested || croppedImageData || cropEditorDocument || processing) {
763
+ return;
764
+ }
765
+ setAndroidScanAutoRequested(true);
766
+ triggerManualCapture();
767
+ }, [
768
+ androidScanAutoRequested,
769
+ cropEditorDocument,
770
+ croppedImageData,
771
+ processing,
772
+ triggerManualCapture,
773
+ usesAndroidScannerActivity,
774
+ ]);
712
775
  const activePreviewImage = croppedImageData ? getActivePreviewImage(croppedImageData) : null;
713
776
  return (react_1.default.createElement(react_native_1.View, { style: styles.container },
714
777
  react_native_1.Platform.OS === 'android' && (react_1.default.createElement(react_native_1.StatusBar, { translucent: true, backgroundColor: "transparent" })),
@@ -763,7 +826,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
763
826
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.retakeButton], onPress: handleCropEditorCancel, accessibilityLabel: mergedStrings.retake, accessibilityRole: "button", disabled: processing },
764
827
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
765
828
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleCropEditorConfirm, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button", disabled: processing },
766
- react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
829
+ 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 },
767
830
  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 },
768
831
  react_1.default.createElement(react_native_1.View, { style: styles.overlayTop, pointerEvents: "box-none" },
769
832
  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.0.0",
3
+ "version": "15.2.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
 
@@ -220,6 +220,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
220
220
  const [scannerSession, setScannerSession] = useState(0);
221
221
  const [cropEditorDocument, setCropEditorDocument] = useState<CapturedDocument | null>(null);
222
222
  const [cropEditorRectangle, setCropEditorRectangle] = useState<Rectangle | null>(null);
223
+ const [androidScanAutoRequested, setAndroidScanAutoRequested] = useState(false);
223
224
  const resolvedGridColor = gridColor ?? overlayColor;
224
225
  const docScannerRef = useRef<DocScannerHandle | null>(null);
225
226
  const captureModeRef = useRef<'grid' | 'no-grid' | null>(null);
@@ -240,6 +241,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
240
241
  setCroppedImageData(null);
241
242
  setCropEditorDocument(null);
242
243
  setCropEditorRectangle(null);
244
+ setAndroidScanAutoRequested(false);
243
245
  setRotationDegrees(0);
244
246
  setRectangleDetected(false);
245
247
  setRectangleHint(false);
@@ -291,10 +293,12 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
291
293
  [strings],
292
294
  );
293
295
 
294
- const autoEnhancementEnabled = useMemo(
295
- () => typeof pdfScannerManager?.applyColorControls === 'function',
296
- [pdfScannerManager],
297
- );
296
+ const autoEnhancementEnabled = useMemo(() => {
297
+ if (usesAndroidScannerActivity) {
298
+ return false;
299
+ }
300
+ return typeof pdfScannerManager?.applyColorControls === 'function';
301
+ }, [pdfScannerManager, usesAndroidScannerActivity]);
298
302
 
299
303
  const ensureBase64ForImage = useCallback(
300
304
  async (image: PreviewImageInfo): Promise<PreviewImageInfo> => {
@@ -578,6 +582,19 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
578
582
  return;
579
583
  }
580
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
+
581
598
  const normalizedDoc = normalizeCapturedDocument(document);
582
599
 
583
600
  const shouldOpenAndroidCropEditor =
@@ -628,10 +645,12 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
628
645
  [
629
646
  emitError,
630
647
  isAndroidCropEditorAvailable,
648
+ onResult,
631
649
  openAndroidCropEditor,
632
650
  openCropper,
633
651
  preparePreviewImage,
634
652
  resetScannerView,
653
+ usesAndroidScannerActivity,
635
654
  ],
636
655
  );
637
656
 
@@ -704,6 +723,12 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
704
723
  captureModeRef.current = null;
705
724
  captureInProgressRef.current = false;
706
725
 
726
+ if (errorMessage.includes('SCAN_CANCELLED')) {
727
+ resetScannerView({ remount: true });
728
+ onClose?.();
729
+ return;
730
+ }
731
+
707
732
  if (error instanceof Error && error.message !== 'capture_in_progress') {
708
733
  emitError(
709
734
  error,
@@ -711,7 +736,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
711
736
  );
712
737
  }
713
738
  });
714
- }, [processing, rectangleDetected, rectangleHint, captureReady, emitError, usesAndroidScannerActivity]);
739
+ }, [
740
+ emitError,
741
+ onClose,
742
+ processing,
743
+ rectangleDetected,
744
+ rectangleHint,
745
+ captureReady,
746
+ resetScannerView,
747
+ usesAndroidScannerActivity,
748
+ ]);
715
749
 
716
750
  const handleGalleryPick = useCallback(async () => {
717
751
  console.log('[FullDocScanner] handleGalleryPick called');
@@ -857,7 +891,19 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
857
891
  }
858
892
 
859
893
  resetScannerView({ remount: true });
860
- }, [capturedPhotos.length, isBusinessMode, resetScannerView]);
894
+
895
+ if (usesAndroidScannerActivity) {
896
+ requestAnimationFrame(() => {
897
+ triggerManualCapture();
898
+ });
899
+ }
900
+ }, [
901
+ capturedPhotos.length,
902
+ isBusinessMode,
903
+ resetScannerView,
904
+ triggerManualCapture,
905
+ usesAndroidScannerActivity,
906
+ ]);
861
907
 
862
908
  const handleRectangleDetect = useCallback((event: RectangleDetectEvent) => {
863
909
  const stableCounter = event.stableCounter ?? 0;
@@ -941,6 +987,26 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
941
987
  }
942
988
  }, [usesAndroidScannerActivity]);
943
989
 
990
+ useEffect(() => {
991
+ if (!usesAndroidScannerActivity) {
992
+ return;
993
+ }
994
+
995
+ if (androidScanAutoRequested || croppedImageData || cropEditorDocument || processing) {
996
+ return;
997
+ }
998
+
999
+ setAndroidScanAutoRequested(true);
1000
+ triggerManualCapture();
1001
+ }, [
1002
+ androidScanAutoRequested,
1003
+ cropEditorDocument,
1004
+ croppedImageData,
1005
+ processing,
1006
+ triggerManualCapture,
1007
+ usesAndroidScannerActivity,
1008
+ ]);
1009
+
944
1010
  const activePreviewImage = croppedImageData ? getActivePreviewImage(croppedImageData) : null;
945
1011
 
946
1012
  return (
@@ -1116,6 +1182,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
1116
1182
  </TouchableOpacity>
1117
1183
  </View>
1118
1184
  </View>
1185
+ ) : usesAndroidScannerActivity ? (
1186
+ <View style={styles.flex} />
1119
1187
  ) : (
1120
1188
  <View style={styles.flex}>
1121
1189
  <DocScanner