react-native-rectangle-doc-scanner 3.117.0 → 3.119.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.
@@ -132,7 +132,6 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
132
132
  const rectangleCaptureTimeoutRef = (0, react_1.useRef)(null);
133
133
  const rectangleHintTimeoutRef = (0, react_1.useRef)(null);
134
134
  const isBusinessMode = type === 'business';
135
- const maxPhotos = isBusinessMode ? 2 : 1;
136
135
  const mergedStrings = (0, react_1.useMemo)(() => ({
137
136
  captureHint: strings?.captureHint,
138
137
  manualHint: strings?.manualHint,
@@ -208,6 +207,10 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
208
207
  errorMessage.includes('cancelled') ||
209
208
  errorMessage.includes('cancel')) {
210
209
  console.log('[FullDocScanner] User cancelled cropper');
210
+ // DocScanner 상태를 리셋하여 카메라가 다시 작동하도록 함
211
+ if (docScannerRef.current?.reset) {
212
+ docScannerRef.current.reset();
213
+ }
211
214
  }
212
215
  else {
213
216
  emitError(error instanceof Error ? error : new Error(errorMessage), 'Failed to crop image. Please try again.');
@@ -392,9 +395,9 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
392
395
  rotationDegrees: rotationNormalized,
393
396
  };
394
397
  const updatedPhotos = [...capturedPhotos, currentPhoto];
395
- console.log('[FullDocScanner] Photos captured:', updatedPhotos.length, 'of', maxPhotos);
396
- // 모든 사진 촬영 완료 - 결과 반환
397
- console.log('[FullDocScanner] All photos captured, returning results');
398
+ console.log('[FullDocScanner] Photos captured:', updatedPhotos.length);
399
+ // 결과 반환
400
+ console.log('[FullDocScanner] Returning results');
398
401
  onResult(updatedPhotos);
399
402
  }, [croppedImageData, rotationDegrees, capturedPhotos, onResult]);
400
403
  const handleCaptureSecondPhoto = (0, react_1.useCallback)(() => {
@@ -431,20 +434,6 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
431
434
  docScannerRef.current.reset();
432
435
  }
433
436
  }, [croppedImageData, rotationDegrees]);
434
- const handleSkipSecondPhoto = (0, react_1.useCallback)(() => {
435
- console.log('[FullDocScanner] Skipping second photo - saving current photo only');
436
- if (!croppedImageData) {
437
- return;
438
- }
439
- // 현재 사진만 저장하고 완료
440
- const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
441
- const currentPhoto = {
442
- path: croppedImageData.path,
443
- base64: croppedImageData.base64,
444
- rotationDegrees: rotationNormalized,
445
- };
446
- onResult([currentPhoto]);
447
- }, [croppedImageData, rotationDegrees, onResult]);
448
437
  const handleRetake = (0, react_1.useCallback)(() => {
449
438
  console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
450
439
  // Business 모드에서 두 번째 사진을 다시 찍는 경우, 첫 번째 사진 유지
@@ -530,15 +519,22 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
530
519
  croppedImageData ? (
531
520
  // check_DP: Show confirmation screen
532
521
  react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
533
- isBusinessMode && (react_1.default.createElement(react_native_1.View, { style: styles.photoHeader },
534
- react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second))),
535
- isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
522
+ isBusinessMode && isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.businessHeaderRow },
523
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonLeft, onPress: () => handleRotateImage(-90), accessibilityLabel: "\uC67C\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
524
+ react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA")),
525
+ react_1.default.createElement(react_native_1.View, { style: styles.photoHeaderCenter },
526
+ react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second)),
527
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonRight, onPress: () => handleRotateImage(90), accessibilityLabel: "\uC624\uB978\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
528
+ react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BB")))) : isBusinessMode ? (react_1.default.createElement(react_native_1.View, { style: styles.photoHeader },
529
+ react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second))) : isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
536
530
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonTop, onPress: () => handleRotateImage(-90), accessibilityLabel: "\uC67C\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
537
531
  react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA"),
538
532
  react_1.default.createElement(react_native_1.Text, { style: styles.rotateButtonLabel }, "\uC88C\uB85C 90\u00B0")),
539
533
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonTop, onPress: () => handleRotateImage(90), accessibilityLabel: "\uC624\uB978\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
540
534
  react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BB"),
541
535
  react_1.default.createElement(react_native_1.Text, { style: styles.rotateButtonLabel }, "\uC6B0\uB85C 90\u00B0")))) : null,
