react-native-rectangle-doc-scanner 3.112.0 → 3.114.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.
@@ -20,9 +20,12 @@ export interface FullDocScannerStrings {
20
20
  retake?: string;
21
21
  confirm?: string;
22
22
  cropTitle?: string;
23
+ first?: string;
24
+ second?: string;
25
+ secondBtn?: string;
23
26
  }
24
27
  export interface FullDocScannerProps {
25
- onResult: (result: FullDocScannerResult) => void;
28
+ onResult: (results: FullDocScannerResult[]) => void;
26
29
  onClose?: () => void;
27
30
  detectionConfig?: DetectionConfig;
28
31
  overlayColor?: string;
@@ -35,5 +38,6 @@ export interface FullDocScannerProps {
35
38
  enableGallery?: boolean;
36
39
  cropWidth?: number;
37
40
  cropHeight?: number;
41
+ type?: 'business';
38
42
  }
39
43
  export declare const FullDocScanner: React.FC<FullDocScannerProps>;
@@ -115,7 +115,7 @@ const normalizeCapturedDocument = (document) => {
115
115
  croppedPath: document.croppedPath ? stripFileUri(document.croppedPath) : null,
116
116
  };
117
117
  };
118
- const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3170f3', gridColor, gridLineWidth, showGrid, strings, minStableFrames, onError, enableGallery = true, cropWidth = 1200, cropHeight = 1600, }) => {
118
+ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3170f3', gridColor, gridLineWidth, showGrid, strings, minStableFrames, onError, enableGallery = true, cropWidth = 1200, cropHeight = 1600, type, }) => {
119
119
  const [processing, setProcessing] = (0, react_1.useState)(false);
120
120
  const [croppedImageData, setCroppedImageData] = (0, react_1.useState)(null);
121
121
  const [isGalleryOpen, setIsGalleryOpen] = (0, react_1.useState)(false);
@@ -123,12 +123,16 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
123
123
  const [rectangleHint, setRectangleHint] = (0, react_1.useState)(false);
124
124
  const [flashEnabled, setFlashEnabled] = (0, react_1.useState)(false);
125
125
  const [rotationDegrees, setRotationDegrees] = (0, react_1.useState)(0);
126
+ const [capturedPhotos, setCapturedPhotos] = (0, react_1.useState)([]);
127
+ const [currentPhotoIndex, setCurrentPhotoIndex] = (0, react_1.useState)(0);
126
128
  const resolvedGridColor = gridColor ?? overlayColor;
127
129
  const docScannerRef = (0, react_1.useRef)(null);
128
130
  const captureModeRef = (0, react_1.useRef)(null);
129
131
  const captureInProgressRef = (0, react_1.useRef)(false);
130
132
  const rectangleCaptureTimeoutRef = (0, react_1.useRef)(null);
131
133
  const rectangleHintTimeoutRef = (0, react_1.useRef)(null);
134
+ const isBusinessMode = type === 'business';
135
+ const maxPhotos = isBusinessMode ? 2 : 1;
132
136
  const mergedStrings = (0, react_1.useMemo)(() => ({
133
137
  captureHint: strings?.captureHint,
134
138
  manualHint: strings?.manualHint,
@@ -138,6 +142,9 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
138
142
  retake: strings?.retake ?? 'Retake',
139
143
  confirm: strings?.confirm ?? 'Confirm',
140
144
  cropTitle: strings?.cropTitle ?? 'Crop Document',
145
+ first: strings?.first ?? 'Front',
146
+ second: strings?.second ?? 'Back',
147
+ secondBtn: strings?.secondBtn ?? 'Capture Back Side?',
141
148
  }), [strings]);
