rn-document-scanner-vision 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hoang Thien
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # React Native Document Scanner
2
+
3
+ A React Native library for scanning documents on iOS and Android. This library provides a native document scanning experience using VisionKit for iOS and a document scanner library for Android.
4
+
5
+ ## Features
6
+
7
+ - 📷 Native document scanning on iOS (VisionKit) and Android
8
+ - 🔲 Automatic document detection and edge detection
9
+ - ✂️ Automatic cropping and perspective correction
10
+ - 📄 Support for scanning multiple documents (Android)
11
+ - 🎨 Adjustable image quality
12
+ - 📤 Export as base64 or file paths
13
+ - 🎯 User-friendly interface with native look and feel
14
+ - ⚡ Full TypeScript support
15
+
16
+ ## Installation
17
+
18
+ ```sh
19
+ npm install react-native-document-scanner
20
+ ```
21
+
22
+ or
23
+
24
+ ```sh
25
+ yarn add react-native-document-scanner
26
+ ```
27
+
28
+ ### iOS Setup
29
+
30
+ 1. **Minimum iOS Version**: iOS 13.0+
31
+
32
+ 2. **Install CocoaPods dependencies**:
33
+ ```sh
34
+ cd ios && pod install
35
+ ```
36
+
37
+ 3. **Add Camera Permission**: Add the following to your `ios/YourApp/Info.plist`:
38
+ ```xml
39
+ <key>NSCameraUsageDescription</key>
40
+ <string>We need camera access to scan documents</string>
41
+ ```
42
+
43
+ ### Android Setup
44
+
45
+ 1. **Minimum SDK**: 21
46
+
47
+ 2. **Add Camera Permission**: The library already includes the necessary permissions in its AndroidManifest.xml, but make sure your app requests runtime permissions. Add the following to your `android/app/src/main/AndroidManifest.xml` if not already present:
48
+ ```xml
49
+ <uses-permission android:name="android.permission.CAMERA" />
50
+ ```
51
+
52
+ 3. **Request Runtime Permissions**: In your React Native component, request camera permission before scanning:
53
+ ```javascript
54
+ import { PermissionsAndroid, Platform } from 'react-native';
55
+
56
+ async function requestCameraPermission() {
57
+ if (Platform.OS === 'android') {
58
+ try {
59
+ const granted = await PermissionsAndroid.request(
60
+ PermissionsAndroid.PERMISSIONS.CAMERA,
61
+ {
62
+ title: 'Camera Permission',
63
+ message: 'App needs camera permission to scan documents',
64
+ buttonNeutral: 'Ask Me Later',
65
+ buttonNegative: 'Cancel',
66
+ buttonPositive: 'OK',
67
+ }
68
+ );
69
+ return granted === PermissionsAndroid.RESULTS.GRANTED;
70
+ } catch (err) {
71
+ console.warn(err);
72
+ return false;
73
+ }
74
+ }
75
+ return true;
76
+ }
77
+ ```
78
+
79
+ ## Usage
80
+
81
+ ### Basic Example
82
+
83
+ ```typescript
84
+ import React from 'react';
85
+ import { Button, View, Text, Image } from 'react-native';
86
+ import { DocumentScanner } from 'react-native-document-scanner';
87
+
88
+ function App() {
89
+ const [scannedImages, setScannedImages] = React.useState<string[]>([]);
90
+
91
+ const handleScan = async () => {
92
+ try {
93
+ const result = await DocumentScanner.scanDocument({
94
+ croppedImageQuality: 100,
95
+ responseType: 'imageFilePath',
96
+ });
97
+
98
+ if (result.status === 'success') {
99
+ setScannedImages(result.scannedImages);
100
+ console.log('Scanned images:', result.scannedImages);
101
+ } else {
102
+ console.log('User cancelled the scan');
103
+ }
104
+ } catch (error) {
105
+ console.error('Error scanning document:', error);
106
+ }
107
+ };
108
+
109
+ return (
110
+ <View>
111
+ <Button title="Scan Document" onPress={handleScan} />
112
+ {scannedImages.map((image, index) => (
113
+ <Image key={index} source={{ uri: image }} style={{ width: 200, height: 300 }} />
114
+ ))}
115
+ </View>
116
+ );
117
+ }
118
+ ```
119
+
120
+ ### Advanced Example with All Options
121
+
122
+ ```typescript
123
+ import { DocumentScanner } from 'react-native-document-scanner';
124
+
125
+ async function scanMultipleDocuments() {
126
+ try {
127
+ const result = await DocumentScanner.scanDocument({
128
+ maxNumDocuments: 3, // Android only: max number of documents to scan
129
+ letUserAdjustCrop: true, // Android only: allow manual crop adjustment
130
+ croppedImageQuality: 100, // Image quality (0-100)
131
+ responseType: 'base64', // 'base64' or 'imageFilePath'
132
+ });
133
+
134
+ if (result.status === 'success') {
135
+ // Process scanned images
136
+ result.scannedImages.forEach((image, index) => {
137
+ console.log(`Image ${index + 1}:`, image.substring(0, 50) + '...');
138
+ });
139
+ }
140
+ } catch (error) {
141
+ console.error('Scan failed:', error);
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## API Reference
147
+
148
+ ### `DocumentScanner.scanDocument(options?)`
149
+
150
+ Starts the document scanning process.
151
+
152
+ #### Parameters
153
+
154
+ - `options` (optional): Configuration object
155
+
156
+ | Option | Type | Default | Platform | Description |
157
+ |--------|------|---------|----------|-------------|
158
+ | `maxNumDocuments` | `number` | `1` | Android | Maximum number of documents to scan |
159
+ | `letUserAdjustCrop` | `boolean` | `true` | Android | Allow user to manually adjust crop |
160
+ | `croppedImageQuality` | `number` | `100` | Both | Image quality from 0 to 100 |
161
+ | `responseType` | `'base64' \| 'imageFilePath'` | `'imageFilePath'` | Both | Format of returned images |
162
+
163
+ #### Returns
164
+
165
+ `Promise<ScanDocumentResponse>`
166
+
167
+ ```typescript
168
+ interface ScanDocumentResponse {
169
+ scannedImages: string[]; // Array of base64 strings or file paths
170
+ status: 'success' | 'cancel';
171
+ }
172
+ ```
173
+
174
+ #### Errors
175
+
176
+ The promise will be rejected with an error if:
177
+ - Camera permission is denied
178
+ - Document scanning is not supported on the device
179
+ - An error occurs during scanning
180
+ - Device is running iOS < 13.0
181
+
182
+ ## TypeScript Support
183
+
184
+ This library is written in TypeScript and includes type definitions out of the box.
185
+
186
+ ```typescript
187
+ import {
188
+ DocumentScanner,
189
+ ScanDocumentOptions,
190
+ ScanDocumentResponse
191
+ } from 'react-native-document-scanner';
192
+ ```
193
+
194
+ ## Platform Differences
195
+
196
+ ### iOS
197
+ - Uses VisionKit's `VNDocumentCameraViewController`
198
+ - Automatically detects document edges
199
+ - Provides built-in crop and perspective correction
200
+ - Returns multiple pages if user scans multiple documents
201
+ - Options `maxNumDocuments` and `letUserAdjustCrop` are ignored
202
+
203
+ ### Android
204
+ - Uses `com.websitebeaver:documentscanner` library
205
+ - Supports configurable maximum number of documents
206
+ - Allows user to adjust crop boundaries
207
+ - Supports all configuration options
208
+
209
+ ## Troubleshooting
210
+
211
+ ### iOS Issues
212
+
213
+ **Problem**: "Document scanning is not supported on this device"
214
+ - **Solution**: VisionKit document scanning requires iOS 13.0 or later. Ensure your deployment target is iOS 13.0+.
215
+
216
+ **Problem**: Camera permission denied
217
+ - **Solution**: Make sure you've added `NSCameraUsageDescription` to your Info.plist.
218
+
219
+ **Problem**: Module not found
220
+ - **Solution**: Run `cd ios && pod install && cd ..` and rebuild your app.
221
+
222
+ ### Android Issues
223
+
224
+ **Problem**: Camera permission denied
225
+ - **Solution**: Request camera permission at runtime using `PermissionsAndroid`.
226
+
227
+ **Problem**: Build errors with Gradle
228
+ - **Solution**: Ensure your `android/build.gradle` has `minSdkVersion` 21 or higher.
229
+
230
+ **Problem**: "Failed to initialize document scanner"
231
+ - **Solution**: Make sure the camera permission is granted before calling `scanDocument`.
232
+
233
+ ### General Issues
234
+
235
+ **Problem**: "The package doesn't seem to be linked"
236
+ - **Solution**:
237
+ - For React Native 0.60+: Autolinking should work automatically. Try cleaning and rebuilding.
238
+ - iOS: Run `cd ios && pod install && cd ..`
239
+ - Android: Clean gradle cache `cd android && ./gradlew clean && cd ..`
240
+ - Rebuild the app completely
241
+
242
+ ## Contributing
243
+
244
+ Contributions are welcome! Please feel free to submit a Pull Request.
245
+
246
+ ## License
247
+
248
+ MIT License - see the [LICENSE](LICENSE) file for details.
249
+
250
+ ## Credits
251
+
252
+ This library is based on the Capacitor Document Scanner plugin and uses:
253
+ - iOS: VisionKit framework by Apple
254
+ - Android: [Document Scanner Library](https://github.com/websitebeaver/documentscanner) by WebsiteBeaver
255
+
256
+ ## Support
257
+
258
+ For issues and feature requests, please use the [GitHub issue tracker](https://github.com/hoangthien123/react-native-document-scanner/issues).
@@ -0,0 +1,43 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ }
4
+
5
+ android {
6
+ compileSdk 34
7
+
8
+ namespace "com.rndocumentscanner"
9
+
10
+ defaultConfig {
11
+ minSdk 21
12
+ targetSdk 34
13
+ versionCode 1
14
+ versionName "1.0"
15
+ }
16
+
17
+ buildTypes {
18
+ release {
19
+ minifyEnabled false
20
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21
+ }
22
+ }
23
+
24
+ compileOptions {
25
+ sourceCompatibility JavaVersion.VERSION_11
26
+ targetCompatibility JavaVersion.VERSION_11
27
+ }
28
+
29
+ lint {
30
+ abortOnError false
31
+ }
32
+ }
33
+
34
+ repositories {
35
+ google()
36
+ mavenCentral()
37
+ }
38
+
39
+ dependencies {
40
+ implementation 'com.facebook.react:react-android'
41
+ implementation "androidx.appcompat:appcompat:1.7.0"
42
+ implementation "com.websitebeaver:documentscanner:1.3.4"
43
+ }
@@ -0,0 +1,7 @@
1
+ distributionBase=GRADLE_USER_HOME
2
+ distributionPath=wrapper/dists
3
+ distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
4
+ networkTimeout=10000
5
+ validateDistributionUrl=true
6
+ zipStoreBase=GRADLE_USER_HOME
7
+ zipStorePath=wrapper/dists
@@ -0,0 +1,248 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # Copyright © 2015 the original authors.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # https://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # SPDX-License-Identifier: Apache-2.0
19
+ #
20
+
21
+ ##############################################################################
22
+ #
23
+ # Gradle start up script for POSIX generated by Gradle.
24
+ #
25
+ # Important for running:
26
+ #
27
+ # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28
+ # noncompliant, but you have some other compliant shell such as ksh or
29
+ # bash, then to run this script, type that shell name before the whole
30
+ # command line, like:
31
+ #
32
+ # ksh Gradle
33
+ #
34
+ # Busybox and similar reduced shells will NOT work, because this script
35
+ # requires all of these POSIX shell features:
36
+ # * functions;
37
+ # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38
+ # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39
+ # * compound commands having a testable exit status, especially «case»;
40
+ # * various built-in commands including «command», «set», and «ulimit».
41
+ #
42
+ # Important for patching:
43
+ #
44
+ # (2) This script targets any POSIX shell, so it avoids extensions provided
45
+ # by Bash, Ksh, etc; in particular arrays are avoided.
46
+ #
47
+ # The "traditional" practice of packing multiple parameters into a
48
+ # space-separated string is a well documented source of bugs and security
49
+ # problems, so this is (mostly) avoided, by progressively accumulating
50
+ # options in "$@", and eventually passing that to Java.
51
+ #
52
+ # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53
+ # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54
+ # see the in-line comments for details.
55
+ #
56
+ # There are tweaks for specific operating systems such as AIX, CygWin,
57
+ # Darwin, MinGW, and NonStop.
58
+ #
59
+ # (3) This script is generated from the Groovy template
60
+ # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61
+ # within the Gradle project.
62
+ #
63
+ # You can find Gradle at https://github.com/gradle/gradle/.
64
+ #
65
+ ##############################################################################
66
+
67
+ # Attempt to set APP_HOME
68
+
69
+ # Resolve links: $0 may be a link
70
+ app_path=$0
71
+
72
+ # Need this for daisy-chained symlinks.
73
+ while
74
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75
+ [ -h "$app_path" ]
76
+ do
77
+ ls=$( ls -ld "$app_path" )
78
+ link=${ls#*' -> '}
79
+ case $link in #(
80
+ /*) app_path=$link ;; #(
81
+ *) app_path=$APP_HOME$link ;;
82
+ esac
83
+ done
84
+
85
+ # This is normally unused
86
+ # shellcheck disable=SC2034
87
+ APP_BASE_NAME=${0##*/}
88
+ # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89
+ APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
90
+
91
+ # Use the maximum available, or set MAX_FD != -1 to use that value.
92
+ MAX_FD=maximum
93
+
94
+ warn () {
95
+ echo "$*"
96
+ } >&2
97
+
98
+ die () {
99
+ echo
100
+ echo "$*"
101
+ echo
102
+ exit 1
103
+ } >&2
104
+
105
+ # OS specific support (must be 'true' or 'false').
106
+ cygwin=false
107
+ msys=false
108
+ darwin=false
109
+ nonstop=false
110
+ case "$( uname )" in #(
111
+ CYGWIN* ) cygwin=true ;; #(
112
+ Darwin* ) darwin=true ;; #(
113
+ MSYS* | MINGW* ) msys=true ;; #(
114
+ NONSTOP* ) nonstop=true ;;
115
+ esac
116
+
117
+
118
+
119
+ # Determine the Java command to use to start the JVM.
120
+ if [ -n "$JAVA_HOME" ] ; then
121
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
122
+ # IBM's JDK on AIX uses strange locations for the executables
123
+ JAVACMD=$JAVA_HOME/jre/sh/java
124
+ else
125
+ JAVACMD=$JAVA_HOME/bin/java
126
+ fi
127
+ if [ ! -x "$JAVACMD" ] ; then
128
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
129
+
130
+ Please set the JAVA_HOME variable in your environment to match the
131
+ location of your Java installation."
132
+ fi
133
+ else
134
+ JAVACMD=java
135
+ if ! command -v java >/dev/null 2>&1
136
+ then
137
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
138
+
139
+ Please set the JAVA_HOME variable in your environment to match the
140
+ location of your Java installation."
141
+ fi
142
+ fi
143
+
144
+ # Increase the maximum file descriptors if we can.
145
+ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
146
+ case $MAX_FD in #(
147
+ max*)
148
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
149
+ # shellcheck disable=SC2039,SC3045
150
+ MAX_FD=$( ulimit -H -n ) ||
151
+ warn "Could not query maximum file descriptor limit"
152
+ esac
153
+ case $MAX_FD in #(
154
+ '' | soft) :;; #(
155
+ *)
156
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
157
+ # shellcheck disable=SC2039,SC3045
158
+ ulimit -n "$MAX_FD" ||
159
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
160
+ esac
161
+ fi
162
+
163
+ # Collect all arguments for the java command, stacking in reverse order:
164
+ # * args from the command line
165
+ # * the main class name
166
+ # * -classpath
167
+ # * -D...appname settings
168
+ # * --module-path (only if needed)
169
+ # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
170
+
171
+ # For Cygwin or MSYS, switch paths to Windows format before running java
172
+ if "$cygwin" || "$msys" ; then
173
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
174
+
175
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
176
+
177
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
178
+ for arg do
179
+ if
180
+ case $arg in #(
181
+ -*) false ;; # don't mess with options #(
182
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183
+ [ -e "$t" ] ;; #(
184
+ *) false ;;
185
+ esac
186
+ then
187
+ arg=$( cygpath --path --ignore --mixed "$arg" )
188
+ fi
189
+ # Roll the args list around exactly as many times as the number of
190
+ # args, so each arg winds up back in the position where it started, but
191
+ # possibly modified.
192
+ #
193
+ # NB: a `for` loop captures its iteration list before it begins, so
194
+ # changing the positional parameters here affects neither the number of
195
+ # iterations, nor the values presented in `arg`.
196
+ shift # remove old arg
197
+ set -- "$@" "$arg" # push replacement arg
198
+ done
199
+ fi
200
+
201
+
202
+ # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203
+ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204
+
205
+ # Collect all arguments for the java command:
206
+ # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207
+ # and any embedded shellness will be escaped.
208
+ # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209
+ # treated as '${Hostname}' itself on the command line.
210
+
211
+ set -- \
212
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
213
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
214
+ "$@"
215
+
216
+ # Stop when "xargs" is not available.
217
+ if ! command -v xargs >/dev/null 2>&1
218
+ then
219
+ die "xargs is not available"
220
+ fi
221
+
222
+ # Use "xargs" to parse quoted args.
223
+ #
224
+ # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
225
+ #
226
+ # In Bash we could simply go:
227
+ #
228
+ # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
229
+ # set -- "${ARGS[@]}" "$@"
230
+ #
231
+ # but POSIX shell has neither arrays nor command substitution, so instead we
232
+ # post-process each arg (as a line of input to sed) to backslash-escape any
233
+ # character that might be a shell metacharacter, then use eval to reverse
234
+ # that process (while maintaining the separation between arguments), and wrap
235
+ # the whole thing up as a single "set" statement.
236
+ #
237
+ # This will of course break if any of these variables contains a newline or
238
+ # an unmatched quote.
239
+ #
240
+
241
+ eval "set -- $(
242
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
243
+ xargs -n1 |
244
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
245
+ tr '\n' ' '
246
+ )" '"$@"'
247
+
248
+ exec "$JAVACMD" "$@"
@@ -0,0 +1,5 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.CAMERA" />
3
+ <uses-feature android:name="android.hardware.camera" android:required="false" />
4
+ <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
5
+ </manifest>
@@ -0,0 +1,126 @@
1
+ package com.rndocumentscanner;
2
+
3
+ import android.app.Activity;
4
+ import android.content.Intent;
5
+ import androidx.activity.result.ActivityResult;
6
+
7
+ import com.facebook.react.bridge.ActivityEventListener;
8
+ import com.facebook.react.bridge.Arguments;
9
+ import com.facebook.react.bridge.BaseActivityEventListener;
10
+ import com.facebook.react.bridge.Promise;
11
+ import com.facebook.react.bridge.ReactApplicationContext;
12
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
13
+ import com.facebook.react.bridge.ReactMethod;
14
+ import com.facebook.react.bridge.ReadableMap;
15
+ import com.facebook.react.bridge.WritableArray;
16
+ import com.facebook.react.bridge.WritableMap;
17
+
18
+ import com.websitebeaver.documentscanner.DocumentScanner;
19
+ import com.websitebeaver.documentscanner.constants.DocumentScannerExtra;
20
+
21
+ import java.util.ArrayList;
22
+
23
+ public class RNDocumentScannerModule extends ReactContextBaseJavaModule {
24
+
25
+ private static final int DOCUMENT_SCAN_REQUEST = 1;
26
+ private Promise scanPromise;
27
+ private DocumentScanner documentScanner;
28
+
29
+ private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
30
+ @Override
31
+ public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
32
+ if (requestCode == DOCUMENT_SCAN_REQUEST) {
33
+ if (documentScanner != null) {
34
+ // Use a wrapper ActivityResult object
35
+ ActivityResult result = new ActivityResult(resultCode, data);
36
+ documentScanner.handleDocumentScanIntentResult(result);
37
+ }
38
+ }
39
+ }
40
+ };
41
+
42
+ public RNDocumentScannerModule(ReactApplicationContext reactContext) {
43
+ super(reactContext);
44
+ reactContext.addActivityEventListener(activityEventListener);
45
+ }
46
+
47
+ @Override
48
+ public String getName() {
49
+ return "RNDocumentScanner";
50
+ }
51
+
52
+ @ReactMethod
53
+ public void scanDocument(ReadableMap options, Promise promise) {
54
+ Activity activity = getCurrentActivity();
55
+
56
+ if (activity == null) {
57
+ promise.reject("NO_ACTIVITY", "Activity doesn't exist");
58
+ return;
59
+ }
60
+
61
+ scanPromise = promise;
62
+
63
+ // Get options with defaults
64
+ String responseType = options.hasKey("responseType") ? options.getString("responseType") : "imageFilePath";
65
+ Boolean letUserAdjustCrop = options.hasKey("letUserAdjustCrop") ? options.getBoolean("letUserAdjustCrop") : true;
66
+ Integer maxNumDocuments = options.hasKey("maxNumDocuments") ? options.getInt("maxNumDocuments") : 1;
67
+ Integer croppedImageQuality = options.hasKey("croppedImageQuality") ? options.getInt("croppedImageQuality") : 100;
68
+
69
+ try {
70
+ // Create document scanner with callbacks
71
+ documentScanner = new DocumentScanner(
72
+ activity,
73
+ (ArrayList<String> documentScanResults) -> {
74
+ // Success callback
75
+ WritableMap response = Arguments.createMap();
76
+ WritableArray scannedImages = Arguments.createArray();
77
+
78
+ for (String imagePath : documentScanResults) {
79
+ scannedImages.pushString(imagePath);
80
+ }
81
+
82
+ response.putArray("scannedImages", scannedImages);
83
+ response.putString("status", "success");
84
+
85
+ if (scanPromise != null) {
86
+ scanPromise.resolve(response);
87
+ scanPromise = null;
88
+ }
89
+ return null;
90
+ },
91
+ (String errorMessage) -> {
92
+ // Error callback
93
+ if (scanPromise != null) {
94
+ scanPromise.reject("SCAN_ERROR", errorMessage);
95
+ scanPromise = null;
96
+ }
97
+ return null;
98
+ },
99
+ () -> {
100
+ // Cancel callback
101
+ WritableMap response = Arguments.createMap();
102
+ WritableArray emptyArray = Arguments.createArray();
103
+ response.putArray("scannedImages", emptyArray);
104
+ response.putString("status", "cancel");
105
+
106
+ if (scanPromise != null) {
107
+ scanPromise.resolve(response);
108
+ scanPromise = null;
109
+ }
110
+ return null;
111
+ },
112
+ responseType,
113
+ letUserAdjustCrop,
114
+ maxNumDocuments,
115
+ croppedImageQuality
116
+ );
117
+
118
+ // Launch the document scanner
119
+ Intent scanIntent = documentScanner.createDocumentScanIntent();
120
+ activity.startActivityForResult(scanIntent, DOCUMENT_SCAN_REQUEST);
121
+
122
+ } catch (Exception e) {
123
+ promise.reject("INITIALIZATION_ERROR", "Failed to initialize document scanner: " + e.getMessage());
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,25 @@
1
+ package com.rndocumentscanner;
2
+
3
+ import com.facebook.react.ReactPackage;
4
+ import com.facebook.react.bridge.NativeModule;
5
+ import com.facebook.react.bridge.ReactApplicationContext;
6
+ import com.facebook.react.uimanager.ViewManager;
7
+
8
+ import java.util.ArrayList;
9
+ import java.util.Collections;
10
+ import java.util.List;
11
+
12
+ public class RNDocumentScannerPackage implements ReactPackage {
13
+
14
+ @Override
15
+ public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
16
+ List<NativeModule> modules = new ArrayList<>();
17
+ modules.add(new RNDocumentScannerModule(reactContext));
18
+ return modules;
19
+ }
20
+
21
+ @Override
22
+ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
23
+ return Collections.emptyList();
24
+ }
25
+ }
@@ -0,0 +1,5 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RNDocumentScanner : NSObject <RCTBridgeModule>
4
+
5
+ @end
@@ -0,0 +1,156 @@
1
+ #import "RNDocumentScanner.h"
2
+ #import <React/RCTLog.h>
3
+ #import <React/RCTUtils.h>
4
+ #import <VisionKit/VisionKit.h>
5
+
6
+ @interface RNDocumentScanner () <VNDocumentCameraViewControllerDelegate>
7
+
8
+ @property (nonatomic, strong) RCTPromiseResolveBlock resolveBlock;
9
+ @property (nonatomic, strong) RCTPromiseRejectBlock rejectBlock;
10
+ @property (nonatomic, assign) NSInteger croppedImageQuality;
11
+ @property (nonatomic, strong) NSString *responseType;
12
+
13
+ @end
14
+
15
+ @implementation RNDocumentScanner
16
+
17
+ RCT_EXPORT_MODULE();
18
+
19
+ + (BOOL)requiresMainQueueSetup
20
+ {
21
+ return YES;
22
+ }
23
+
24
+ RCT_EXPORT_METHOD(scanDocument:(NSDictionary *)options
25
+ resolver:(RCTPromiseResolveBlock)resolve
26
+ rejecter:(RCTPromiseRejectBlock)reject)
27
+ {
28
+ // Check iOS version
29
+ if (@available(iOS 13.0, *)) {
30
+ // Check if document scanning is supported
31
+ if (![VNDocumentCameraViewController isSupported]) {
32
+ reject(@"NOT_SUPPORTED", @"Document scanning is not supported on this device", nil);
33
+ return;
34
+ }
35
+
36
+ // Store promise handlers
37
+ self.resolveBlock = resolve;
38
+ self.rejectBlock = reject;
39
+
40
+ // Get options
41
+ self.croppedImageQuality = options[@"croppedImageQuality"] ? [options[@"croppedImageQuality"] integerValue] : 100;
42
+ self.responseType = options[@"responseType"] ?: @"imageFilePath";
43
+
44
+ // Present document scanner on main thread
45
+ dispatch_async(dispatch_get_main_queue(), ^{
46
+ VNDocumentCameraViewController *documentCameraViewController = [[VNDocumentCameraViewController alloc] init];
47
+ documentCameraViewController.delegate = self;
48
+
49
+ UIViewController *rootViewController = RCTPresentedViewController();
50
+ if (rootViewController) {
51
+ [rootViewController presentViewController:documentCameraViewController animated:YES completion:nil];
52
+ } else {
53
+ reject(@"NO_VIEWCONTROLLER", @"No view controller available to present scanner", nil);
54
+ }
55
+ });
56
+ } else {
57
+ reject(@"IOS_VERSION", @"Document scanning requires iOS 13.0 or later", nil);
58
+ }
59
+ }
60
+
61
+ #pragma mark - VNDocumentCameraViewControllerDelegate
62
+
63
+ - (void)documentCameraViewController:(VNDocumentCameraViewController *)controller didFinishWithScan:(VNDocumentCameraScan *)scan API_AVAILABLE(ios(13.0))
64
+ {
65
+ NSMutableArray *scannedImages = [NSMutableArray array];
66
+
67
+ // Process each scanned page
68
+ for (NSUInteger i = 0; i < scan.pageCount; i++) {
69
+ UIImage *image = [scan imageOfPageAtIndex:i];
70
+
71
+ // Convert to JPEG with quality
72
+ CGFloat quality = (CGFloat)self.croppedImageQuality / 100.0;
73
+ NSData *imageData = UIImageJPEGRepresentation(image, quality);
74
+
75
+ if (!imageData) {
76
+ [controller dismissViewControllerAnimated:YES completion:^{
77
+ if (self.rejectBlock) {
78
+ self.rejectBlock(@"IMAGE_ERROR", @"Failed to convert scanned image to JPEG", nil);
79
+ self.rejectBlock = nil;
80
+ self.resolveBlock = nil;
81
+ }
82
+ }];
83
+ return;
84
+ }
85
+
86
+ if ([self.responseType isEqualToString:@"base64"]) {
87
+ // Return base64 encoded image
88
+ NSString *base64String = [imageData base64EncodedStringWithOptions:0];
89
+ [scannedImages addObject:base64String];
90
+ } else {
91
+ // Save to temporary directory and return file path
92
+ NSString *timestamp = [NSString stringWithFormat:@"%.0f", [[NSDate date] timeIntervalSince1970] * 1000];
93
+ NSString *fileName = [NSString stringWithFormat:@"scanned_doc_%@_%lu.jpg", timestamp, (unsigned long)i];
94
+ NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
95
+
96
+ NSError *error = nil;
97
+ BOOL success = [imageData writeToFile:filePath options:NSDataWritingAtomic error:&error];
98
+
99
+ if (!success) {
100
+ [controller dismissViewControllerAnimated:YES completion:^{
101
+ if (self.rejectBlock) {
102
+ self.rejectBlock(@"FILE_ERROR", [NSString stringWithFormat:@"Failed to save image: %@", error.localizedDescription], error);
103
+ self.rejectBlock = nil;
104
+ self.resolveBlock = nil;
105
+ }
106
+ }];
107
+ return;
108
+ }
109
+
110
+ // Return file:// URL
111
+ NSString *fileURL = [NSURL fileURLWithPath:filePath].absoluteString;
112
+ [scannedImages addObject:fileURL];
113
+ }
114
+ }
115
+
116
+ // Dismiss and resolve
117
+ [controller dismissViewControllerAnimated:YES completion:^{
118
+ if (self.resolveBlock) {
119
+ NSDictionary *response = @{
120
+ @"scannedImages": scannedImages,
121
+ @"status": @"success"
122
+ };
123
+ self.resolveBlock(response);
124
+ self.resolveBlock = nil;
125
+ self.rejectBlock = nil;
126
+ }
127
+ }];
128
+ }
129
+
130
+ - (void)documentCameraViewControllerDidCancel:(VNDocumentCameraViewController *)controller API_AVAILABLE(ios(13.0))
131
+ {
132
+ [controller dismissViewControllerAnimated:YES completion:^{
133
+ if (self.resolveBlock) {
134
+ NSDictionary *response = @{
135
+ @"scannedImages": @[],
136
+ @"status": @"cancel"
137
+ };
138
+ self.resolveBlock(response);
139
+ self.resolveBlock = nil;
140
+ self.rejectBlock = nil;
141
+ }
142
+ }];
143
+ }
144
+
145
+ - (void)documentCameraViewController:(VNDocumentCameraViewController *)controller didFailWithError:(NSError *)error API_AVAILABLE(ios(13.0))
146
+ {
147
+ [controller dismissViewControllerAnimated:YES completion:^{
148
+ if (self.rejectBlock) {
149
+ self.rejectBlock(@"SCAN_ERROR", error.localizedDescription, error);
150
+ self.rejectBlock = nil;
151
+ self.resolveBlock = nil;
152
+ }
153
+ }];
154
+ }
155
+
156
+ @end
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.DocumentScanner = void 0;
7
+ var _reactNative = require("react-native");
8
+ const LINKING_ERROR = 'The package \'react-native-document-scanner\' doesn\'t seem to be linked. Make sure: \n\n' + _reactNative.Platform.select({
9
+ ios: "- You have run 'pod install'\n",
10
+ default: ''
11
+ }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
12
+ const RNDocumentScanner = _reactNative.NativeModules.RNDocumentScanner ? _reactNative.NativeModules.RNDocumentScanner : new Proxy({}, {
13
+ get() {
14
+ throw new Error(LINKING_ERROR);
15
+ }
16
+ });
17
+
18
+ /**
19
+ * Options for document scanning
20
+ */
21
+
22
+ /**
23
+ * Response from document scanning
24
+ */
25
+
26
+ /**
27
+ * Document Scanner module
28
+ */
29
+ const DocumentScanner = exports.DocumentScanner = {
30
+ /**
31
+ * Start document scanning
32
+ *
33
+ * @param options - Optional configuration for the document scanner
34
+ * @returns Promise that resolves with scanned document data or rejects with error
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * import { DocumentScanner } from 'react-native-document-scanner';
39
+ *
40
+ * try {
41
+ * const result = await DocumentScanner.scanDocument({
42
+ * maxNumDocuments: 3,
43
+ * letUserAdjustCrop: true,
44
+ * croppedImageQuality: 100,
45
+ * responseType: 'base64'
46
+ * });
47
+ *
48
+ * if (result.status === 'success') {
49
+ * console.log('Scanned images:', result.scannedImages);
50
+ * } else {
51
+ * console.log('User cancelled');
52
+ * }
53
+ * } catch (error) {
54
+ * console.error('Scan error:', error);
55
+ * }
56
+ * ```
57
+ */
58
+ scanDocument(options) {
59
+ const defaultOptions = {
60
+ maxNumDocuments: 1,
61
+ letUserAdjustCrop: true,
62
+ croppedImageQuality: 100,
63
+ responseType: 'imageFilePath'
64
+ };
65
+ const finalOptions = {
66
+ ...defaultOptions,
67
+ ...options
68
+ };
69
+ return RNDocumentScanner.scanDocument(finalOptions);
70
+ }
71
+ };
72
+ var _default = exports.default = DocumentScanner;
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","RNDocumentScanner","NativeModules","Proxy","get","Error","DocumentScanner","exports","scanDocument","options","defaultOptions","maxNumDocuments","letUserAdjustCrop","croppedImageQuality","responseType","finalOptions","_default"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEA,MAAMC,aAAa,GACjB,2FAA2F,GAC3FC,qBAAQ,CAACC,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,iBAAiB,GAAGC,0BAAa,CAACD,iBAAiB,GACrDC,0BAAa,CAACD,iBAAiB,GAC/B,IAAIE,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACT,aAAa,CAAC;EAChC;AACF,CACF,CAAC;;AAEL;AACA;AACA;;AA6BA;AACA;AACA;;AAaA;AACA;AACA;AACO,MAAMU,eAAe,GAAAC,OAAA,CAAAD,eAAA,GAAG;EAC7B;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEE,YAAYA,CAACC,OAA6B,EAAiC;IACzE,MAAMC,cAAmC,GAAG;MAC1CC,eAAe,EAAE,CAAC;MAClBC,iBAAiB,EAAE,IAAI;MACvBC,mBAAmB,EAAE,GAAG;MACxBC,YAAY,EAAE;IAChB,CAAC;IAED,MAAMC,YAAY,GAAG;MAAE,GAAGL,cAAc;MAAE,GAAGD;IAAQ,CAAC;IAEtD,OAAOR,iBAAiB,CAACO,YAAY,CAACO,YAAY,CAAC;EACrD;AACF,CAAC;AAAC,IAAAC,QAAA,GAAAT,OAAA,CAAAP,OAAA,GAEaM,eAAe","ignoreList":[]}
@@ -0,0 +1,67 @@
1
+ import { NativeModules, Platform } from 'react-native';
2
+ const LINKING_ERROR = 'The package \'react-native-document-scanner\' doesn\'t seem to be linked. Make sure: \n\n' + Platform.select({
3
+ ios: "- You have run 'pod install'\n",
4
+ default: ''
5
+ }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
6
+ const RNDocumentScanner = NativeModules.RNDocumentScanner ? NativeModules.RNDocumentScanner : new Proxy({}, {
7
+ get() {
8
+ throw new Error(LINKING_ERROR);
9
+ }
10
+ });
11
+
12
+ /**
13
+ * Options for document scanning
14
+ */
15
+
16
+ /**
17
+ * Response from document scanning
18
+ */
19
+
20
+ /**
21
+ * Document Scanner module
22
+ */
23
+ export const DocumentScanner = {
24
+ /**
25
+ * Start document scanning
26
+ *
27
+ * @param options - Optional configuration for the document scanner
28
+ * @returns Promise that resolves with scanned document data or rejects with error
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * import { DocumentScanner } from 'react-native-document-scanner';
33
+ *
34
+ * try {
35
+ * const result = await DocumentScanner.scanDocument({
36
+ * maxNumDocuments: 3,
37
+ * letUserAdjustCrop: true,
38
+ * croppedImageQuality: 100,
39
+ * responseType: 'base64'
40
+ * });
41
+ *
42
+ * if (result.status === 'success') {
43
+ * console.log('Scanned images:', result.scannedImages);
44
+ * } else {
45
+ * console.log('User cancelled');
46
+ * }
47
+ * } catch (error) {
48
+ * console.error('Scan error:', error);
49
+ * }
50
+ * ```
51
+ */
52
+ scanDocument(options) {
53
+ const defaultOptions = {
54
+ maxNumDocuments: 1,
55
+ letUserAdjustCrop: true,
56
+ croppedImageQuality: 100,
57
+ responseType: 'imageFilePath'
58
+ };
59
+ const finalOptions = {
60
+ ...defaultOptions,
61
+ ...options
62
+ };
63
+ return RNDocumentScanner.scanDocument(finalOptions);
64
+ }
65
+ };
66
+ export default DocumentScanner;
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeModules","Platform","LINKING_ERROR","select","ios","default","RNDocumentScanner","Proxy","get","Error","DocumentScanner","scanDocument","options","defaultOptions","maxNumDocuments","letUserAdjustCrop","croppedImageQuality","responseType","finalOptions"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"AAAA,SAASA,aAAa,EAAEC,QAAQ,QAAQ,cAAc;AAEtD,MAAMC,aAAa,GACjB,2FAA2F,GAC3FD,QAAQ,CAACE,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,iBAAiB,GAAGN,aAAa,CAACM,iBAAiB,GACrDN,aAAa,CAACM,iBAAiB,GAC/B,IAAIC,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACP,aAAa,CAAC;EAChC;AACF,CACF,CAAC;;AAEL;AACA;AACA;;AA6BA;AACA;AACA;;AAaA;AACA;AACA;AACA,OAAO,MAAMQ,eAAe,GAAG;EAC7B;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEC,YAAYA,CAACC,OAA6B,EAAiC;IACzE,MAAMC,cAAmC,GAAG;MAC1CC,eAAe,EAAE,CAAC;MAClBC,iBAAiB,EAAE,IAAI;MACvBC,mBAAmB,EAAE,GAAG;MACxBC,YAAY,EAAE;IAChB,CAAC;IAED,MAAMC,YAAY,GAAG;MAAE,GAAGL,cAAc;MAAE,GAAGD;IAAQ,CAAC;IAEtD,OAAON,iBAAiB,CAACK,YAAY,CAACO,YAAY,CAAC;EACrD;AACF,CAAC;AAED,eAAeR,eAAe","ignoreList":[]}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Options for document scanning
3
+ */
4
+ export interface ScanDocumentOptions {
5
+ /**
6
+ * Maximum number of documents to scan (Android only)
7
+ * @default 1
8
+ * @platform android
9
+ */
10
+ maxNumDocuments?: number;
11
+ /**
12
+ * Allow user to adjust crop manually (Android only)
13
+ * @default true
14
+ * @platform android
15
+ */
16
+ letUserAdjustCrop?: boolean;
17
+ /**
18
+ * Quality of the cropped image (0-100)
19
+ * @default 100
20
+ */
21
+ croppedImageQuality?: number;
22
+ /**
23
+ * Response type for scanned images
24
+ * @default 'imageFilePath'
25
+ */
26
+ responseType?: 'base64' | 'imageFilePath';
27
+ }
28
+ /**
29
+ * Response from document scanning
30
+ */
31
+ export interface ScanDocumentResponse {
32
+ /**
33
+ * Array of scanned images (base64 strings or file paths)
34
+ */
35
+ scannedImages: string[];
36
+ /**
37
+ * Status of the scan operation
38
+ */
39
+ status: 'success' | 'cancel';
40
+ }
41
+ /**
42
+ * Document Scanner module
43
+ */
44
+ export declare const DocumentScanner: {
45
+ /**
46
+ * Start document scanning
47
+ *
48
+ * @param options - Optional configuration for the document scanner
49
+ * @returns Promise that resolves with scanned document data or rejects with error
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * import { DocumentScanner } from 'react-native-document-scanner';
54
+ *
55
+ * try {
56
+ * const result = await DocumentScanner.scanDocument({
57
+ * maxNumDocuments: 3,
58
+ * letUserAdjustCrop: true,
59
+ * croppedImageQuality: 100,
60
+ * responseType: 'base64'
61
+ * });
62
+ *
63
+ * if (result.status === 'success') {
64
+ * console.log('Scanned images:', result.scannedImages);
65
+ * } else {
66
+ * console.log('User cancelled');
67
+ * }
68
+ * } catch (error) {
69
+ * console.error('Scan error:', error);
70
+ * }
71
+ * ```
72
+ */
73
+ scanDocument(options?: ScanDocumentOptions): Promise<ScanDocumentResponse>;
74
+ };
75
+ export default DocumentScanner;
76
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAmBA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,eAAe,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IAExB;;OAEG;IACH,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC9B;AAED;;GAEG;AACH,eAAO,MAAM,eAAe;IAC1B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;2BACoB,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAY3E,CAAC;AAEF,eAAe,eAAe,CAAC"}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "rn-document-scanner-vision",
3
+ "version": "1.0.0",
4
+ "description": "A React Native library for scanning documents on iOS and Android",
5
+ "main": "lib/commonjs/index.js",
6
+ "module": "lib/module/index.js",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "react-native": "src/index.tsx",
9
+ "source": "src/index.tsx",
10
+ "files": [
11
+ "src",
12
+ "lib",
13
+ "android",
14
+ "ios",
15
+ "react-native-document-scanner.podspec",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "typescript": "tsc --noEmit",
21
+ "build": "bob build",
22
+ "prepare": "bob build",
23
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
24
+ "lint:fix": "eslint \"**/*.{js,ts,tsx}\" --fix"
25
+ },
26
+ "keywords": [
27
+ "react-native",
28
+ "ios",
29
+ "android",
30
+ "document-scanner",
31
+ "scanner",
32
+ "ocr",
33
+ "document",
34
+ "scan",
35
+ "camera",
36
+ "visionkit",
37
+ "mlkit"
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/hoangthien123/react-native-document-scanner.git"
42
+ },
43
+ "author": "Hoang Thien <hoangthien123@example.com>",
44
+ "license": "MIT",
45
+ "bugs": {
46
+ "url": "https://github.com/hoangthien123/react-native-document-scanner/issues"
47
+ },
48
+ "homepage": "https://github.com/hoangthien123/react-native-document-scanner#readme",
49
+ "publishConfig": {
50
+ "registry": "https://registry.npmjs.org/"
51
+ },
52
+ "peerDependencies": {
53
+ "react": "*",
54
+ "react-native": "*"
55
+ },
56
+ "devDependencies": {
57
+ "@react-native/eslint-config": "^0.74.0",
58
+ "@types/react": "^18.2.44",
59
+ "@types/react-native": "^0.72.8",
60
+ "eslint": "^8.19.0",
61
+ "prettier": "^3.0.0",
62
+ "react": "^18.2.0",
63
+ "react-native": "^0.74.0",
64
+ "react-native-builder-bob": "^0.23.2",
65
+ "typescript": "^5.3.3"
66
+ },
67
+ "react-native-builder-bob": {
68
+ "source": "src",
69
+ "output": "lib",
70
+ "targets": [
71
+ "commonjs",
72
+ "module",
73
+ [
74
+ "typescript",
75
+ {
76
+ "project": "tsconfig.build.json"
77
+ }
78
+ ]
79
+ ]
80
+ }
81
+ }
@@ -0,0 +1,21 @@
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-document-scanner"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+ s.platforms = { :ios => "13.0" }
13
+ s.source = { :git => package["repository"]["url"], :tag => "#{s.version}" }
14
+
15
+ s.source_files = "ios/**/*.{h,m,mm}"
16
+
17
+ s.dependency "React-Core"
18
+
19
+ # VisionKit is required for document scanning
20
+ s.frameworks = "VisionKit"
21
+ end
package/src/index.tsx ADDED
@@ -0,0 +1,112 @@
1
+ import { NativeModules, Platform } from 'react-native';
2
+
3
+ const LINKING_ERROR =
4
+ 'The package \'react-native-document-scanner\' doesn\'t seem to be linked. Make sure: \n\n' +
5
+ Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
6
+ '- You rebuilt the app after installing the package\n' +
7
+ '- You are not using Expo Go\n';
8
+
9
+ const RNDocumentScanner = NativeModules.RNDocumentScanner
10
+ ? NativeModules.RNDocumentScanner
11
+ : new Proxy(
12
+ {},
13
+ {
14
+ get() {
15
+ throw new Error(LINKING_ERROR);
16
+ },
17
+ }
18
+ );
19
+
20
+ /**
21
+ * Options for document scanning
22
+ */
23
+ export interface ScanDocumentOptions {
24
+ /**
25
+ * Maximum number of documents to scan (Android only)
26
+ * @default 1
27
+ * @platform android
28
+ */
29
+ maxNumDocuments?: number;
30
+
31
+ /**
32
+ * Allow user to adjust crop manually (Android only)
33
+ * @default true
34
+ * @platform android
35
+ */
36
+ letUserAdjustCrop?: boolean;
37
+
38
+ /**
39
+ * Quality of the cropped image (0-100)
40
+ * @default 100
41
+ */
42
+ croppedImageQuality?: number;
43
+
44
+ /**
45
+ * Response type for scanned images
46
+ * @default 'imageFilePath'
47
+ */
48
+ responseType?: 'base64' | 'imageFilePath';
49
+ }
50
+
51
+ /**
52
+ * Response from document scanning
53
+ */
54
+ export interface ScanDocumentResponse {
55
+ /**
56
+ * Array of scanned images (base64 strings or file paths)
57
+ */
58
+ scannedImages: string[];
59
+
60
+ /**
61
+ * Status of the scan operation
62
+ */
63
+ status: 'success' | 'cancel';
64
+ }
65
+
66
+ /**
67
+ * Document Scanner module
68
+ */
69
+ export const DocumentScanner = {
70
+ /**
71
+ * Start document scanning
72
+ *
73
+ * @param options - Optional configuration for the document scanner
74
+ * @returns Promise that resolves with scanned document data or rejects with error
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * import { DocumentScanner } from 'react-native-document-scanner';
79
+ *
80
+ * try {
81
+ * const result = await DocumentScanner.scanDocument({
82
+ * maxNumDocuments: 3,
83
+ * letUserAdjustCrop: true,
84
+ * croppedImageQuality: 100,
85
+ * responseType: 'base64'
86
+ * });
87
+ *
88
+ * if (result.status === 'success') {
89
+ * console.log('Scanned images:', result.scannedImages);
90
+ * } else {
91
+ * console.log('User cancelled');
92
+ * }
93
+ * } catch (error) {
94
+ * console.error('Scan error:', error);
95
+ * }
96
+ * ```
97
+ */
98
+ scanDocument(options?: ScanDocumentOptions): Promise<ScanDocumentResponse> {
99
+ const defaultOptions: ScanDocumentOptions = {
100
+ maxNumDocuments: 1,
101
+ letUserAdjustCrop: true,
102
+ croppedImageQuality: 100,
103
+ responseType: 'imageFilePath',
104
+ };
105
+
106
+ const finalOptions = { ...defaultOptions, ...options };
107
+
108
+ return RNDocumentScanner.scanDocument(finalOptions);
109
+ },
110
+ };
111
+
112
+ export default DocumentScanner;