536
+ isBusinessMode && capturedPhotos.length === 0 && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.captureBackButton, onPress: handleCaptureSecondPhoto, accessibilityLabel: mergedStrings.secondBtn, accessibilityRole: "button" },
537
+ react_1.default.createElement(react_native_1.Text, { style: styles.captureBackButtonText }, mergedStrings.secondBtn))),
542
538
  react_1.default.createElement(react_native_1.Image, { source: { uri: croppedImageData.path }, style: [
543
539
  styles.previewImage,
544
540
  { transform: [{ rotate: `${rotationDegrees}deg` }] }
@@ -546,12 +542,8 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
546
542
  react_1.default.createElement(react_native_1.View, { style: styles.confirmationButtons },
547
543
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.retakeButton], onPress: handleRetake, accessibilityLabel: mergedStrings.retake, accessibilityRole: "button" },
548
544
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
549
- isBusinessMode && capturedPhotos.length === 0 ? (react_1.default.createElement(react_1.default.Fragment, null,
550
- react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleCaptureSecondPhoto, accessibilityLabel: mergedStrings.secondBtn, accessibilityRole: "button" },
551
- react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.secondBtn)),
552
- react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.skipButton], onPress: handleSkipSecondPhoto, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
553
- react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm)))) : (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleConfirm, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
554
- react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm)))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
545
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleConfirm, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
546
+ react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
555
547
  react_1.default.createElement(DocScanner_1.DocScanner, { 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 },
556
548
  react_1.default.createElement(react_native_1.View, { style: styles.overlayTop, pointerEvents: "box-none" },
557
549
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
@@ -814,4 +806,58 @@ const styles = react_native_1.StyleSheet.create({
814
806
  paddingVertical: 6,
815
807
  borderRadius: 16,
816
808
  },
809
+ captureBackButton: {
810
+ position: 'absolute',
811
+ bottom: 140,
812
+ left: 0,
813
+ right: 0,
814
+ alignItems: 'center',
815
+ zIndex: 15,
816
+ },
817
+ captureBackButtonText: {
818
+ color: '#fff',
819
+ fontSize: 16,
820
+ fontWeight: '600',
821
+ // backgroundColor: 'rgba(255,100,50,0.9)',
822
+ paddingHorizontal: 32,
823
+ paddingVertical: 14,
824
+ borderRadius: 24,
825
+ borderWidth: 2,
826
+ borderColor: '#fff',
827
+ overflow: 'hidden',
828
+ },
829
+ businessHeaderRow: {
830
+ position: 'absolute',
831
+ top: 80,
832
+ left: 0,
833
+ right: 0,
834
+ flexDirection: 'row',
835
+ justifyContent: 'space-between',
836
+ alignItems: 'center',
837
+ paddingHorizontal: 20,
838
+ zIndex: 10,
839
+ },
840
+ rotateButtonLeft: {
841
+ width: 48,
842
+ height: 48,
843
+ borderRadius: 24,
844
+ backgroundColor: 'rgba(50,50,50,0.8)',
845
+ justifyContent: 'center',
846
+ alignItems: 'center',
847
+ borderWidth: 1,
848
+ borderColor: 'rgba(255,255,255,0.3)',
849
+ },
850
+ rotateButtonRight: {
851
+ width: 48,
852
+ height: 48,
853
+ borderRadius: 24,
854
+ backgroundColor: 'rgba(50,50,50,0.8)',
855
+ justifyContent: 'center',
856
+ alignItems: 'center',
857
+ borderWidth: 1,
858
+ borderColor: 'rgba(255,255,255,0.3)',
859
+ },
860
+ photoHeaderCenter: {
861
+ alignItems: 'center',
862
+ },
817
863
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.117.0",
3
+ "version": "3.119.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -191,7 +191,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
191
191
  const rectangleHintTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
192
192
 
193
193
  const isBusinessMode = type === 'business';
194
- const maxPhotos = isBusinessMode ? 2 : 1;
195
194
 
196
195
  const mergedStrings = useMemo(
197
196
  () => ({
@@ -292,6 +291,10 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
292
291
  errorMessage.includes('cancel')
293
292
  ) {
294
293
  console.log('[FullDocScanner] User cancelled cropper');
294
+ // DocScanner 상태를 리셋하여 카메라가 다시 작동하도록 함
295
+ if (docScannerRef.current?.reset) {
296
+ docScannerRef.current.reset();
297
+ }
295
298
  } else {
296
299
  emitError(
297
300
  error instanceof Error ? error : new Error(errorMessage),
@@ -529,10 +532,10 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
529
532
  };
530
533
 
531
534
  const updatedPhotos = [...capturedPhotos, currentPhoto];
532
- console.log('[FullDocScanner] Photos captured:', updatedPhotos.length, 'of', maxPhotos);
535
+ console.log('[FullDocScanner] Photos captured:', updatedPhotos.length);
533
536
 
534
- // 모든 사진 촬영 완료 - 결과 반환
535
- console.log('[FullDocScanner] All photos captured, returning results');
537
+ // 결과 반환
538
+ console.log('[FullDocScanner] Returning results');
536
539
  onResult(updatedPhotos);
537
540
  }, [croppedImageData, rotationDegrees, capturedPhotos, onResult]);
538
541
 
@@ -575,23 +578,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
575
578
  }
576
579
  }, [croppedImageData, rotationDegrees]);
577
580
 
578
- const handleSkipSecondPhoto = useCallback(() => {
579
- console.log('[FullDocScanner] Skipping second photo - saving current photo only');
580
-
581
- if (!croppedImageData) {
582
- return;
583
- }
584
-
585
- // 현재 사진만 저장하고 완료
586
- const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
587
- const currentPhoto: FullDocScannerResult = {
588
- path: croppedImageData.path,
589
- base64: croppedImageData.base64,
590
- rotationDegrees: rotationNormalized,
591
- };
592
-
593
- onResult([currentPhoto]);
594
- }, [croppedImageData, rotationDegrees, onResult]);
595
581
 
596
582
  const handleRetake = useCallback(() => {
597
583
  console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
@@ -690,17 +676,40 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
690
676
  {croppedImageData ? (
691
677
  // check_DP: Show confirmation screen
692
678
  <View style={styles.confirmationContainer}>
693
- {/* 헤더 - 앞면/뒷면 표시 */}
694
- {isBusinessMode && (
679
+ {/* Business 모드: 회전 버튼(왼쪽/오른쪽) + 헤더(가운데) 배치 */}
680
+ {isBusinessMode && isImageRotationSupported() ? (
681
+ <View style={styles.businessHeaderRow}>
682
+ <TouchableOpacity
683
+ style={styles.rotateButtonLeft}
684
+ onPress={() => handleRotateImage(-90)}
685
+ accessibilityLabel="왼쪽으로 90도 회전"
686
+ accessibilityRole="button"
687
+ >
688
+ <Text style={styles.rotateIconText}>↺</Text>
689
+ </TouchableOpacity>
690
+
691
+ <View style={styles.photoHeaderCenter}>
692
+ <Text style={styles.photoHeaderText}>
693
+ {currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second}
694
+ </Text>
695
+ </View>
696
+
697
+ <TouchableOpacity
698
+ style={styles.rotateButtonRight}
699
+ onPress={() => handleRotateImage(90)}
700
+ accessibilityLabel="오른쪽으로 90도 회전"
701
+ accessibilityRole="button"
702
+ >
703
+ <Text style={styles.rotateIconText}>↻</Text>
704
+ </TouchableOpacity>
705
+ </View>
706
+ ) : isBusinessMode ? (
695
707
  <View style={styles.photoHeader}>
696
708
  <Text style={styles.photoHeaderText}>
697
709
  {currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second}
698
710
  </Text>
699
711
  </View>
700
- )}
701
-
702
- {/* 회전 버튼들 - 가운데 정렬 */}
703
- {isImageRotationSupported() ? (
712
+ ) : isImageRotationSupported() ? (
704
713
  <View style={styles.rotateButtonsCenter}>
705
714
  <TouchableOpacity
706
715
  style={styles.rotateButtonTop}
@@ -722,6 +731,19 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
722
731
  </TouchableOpacity>
723
732
  </View>
724
733
  ) : null}
734
+
735
+ {/* 뒷면 촬영 버튼 - 상단에 표시 (Business 모드이고 첫 번째 사진일 때만) */}
736
+ {isBusinessMode && capturedPhotos.length === 0 && (
737
+ <TouchableOpacity
738
+ style={styles.captureBackButton}
739
+ onPress={handleCaptureSecondPhoto}
740
+ accessibilityLabel={mergedStrings.secondBtn}
741
+ accessibilityRole="button"
742
+ >
743
+ <Text style={styles.captureBackButtonText}>{mergedStrings.secondBtn}</Text>
744
+ </TouchableOpacity>
745
+ )}
746
+
725
747
  <Image
726
748
  source={{ uri: croppedImageData.path }}
727
749
  style={[
@@ -740,36 +762,14 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
740
762
  <Text style={styles.confirmButtonText}>{mergedStrings.retake}</Text>
741
763
  </TouchableOpacity>
742
764
 
743
- {/* Business 모드이고 첫 번째 사진일 때: 뒷면 촬영 버튼과 확인 버튼 동시 표시 */}
744
- {isBusinessMode && capturedPhotos.length === 0 ? (
745
- <>
746
- <TouchableOpacity
747
- style={[styles.confirmButton, styles.confirmButtonPrimary]}
748
- onPress={handleCaptureSecondPhoto}
749
- accessibilityLabel={mergedStrings.secondBtn}
750
- accessibilityRole="button"
751
- >
752
- <Text style={styles.confirmButtonText}>{mergedStrings.secondBtn}</Text>
753
- </TouchableOpacity>
754
- <TouchableOpacity
755
- style={[styles.confirmButton, styles.skipButton]}
756
- onPress={handleSkipSecondPhoto}
757
- accessibilityLabel={mergedStrings.confirm}
758
- accessibilityRole="button"
759
- >
760
- <Text style={styles.confirmButtonText}>{mergedStrings.confirm}</Text>
761
- </TouchableOpacity>
762
- </>
763
- ) : (
764
- <TouchableOpacity
765
- style={[styles.confirmButton, styles.confirmButtonPrimary]}
766
- onPress={handleConfirm}
767
- accessibilityLabel={mergedStrings.confirm}
768
- accessibilityRole="button"
769
- >
770
- <Text style={styles.confirmButtonText}>{mergedStrings.confirm}</Text>
771
- </TouchableOpacity>
772
- )}
765
+ <TouchableOpacity
766
+ style={[styles.confirmButton, styles.confirmButtonPrimary]}
767
+ onPress={handleConfirm}
768
+ accessibilityLabel={mergedStrings.confirm}
769
+ accessibilityRole="button"
770
+ >
771
+ <Text style={styles.confirmButtonText}>{mergedStrings.confirm}</Text>
772
+ </TouchableOpacity>
773
773
  </View>
774
774
  </View>
775
775
  ) : (
@@ -1112,4 +1112,58 @@ const styles = StyleSheet.create({
1112
1112
  paddingVertical: 6,
1113
1113
  borderRadius: 16,
1114
1114
  },
1115
+ captureBackButton: {
1116
+ position: 'absolute',
1117
+ bottom: 140,
1118
+ left: 0,
1119
+ right: 0,
1120
+ alignItems: 'center',
1121
+ zIndex: 15,
1122
+ },
1123
+ captureBackButtonText: {
1124
+ color: '#fff',
1125
+ fontSize: 16,
1126
+ fontWeight: '600',
1127
+ // backgroundColor: 'rgba(255,100,50,0.9)',
1128
+ paddingHorizontal: 32,
1129
+ paddingVertical: 14,
1130
+ borderRadius: 24,
1131
+ borderWidth: 2,
1132
+ borderColor: '#fff',
1133
+ overflow: 'hidden',
1134
+ },
1135
+ businessHeaderRow: {
1136
+ position: 'absolute',
1137
+ top: 80,
1138
+ left: 0,
1139
+ right: 0,
1140
+ flexDirection: 'row',
1141
+ justifyContent: 'space-between',
1142
+ alignItems: 'center',
1143
+ paddingHorizontal: 20,
1144
+ zIndex: 10,
1145
+ },
1146
+ rotateButtonLeft: {
1147
+ width: 48,
1148
+ height: 48,
1149
+ borderRadius: 24,
1150
+ backgroundColor: 'rgba(50,50,50,0.8)',
1151
+ justifyContent: 'center',
1152
+ alignItems: 'center',
1153
+ borderWidth: 1,
1154
+ borderColor: 'rgba(255,255,255,0.3)',
1155
+ },
1156
+ rotateButtonRight: {
1157
+ width: 48,
1158
+ height: 48,
1159
+ borderRadius: 24,
1160
+ backgroundColor: 'rgba(50,50,50,0.8)',
1161
+ justifyContent: 'center',
1162
+ alignItems: 'center',
1163
+ borderWidth: 1,
1164
+ borderColor: 'rgba(255,255,255,0.3)',
1165
+ },
1166
+ photoHeaderCenter: {
1167
+ alignItems: 'center',
1168
+ },
1115
1169
  });