142
149
  const emitError = (0, react_1.useCallback)((error, fallbackMessage) => {
143
150
  console.error('[FullDocScanner] error', error);
@@ -377,14 +384,53 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
377
384
  // 회전 각도 정규화 (0, 90, 180, 270)
378
385
  const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
379
386
  console.log('[FullDocScanner] Confirm - rotation degrees:', rotationDegrees, 'normalized:', rotationNormalized);
380
- // 원본 이미지와 회전 각도를 함께 반환
381
- // tdb 앱에서 expo-image-manipulator를 사용해서 회전 처리
382
- onResult({
387
+ // 현재 사진을 capturedPhotos에 추가
388
+ const currentPhoto = {
383
389
  path: croppedImageData.path,
384
390
  base64: croppedImageData.base64,
385
391
  rotationDegrees: rotationNormalized,
386
- });
387
- }, [croppedImageData, rotationDegrees, onResult]);
392
+ };
393
+ const updatedPhotos = [...capturedPhotos, currentPhoto];
394
+ console.log('[FullDocScanner] Photos captured:', updatedPhotos.length, 'of', maxPhotos);
395
+ // Business 모드이고 아직 첫 번째 사진만 찍은 경우
396
+ if (isBusinessMode && updatedPhotos.length === 1) {
397
+ // 두 번째 사진 촬영 여부를 물어봄 (UI에서 버튼으로 표시)
398
+ setCapturedPhotos(updatedPhotos);
399
+ setCurrentPhotoIndex(1);
400
+ // 확인 화면을 유지하고 "뒷면 촬영" 버튼을 표시
401
+ return;
402
+ }
403
+ // 모든 사진 촬영 완료 - 결과 반환
404
+ console.log('[FullDocScanner] All photos captured, returning results');
405
+ onResult(updatedPhotos);
406
+ }, [croppedImageData, rotationDegrees, capturedPhotos, isBusinessMode, maxPhotos, onResult]);
407
+ const handleCaptureSecondPhoto = (0, react_1.useCallback)(() => {
408
+ console.log('[FullDocScanner] Capturing second photo');
409
+ // 확인 화면을 닫고 카메라로 돌아감
410
+ setCroppedImageData(null);
411
+ setRotationDegrees(0);
412
+ setProcessing(false);
413
+ setRectangleDetected(false);
414
+ setRectangleHint(false);
415
+ captureModeRef.current = null;
416
+ captureInProgressRef.current = false;
417
+ if (rectangleCaptureTimeoutRef.current) {
418
+ clearTimeout(rectangleCaptureTimeoutRef.current);
419
+ rectangleCaptureTimeoutRef.current = null;
420
+ }
421
+ if (rectangleHintTimeoutRef.current) {
422
+ clearTimeout(rectangleHintTimeoutRef.current);
423
+ rectangleHintTimeoutRef.current = null;
424
+ }
425
+ if (docScannerRef.current?.reset) {
426
+ docScannerRef.current.reset();
427
+ }
428
+ }, []);
429
+ const handleSkipSecondPhoto = (0, react_1.useCallback)(() => {
430
+ console.log('[FullDocScanner] Skipping second photo');
431
+ // 첫 번째 사진만 반환
432
+ onResult(capturedPhotos);
433
+ }, [capturedPhotos, onResult]);
388
434
  const handleRetake = (0, react_1.useCallback)(() => {
389
435
  console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
390
436
  setCroppedImageData(null);
@@ -459,6 +505,8 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
459
505
  croppedImageData ? (
460
506
  // check_DP: Show confirmation screen
461
507
  react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
508
+ isBusinessMode && (react_1.default.createElement(react_native_1.View, { style: styles.photoHeader },
509
+ react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second))),
462
510
  isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
463
511
  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" },
464
512
  react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA"),
@@ -473,8 +521,12 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
473
521
  react_1.default.createElement(react_native_1.View, { style: styles.confirmationButtons },
474
522
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.retakeButton], onPress: handleRetake, accessibilityLabel: mergedStrings.retake, accessibilityRole: "button" },
475
523
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
476
- react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleConfirm, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
477
- react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
524
+ isBusinessMode && capturedPhotos.length === 1 && currentPhotoIndex === 1 ? (react_1.default.createElement(react_1.default.Fragment, null,
525
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleCaptureSecondPhoto, accessibilityLabel: mergedStrings.secondBtn, accessibilityRole: "button" },
526
+ react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.secondBtn)),
527
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.skipButton], onPress: handleSkipSecondPhoto, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
528
+ 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" },
529
+ react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm)))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
478
530
  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 },
479
531
  react_1.default.createElement(react_native_1.View, { style: styles.overlayTop, pointerEvents: "box-none" },
480
532
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
@@ -484,6 +536,8 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
484
536
  ], onPress: handleFlashToggle, disabled: processing, accessibilityLabel: "Toggle flash", accessibilityRole: "button" },
485
537
  react_1.default.createElement(react_native_1.View, { style: styles.iconContainer },
486
538
  react_1.default.createElement(react_native_1.Text, { style: styles.iconText }, "\u26A1\uFE0F"))),
