@dariyd/react-native-document-scanner 2.0.13
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/CHANGELOG.md +170 -0
- package/LICENSE +201 -0
- package/README.md +224 -0
- package/android/README.md +43 -0
- package/android/build.gradle +182 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/docscanner/DocumentScannerModule.java +261 -0
- package/android/src/main/java/com/docscanner/DocumentScannerPackage.java +49 -0
- package/android/src/main/java/com/reactlibrary/DocumentScannerModule.java +1 -0
- package/android/src/main/java/com/reactlibrary/DocumentScannerPackage.java +1 -0
- package/assets/.gitkeep +2 -0
- package/assets/android_demo.gif +0 -0
- package/assets/ios_demo.gif +0 -0
- package/index.d.ts +126 -0
- package/index.js +46 -0
- package/ios/DocumentScanner.h +13 -0
- package/ios/DocumentScanner.mm +180 -0
- package/ios/DocumentScanner.xcodeproj/project.pbxproj +281 -0
- package/ios/DocumentScanner.xcodeproj/xcuserdata/dariy.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/ios/DocumentScanner.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/DocumentScanner.xcworkspace/xcuserdata/dariy.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/package.json +68 -0
- package/react-native-document-scanner.podspec +45 -0
- package/react-native.config.js +15 -0
- package/src/NativeDocumentScanner.ts +31 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// android/build.gradle
|
|
2
|
+
|
|
3
|
+
// based on:
|
|
4
|
+
//
|
|
5
|
+
// * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle
|
|
6
|
+
// previous location:
|
|
7
|
+
// - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle
|
|
8
|
+
//
|
|
9
|
+
// * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle
|
|
10
|
+
// previous location:
|
|
11
|
+
// - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle
|
|
12
|
+
|
|
13
|
+
// These defaults should reflect the SDK versions used by
|
|
14
|
+
// the minimum React Native version supported.
|
|
15
|
+
def DEFAULT_COMPILE_SDK_VERSION = 35
|
|
16
|
+
def DEFAULT_BUILD_TOOLS_VERSION = '35.0.0'
|
|
17
|
+
def DEFAULT_MIN_SDK_VERSION = 21
|
|
18
|
+
def DEFAULT_TARGET_SDK_VERSION = 35
|
|
19
|
+
|
|
20
|
+
def safeExtGet(prop, fallback) {
|
|
21
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
apply plugin: 'com.android.library'
|
|
25
|
+
apply plugin: 'maven-publish'
|
|
26
|
+
// Needed so RN Gradle plugin can run codegen for this library when newArchEnabled=true in the app
|
|
27
|
+
apply plugin: 'com.facebook.react'
|
|
28
|
+
|
|
29
|
+
buildscript {
|
|
30
|
+
// The Android Gradle plugin is only required when opening the android folder stand-alone.
|
|
31
|
+
// This avoids unnecessary downloads and potential conflicts when the library is included as a
|
|
32
|
+
// module dependency in an application project.
|
|
33
|
+
// ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
|
|
34
|
+
if (project == rootProject) {
|
|
35
|
+
repositories {
|
|
36
|
+
google()
|
|
37
|
+
}
|
|
38
|
+
dependencies {
|
|
39
|
+
// This should reflect the Gradle plugin version used by
|
|
40
|
+
// the minimum React Native version supported.
|
|
41
|
+
classpath 'com.android.tools.build:gradle:3.4.1'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
android {
|
|
47
|
+
namespace "com.docscanner"
|
|
48
|
+
compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
|
|
49
|
+
buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
|
|
50
|
+
defaultConfig {
|
|
51
|
+
minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
|
|
52
|
+
targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
|
|
53
|
+
versionCode 1
|
|
54
|
+
versionName "1.0"
|
|
55
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", (findProperty("newArchEnabled") ?: "false")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
lintOptions {
|
|
59
|
+
abortOnError false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Configure React Native codegen for this library
|
|
64
|
+
react {
|
|
65
|
+
libraryName = "RNDocumentScannerSpec"
|
|
66
|
+
codegenJavaPackageName = "com.docscanner"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
repositories {
|
|
70
|
+
// ref: https://www.baeldung.com/maven-local-repository
|
|
71
|
+
mavenLocal()
|
|
72
|
+
maven {
|
|
73
|
+
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
|
74
|
+
url "$rootDir/../node_modules/react-native/android"
|
|
75
|
+
}
|
|
76
|
+
maven {
|
|
77
|
+
// Android JSC is installed from npm
|
|
78
|
+
url "$rootDir/../node_modules/jsc-android/dist"
|
|
79
|
+
}
|
|
80
|
+
google()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
dependencies {
|
|
84
|
+
//noinspection GradleDynamicVersion
|
|
85
|
+
implementation 'com.facebook.react:react-native:+' // From node_modules
|
|
86
|
+
implementation 'com.google.android.gms:play-services-mlkit-document-scanner:16.0.0-beta1'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
afterEvaluate { project ->
|
|
90
|
+
// some Gradle build hooks ref:
|
|
91
|
+
// https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html
|
|
92
|
+
task androidJavadoc(type: Javadoc) {
|
|
93
|
+
source = android.sourceSets.main.java.srcDirs
|
|
94
|
+
classpath += files(android.bootClasspath)
|
|
95
|
+
// Use a resolvable configuration when available; on some AGP setups
|
|
96
|
+
// (when this library is consumed from node_modules), a top-level
|
|
97
|
+
// 'compileClasspath' config may not exist. In that case, skip adding it.
|
|
98
|
+
def cc = project.configurations.findByName('compileClasspath')
|
|
99
|
+
if (cc != null) {
|
|
100
|
+
classpath += files(cc.asList())
|
|
101
|
+
} else {
|
|
102
|
+
logger.lifecycle("react-native-document-scanner: 'compileClasspath' not found; skipping extra Javadoc classpath setup.")
|
|
103
|
+
}
|
|
104
|
+
include '**/*.java'
|
|
105
|
+
|
|
106
|
+
// Suppress warnings for missing documentation
|
|
107
|
+
options.addStringOption('Xdoclint:none', '-quiet')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) {
|
|
111
|
+
archiveClassifier.set('javadoc')
|
|
112
|
+
from androidJavadoc.destinationDir
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
task androidSourcesJar(type: Jar) {
|
|
116
|
+
archiveClassifier.set('sources')
|
|
117
|
+
from android.sourceSets.main.java.srcDirs
|
|
118
|
+
include '**/*.java'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
android.libraryVariants.all { variant ->
|
|
122
|
+
def name = variant.name.capitalize()
|
|
123
|
+
def javaCompileTask = variant.javaCompileProvider.get()
|
|
124
|
+
|
|
125
|
+
task "jar${name}"(type: Jar, dependsOn: javaCompileTask) {
|
|
126
|
+
from javaCompileTask.destinationDir
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Modern maven-publish plugin configuration
|
|
132
|
+
def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text)
|
|
133
|
+
|
|
134
|
+
afterEvaluate {
|
|
135
|
+
// Only configure publishing if the Android components expose a 'release' variant
|
|
136
|
+
def releaseComponent = components.findByName("release")
|
|
137
|
+
if (releaseComponent != null) {
|
|
138
|
+
publishing {
|
|
139
|
+
publications {
|
|
140
|
+
release(MavenPublication) {
|
|
141
|
+
from releaseComponent
|
|
142
|
+
|
|
143
|
+
groupId = 'com.docscanner'
|
|
144
|
+
artifactId = packageJson.name
|
|
145
|
+
version = packageJson.version
|
|
146
|
+
|
|
147
|
+
artifact androidSourcesJar
|
|
148
|
+
artifact androidJavadocJar
|
|
149
|
+
|
|
150
|
+
pom {
|
|
151
|
+
name = packageJson.title
|
|
152
|
+
description = packageJson.description
|
|
153
|
+
url = packageJson.repository.baseUrl
|
|
154
|
+
|
|
155
|
+
licenses {
|
|
156
|
+
license {
|
|
157
|
+
name = packageJson.license
|
|
158
|
+
url = packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename
|
|
159
|
+
distribution = 'repo'
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
developers {
|
|
164
|
+
developer {
|
|
165
|
+
id = packageJson.author.name
|
|
166
|
+
name = packageJson.author.name
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
repositories {
|
|
173
|
+
maven {
|
|
174
|
+
// Deploy to react-native-document-scanner/android/maven, ready to publish to npm
|
|
175
|
+
url = uri("file://${projectDir}/../android/maven")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
logger.lifecycle("react-native-document-scanner: Skipping maven-publish because no 'release' component is available (AGP too old or not applied).")
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// DocumentScannerModule.java
|
|
2
|
+
|
|
3
|
+
package com.docscanner;
|
|
4
|
+
|
|
5
|
+
import android.app.Activity;
|
|
6
|
+
import android.content.Intent;
|
|
7
|
+
import android.graphics.Bitmap;
|
|
8
|
+
import android.net.Uri;
|
|
9
|
+
import android.util.Base64;
|
|
10
|
+
import android.util.Log;
|
|
11
|
+
|
|
12
|
+
import androidx.activity.result.ActivityResult;
|
|
13
|
+
import androidx.activity.result.ActivityResultLauncher;
|
|
14
|
+
import androidx.activity.result.IntentSenderRequest;
|
|
15
|
+
import androidx.activity.result.contract.ActivityResultContracts;
|
|
16
|
+
|
|
17
|
+
import com.facebook.react.bridge.ActivityEventListener;
|
|
18
|
+
import com.facebook.react.bridge.BaseActivityEventListener;
|
|
19
|
+
import com.facebook.react.bridge.Callback;
|
|
20
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
21
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
22
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
23
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
24
|
+
import com.facebook.react.bridge.WritableArray;
|
|
25
|
+
import com.facebook.react.bridge.WritableMap;
|
|
26
|
+
import com.facebook.react.bridge.WritableNativeArray;
|
|
27
|
+
import com.facebook.react.bridge.WritableNativeMap;
|
|
28
|
+
|
|
29
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScanner;
|
|
30
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions;
|
|
31
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScanning;
|
|
32
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult;
|
|
33
|
+
|
|
34
|
+
import java.io.ByteArrayOutputStream;
|
|
35
|
+
import java.io.File;
|
|
36
|
+
import java.io.FileOutputStream;
|
|
37
|
+
import java.io.IOException;
|
|
38
|
+
import java.io.InputStream;
|
|
39
|
+
import java.util.UUID;
|
|
40
|
+
|
|
41
|
+
// For New Architecture: extend the generated spec base class
|
|
42
|
+
// This class is generated by React Native Codegen based on src/NativeDocumentScanner.ts
|
|
43
|
+
// and will be available when newArchEnabled=true
|
|
44
|
+
@SuppressWarnings("KotlinInternalInJava")
|
|
45
|
+
public class DocumentScannerModule extends com.docscanner.NativeDocumentScannerSpec {
|
|
46
|
+
|
|
47
|
+
public static final String NAME = "DocumentScanner";
|
|
48
|
+
private static final String TAG = "DocumentScannerModule";
|
|
49
|
+
private static final int DOCUMENT_SCAN_REQUEST = 1001;
|
|
50
|
+
|
|
51
|
+
private final ReactApplicationContext reactContext;
|
|
52
|
+
private Callback scannerCallback;
|
|
53
|
+
private ReadableMap scannerOptions;
|
|
54
|
+
|
|
55
|
+
private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
|
|
56
|
+
@Override
|
|
57
|
+
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
|
58
|
+
if (requestCode == DOCUMENT_SCAN_REQUEST) {
|
|
59
|
+
handleScanResult(resultCode, data);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
public DocumentScannerModule(ReactApplicationContext reactContext) {
|
|
65
|
+
super(reactContext);
|
|
66
|
+
this.reactContext = reactContext;
|
|
67
|
+
reactContext.addActivityEventListener(activityEventListener);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Override
|
|
71
|
+
public String getName() {
|
|
72
|
+
return NAME;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@ReactMethod
|
|
76
|
+
@Override
|
|
77
|
+
public void launchScanner(ReadableMap options, Callback callback) {
|
|
78
|
+
Activity currentActivity = getCurrentActivity();
|
|
79
|
+
|
|
80
|
+
if (currentActivity == null) {
|
|
81
|
+
WritableMap errorResponse = new WritableNativeMap();
|
|
82
|
+
errorResponse.putBoolean("error", true);
|
|
83
|
+
errorResponse.putString("errorMessage", "Activity doesn't exist");
|
|
84
|
+
callback.invoke(errorResponse);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
scannerCallback = callback;
|
|
89
|
+
scannerOptions = options;
|
|
90
|
+
|
|
91
|
+
// Configure the scanner
|
|
92
|
+
GmsDocumentScannerOptions.Builder optionsBuilder = new GmsDocumentScannerOptions.Builder()
|
|
93
|
+
.setGalleryImportAllowed(false)
|
|
94
|
+
.setPageLimit(10)
|
|
95
|
+
.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG, GmsDocumentScannerOptions.RESULT_FORMAT_PDF)
|
|
96
|
+
.setScannerMode(GmsDocumentScannerOptions.SCANNER_MODE_FULL);
|
|
97
|
+
|
|
98
|
+
GmsDocumentScannerOptions scannerOptions = optionsBuilder.build();
|
|
99
|
+
GmsDocumentScanner scanner = GmsDocumentScanning.getClient(scannerOptions);
|
|
100
|
+
|
|
101
|
+
scanner.getStartScanIntent(currentActivity)
|
|
102
|
+
.addOnSuccessListener(intentSender -> {
|
|
103
|
+
try {
|
|
104
|
+
currentActivity.startIntentSenderForResult(
|
|
105
|
+
intentSender,
|
|
106
|
+
DOCUMENT_SCAN_REQUEST,
|
|
107
|
+
null,
|
|
108
|
+
0,
|
|
109
|
+
0,
|
|
110
|
+
0
|
|
111
|
+
);
|
|
112
|
+
} catch (Exception e) {
|
|
113
|
+
Log.e(TAG, "Error starting scanner", e);
|
|
114
|
+
WritableMap errorResponse = new WritableNativeMap();
|
|
115
|
+
errorResponse.putBoolean("error", true);
|
|
116
|
+
errorResponse.putString("errorMessage", e.getMessage());
|
|
117
|
+
callback.invoke(errorResponse);
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
.addOnFailureListener(e -> {
|
|
121
|
+
Log.e(TAG, "Error getting scan intent", e);
|
|
122
|
+
WritableMap errorResponse = new WritableNativeMap();
|
|
123
|
+
errorResponse.putBoolean("error", true);
|
|
124
|
+
errorResponse.putString("errorMessage", e.getMessage());
|
|
125
|
+
callback.invoke(errorResponse);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private void handleScanResult(int resultCode, Intent data) {
|
|
130
|
+
if (scannerCallback == null) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (resultCode == Activity.RESULT_CANCELED) {
|
|
135
|
+
WritableMap response = new WritableNativeMap();
|
|
136
|
+
response.putBoolean("didCancel", true);
|
|
137
|
+
scannerCallback.invoke(response);
|
|
138
|
+
scannerCallback = null;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (resultCode != Activity.RESULT_OK || data == null) {
|
|
143
|
+
WritableMap errorResponse = new WritableNativeMap();
|
|
144
|
+
errorResponse.putBoolean("error", true);
|
|
145
|
+
errorResponse.putString("errorMessage", "Scan failed");
|
|
146
|
+
scannerCallback.invoke(errorResponse);
|
|
147
|
+
scannerCallback = null;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
GmsDocumentScanningResult result = GmsDocumentScanningResult.fromActivityResultIntent(data);
|
|
152
|
+
if (result == null) {
|
|
153
|
+
WritableMap errorResponse = new WritableNativeMap();
|
|
154
|
+
errorResponse.putBoolean("error", true);
|
|
155
|
+
errorResponse.putString("errorMessage", "Failed to get scan result");
|
|
156
|
+
scannerCallback.invoke(errorResponse);
|
|
157
|
+
scannerCallback = null;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
WritableArray imagesArray = new WritableNativeArray();
|
|
162
|
+
|
|
163
|
+
// Process each scanned page
|
|
164
|
+
GmsDocumentScanningResult.Page[] pages = result.getPages().toArray(new GmsDocumentScanningResult.Page[0]);
|
|
165
|
+
for (GmsDocumentScanningResult.Page page : pages) {
|
|
166
|
+
Uri imageUri = page.getImageUri();
|
|
167
|
+
try {
|
|
168
|
+
WritableMap imageObject = processImage(imageUri);
|
|
169
|
+
if (imageObject != null) {
|
|
170
|
+
imagesArray.pushMap(imageObject);
|
|
171
|
+
}
|
|
172
|
+
} catch (Exception e) {
|
|
173
|
+
Log.e(TAG, "Error processing image", e);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
WritableMap response = new WritableNativeMap();
|
|
178
|
+
response.putArray("images", imagesArray);
|
|
179
|
+
scannerCallback.invoke(response);
|
|
180
|
+
scannerCallback = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private WritableMap processImage(Uri imageUri) throws IOException {
|
|
184
|
+
WritableMap imageObject = new WritableNativeMap();
|
|
185
|
+
|
|
186
|
+
Activity currentActivity = getCurrentActivity();
|
|
187
|
+
if (currentActivity == null) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Read the image
|
|
192
|
+
InputStream inputStream = currentActivity.getContentResolver().openInputStream(imageUri);
|
|
193
|
+
if (inputStream == null) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
byte[] imageBytes = readBytes(inputStream);
|
|
198
|
+
inputStream.close();
|
|
199
|
+
|
|
200
|
+
// Decode bitmap to get dimensions
|
|
201
|
+
android.graphics.BitmapFactory.Options options = new android.graphics.BitmapFactory.Options();
|
|
202
|
+
options.inJustDecodeBounds = false;
|
|
203
|
+
Bitmap bitmap = android.graphics.BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options);
|
|
204
|
+
|
|
205
|
+
if (bitmap == null) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Apply quality compression if needed
|
|
210
|
+
double quality = 1.0;
|
|
211
|
+
if (scannerOptions != null && scannerOptions.hasKey("quality")) {
|
|
212
|
+
quality = scannerOptions.getDouble("quality");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
216
|
+
int qualityPercent = (int) (quality * 100);
|
|
217
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, qualityPercent, outputStream);
|
|
218
|
+
byte[] processedImageBytes = outputStream.toByteArray();
|
|
219
|
+
outputStream.close();
|
|
220
|
+
|
|
221
|
+
// Save to cache directory
|
|
222
|
+
String fileName = UUID.randomUUID().toString() + ".jpg";
|
|
223
|
+
File cacheDir = currentActivity.getCacheDir();
|
|
224
|
+
File imageFile = new File(cacheDir, fileName);
|
|
225
|
+
|
|
226
|
+
FileOutputStream fileOutputStream = new FileOutputStream(imageFile);
|
|
227
|
+
fileOutputStream.write(processedImageBytes);
|
|
228
|
+
fileOutputStream.close();
|
|
229
|
+
|
|
230
|
+
// Build response
|
|
231
|
+
imageObject.putString("uri", Uri.fromFile(imageFile).toString());
|
|
232
|
+
imageObject.putString("fileName", fileName);
|
|
233
|
+
imageObject.putString("type", "image/jpeg");
|
|
234
|
+
imageObject.putInt("width", bitmap.getWidth());
|
|
235
|
+
imageObject.putInt("height", bitmap.getHeight());
|
|
236
|
+
imageObject.putInt("fileSize", processedImageBytes.length);
|
|
237
|
+
|
|
238
|
+
// Add base64 if requested
|
|
239
|
+
if (scannerOptions != null && scannerOptions.hasKey("includeBase64") &&
|
|
240
|
+
scannerOptions.getBoolean("includeBase64")) {
|
|
241
|
+
String base64 = Base64.encodeToString(processedImageBytes, Base64.NO_WRAP);
|
|
242
|
+
imageObject.putString("base64", base64);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
bitmap.recycle();
|
|
246
|
+
|
|
247
|
+
return imageObject;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private byte[] readBytes(InputStream inputStream) throws IOException {
|
|
251
|
+
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
|
|
252
|
+
byte[] buffer = new byte[16384];
|
|
253
|
+
int len;
|
|
254
|
+
while ((len = inputStream.read(buffer)) != -1) {
|
|
255
|
+
byteBuffer.write(buffer, 0, len);
|
|
256
|
+
}
|
|
257
|
+
return byteBuffer.toByteArray();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// DocumentScannerPackage.java
|
|
2
|
+
|
|
3
|
+
package com.docscanner;
|
|
4
|
+
|
|
5
|
+
import androidx.annotation.Nullable;
|
|
6
|
+
|
|
7
|
+
import java.util.HashMap;
|
|
8
|
+
import java.util.Map;
|
|
9
|
+
|
|
10
|
+
import com.facebook.react.bridge.NativeModule;
|
|
11
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
12
|
+
import com.facebook.react.TurboReactPackage;
|
|
13
|
+
import com.facebook.react.module.model.ReactModuleInfo;
|
|
14
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
15
|
+
|
|
16
|
+
public class DocumentScannerPackage extends TurboReactPackage {
|
|
17
|
+
|
|
18
|
+
@Nullable
|
|
19
|
+
@Override
|
|
20
|
+
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
|
|
21
|
+
if (name.equals(DocumentScannerModule.NAME)) {
|
|
22
|
+
return new DocumentScannerModule(reactContext);
|
|
23
|
+
} else {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Override
|
|
29
|
+
public ReactModuleInfoProvider getReactModuleInfoProvider() {
|
|
30
|
+
return () -> {
|
|
31
|
+
final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
|
|
32
|
+
boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
|
33
|
+
moduleInfos.put(
|
|
34
|
+
DocumentScannerModule.NAME,
|
|
35
|
+
new ReactModuleInfo(
|
|
36
|
+
DocumentScannerModule.NAME,
|
|
37
|
+
DocumentScannerModule.NAME,
|
|
38
|
+
false, // canOverrideExistingModule
|
|
39
|
+
false, // needsEagerInit
|
|
40
|
+
true, // hasConstants
|
|
41
|
+
false, // isCxxModule
|
|
42
|
+
isTurboModule // isTurboModule
|
|
43
|
+
));
|
|
44
|
+
return moduleInfos;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/assets/.gitkeep
ADDED
|
Binary file
|
|
Binary file
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Type definitions for react-native-document-scanner
|
|
2
|
+
// Project: https://github.com/dariyd/react-native-document-scanner
|
|
3
|
+
|
|
4
|
+
export interface ImageObject {
|
|
5
|
+
/**
|
|
6
|
+
* The base64 string of the image (only if includeBase64 option is true)
|
|
7
|
+
*/
|
|
8
|
+
base64?: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The file URI in app specific cache storage
|
|
12
|
+
*/
|
|
13
|
+
uri: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Image width in pixels
|
|
17
|
+
*/
|
|
18
|
+
width: number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Image height in pixels
|
|
22
|
+
*/
|
|
23
|
+
height: number;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The file size in bytes
|
|
27
|
+
*/
|
|
28
|
+
fileSize: number;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The file MIME type (e.g., "image/jpeg")
|
|
32
|
+
*/
|
|
33
|
+
type: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The file name
|
|
37
|
+
*/
|
|
38
|
+
fileName: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ScanResult {
|
|
42
|
+
/**
|
|
43
|
+
* True if the user cancelled the scanning process
|
|
44
|
+
*/
|
|
45
|
+
didCancel?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* True if an error occurred
|
|
49
|
+
*/
|
|
50
|
+
error?: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Description of the error (for debug purposes only)
|
|
54
|
+
*/
|
|
55
|
+
errorMessage?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Array of scanned images
|
|
59
|
+
*/
|
|
60
|
+
images?: ImageObject[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ScanOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Image quality from 0 to 1 (default: 1)
|
|
66
|
+
* Lower values reduce file size
|
|
67
|
+
*
|
|
68
|
+
* @default 1
|
|
69
|
+
*/
|
|
70
|
+
quality?: number;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* If true, includes base64 string of the image in the result
|
|
74
|
+
* Avoid using on large image files due to performance impact
|
|
75
|
+
*
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
includeBase64?: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Launch the document scanner
|
|
83
|
+
*
|
|
84
|
+
* @param options - Scanner options
|
|
85
|
+
* @param callback - Optional callback function
|
|
86
|
+
* @returns Promise that resolves with the scan result
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* import { launchScanner } from 'react-native-document-scanner';
|
|
91
|
+
*
|
|
92
|
+
* // Basic usage
|
|
93
|
+
* const result = await launchScanner();
|
|
94
|
+
*
|
|
95
|
+
* // With options
|
|
96
|
+
* const result = await launchScanner({
|
|
97
|
+
* quality: 0.8,
|
|
98
|
+
* includeBase64: false,
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* // With callback
|
|
102
|
+
* launchScanner({ quality: 0.9 }, (result) => {
|
|
103
|
+
* if (result.didCancel) {
|
|
104
|
+
* console.log('User cancelled');
|
|
105
|
+
* } else if (result.error) {
|
|
106
|
+
* console.log('Error:', result.errorMessage);
|
|
107
|
+
* } else {
|
|
108
|
+
* console.log('Scanned images:', result.images);
|
|
109
|
+
* }
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function launchScanner(
|
|
114
|
+
options?: ScanOptions,
|
|
115
|
+
callback?: (result: ScanResult) => void
|
|
116
|
+
): Promise<ScanResult>;
|
|
117
|
+
|
|
118
|
+
declare const DocumentScanner: {
|
|
119
|
+
launchScanner: (
|
|
120
|
+
options: ScanOptions,
|
|
121
|
+
callback: (result: ScanResult) => void
|
|
122
|
+
) => void;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default DocumentScanner;
|
|
126
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// main index.js
|
|
2
|
+
|
|
3
|
+
import { NativeModules, Platform } from 'react-native';
|
|
4
|
+
|
|
5
|
+
// Try to get the TurboModule (new architecture)
|
|
6
|
+
let DocumentScannerModule;
|
|
7
|
+
try {
|
|
8
|
+
DocumentScannerModule = require('./src/NativeDocumentScanner').default;
|
|
9
|
+
} catch (e) {
|
|
10
|
+
// Fall back to old architecture
|
|
11
|
+
DocumentScannerModule = NativeModules.DocumentScanner;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// If still not available, try the old way
|
|
15
|
+
if (!DocumentScannerModule) {
|
|
16
|
+
DocumentScannerModule = NativeModules.DocumentScanner;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default DocumentScannerModule;
|
|
20
|
+
|
|
21
|
+
const DEFAULT_OPTIONS = {
|
|
22
|
+
quality: 1,
|
|
23
|
+
includeBase64: false,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function launchScanner(options = {}, callback) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
if (!DocumentScannerModule) {
|
|
29
|
+
const error = {
|
|
30
|
+
error: true,
|
|
31
|
+
errorMessage: 'DocumentScanner module is not available',
|
|
32
|
+
};
|
|
33
|
+
if (callback) callback(error);
|
|
34
|
+
reject(error);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
DocumentScannerModule.launchScanner(
|
|
39
|
+
{...DEFAULT_OPTIONS, ...options},
|
|
40
|
+
(result) => {
|
|
41
|
+
if (callback) callback(result);
|
|
42
|
+
resolve(result);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
}
|