react-native-rectangle-doc-scanner 3.115.0 → 3.117.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.
- package/dist/FullDocScanner.js +40 -46
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +47 -52
package/dist/FullDocScanner.js
CHANGED
|
@@ -125,19 +125,14 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
125
125
|
const [rotationDegrees, setRotationDegrees] = (0, react_1.useState)(0);
|
|
126
126
|
const [capturedPhotos, setCapturedPhotos] = (0, react_1.useState)([]);
|
|
127
127
|
const [currentPhotoIndex, setCurrentPhotoIndex] = (0, react_1.useState)(0);
|
|
128
|
-
const [previewPhotoIndex, setPreviewPhotoIndex] = (0, react_1.useState)(0);
|
|
129
128
|
const resolvedGridColor = gridColor ?? overlayColor;
|
|
130
129
|
const docScannerRef = (0, react_1.useRef)(null);
|
|
131
130
|
const captureModeRef = (0, react_1.useRef)(null);
|
|
132
131
|
const captureInProgressRef = (0, react_1.useRef)(false);
|
|
133
132
|
const rectangleCaptureTimeoutRef = (0, react_1.useRef)(null);
|
|
134
133
|
const rectangleHintTimeoutRef = (0, react_1.useRef)(null);
|
|
135
|
-
const currentPhotoIndexRef = (0, react_1.useRef)(currentPhotoIndex);
|
|
136
134
|
const isBusinessMode = type === 'business';
|
|
137
135
|
const maxPhotos = isBusinessMode ? 2 : 1;
|
|
138
|
-
(0, react_1.useEffect)(() => {
|
|
139
|
-
currentPhotoIndexRef.current = currentPhotoIndex;
|
|
140
|
-
}, [currentPhotoIndex]);
|
|
141
136
|
const mergedStrings = (0, react_1.useMemo)(() => ({
|
|
142
137
|
captureHint: strings?.captureHint,
|
|
143
138
|
manualHint: strings?.manualHint,
|
|
@@ -190,7 +185,6 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
190
185
|
hasBase64: !!croppedImage.data,
|
|
191
186
|
});
|
|
192
187
|
setProcessing(false);
|
|
193
|
-
setPreviewPhotoIndex(currentPhotoIndexRef.current);
|
|
194
188
|
// Show confirmation screen
|
|
195
189
|
setCroppedImageData({
|
|
196
190
|
path: croppedImage.path,
|
|
@@ -237,8 +231,6 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
237
231
|
console.warn('[FullDocScanner] Missing capture mode for capture result, ignoring');
|
|
238
232
|
return;
|
|
239
233
|
}
|
|
240
|
-
const previewIndex = currentPhotoIndexRef.current;
|
|
241
|
-
setPreviewPhotoIndex(previewIndex);
|
|
242
234
|
const normalizedDoc = normalizeCapturedDocument(document);
|
|
243
235
|
if (captureMode === 'no-grid') {
|
|
244
236
|
console.log('[FullDocScanner] No grid at capture button press: opening cropper for manual selection');
|
|
@@ -401,20 +393,24 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
401
393
|
};
|
|
402
394
|
const updatedPhotos = [...capturedPhotos, currentPhoto];
|
|
403
395
|
console.log('[FullDocScanner] Photos captured:', updatedPhotos.length, 'of', maxPhotos);
|
|
404
|
-
// Business 모드이고 아직 첫 번째 사진만 찍은 경우
|
|
405
|
-
if (isBusinessMode && updatedPhotos.length === 1) {
|
|
406
|
-
// 두 번째 사진 촬영 여부를 물어봄 (UI에서 버튼으로 표시)
|
|
407
|
-
setCapturedPhotos(updatedPhotos);
|
|
408
|
-
setCurrentPhotoIndex(1);
|
|
409
|
-
// 확인 화면을 유지하고 "뒷면 촬영" 버튼을 표시
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
396
|
// 모든 사진 촬영 완료 - 결과 반환
|
|
413
397
|
console.log('[FullDocScanner] All photos captured, returning results');
|
|
414
398
|
onResult(updatedPhotos);
|
|
415
|
-
}, [croppedImageData, rotationDegrees, capturedPhotos,
|
|
399
|
+
}, [croppedImageData, rotationDegrees, capturedPhotos, onResult]);
|
|
416
400
|
const handleCaptureSecondPhoto = (0, react_1.useCallback)(() => {
|
|
417
401
|
console.log('[FullDocScanner] Capturing second photo');
|
|
402
|
+
if (!croppedImageData) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
// 현재 사진(앞면)을 먼저 저장
|
|
406
|
+
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
407
|
+
const currentPhoto = {
|
|
408
|
+
path: croppedImageData.path,
|
|
409
|
+
base64: croppedImageData.base64,
|
|
410
|
+
rotationDegrees: rotationNormalized,
|
|
411
|
+
};
|
|
412
|
+
setCapturedPhotos([currentPhoto]);
|
|
413
|
+
setCurrentPhotoIndex(1);
|
|
418
414
|
// 확인 화면을 닫고 카메라로 돌아감
|
|
419
415
|
setCroppedImageData(null);
|
|
420
416
|
setRotationDegrees(0);
|
|
@@ -434,27 +430,33 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
434
430
|
if (docScannerRef.current?.reset) {
|
|
435
431
|
docScannerRef.current.reset();
|
|
436
432
|
}
|
|
437
|
-
}, []);
|
|
433
|
+
}, [croppedImageData, rotationDegrees]);
|
|
438
434
|
const handleSkipSecondPhoto = (0, react_1.useCallback)(() => {
|
|
439
|
-
console.log('[FullDocScanner] Skipping second photo');
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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]);
|
|
443
448
|
const handleRetake = (0, react_1.useCallback)(() => {
|
|
444
449
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
setCurrentPhotoIndex(1);
|
|
456
|
-
setPreviewPhotoIndex(1);
|
|
457
|
-
}
|
|
450
|
+
// Business 모드에서 두 번째 사진을 다시 찍는 경우, 첫 번째 사진 유지
|
|
451
|
+
if (isBusinessMode && capturedPhotos.length === 1) {
|
|
452
|
+
console.log('[FullDocScanner] Retake detected on back photo - keeping front photo');
|
|
453
|
+
setCurrentPhotoIndex(1);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
// 첫 번째 사진 또는 일반 모드: 모든 상태 초기화
|
|
457
|
+
console.log('[FullDocScanner] Retake detected - resetting all photos');
|
|
458
|
+
setCapturedPhotos([]);
|
|
459
|
+
setCurrentPhotoIndex(0);
|
|
458
460
|
}
|
|
459
461
|
setCroppedImageData(null);
|
|
460
462
|
setRotationDegrees(0);
|
|
@@ -475,7 +477,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
475
477
|
if (docScannerRef.current?.reset) {
|
|
476
478
|
docScannerRef.current.reset();
|
|
477
479
|
}
|
|
478
|
-
}, [capturedPhotos,
|
|
480
|
+
}, [capturedPhotos.length, isBusinessMode]);
|
|
479
481
|
const handleRectangleDetect = (0, react_1.useCallback)((event) => {
|
|
480
482
|
const stableCounter = event.stableCounter ?? 0;
|
|
481
483
|
const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
|
|
@@ -529,7 +531,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
529
531
|
// check_DP: Show confirmation screen
|
|
530
532
|
react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
|
|
531
533
|
isBusinessMode && (react_1.default.createElement(react_native_1.View, { style: styles.photoHeader },
|
|
532
|
-
react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText },
|
|
534
|
+
react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second))),
|
|
533
535
|
isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
|
|
534
536
|
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" },
|
|
535
537
|
react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA"),
|
|
@@ -541,18 +543,10 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
541
543
|
styles.previewImage,
|
|
542
544
|
{ transform: [{ rotate: `${rotationDegrees}deg` }] }
|
|
543
545
|
], resizeMode: "contain" }),
|
|
544
|
-
isBusinessMode &&
|
|
545
|
-
capturedPhotos.length === 1 &&
|
|
546
|
-
currentPhotoIndex === 1 &&
|
|
547
|
-
previewPhotoIndex === 0 &&
|
|
548
|
-
mergedStrings.secondPrompt ? (react_1.default.createElement(react_native_1.Text, { style: styles.confirmationPromptText }, mergedStrings.secondPrompt)) : null,
|
|
549
546
|
react_1.default.createElement(react_native_1.View, { style: styles.confirmationButtons },
|
|
550
547
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.retakeButton], onPress: handleRetake, accessibilityLabel: mergedStrings.retake, accessibilityRole: "button" },
|
|
551
548
|
react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
|
|
552
|
-
isBusinessMode &&
|
|
553
|
-
capturedPhotos.length === 1 &&
|
|
554
|
-
currentPhotoIndex === 1 &&
|
|
555
|
-
previewPhotoIndex === 0 ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
549
|
+
isBusinessMode && capturedPhotos.length === 0 ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
556
550
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleCaptureSecondPhoto, accessibilityLabel: mergedStrings.secondBtn, accessibilityRole: "button" },
|
|
557
551
|
react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.secondBtn)),
|
|
558
552
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.skipButton], onPress: handleSkipSecondPhoto, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
|
package/package.json
CHANGED
package/src/FullDocScanner.tsx
CHANGED
|
@@ -183,22 +183,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
183
183
|
const [rotationDegrees, setRotationDegrees] = useState(0);
|
|
184
184
|
const [capturedPhotos, setCapturedPhotos] = useState<FullDocScannerResult[]>([]);
|
|
185
185
|
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
|
|
186
|
-
const [previewPhotoIndex, setPreviewPhotoIndex] = useState(0);
|
|
187
186
|
const resolvedGridColor = gridColor ?? overlayColor;
|
|
188
187
|
const docScannerRef = useRef<DocScannerHandle | null>(null);
|
|
189
188
|
const captureModeRef = useRef<'grid' | 'no-grid' | null>(null);
|
|
190
189
|
const captureInProgressRef = useRef(false);
|
|
191
190
|
const rectangleCaptureTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
192
191
|
const rectangleHintTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
193
|
-
const currentPhotoIndexRef = useRef(currentPhotoIndex);
|
|
194
192
|
|
|
195
193
|
const isBusinessMode = type === 'business';
|
|
196
194
|
const maxPhotos = isBusinessMode ? 2 : 1;
|
|
197
195
|
|
|
198
|
-
useEffect(() => {
|
|
199
|
-
currentPhotoIndexRef.current = currentPhotoIndex;
|
|
200
|
-
}, [currentPhotoIndex]);
|
|
201
|
-
|
|
202
196
|
const mergedStrings = useMemo(
|
|
203
197
|
() => ({
|
|
204
198
|
captureHint: strings?.captureHint,
|
|
@@ -269,8 +263,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
269
263
|
|
|
270
264
|
setProcessing(false);
|
|
271
265
|
|
|
272
|
-
setPreviewPhotoIndex(currentPhotoIndexRef.current);
|
|
273
|
-
|
|
274
266
|
// Show confirmation screen
|
|
275
267
|
setCroppedImageData({
|
|
276
268
|
path: croppedImage.path,
|
|
@@ -333,9 +325,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
333
325
|
return;
|
|
334
326
|
}
|
|
335
327
|
|
|
336
|
-
const previewIndex = currentPhotoIndexRef.current;
|
|
337
|
-
setPreviewPhotoIndex(previewIndex);
|
|
338
|
-
|
|
339
328
|
const normalizedDoc = normalizeCapturedDocument(document);
|
|
340
329
|
|
|
341
330
|
if (captureMode === 'no-grid') {
|
|
@@ -542,22 +531,29 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
542
531
|
const updatedPhotos = [...capturedPhotos, currentPhoto];
|
|
543
532
|
console.log('[FullDocScanner] Photos captured:', updatedPhotos.length, 'of', maxPhotos);
|
|
544
533
|
|
|
545
|
-
// Business 모드이고 아직 첫 번째 사진만 찍은 경우
|
|
546
|
-
if (isBusinessMode && updatedPhotos.length === 1) {
|
|
547
|
-
// 두 번째 사진 촬영 여부를 물어봄 (UI에서 버튼으로 표시)
|
|
548
|
-
setCapturedPhotos(updatedPhotos);
|
|
549
|
-
setCurrentPhotoIndex(1);
|
|
550
|
-
// 확인 화면을 유지하고 "뒷면 촬영" 버튼을 표시
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
534
|
// 모든 사진 촬영 완료 - 결과 반환
|
|
555
535
|
console.log('[FullDocScanner] All photos captured, returning results');
|
|
556
536
|
onResult(updatedPhotos);
|
|
557
|
-
}, [croppedImageData, rotationDegrees, capturedPhotos,
|
|
537
|
+
}, [croppedImageData, rotationDegrees, capturedPhotos, onResult]);
|
|
558
538
|
|
|
559
539
|
const handleCaptureSecondPhoto = useCallback(() => {
|
|
560
540
|
console.log('[FullDocScanner] Capturing second photo');
|
|
541
|
+
|
|
542
|
+
if (!croppedImageData) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// 현재 사진(앞면)을 먼저 저장
|
|
547
|
+
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
548
|
+
const currentPhoto: FullDocScannerResult = {
|
|
549
|
+
path: croppedImageData.path,
|
|
550
|
+
base64: croppedImageData.base64,
|
|
551
|
+
rotationDegrees: rotationNormalized,
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
setCapturedPhotos([currentPhoto]);
|
|
555
|
+
setCurrentPhotoIndex(1);
|
|
556
|
+
|
|
561
557
|
// 확인 화면을 닫고 카메라로 돌아감
|
|
562
558
|
setCroppedImageData(null);
|
|
563
559
|
setRotationDegrees(0);
|
|
@@ -577,29 +573,38 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
577
573
|
if (docScannerRef.current?.reset) {
|
|
578
574
|
docScannerRef.current.reset();
|
|
579
575
|
}
|
|
580
|
-
}, []);
|
|
576
|
+
}, [croppedImageData, rotationDegrees]);
|
|
581
577
|
|
|
582
578
|
const handleSkipSecondPhoto = useCallback(() => {
|
|
583
|
-
console.log('[FullDocScanner] Skipping second photo');
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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]);
|
|
587
595
|
|
|
588
596
|
const handleRetake = useCallback(() => {
|
|
589
597
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|
|
590
598
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
setCurrentPhotoIndex(1);
|
|
601
|
-
setPreviewPhotoIndex(1);
|
|
602
|
-
}
|
|
599
|
+
// Business 모드에서 두 번째 사진을 다시 찍는 경우, 첫 번째 사진 유지
|
|
600
|
+
if (isBusinessMode && capturedPhotos.length === 1) {
|
|
601
|
+
console.log('[FullDocScanner] Retake detected on back photo - keeping front photo');
|
|
602
|
+
setCurrentPhotoIndex(1);
|
|
603
|
+
} else {
|
|
604
|
+
// 첫 번째 사진 또는 일반 모드: 모든 상태 초기화
|
|
605
|
+
console.log('[FullDocScanner] Retake detected - resetting all photos');
|
|
606
|
+
setCapturedPhotos([]);
|
|
607
|
+
setCurrentPhotoIndex(0);
|
|
603
608
|
}
|
|
604
609
|
|
|
605
610
|
setCroppedImageData(null);
|
|
@@ -621,7 +626,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
621
626
|
if (docScannerRef.current?.reset) {
|
|
622
627
|
docScannerRef.current.reset();
|
|
623
628
|
}
|
|
624
|
-
}, [capturedPhotos,
|
|
629
|
+
}, [capturedPhotos.length, isBusinessMode]);
|
|
625
630
|
|
|
626
631
|
const handleRectangleDetect = useCallback((event: RectangleDetectEvent) => {
|
|
627
632
|
const stableCounter = event.stableCounter ?? 0;
|
|
@@ -689,7 +694,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
689
694
|
{isBusinessMode && (
|
|
690
695
|
<View style={styles.photoHeader}>
|
|
691
696
|
<Text style={styles.photoHeaderText}>
|
|
692
|
-
{
|
|
697
|
+
{currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second}
|
|
693
698
|
</Text>
|
|
694
699
|
</View>
|
|
695
700
|
)}
|
|
@@ -725,13 +730,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
725
730
|
]}
|
|
726
731
|
resizeMode="contain"
|
|
727
732
|
/>
|
|
728
|
-
{isBusinessMode &&
|
|
729
|
-
capturedPhotos.length === 1 &&
|
|
730
|
-
currentPhotoIndex === 1 &&
|
|
731
|
-
previewPhotoIndex === 0 &&
|
|
732
|
-
mergedStrings.secondPrompt ? (
|
|
733
|
-
<Text style={styles.confirmationPromptText}>{mergedStrings.secondPrompt}</Text>
|
|
734
|
-
) : null}
|
|
735
733
|
<View style={styles.confirmationButtons}>
|
|
736
734
|
<TouchableOpacity
|
|
737
735
|
style={[styles.confirmButton, styles.retakeButton]}
|
|
@@ -742,11 +740,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
742
740
|
<Text style={styles.confirmButtonText}>{mergedStrings.retake}</Text>
|
|
743
741
|
</TouchableOpacity>
|
|
744
742
|
|
|
745
|
-
{/* Business 모드이고 첫 번째
|
|
746
|
-
{isBusinessMode &&
|
|
747
|
-
capturedPhotos.length === 1 &&
|
|
748
|
-
currentPhotoIndex === 1 &&
|
|
749
|
-
previewPhotoIndex === 0 ? (
|
|
743
|
+
{/* Business 모드이고 첫 번째 사진일 때: 뒷면 촬영 버튼과 확인 버튼 동시 표시 */}
|
|
744
|
+
{isBusinessMode && capturedPhotos.length === 0 ? (
|
|
750
745
|
<>
|
|
751
746
|
<TouchableOpacity
|
|
752
747
|
style={[styles.confirmButton, styles.confirmButtonPrimary]}
|