react-native-rectangle-doc-scanner 0.65.0 → 0.69.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.
@@ -0,0 +1,228 @@
1
+ import AVFoundation
2
+ import Foundation
3
+ import React
4
+ import Vision
5
+
6
+ @objc(RNRDocScannerView)
7
+ class RNRDocScannerView: UIView {
8
+ @objc var detectionCountBeforeCapture: NSNumber = 8
9
+ @objc var autoCapture: Bool = true
10
+ @objc var enableTorch: Bool = false {
11
+ didSet {
12
+ updateTorchMode()
13
+ }
14
+ }
15
+ @objc var quality: NSNumber = 90
16
+ @objc var useBase64: Bool = false
17
+
18
+ @objc var onRectangleDetect: RCTDirectEventBlock?
19
+ @objc var onPictureTaken: RCTDirectEventBlock?
20
+
21
+ private let session = AVCaptureSession()
22
+ private let sessionQueue = DispatchQueue(label: "com.reactnative.rectangledocscanner.session")
23
+ private let analysisQueue = DispatchQueue(label: "com.reactnative.rectangledocscanner.analysis")
24
+ private var previewLayer: AVCaptureVideoPreviewLayer?
25
+ private var photoOutput = AVCapturePhotoOutput()
26
+
27
+ private var currentStableCounter: Int = 0
28
+ private var isCaptureInFlight = false
29
+
30
+ override init(frame: CGRect) {
31
+ super.init(frame: frame)
32
+ commonInit()
33
+ }
34
+
35
+ required init?(coder: NSCoder) {
36
+ super.init(coder: coder)
37
+ commonInit()
38
+ }
39
+
40
+ private func commonInit() {
41
+ backgroundColor = .black
42
+ configurePreviewLayer()
43
+ configureSession()
44
+ }
45
+
46
+ private func configurePreviewLayer() {
47
+ let layer = AVCaptureVideoPreviewLayer(session: session)
48
+ layer.videoGravity = .resizeAspectFill
49
+ self.layer.insertSublayer(layer, at: 0)
50
+ previewLayer = layer
51
+ }
52
+
53
+ private func configureSession() {
54
+ sessionQueue.async { [weak self] in
55
+ guard let self else { return }
56
+
57
+ session.beginConfiguration()
58
+ session.sessionPreset = .photo
59
+
60
+ defer {
61
+ session.commitConfiguration()
62
+ if !session.isRunning {
63
+ session.startRunning()
64
+ }
65
+ }
66
+
67
+ guard
68
+ let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
69
+ let videoInput = try? AVCaptureDeviceInput(device: videoDevice),
70
+ session.canAddInput(videoInput)
71
+ else {
72
+ NSLog("[RNRDocScanner] Unable to create AVCaptureDeviceInput")
73
+ return
74
+ }
75
+
76
+ session.addInput(videoInput)
77
+
78
+ if session.canAddOutput(photoOutput) {
79
+ session.addOutput(photoOutput)
80
+ }
81
+
82
+ // TODO: Wire up AVCaptureVideoDataOutput + rectangle detection pipeline.
83
+ }
84
+ }
85
+
86
+ override func layoutSubviews() {
87
+ super.layoutSubviews()
88
+ previewLayer?.frame = bounds
89
+ }
90
+
91
+ private func updateTorchMode() {
92
+ sessionQueue.async { [weak self] in
93
+ guard
94
+ let self,
95
+ let device = self.videoDevice(for: .back),
96
+ device.hasTorch
97
+ else {
98
+ return
99
+ }
100
+
101
+ do {
102
+ try device.lockForConfiguration()
103
+ device.torchMode = self.enableTorch ? .on : .off
104
+ device.unlockForConfiguration()
105
+ } catch {
106
+ NSLog("[RNRDocScanner] Failed to update torch mode: \(error)")
107
+ }
108
+ }
109
+ }
110
+
111
+ private func videoDevice(for position: AVCaptureDevice.Position) -> AVCaptureDevice? {
112
+ if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position) {
113
+ return device
114
+ }
115
+ return AVCaptureDevice.devices(for: .video).first(where: { $0.position == position })
116
+ }
117
+
118
+ func handleDetectedRectangle(_ rectangle: VNRectangleObservation?, frameSize: CGSize) {
119
+ guard let onRectangleDetect else { return }
120
+
121
+ let payload: [String: Any?]
122
+ if let rectangle {
123
+ let points = [
124
+ point(from: rectangle.topLeft, frameSize: frameSize),
125
+ point(from: rectangle.topRight, frameSize: frameSize),
126
+ point(from: rectangle.bottomRight, frameSize: frameSize),
127
+ point(from: rectangle.bottomLeft, frameSize: frameSize),
128
+ ]
129
+
130
+ currentStableCounter = min(currentStableCounter + 1, Int(truncating: detectionCountBeforeCapture))
131
+ payload = [
132
+ "rectangleCoordinates": [
133
+ "topLeft": ["x": points[0].x, "y": points[0].y],
134
+ "topRight": ["x": points[1].x, "y": points[1].y],
135
+ "bottomRight": ["x": points[2].x, "y": points[2].y],
136
+ "bottomLeft": ["x": points[3].x, "y": points[3].y],
137
+ ],
138
+ "stableCounter": currentStableCounter,
139
+ "frameWidth": frameSize.width,
140
+ "frameHeight": frameSize.height,
141
+ ]
142
+ } else {
143
+ currentStableCounter = 0
144
+ payload = [
145
+ "rectangleCoordinates": NSNull(),
146
+ "stableCounter": currentStableCounter,
147
+ "frameWidth": frameSize.width,
148
+ "frameHeight": frameSize.height,
149
+ ]
150
+ }
151
+
152
+ DispatchQueue.main.async {
153
+ onRectangleDetect(payload.compactMapValues { $0 })
154
+ }
155
+ }
156
+
157
+ private func point(from normalizedPoint: CGPoint, frameSize: CGSize) -> CGPoint {
158
+ CGPoint(x: normalizedPoint.x * frameSize.width, y: (1 - normalizedPoint.y) * frameSize.height)
159
+ }
160
+
161
+ func capture(completion: @escaping (Result<RNRDocScannerCaptureResult, Error>) -> Void) {
162
+ sessionQueue.async { [weak self] in
163
+ guard let self else { return }
164
+
165
+ if isCaptureInFlight {
166
+ completion(.failure(RNRDocScannerError.captureInProgress))
167
+ return
168
+ }
169
+
170
+ guard photoOutput.connections.isEmpty == false else {
171
+ completion(.failure(RNRDocScannerError.captureUnavailable))
172
+ return
173
+ }
174
+
175
+ isCaptureInFlight = true
176
+
177
+ // TODO: Implement real capture logic; emit stub callback for now.
178
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
179
+ self.isCaptureInFlight = false
180
+ completion(.failure(RNRDocScannerError.notImplemented))
181
+ }
182
+ }
183
+ }
184
+
185
+ func resetStability() {
186
+ currentStableCounter = 0
187
+ }
188
+ }
189
+
190
+ struct RNRDocScannerCaptureResult {
191
+ let croppedImage: String?
192
+ let originalImage: String
193
+ let width: CGFloat
194
+ let height: CGFloat
195
+ }
196
+
197
+ enum RNRDocScannerError: Error {
198
+ case captureInProgress
199
+ case captureUnavailable
200
+ case notImplemented
201
+ case viewNotFound
202
+
203
+ var code: String {
204
+ switch self {
205
+ case .captureInProgress:
206
+ return "capture_in_progress"
207
+ case .captureUnavailable:
208
+ return "capture_unavailable"
209
+ case .notImplemented:
210
+ return "not_implemented"
211
+ case .viewNotFound:
212
+ return "view_not_found"
213
+ }
214
+ }
215
+
216
+ var message: String {
217
+ switch self {
218
+ case .captureInProgress:
219
+ return "A capture request is already in flight."
220
+ case .captureUnavailable:
221
+ return "Photo output is not configured yet."
222
+ case .notImplemented:
223
+ return "Native capture is not implemented yet."
224
+ case .viewNotFound:
225
+ return "Unable to locate the native DocScanner view."
226
+ }
227
+ }
228
+ }
@@ -0,0 +1,21 @@
1
+ #import <React/RCTBridge.h>
2
+ #import <React/RCTUIManager.h>
3
+ #import <React/RCTViewManager.h>
4
+
5
+ #import "react-native-rectangle-doc-scanner-Swift.h"
6
+
7
+ @interface RCT_EXTERN_MODULE(RNRDocScannerViewManager, RCTViewManager)
8
+ RCT_EXPORT_VIEW_PROPERTY(detectionCountBeforeCapture, NSNumber)
9
+ RCT_EXPORT_VIEW_PROPERTY(autoCapture, BOOL)
10
+ RCT_EXPORT_VIEW_PROPERTY(enableTorch, BOOL)
11
+ RCT_EXPORT_VIEW_PROPERTY(quality, NSNumber)
12
+ RCT_EXPORT_VIEW_PROPERTY(useBase64, BOOL)
13
+ RCT_EXPORT_VIEW_PROPERTY(onRectangleDetect, RCTDirectEventBlock)
14
+ RCT_EXPORT_VIEW_PROPERTY(onPictureTaken, RCTDirectEventBlock)
15
+
16
+ RCT_EXTERN_METHOD(capture:(nonnull NSNumber *)reactTag
17
+ resolver:(RCTPromiseResolveBlock)resolve
18
+ rejecter:(RCTPromiseRejectBlock)reject)
19
+
20
+ RCT_EXTERN_METHOD(reset:(nonnull NSNumber *)reactTag)
21
+ @end
@@ -0,0 +1,47 @@
1
+ import Foundation
2
+ import React
3
+
4
+ @objc(RNRDocScannerViewManager)
5
+ class RNRDocScannerViewManager: RCTViewManager {
6
+ override static func requiresMainQueueSetup() -> Bool {
7
+ true
8
+ }
9
+
10
+ override func view() -> UIView! {
11
+ RNRDocScannerView()
12
+ }
13
+
14
+ @objc func capture(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
15
+ bridge.uiManager.addUIBlock { _, viewRegistry in
16
+ guard let view = viewRegistry?[reactTag] as? RNRDocScannerView else {
17
+ reject(RNRDocScannerError.viewNotFound.code, RNRDocScannerError.viewNotFound.message, nil)
18
+ return
19
+ }
20
+
21
+ view.capture { result in
22
+ switch result {
23
+ case let .success(payload):
24
+ resolve([
25
+ "croppedImage": payload.croppedImage as Any,
26
+ "initialImage": payload.originalImage,
27
+ "width": payload.width,
28
+ "height": payload.height,
29
+ ])
30
+ case let .failure(error as RNRDocScannerError):
31
+ reject(error.code, error.message, error)
32
+ case let .failure(error):
33
+ reject("capture_failed", error.localizedDescription, error)
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ @objc func reset(_ reactTag: NSNumber) {
40
+ bridge.uiManager.addUIBlock { _, viewRegistry in
41
+ guard let view = viewRegistry?[reactTag] as? RNRDocScannerView else {
42
+ return
43
+ }
44
+ view.resetStability()
45
+ }
46
+ }
47
+ }
package/package.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "0.65.0",
3
+ "version": "0.69.0",
4
+ "description": "Native-backed document scanner for React Native with customizable overlays.",
5
+ "license": "MIT",
4
6
  "main": "dist/index.js",
5
7
  "types": "dist/index.d.ts",
6
8
  "repository": {
@@ -18,14 +20,13 @@
18
20
  "peerDependencies": {
19
21
  "@shopify/react-native-skia": "*",
20
22
  "react": "*",
21
- "react-native": "*"
23
+ "react-native": "*",
24
+ "react-native-perspective-image-cropper": "*"
22
25
  },
23
26
  "devDependencies": {
24
27
  "@types/react": "^18.2.41",
25
28
  "@types/react-native": "0.73.0",
26
29
  "typescript": "^5.3.3"
27
30
  },
28
- "dependencies": {
29
- "react-native-document-scanner-plugin": "^1.6.3"
30
- }
31
+ "dependencies": {}
31
32
  }
@@ -0,0 +1,22 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'react-native-rectangle-doc-scanner'
7
+ s.version = package['version']
8
+ s.summary = package.fetch('description', 'Document scanner with native camera overlay support for React Native.')
9
+ s.homepage = package['homepage'] || 'https://github.com/danchew90/react-native-rectangle-doc-scanner'
10
+ s.license = package['license'] || { :type => 'MIT' }
11
+ s.author = package['author'] || { 'react-native-rectangle-doc-scanner' => 'opensource@example.com' }
12
+ s.source = { :git => package.dig('repository', 'url') || s.homepage, :tag => "v#{s.version}" }
13
+
14
+ s.platform = :ios, '13.0'
15
+ s.swift_version = '5.0'
16
+
17
+ s.source_files = 'ios/**/*.{h,m,mm,swift}'
18
+ s.public_header_files = 'ios/**/*.h'
19
+ s.requires_arc = true
20
+
21
+ s.dependency 'React-Core'
22
+ end
@@ -1,21 +1,36 @@
1
1
  import React, {
2
- ComponentType,
3
2
  ReactNode,
3
+ forwardRef,
4
4
  useCallback,
5
+ useImperativeHandle,
5
6
  useMemo,
6
7
  useRef,
7
8
  useState,
8
9
  } from 'react';
9
10
  import {
10
- LayoutChangeEvent,
11
+ findNodeHandle,
12
+ NativeModules,
13
+ requireNativeComponent,
11
14
  StyleSheet,
12
15
  TouchableOpacity,
13
16
  View,
14
17
  } from 'react-native';
15
- import DocumentScanner from 'react-native-document-scanner-plugin';
18
+ import type { NativeSyntheticEvent } from 'react-native';
16
19
  import { Overlay } from './utils/overlay';
17
20
  import type { Point } from './types';
18
21
 
22
+ const MODULE_NAME = 'RNRDocScannerModule';
23
+ const VIEW_NAME = 'RNRDocScannerView';
24
+
25
+ const NativeDocScannerModule = NativeModules[MODULE_NAME];
26
+
27
+ if (!NativeDocScannerModule) {
28
+ const fallbackMessage =
29
+ `The native module '${MODULE_NAME}' is not linked. Make sure you have run pod install, ` +
30
+ `synced Gradle, and rebuilt the app after installing 'react-native-rectangle-doc-scanner'.`;
31
+ throw new Error(fallbackMessage);
32
+ }
33
+
19
34
  type NativeRectangle = {
20
35
  topLeft: Point;
21
36
  topRight: Point;
@@ -23,43 +38,39 @@ type NativeRectangle = {
23
38
  bottomLeft: Point;
24
39
  };
25
40
 
26
- type NativeRectangleEvent = {
27
- rectangleCoordinates?: NativeRectangle | null;
28
- stableCounter?: number;
41
+ type RectangleEvent = {
42
+ rectangleCoordinates: NativeRectangle | null;
43
+ stableCounter: number;
44
+ frameWidth: number;
45
+ frameHeight: number;
29
46
  };
30
47
 
31
- type NativeCaptureResult = {
32
- croppedImage?: string;
48
+ type PictureEvent = {
49
+ croppedImage?: string | null;
33
50
  initialImage?: string;
34
51
  width?: number;
35
52
  height?: number;
36
53
  };
37
54
 
38
- type DocumentScannerHandle = {
39
- capture: () => Promise<NativeCaptureResult>;
40
- };
41
-
42
- type NativeDocumentScannerProps = {
55
+ type NativeDocScannerProps = {
43
56
  style?: object;
44
- overlayColor?: string;
45
57
  detectionCountBeforeCapture?: number;
58
+ autoCapture?: boolean;
46
59
  enableTorch?: boolean;
47
- hideControls?: boolean;
48
- useBase64?: boolean;
49
60
  quality?: number;
50
- onRectangleDetect?: (event: NativeRectangleEvent) => void;
51
- onPictureTaken?: (event: NativeCaptureResult) => void;
61
+ useBase64?: boolean;
62
+ onRectangleDetect?: (event: NativeSyntheticEvent<RectangleEvent>) => void;
63
+ onPictureTaken?: (event: NativeSyntheticEvent<PictureEvent>) => void;
64
+ };
65
+
66
+ type DocScannerHandle = {
67
+ capture: () => Promise<PictureEvent>;
68
+ reset: () => void;
52
69
  };
53
70
 
54
- const NativeDocumentScanner = DocumentScanner as unknown as ComponentType<
55
- NativeDocumentScannerProps & { ref?: React.Ref<DocumentScannerHandle> }
56
- >;
71
+ const NativeDocScanner = requireNativeComponent<NativeDocScannerProps>(VIEW_NAME);
72
+ type NativeDocScannerInstance = React.ElementRef<typeof NativeDocScanner>;
57
73
 
58
- /**
59
- * Detection configuration is no longer used now that the native
60
- * implementation handles edge detection. Keeping it for backwards
61
- * compatibility with existing consumer code.
62
- */
63
74
  export interface DetectionConfig {
64
75
  processingWidth?: number;
65
76
  cannyLowThreshold?: number;
@@ -87,22 +98,23 @@ interface Props {
87
98
  const DEFAULT_OVERLAY_COLOR = '#e7a649';
88
99
  const GRID_COLOR_FALLBACK = 'rgba(231, 166, 73, 0.35)';
89
100
 
90
- export const DocScanner: React.FC<Props> = ({
101
+ export const DocScanner = forwardRef<DocScannerHandle, Props>(({
91
102
  onCapture,
92
103
  overlayColor = DEFAULT_OVERLAY_COLOR,
93
104
  autoCapture = true,
94
105
  minStableFrames = 8,
95
106
  enableTorch = false,
96
- quality,
107
+ quality = 90,
97
108
  useBase64 = false,
98
109
  children,
99
110
  showGrid = true,
100
111
  gridColor,
101
112
  gridLineWidth = 2,
102
- }) => {
103
- const scannerRef = useRef<DocumentScannerHandle | null>(null);
113
+ }, ref) => {
114
+ const viewRef = useRef<NativeDocScannerInstance | null>(null);
104
115
  const capturingRef = useRef(false);
105
116
  const [quad, setQuad] = useState<Point[] | null>(null);
117
+ const [stable, setStable] = useState(0);
106
118
  const [frameSize, setFrameSize] = useState<{ width: number; height: number } | null>(null);
107
119
 
108
120
  const effectiveGridColor = useMemo(
@@ -110,80 +122,142 @@ export const DocScanner: React.FC<Props> = ({
110
122
  [gridColor],
111
123
  );
112
124
 
113
- const handleLayout = useCallback((event: LayoutChangeEvent) => {
114
- const { width, height } = event.nativeEvent.layout;
115
- if (width > 0 && height > 0) {
116
- setFrameSize({ width, height });
125
+ const ensureViewHandle = useCallback(() => {
126
+ const nodeHandle = findNodeHandle(viewRef.current);
127
+ if (!nodeHandle) {
128
+ throw new Error('Unable to obtain native view handle for DocScanner.');
117
129
  }
130
+ return nodeHandle;
118
131
  }, []);
119
132
 
120
- const handleRectangleDetect = useCallback((event: NativeRectangleEvent) => {
121
- const coordinates = event?.rectangleCoordinates;
122
-
123
- if (!coordinates) {
124
- setQuad(null);
125
- return;
133
+ const resetNativeStability = useCallback(() => {
134
+ try {
135
+ const handle = ensureViewHandle();
136
+ NativeDocScannerModule.reset(handle);
137
+ } catch (error) {
138
+ console.warn('[DocScanner] unable to reset native stability', error);
126
139
  }
140
+ }, [ensureViewHandle]);
127
141
 
128
- const nextQuad: Point[] = [
129
- coordinates.topLeft,
130
- coordinates.topRight,
131
- coordinates.bottomRight,
132
- coordinates.bottomLeft,
133
- ];
134
-
135
- setQuad(nextQuad);
136
- }, []);
137
-
138
- const handlePictureTaken = useCallback(
139
- (event: NativeCaptureResult) => {
142
+ const emitCaptureResult = useCallback(
143
+ (payload: PictureEvent) => {
140
144
  capturingRef.current = false;
141
145
 
142
- const path = event?.croppedImage ?? event?.initialImage;
146
+ const path = payload.croppedImage ?? payload.initialImage;
143
147
  if (!path) {
144
148
  return;
145
149
  }
146
150
 
147
- const width = event?.width ?? frameSize?.width ?? 0;
148
- const height = event?.height ?? frameSize?.height ?? 0;
149
-
151
+ const width = payload.width ?? frameSize?.width ?? 0;
152
+ const height = payload.height ?? frameSize?.height ?? 0;
150
153
  onCapture?.({
151
154
  path,
152
155
  quad,
153
156
  width,
154
157
  height,
155
158
  });
159
+ setStable(0);
160
+ resetNativeStability();
161
+ },
162
+ [frameSize, onCapture, quad, resetNativeStability],
163
+ );
164
+
165
+ const handleRectangleDetect = useCallback(
166
+ (event: NativeSyntheticEvent<RectangleEvent>) => {
167
+ const { rectangleCoordinates, stableCounter, frameWidth, frameHeight } = event.nativeEvent;
168
+ setStable(stableCounter);
169
+ setFrameSize({ width: frameWidth, height: frameHeight });
170
+
171
+ if (!rectangleCoordinates) {
172
+ setQuad(null);
173
+ return;
174
+ }
175
+
176
+ setQuad([
177
+ rectangleCoordinates.topLeft,
178
+ rectangleCoordinates.topRight,
179
+ rectangleCoordinates.bottomRight,
180
+ rectangleCoordinates.bottomLeft,
181
+ ]);
182
+
183
+ if (autoCapture && stableCounter >= minStableFrames) {
184
+ triggerCapture();
185
+ }
186
+ },
187
+ [autoCapture, minStableFrames],
188
+ );
189
+
190
+ const handlePictureTaken = useCallback(
191
+ (event: NativeSyntheticEvent<PictureEvent>) => {
192
+ emitCaptureResult(event.nativeEvent);
156
193
  },
157
- [frameSize, onCapture, quad],
194
+ [emitCaptureResult],
158
195
  );
159
196
 
197
+ const captureNative = useCallback((): Promise<PictureEvent> => {
198
+ if (capturingRef.current) {
199
+ return Promise.reject(new Error('capture_in_progress'));
200
+ }
201
+
202
+ try {
203
+ const handle = ensureViewHandle();
204
+ capturingRef.current = true;
205
+ return NativeDocScannerModule.capture(handle)
206
+ .then((result: PictureEvent) => {
207
+ emitCaptureResult(result);
208
+ return result;
209
+ })
210
+ .catch((error: Error) => {
211
+ capturingRef.current = false;
212
+ throw error;
213
+ });
214
+ } catch (error) {
215
+ capturingRef.current = false;
216
+ return Promise.reject(error);
217
+ }
218
+ }, [emitCaptureResult, ensureViewHandle]);
219
+
220
+ const triggerCapture = useCallback(() => {
221
+ if (capturingRef.current) {
222
+ return;
223
+ }
224
+
225
+ captureNative().catch((error: Error) => {
226
+ console.warn('[DocScanner] capture failed', error);
227
+ });
228
+ }, [captureNative]);
229
+
160
230
  const handleManualCapture = useCallback(() => {
161
- if (autoCapture || capturingRef.current || !scannerRef.current) {
231
+ if (autoCapture) {
162
232
  return;
163
233
  }
234
+ captureNative().catch((error: Error) => {
235
+ console.warn('[DocScanner] manual capture failed', error);
236
+ });
237
+ }, [autoCapture, captureNative]);
164
238
 
165
- capturingRef.current = true;
166
- scannerRef.current
167
- .capture()
168
- .catch((error) => {
169
- console.warn('[DocScanner] manual capture failed', error);
170
- capturingRef.current = false;
171
- });
172
- }, [autoCapture]);
239
+ useImperativeHandle(
240
+ ref,
241
+ () => ({
242
+ capture: captureNative,
243
+ reset: () => {
244
+ setStable(0);
245
+ resetNativeStability();
246
+ },
247
+ }),
248
+ [captureNative, resetNativeStability],
249
+ );
173
250
 
174
251
  return (
175
- <View style={styles.container} onLayout={handleLayout}>
176
- <NativeDocumentScanner
177
- ref={(instance) => {
178
- scannerRef.current = instance as DocumentScannerHandle | null;
179
- }}
252
+ <View style={styles.container}>
253
+ <NativeDocScanner
254
+ ref={viewRef}
180
255
  style={StyleSheet.absoluteFill}
181
- overlayColor="transparent"
182
- detectionCountBeforeCapture={autoCapture ? minStableFrames : 10000}
256
+ detectionCountBeforeCapture={minStableFrames}
257
+ autoCapture={autoCapture}
183
258
  enableTorch={enableTorch}
184
- hideControls
185
- useBase64={useBase64}
186
259
  quality={quality}
260
+ useBase64={useBase64}
187
261
  onRectangleDetect={handleRectangleDetect}
188
262
  onPictureTaken={handlePictureTaken}
189
263
  />
@@ -201,11 +275,12 @@ export const DocScanner: React.FC<Props> = ({
201
275
  {children}
202
276
  </View>
203
277
  );
204
- };
278
+ });
205
279
 
206
280
  const styles = StyleSheet.create({
207
281
  container: {
208
282
  flex: 1,
283
+ backgroundColor: '#000',
209
284
  },
210
285
  button: {
211
286
  position: 'absolute',
@@ -217,3 +292,5 @@ const styles = StyleSheet.create({
217
292
  backgroundColor: '#fff',
218
293
  },
219
294
  });
295
+
296
+ export type { DocScannerHandle };