539
+ isBusinessMode && (react_1.default.createElement(react_native_1.View, { style: styles.cameraHeaderContainer },
540
+ react_1.default.createElement(react_native_1.Text, { style: styles.cameraHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second))),
487
541
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.iconButton, onPress: handleClose, accessibilityLabel: mergedStrings.cancel, accessibilityRole: "button" },
488
542
  react_1.default.createElement(react_native_1.View, { style: styles.iconContainer },
489
543
  react_1.default.createElement(react_native_1.Text, { style: styles.closeIconText }, "\u00D7")))),
@@ -687,9 +741,44 @@ const styles = react_native_1.StyleSheet.create({
687
741
  confirmButtonPrimary: {
688
742
  backgroundColor: '#3170f3',
689
743
  },
744
+ skipButton: {
745
+ backgroundColor: 'rgba(100,100,100,0.8)',
746
+ },
690
747
  confirmButtonText: {
691
748
  color: '#fff',
692
749
  fontSize: 18,
693
750
  fontWeight: '600',
694
751
  },
752
+ photoHeader: {
753
+ position: 'absolute',
754
+ top: 20,
755
+ left: 0,
756
+ right: 0,
757
+ alignItems: 'center',
758
+ zIndex: 5,
759
+ },
760
+ photoHeaderText: {
761
+ color: '#fff',
762
+ fontSize: 20,
763
+ fontWeight: 'bold',
764
+ backgroundColor: 'rgba(0,0,0,0.6)',
765
+ paddingHorizontal: 24,
766
+ paddingVertical: 8,
767
+ borderRadius: 20,
768
+ },
769
+ cameraHeaderContainer: {
770
+ position: 'absolute',
771
+ left: 0,
772
+ right: 0,
773
+ alignItems: 'center',
774
+ },
775
+ cameraHeaderText: {
776
+ color: '#fff',
777
+ fontSize: 18,
778
+ fontWeight: 'bold',
779
+ backgroundColor: 'rgba(0,0,0,0.7)',
780
+ paddingHorizontal: 20,
781
+ paddingVertical: 6,
782
+ borderRadius: 16,
783
+ },
695
784
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.112.0",
3
+ "version": "3.114.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -135,10 +135,13 @@ export interface FullDocScannerStrings {
135
135
  retake?: string;
136
136
  confirm?: string;
137
137
  cropTitle?: string;
138
+ first?: string;
139
+ second?: string;
140
+ secondBtn?: string;
138
141
  }
139
142
 
140
143
  export interface FullDocScannerProps {
141
- onResult: (result: FullDocScannerResult) => void;
144
+ onResult: (results: FullDocScannerResult[]) => void;
142
145
  onClose?: () => void;
143
146
  detectionConfig?: DetectionConfig;
144
147
  overlayColor?: string;
@@ -151,6 +154,7 @@ export interface FullDocScannerProps {
151
154
  enableGallery?: boolean;
152
155
  cropWidth?: number;
153
156
  cropHeight?: number;
157
+ type?: 'business';
154
158
  }
155
159
 
156
160
  export const FullDocScanner: React.FC<FullDocScannerProps> = ({
@@ -167,6 +171,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
167
171
  enableGallery = true,
168
172
  cropWidth = 1200,
169
173
  cropHeight = 1600,
174
+ type,
170
175
  }) => {
171
176
  const [processing, setProcessing] = useState(false);
172
177
  const [croppedImageData, setCroppedImageData] = useState<{path: string; base64?: string} | null>(null);
@@ -175,6 +180,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
175
180
  const [rectangleHint, setRectangleHint] = useState(false);
176
181
  const [flashEnabled, setFlashEnabled] = useState(false);
177
182
  const [rotationDegrees, setRotationDegrees] = useState(0);
183
+ const [capturedPhotos, setCapturedPhotos] = useState<FullDocScannerResult[]>([]);
184
+ const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
178
185
  const resolvedGridColor = gridColor ?? overlayColor;
179
186
  const docScannerRef = useRef<DocScannerHandle | null>(null);
180
187
  const captureModeRef = useRef<'grid' | 'no-grid' | null>(null);
@@ -182,6 +189,9 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
182
189
  const rectangleCaptureTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
183
190
  const rectangleHintTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
184
191
 
192
+ const isBusinessMode = type === 'business';
193
+ const maxPhotos = isBusinessMode ? 2 : 1;
194
+
185
195
  const mergedStrings = useMemo(
186
196
  () => ({
187
197
  captureHint: strings?.captureHint,
@@ -192,6 +202,9 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
192
202
  retake: strings?.retake ?? 'Retake',
193
203
  confirm: strings?.confirm ?? 'Confirm',
194
204
  cropTitle: strings?.cropTitle ?? 'Crop Document',
205
+ first: strings?.first ?? 'Front',
206
+ second: strings?.second ?? 'Back',
207
+ secondBtn: strings?.secondBtn ?? 'Capture Back Side?',
195
208
  }),
196
209
  [strings],
197
210
  );
@@ -506,14 +519,58 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
506
519
  const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
507
520
  console.log('[FullDocScanner] Confirm - rotation degrees:', rotationDegrees, 'normalized:', rotationNormalized);
508
521
 
509
- // 원본 이미지와 회전 각도를 함께 반환
510
- // tdb 앱에서 expo-image-manipulator를 사용해서 회전 처리
511
- onResult({
522
+ // 현재 사진을 capturedPhotos에 추가
523
+ const currentPhoto: FullDocScannerResult = {
512
524
  path: croppedImageData.path,
513
525
  base64: croppedImageData.base64,
514
526
  rotationDegrees: rotationNormalized,
515
- });
516
- }, [croppedImageData, rotationDegrees, onResult]);
527
+ };
528
+
529
+ const updatedPhotos = [...capturedPhotos, currentPhoto];
530
+ console.log('[FullDocScanner] Photos captured:', updatedPhotos.length, 'of', maxPhotos);
531
+
532
+ // Business 모드이고 아직 첫 번째 사진만 찍은 경우
533
+ if (isBusinessMode && updatedPhotos.length === 1) {
534
+ // 두 번째 사진 촬영 여부를 물어봄 (UI에서 버튼으로 표시)
535
+ setCapturedPhotos(updatedPhotos);
536
+ setCurrentPhotoIndex(1);
537
+ // 확인 화면을 유지하고 "뒷면 촬영" 버튼을 표시
538
+ return;
539
+ }
540
+
541
+ // 모든 사진 촬영 완료 - 결과 반환
542
+ console.log('[FullDocScanner] All photos captured, returning results');
543
+ onResult(updatedPhotos);
544
+ }, [croppedImageData, rotationDegrees, capturedPhotos, isBusinessMode, maxPhotos, onResult]);
545
+
546
+ const handleCaptureSecondPhoto = useCallback(() => {
547
+ console.log('[FullDocScanner] Capturing second photo');
548
+ // 확인 화면을 닫고 카메라로 돌아감
549
+ setCroppedImageData(null);
550
+ setRotationDegrees(0);
551
+ setProcessing(false);
552
+ setRectangleDetected(false);
553
+ setRectangleHint(false);
554
+ captureModeRef.current = null;
555
+ captureInProgressRef.current = false;
556
+ if (rectangleCaptureTimeoutRef.current) {
557
+ clearTimeout(rectangleCaptureTimeoutRef.current);
558
+ rectangleCaptureTimeoutRef.current = null;
559
+ }
560
+ if (rectangleHintTimeoutRef.current) {
561
+ clearTimeout(rectangleHintTimeoutRef.current);
562
+ rectangleHintTimeoutRef.current = null;
563
+ }
564
+ if (docScannerRef.current?.reset) {
565
+ docScannerRef.current.reset();
566
+ }
567
+ }, []);
568
+
569
+ const handleSkipSecondPhoto = useCallback(() => {
570
+ console.log('[FullDocScanner] Skipping second photo');
571
+ // 첫 번째 사진만 반환
572
+ onResult(capturedPhotos);
573
+ }, [capturedPhotos, onResult]);
517
574
 
518
575
  const handleRetake = useCallback(() => {
519
576
  console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
@@ -600,6 +657,15 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
600
657
  {croppedImageData ? (
601
658
  // check_DP: Show confirmation screen
602
659
  <View style={styles.confirmationContainer}>
660
+ {/* 헤더 - 앞면/뒷면 표시 */}
661
+ {isBusinessMode && (
662
+ <View style={styles.photoHeader}>
663
+ <Text style={styles.photoHeaderText}>
664
+ {currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second}
665
+ </Text>
666
+ </View>
667
+ )}
668
+
603
669
  {/* 회전 버튼들 - 가운데 정렬 */}
604
670
  {isImageRotationSupported() ? (
605
671
  <View style={styles.rotateButtonsCenter}>
@@ -640,14 +706,37 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
640
706
  >
641
707
  <Text style={styles.confirmButtonText}>{mergedStrings.retake}</Text>
642
708
  </TouchableOpacity>
643
- <TouchableOpacity
644
- style={[styles.confirmButton, styles.confirmButtonPrimary]}
645
- onPress={handleConfirm}
646
- accessibilityLabel={mergedStrings.confirm}
647
- accessibilityRole="button"
648
- >
649
- <Text style={styles.confirmButtonText}>{mergedStrings.confirm}</Text>
650
- </TouchableOpacity>
709
+
710
+ {/* Business 모드이고 첫 번째 사진을 찍은 후: 뒷면 촬영 버튼 또는 확인 버튼 */}
711
+ {isBusinessMode && capturedPhotos.length === 1 && currentPhotoIndex === 1 ? (
712
+ <>
713
+ <TouchableOpacity
714
+ style={[styles.confirmButton, styles.confirmButtonPrimary]}
715
+ onPress={handleCaptureSecondPhoto}
716
+ accessibilityLabel={mergedStrings.secondBtn}
717
+ accessibilityRole="button"
718
+ >
719
+ <Text style={styles.confirmButtonText}>{mergedStrings.secondBtn}</Text>
720
+ </TouchableOpacity>
721
+ <TouchableOpacity
722
+ style={[styles.confirmButton, styles.skipButton]}
723
+ onPress={handleSkipSecondPhoto}
724
+ accessibilityLabel={mergedStrings.confirm}
725
+ accessibilityRole="button"
726
+ >
727
+ <Text style={styles.confirmButtonText}>{mergedStrings.confirm}</Text>
728
+ </TouchableOpacity>
729
+ </>
730
+ ) : (
731
+ <TouchableOpacity
732
+ style={[styles.confirmButton, styles.confirmButtonPrimary]}
733
+ onPress={handleConfirm}
734
+ accessibilityLabel={mergedStrings.confirm}
735
+ accessibilityRole="button"
736
+ >
737
+ <Text style={styles.confirmButtonText}>{mergedStrings.confirm}</Text>
738
+ </TouchableOpacity>
739
+ )}
651
740
  </View>
652
741
  </View>
653
742
  ) : (
@@ -667,6 +756,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
667
756
  enableTorch={flashEnabled}
668
757
  >
669
758
  <View style={styles.overlayTop} pointerEvents="box-none">
759
+ {/* 좌측: 플래시 버튼 */}
670
760
  <TouchableOpacity
671
761
  style={[
672
762
  styles.iconButton,
@@ -682,6 +772,17 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
682
772
  <Text style={styles.iconText}>⚡️</Text>
683
773
  </View>
684
774
  </TouchableOpacity>
775
+
776
+ {/* 중앙: 앞면/뒷면 헤더 */}
777
+ {isBusinessMode && (
778
+ <View style={styles.cameraHeaderContainer}>
779
+ <Text style={styles.cameraHeaderText}>
780
+ {currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second}
781
+ </Text>
782
+ </View>
783
+ )}
784
+
785
+ {/* 우측: 닫기 버튼 */}
685
786
  <TouchableOpacity
686
787
  style={styles.iconButton}
687
788
  onPress={handleClose}
@@ -930,9 +1031,44 @@ const styles = StyleSheet.create({
930
1031
  confirmButtonPrimary: {
931
1032
  backgroundColor: '#3170f3',
932
1033
  },
1034
+ skipButton: {
1035
+ backgroundColor: 'rgba(100,100,100,0.8)',
1036
+ },
933
1037
  confirmButtonText: {
934
1038
  color: '#fff',
935
1039
  fontSize: 18,
936
1040
  fontWeight: '600',
937
1041
  },
1042
+ photoHeader: {
1043
+ position: 'absolute',
1044
+ top: 20,
1045
+ left: 0,
1046
+ right: 0,
1047
+ alignItems: 'center',
1048
+ zIndex: 5,
1049
+ },
1050
+ photoHeaderText: {
1051
+ color: '#fff',
1052
+ fontSize: 20,
1053
+ fontWeight: 'bold',
1054
+ backgroundColor: 'rgba(0,0,0,0.6)',
1055
+ paddingHorizontal: 24,
1056
+ paddingVertical: 8,
1057
+ borderRadius: 20,
1058
+ },
1059
+ cameraHeaderContainer: {
1060
+ position: 'absolute',
1061
+ left: 0,
1062
+ right: 0,
1063
+ alignItems: 'center',
1064
+ },
1065
+ cameraHeaderText: {
1066
+ color: '#fff',
1067
+ fontSize: 18,
1068
+ fontWeight: 'bold',
1069
+ backgroundColor: 'rgba(0,0,0,0.7)',
1070
+ paddingHorizontal: 20,
1071
+ paddingVertical: 6,
1072
+ borderRadius: 16,
1073
+ },
938
1074
  });