From 8122141fd9ee49832c926c017309ff6c51278aca Mon Sep 17 00:00:00 2001 From: Tyler Williams Date: Thu, 4 Dec 2025 10:44:08 -0500 Subject: [PATCH 01/31] wip: copied module structure over, started working on types --- .../.eslintrc.js | 9 ++ .../.gitignore | 57 ++++++++++ .../.npmignore | 11 ++ .../README.md | 37 +++++++ .../android/build.gradle | 96 ++++++++++++++++ .../android/src/main/AndroidManifest.xml | 2 + .../DocumentScannerExceptions.kt | 11 ++ .../RNMLKitDocumentScannerModule.kt | 103 ++++++++++++++++++ .../RNMLKitDocumentScannerOptions.kt | 40 +++++++ .../RNMLKitDocumentScannerResponse.kt | 11 ++ .../contracts/ContractUtils.kt | 19 ++++ .../contracts/DocumentScannerContract.kt | 102 +++++++++++++++++ .../expo-module.config.json | 17 +++ .../ios/RNMLKitTextRecognition.podspec | 29 +++++ .../ios/RNMLKitTextRecognitionModule.swift | 34 ++++++ .../ios/RNMLKitTextRecord.swift | 78 +++++++++++++ .../package.json | 47 ++++++++ .../src/constants.ts | 1 + .../src/index.test.ts | 5 + .../src/index.ts | 21 ++++ .../module/RNMLKitBarcodeScanning.types.ts | 51 +++++++++ .../module/RNMLKitBarcodeScanningModule.ts | 5 + .../tsconfig.json | 8 ++ 23 files changed, 794 insertions(+) create mode 100644 modules/react-native-mlkit-barcode-scanning/.eslintrc.js create mode 100644 modules/react-native-mlkit-barcode-scanning/.gitignore create mode 100644 modules/react-native-mlkit-barcode-scanning/.npmignore create mode 100644 modules/react-native-mlkit-barcode-scanning/README.md create mode 100644 modules/react-native-mlkit-barcode-scanning/android/build.gradle create mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/AndroidManifest.xml create mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/DocumentScannerExceptions.kt create mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerModule.kt create mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerOptions.kt create mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerResponse.kt create mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/ContractUtils.kt create mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/DocumentScannerContract.kt create mode 100644 modules/react-native-mlkit-barcode-scanning/expo-module.config.json create mode 100644 modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognition.podspec create mode 100644 modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognitionModule.swift create mode 100644 modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecord.swift create mode 100644 modules/react-native-mlkit-barcode-scanning/package.json create mode 100644 modules/react-native-mlkit-barcode-scanning/src/constants.ts create mode 100644 modules/react-native-mlkit-barcode-scanning/src/index.test.ts create mode 100644 modules/react-native-mlkit-barcode-scanning/src/index.ts create mode 100644 modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanning.types.ts create mode 100644 modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanningModule.ts create mode 100644 modules/react-native-mlkit-barcode-scanning/tsconfig.json diff --git a/modules/react-native-mlkit-barcode-scanning/.eslintrc.js b/modules/react-native-mlkit-barcode-scanning/.eslintrc.js new file mode 100644 index 00000000..8ef9bd99 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + root: true, + extends: ["universe/native", "universe/web"], + ignorePatterns: ["build"], + rules: { + "@typescript-eslint/array-type": "off", + "prettier/prettier": "off", + }, +}; diff --git a/modules/react-native-mlkit-barcode-scanning/.gitignore b/modules/react-native-mlkit-barcode-scanning/.gitignore new file mode 100644 index 00000000..e64b91c9 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/.gitignore @@ -0,0 +1,57 @@ +# OSX +# +.DS_Store + +# VSCode +.vscode/ +jsconfig.json + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +# +.classpath +.cxx +.gradle +.idea +.project +.settings +local.properties +android.iml +android/app/libs +android/keystores/debug.keystore + +# Cocoapods +# +example/ios/Pods + +# Ruby +example/vendor/ + +# node.js +# +node_modules/ +npm-debug.log +yarn-debug.log +yarn-error.log + +# Expo +.expo/* diff --git a/modules/react-native-mlkit-barcode-scanning/.npmignore b/modules/react-native-mlkit-barcode-scanning/.npmignore new file mode 100644 index 00000000..fa6abe7c --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/.npmignore @@ -0,0 +1,11 @@ +# Exclude all top-level hidden directories by convention +/.*/ + +__mocks__ +__tests__ + +/babel.config.js +/android/src/androidTest/ +/android/src/test/ +/android/build/ +/example/ diff --git a/modules/react-native-mlkit-barcode-scanning/README.md b/modules/react-native-mlkit-barcode-scanning/README.md new file mode 100644 index 00000000..4490d24e --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/README.md @@ -0,0 +1,37 @@ +# react-native-mlkit-barcode-scanning + +MLKit Barcode Scanning for Expo + +# API documentation + +### [Check the friendly docs here! 📖](https://docs.infinite.red/react-native-mlkit/barcode-scanning) + +# Installation in managed Expo projects + +For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions +in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no +documentation available then this library is not yet usable within managed projects — it is likely to be included +in an upcoming Expo SDK release. + +# Installation in bare React Native projects + +For bare React Native projects, you must ensure that you +have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before +continuing. + +### Add the package to your npm dependencies + +``` +npm install @infinitered/react-native-mlkit-barcode-scanning +``` + +### Configure for iOS + +Run `npx pod-install` after installing the npm package. + +### Configure for Android + +# Contributing + +Contributions are very welcome! Please refer to guidelines described in +the [contributing guide](https://github.com/expo/expo#contributing). diff --git a/modules/react-native-mlkit-barcode-scanning/android/build.gradle b/modules/react-native-mlkit-barcode-scanning/android/build.gradle new file mode 100644 index 00000000..58c1308a --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/build.gradle @@ -0,0 +1,96 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' + +group = 'red.infinite.reactnativemlkit.documentscanner' +version = '0.5.0' + +buildscript { + def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") + if (expoModulesCorePlugin.exists()) { + apply from: expoModulesCorePlugin + applyKotlinExpoModulesCorePlugin() + } + + // Simple helper that allows the root project to override versions declared by this library. + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + + // Ensures backward compatibility + ext.getKotlinVersion = { + if (ext.has("kotlinVersion")) { + ext.kotlinVersion() + } else { + ext.safeExtGet("kotlinVersion", "1.8.10") + } + } + + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") + } +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + } + } + repositories { + maven { + url = mavenLocal().url + } + } + } +} + +android { + compileSdkVersion safeExtGet("compileSdkVersion", 34) + + def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION + if (agpVersion.tokenize('.')[0].toInteger() < 8) { + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.majorVersion + } + } + + namespace "red.infinite.reactnativemlkit.documentscanner" + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 21) + targetSdkVersion safeExtGet("targetSdkVersion", 34) + versionCode 1 + versionName "0.5.0" + } + lintOptions { + abortOnError false + } + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':expo-modules-core') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" + + implementation 'com.google.android.gms:play-services-mlkit-document-scanner:16.0.0-beta1' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3" +} diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/AndroidManifest.xml b/modules/react-native-mlkit-barcode-scanning/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..bdae66c8 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/DocumentScannerExceptions.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/DocumentScannerExceptions.kt new file mode 100644 index 00000000..f358f4b7 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/DocumentScannerExceptions.kt @@ -0,0 +1,11 @@ +package red.infinite.reactnativemlkit.documentscanner + +import androidx.core.net.toUri +import expo.modules.kotlin.exception.CodedException +import java.io.File + +internal class FailedToScanDocumentException : + CodedException("Failed to parse GmsDocumentScanner result") + +internal class MissingCurrentActivityException : + CodedException("Activity which was provided during module initialization is no longer available") \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerModule.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerModule.kt new file mode 100644 index 00000000..96f5a364 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerModule.kt @@ -0,0 +1,103 @@ +package red.infinite.reactnativemlkit.documentscanner + +import android.util.Log +import android.os.OperationCanceledException +import expo.modules.kotlin.activityresult.AppContextActivityResultLauncher +import expo.modules.kotlin.exception.CodedException +import expo.modules.kotlin.functions.Coroutine +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition +import expo.modules.kotlin.Promise +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + + +import red.infinite.reactnativemlkit.documentscanner.contracts.DocumentScannerContract +import red.infinite.reactnativemlkit.documentscanner.contracts.DocumentScannerContractOptions +import red.infinite.reactnativemlkit.documentscanner.contracts.DocumentScannerContractResult +import red.infinite.reactnativemlkit.documentscanner.contracts.PdfInfo + +import android.app.Activity + +class RNMLKitDocumentScannerModule : Module() { + override fun definition() = ModuleDefinition { + Name("RNMLKitDocumentScanner") + + // region JS API + + AsyncFunction("launchDocumentScannerAsync") Coroutine { options: RNMLKitDocumentScannerOptions -> + val contractOptions = options.toDocumentScannerContractOptions() + launchContract({ scannerLauncher.launch(contractOptions) }, options) + } + + // endregion + + + RegisterActivityContracts { + scannerLauncher = registerForActivityResult( + DocumentScannerContract(this@RNMLKitDocumentScannerModule) + ) { input, result -> handleResultUponActivityDestruction(result, input.options) } + } + } + + private lateinit var scannerLauncher: AppContextActivityResultLauncher + private var pendingDocumentScannerResult: PendingDocumentScannerResult? = null + + + private fun handleException(promise: Promise, e: Throwable? = null, message: String? = null) { + val errorMessage = message ?: "RNMLKitDocScan - Error: ${e?.message}" + Log.e("RNMLKitDocScan", "Error: $errorMessage", e) + promise.reject(CodedException(errorMessage, e)) + } + + /** + * Calls [scannerLauncher] and handles the result. + */ + private suspend fun launchContract( + scannerLauncher: suspend () -> DocumentScannerContractResult, + options: RNMLKitDocumentScannerOptions + ): Any { + return try { + var result = launchScanner(scannerLauncher) + return RNMLKitDocumentScannerResponse( + canceled = false, + pages = result.pages, + pdf = result.pdf + ) + } catch (cause: OperationCanceledException) { + return RNMLKitDocumentScannerResponse(canceled = true) + } + } + + /** + * Function that would store the results coming from 3-rd party Activity in case Android decides to + * destroy the launching application that is backgrounded. + */ + private fun handleResultUponActivityDestruction(result: DocumentScannerContractResult, options: RNMLKitDocumentScannerOptions) { + if (result is DocumentScannerContractResult.Success) { + pendingDocumentScannerResult = PendingDocumentScannerResult(result.pages, result.pdf, options) + } + } + + /** + * Launches document scanner + */ + private suspend fun launchScanner( + scannerLauncher: suspend () -> DocumentScannerContractResult + ): DocumentScannerContractResult.Success = withContext(Dispatchers.IO) { + when (val scannerResult = scannerLauncher()) { + is DocumentScannerContractResult.Success -> scannerResult + is DocumentScannerContractResult.Cancelled -> throw OperationCanceledException() + is DocumentScannerContractResult.Error -> throw FailedToScanDocumentException() + } + } +} + +/** + * Simple data structure to hold the data that has to be preserved after the Activity is destroyed. + */ +internal data class PendingDocumentScannerResult( + val pages: List? = null, + val pdf: PdfInfo? = null, + val options: RNMLKitDocumentScannerOptions +) diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerOptions.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerOptions.kt new file mode 100644 index 00000000..c62cf49c --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerOptions.kt @@ -0,0 +1,40 @@ +package red.infinite.reactnativemlkit.documentscanner + +import java.io.Serializable +import expo.modules.kotlin.records.Field +import expo.modules.kotlin.records.Record +import expo.modules.kotlin.types.Enumerable +import red.infinite.reactnativemlkit.documentscanner.contracts.DocumentScannerContractOptions + +/** + * A class representing the possible options for GmsDocumentScannerOptions for the JS side of things + * + * @see https://developers.google.com/android/reference/com/google/mlkit/vision/documentscanner/GmsDocumentScannerOptions.Builder#setScannerMode(int) + */ +internal class RNMLKitDocumentScannerOptions : Record, Serializable { + @Field + var pageLimit: Int = 1 + + @Field + var galleryImportAllowed: Boolean = true + + @Field + var scannerMode: ScannerMode = ScannerMode.FULL + + @Field + var resultFormats: ResultFormats = ResultFormats.JPEG + + fun toDocumentScannerContractOptions() = DocumentScannerContractOptions(this) +} + +internal enum class ScannerMode(val value: String) : Enumerable { + FULL("full"), + BASE_WITH_FILTER("base_with_filter"), + BASE("base") +} + +internal enum class ResultFormats(val value: String) : Enumerable { + ALL("all"), + PDF("pdf"), + JPEG("jpeg") +} \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerResponse.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerResponse.kt new file mode 100644 index 00000000..f90c0c85 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerResponse.kt @@ -0,0 +1,11 @@ +package red.infinite.reactnativemlkit.documentscanner + +import expo.modules.kotlin.records.Record +import expo.modules.kotlin.records.Field +import red.infinite.reactnativemlkit.documentscanner.contracts.PdfInfo + +internal class RNMLKitDocumentScannerResponse( + @Field val canceled: Boolean = false, + @Field var pages: List? = null, + @Field var pdf: PdfInfo? = null +) : Record diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/ContractUtils.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/ContractUtils.kt new file mode 100644 index 00000000..b176dded --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/ContractUtils.kt @@ -0,0 +1,19 @@ +package red.infinite.reactnativemlkit.documentscanner.contracts + +import android.net.Uri +import expo.modules.kotlin.records.Record +import expo.modules.kotlin.records.Field + +/** + * Data required to be returned upon successful contract completion + */ +internal sealed class DocumentScannerContractResult private constructor() { + class Success(val pages: List? = null, val pdf: PdfInfo? = null) : DocumentScannerContractResult() + object Cancelled : DocumentScannerContractResult() + object Error : DocumentScannerContractResult() +} + +data class PdfInfo( + @Field var uri: String, + @Field var pageCount: Int +) : Record \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/DocumentScannerContract.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/DocumentScannerContract.kt new file mode 100644 index 00000000..29b290ae --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/DocumentScannerContract.kt @@ -0,0 +1,102 @@ +package red.infinite.reactnativemlkit.documentscanner.contracts + +import expo.modules.kotlin.activityresult.AppContextActivityResultContract + +import red.infinite.reactnativemlkit.documentscanner.RNMLKitDocumentScannerOptions +import red.infinite.reactnativemlkit.documentscanner.MissingCurrentActivityException +import red.infinite.reactnativemlkit.documentscanner.ScannerMode +import red.infinite.reactnativemlkit.documentscanner.ResultFormats + +import android.app.Activity +import android.util.Log +import android.net.Uri +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.IntentSenderRequest +import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult + +import expo.modules.kotlin.exception.Exceptions +import expo.modules.kotlin.providers.AppContextProvider +import java.io.Serializable + +import com.google.mlkit.vision.documentscanner.GmsDocumentScanner +import com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions +import com.google.mlkit.vision.documentscanner.GmsDocumentScanning +import com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult +import com.google.android.gms.tasks.Tasks + +/** + * An [androidx.activity.result.contract.ActivityResultContract] to prompt the user to scan a document + */ +internal class DocumentScannerContract( + private val appContextProvider: AppContextProvider +) : AppContextActivityResultContract { + private val currentActivity + get() = appContextProvider.appContext.activityProvider?.currentActivity + ?: throw MissingCurrentActivityException() + + override fun createIntent(context: Context, input: DocumentScannerContractOptions): Intent { + val options = GmsDocumentScannerOptions.Builder() + .setGalleryImportAllowed(input.options.galleryImportAllowed) + .setPageLimit(input.options.pageLimit) + .setScannerMode( + when (input.options.scannerMode) { + ScannerMode.BASE -> { + GmsDocumentScannerOptions.SCANNER_MODE_BASE + } + + ScannerMode.BASE_WITH_FILTER -> { + GmsDocumentScannerOptions.SCANNER_MODE_BASE_WITH_FILTER + } + + else -> { + GmsDocumentScannerOptions.SCANNER_MODE_FULL + } + } + ) + + if (input.options.resultFormats == ResultFormats.PDF) { + options.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_PDF) + } else if (input.options.resultFormats == ResultFormats.JPEG) { + options.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG) + } else { + options.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG, GmsDocumentScannerOptions.RESULT_FORMAT_PDF) + } + + val scanner = GmsDocumentScanning.getClient(options.build()) + val intentSender = Tasks.await(scanner.getStartScanIntent(currentActivity)) + val request = IntentSenderRequest.Builder(intentSender).build() + + return StartIntentSenderForResult().createIntent(context, request) + } + + override fun parseResult(input: DocumentScannerContractOptions, resultCode: Int, intent: Intent?) = + if (resultCode == Activity.RESULT_CANCELED) { + DocumentScannerContractResult.Cancelled + } else { + if (resultCode == Activity.RESULT_OK) { + val result = GmsDocumentScanningResult.fromActivityResultIntent(intent) + + DocumentScannerContractResult.Success( + pages = result?.getPages()?.map { page -> + page.getImageUri().toString() + }, + pdf = result?.getPdf()?.let { pdf -> + PdfInfo( + uri = pdf.getUri().toString(), + pageCount = pdf.getPageCount() + ) + } + ) + } else { + DocumentScannerContractResult.Error + } + } +} + +internal data class DocumentScannerContractOptions( + val options: RNMLKitDocumentScannerOptions +) : Serializable \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/expo-module.config.json b/modules/react-native-mlkit-barcode-scanning/expo-module.config.json new file mode 100644 index 00000000..21004f71 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/expo-module.config.json @@ -0,0 +1,17 @@ +{ + "platforms": [ + "ios", + "android", + "web" + ], + "ios": { + "modules": [ + "RNMLKitBarcodeScanningModule" + ] + }, + "android": { + "modules": [ + "red.infinite.reactnativemlkit.barcodescanning.RNMLKitBarcodeScanningModule" + ] + } +} diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognition.podspec b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognition.podspec new file mode 100644 index 00000000..cb805928 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognition.podspec @@ -0,0 +1,29 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json'))) + +Pod::Spec.new do |s| + s.name = 'RNMLKitTextRecognition' + s.version = package['version'] + s.summary = package['description'] + s.description = package['description'] + s.license = package['license'] + s.author = package['author'] + s.homepage = package['homepage'] + s.platform = :ios, '15.1' + s.swift_version = '5.4' + s.source = { git: 'http://github.com/infinitered/react-native-mlkit' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + s.dependency 'RNMLKitCore' + s.dependency 'GoogleMLKit/TextRecognition' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'SWIFT_COMPILATION_MODE' => 'wholemodule' + } + + s.source_files = "**/*.{h,m,swift}" +end diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognitionModule.swift b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognitionModule.swift new file mode 100644 index 00000000..0357141d --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognitionModule.swift @@ -0,0 +1,34 @@ +import ExpoModulesCore +import MLKitCommon +import MLKitTextRecognition +import RNMLKitCore + +public class RNMLKitTextRecognitionModule: Module { + let ERROR_DOMAIN: String = "red.infinite.RNMLKit.RNMLKitTextRecognitionModuleErrorDomain" + + public func definition() -> ModuleDefinition { + Name("RNMLKitTextRecognition") + + AsyncFunction("recognizeText") { (imagePath: String, promise: Promise) in + let logger = Logger(logHandlers: [createOSLogHandler(category: Logger.EXPO_LOG_CATEGORY)]) + logger.info("RNMLKit", "recognize text: Recognizing text from image: \(imagePath) ") + + let options = TextRecognizerOptions() + let textRecognizer = TextRecognizer.textRecognizer(options:options) + + let image = try RNMLKitImage(imagePath: imagePath) + + Task { + do { + let result = try await textRecognizer.process(image.visionImage) + + promise.resolve(mapTextToRecord(result)) + } catch { + promise.reject( + NSError(domain: ERROR_DOMAIN, code: 1, userInfo: [NSLocalizedDescriptionKey: "Error occurred: \(error)"]) + ) + } + } + } + } +} diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecord.swift b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecord.swift new file mode 100644 index 00000000..7672854c --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecord.swift @@ -0,0 +1,78 @@ +import ExpoModulesCore +import MLKitCommon +import MLKitTextRecognition + +struct RectRecord: Record { + @Field var left: Double = 0.0 + @Field var top: Double = 0.0 + @Field var right: Double = 0.0 + @Field var bottom: Double = 0.0 +} + +struct TextElementRecord: Record { + @Field var text: String = "" + @Field var frame: RectRecord = RectRecord() + @Field var recognizedLanguages: [String] = [] +} + +struct TextLineRecord: Record { + @Field var text: String = "" + @Field var frame: RectRecord = RectRecord() + @Field var recognizedLanguages: [String] = [] + @Field var elements: [TextElementRecord] = [] +} + +struct BlockRecord: Record { + @Field var text: String = "" + @Field var frame: RectRecord = RectRecord() + @Field var recognizedLanguages: [String] = [] + @Field var lines: [TextLineRecord] = [] +} + +struct TextRecord: Record { + @Field var text: String = "" + @Field var blocks: [BlockRecord] = [] +} + +func mapRectToRecord(_ rect: CGRect?) -> RectRecord { + guard let rect = rect else { return RectRecord() } + return RectRecord( + left: Double(rect.minX), + top: Double(rect.minY), + right: Double(rect.maxX), + bottom: Double(rect.maxY) + ) +} + +func mapElementToRecord(_ element: TextElement) -> TextElementRecord { + return TextElementRecord( + text: element.text, + frame: mapRectToRecord(element.frame), + recognizedLanguages: element.recognizedLanguages.map { $0.languageCode ?? "" } + ) +} + +func mapLineToRecord(_ line: TextLine) -> TextLineRecord { + return TextLineRecord( + text: line.text, + frame: mapRectToRecord(line.frame), + recognizedLanguages: line.recognizedLanguages.map { $0.languageCode ?? "" }, + elements: line.elements.map(mapElementToRecord) + ) +} + +func mapTextBlockToRecord(_ textBlock: TextBlock) -> BlockRecord { + return BlockRecord( + text: textBlock.text, + frame: mapRectToRecord(textBlock.frame), + recognizedLanguages: textBlock.recognizedLanguages.map { $0.languageCode ?? "" }, + lines: textBlock.lines.map(mapLineToRecord) + ) +} + +func mapTextToRecord(_ text: Text) -> TextRecord { + return TextRecord( + text: text.text, + blocks: text.blocks.map(mapTextBlockToRecord) + ) +} diff --git a/modules/react-native-mlkit-barcode-scanning/package.json b/modules/react-native-mlkit-barcode-scanning/package.json new file mode 100644 index 00000000..4dc86ac5 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/package.json @@ -0,0 +1,47 @@ +{ + "name": "@infinitered/react-native-mlkit-barcode-scanning", + "version": "5.0.0", + "description": "MLKit Barcode Scanning for Expo", + "main": "build/index.js", + "types": "build/index.d.ts", + "scripts": { + "build": "expo-module build", + "ci:build": "tsc", + "clean": "expo-module clean", + "lint": "expo-module lint", + "test": "../../scripts/test-module.sh", + "prepublishOnly": "expo-module prepublishOnly", + "expo-module": "expo-module" + }, + "keywords": [ + "react-native", + "expo", + "react-native-mlkit-document-scanner", + "RNMLKitDocumentScanner" + ], + "repository": "https://github.com/infinitered/react-native-mlkit", + "bugs": { + "url": "https://github.com/infinitered/react-native-mlkit/issues" + }, + "author": "Tyler Williams (https://github.com/coolsoftwaretyler)", + "license": "MIT", + "homepage": "https://docs.infinite.red/react-native-mlkit/barcode-scanning", + "dependencies": { + "@infinitered/react-native-mlkit-core": "5.0.0" + }, + "devDependencies": { + "@types/react": "~19.1.10", + "expo-module-scripts": "^3.4.1", + "expo-modules-core": "~3.0.25" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + }, + "expo": { + "autolinking": { + "nativeModulesDir": "../../modules" + } + } +} diff --git a/modules/react-native-mlkit-barcode-scanning/src/constants.ts b/modules/react-native-mlkit-barcode-scanning/src/constants.ts new file mode 100644 index 00000000..fa6010b1 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/src/constants.ts @@ -0,0 +1 @@ +export const WEB_ERROR = "react-native-mlkit is not supported on web"; diff --git a/modules/react-native-mlkit-barcode-scanning/src/index.test.ts b/modules/react-native-mlkit-barcode-scanning/src/index.test.ts new file mode 100644 index 00000000..dd21e350 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/src/index.test.ts @@ -0,0 +1,5 @@ +describe("placeholder test", () => { + it("should pass", () => { + expect(true).toBe(true); + }); +}); diff --git a/modules/react-native-mlkit-barcode-scanning/src/index.ts b/modules/react-native-mlkit-barcode-scanning/src/index.ts new file mode 100644 index 00000000..5190ec84 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/src/index.ts @@ -0,0 +1,21 @@ +import { requireNativeModule } from "expo"; +import { UnavailabilityError } from "expo-modules-core"; + +// Import the native module. On web, it will be resolved to RNMLKitBarcodeScanningModule.web.ts +// and on native platforms to RNMLKitBarcodeScanningModule.ts +import RNMLKitBarcodeScanningModule from "./module/RNMLKitBarcodeScanningModule"; +import type { BarcodeScannerResult } from "./module/RNMLKitBarcodeScanning.types"; + +export * from "./module/RNMLKitBarcodeScanning.types"; + +interface RNMLKitBarcodeScanningModule { + process: (imagePath: string) => Promise; +} + +async function process(imagePath: string): Promise { + return await barcodeScanningModule.process(imagePath); +} + +const barcodeScanningModule = requireNativeModule("RNMLKitBarcodeScanning"); + +export { process} \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanning.types.ts b/modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanning.types.ts new file mode 100644 index 00000000..fbb82737 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanning.types.ts @@ -0,0 +1,51 @@ +export enum BarcodeFormat { + ALL = "all", + code128 = "code128", + code39 = "code39", + code93 = "code93", + codaBar = "codaBar", + dataMatrix = "dataMatrix", + EAN13 = "EAN13", + EAN8 = "EAN8", + ITF = "ITF", + qrCode = "qrCode", + UPCA = "UPCA", + UPCE = "UPCE", + PDF417 = "PDF417", + aztec = "AZTEC", +} + +// TOOD: should we have a shared interface from core for this? +interface Rect { + left: number; + top: number; + right: number; + bottom: number; +} + +export interface BarcodeScannerOptions { + formats: BarcodeFormat[] +} + +export interface BarcodeScannerResult { + barcodes: Barcode[] +} + +export interface Barcode { + frame: Rect + rawValue?: string + rawData?: Data + displayValue?: string + format: BarcodeFormat + cornerPoints: Point[] + valueType: BarcodeValueType + email?: BarcodeEmail + phone?: BarcodePhone + sms?: BarcodeSMS + url?: BarcodeURLBookmark + wifi?: BarcodeWifi + geoPoint?: BarcodeGeoPoint + contactInfo?: BarcodeContactInfo + calendarEvent?: BarcodeCalendarEvent + driverLicense?: BarcodeDriverLicense +} \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanningModule.ts b/modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanningModule.ts new file mode 100644 index 00000000..f1cbec36 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanningModule.ts @@ -0,0 +1,5 @@ +import { requireNativeModule } from "expo-modules-core"; + +// It loads the native module object from the JSI or falls back to +// the bridge module (from NativeModulesProxy) if the remote debugger is on. +export default requireNativeModule("RNMLKitDocumentScanner"); diff --git a/modules/react-native-mlkit-barcode-scanning/tsconfig.json b/modules/react-native-mlkit-barcode-scanning/tsconfig.json new file mode 100644 index 00000000..e4c7cad1 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@infinitered/tsconfig/expo-module", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} From e4e65a353bef636fcee45d95b0a534e22ea94b50 Mon Sep 17 00:00:00 2001 From: Tyler Williams Date: Fri, 5 Dec 2025 10:06:38 -0500 Subject: [PATCH 02/31] fix: scaffold with create expo module, add placeholder doc --- docs/barcode-scanning/category.json | 4 + docs/barcode-scanning/index.md | 7 ++ .../.eslintrc.js | 9 -- .../.gitignore | 57 ---------- .../.npmignore | 11 -- .../README.md | 37 ------ .../android/build.gradle | 107 +++++------------- .../ReactNativeMlkitBarcodeScanningModule.kt | 50 ++++++++ .../ReactNativeMlkitBarcodeScanningView.kt | 30 +++++ .../DocumentScannerExceptions.kt | 11 -- .../RNMLKitDocumentScannerModule.kt | 103 ----------------- .../RNMLKitDocumentScannerOptions.kt | 40 ------- .../RNMLKitDocumentScannerResponse.kt | 11 -- .../contracts/ContractUtils.kt | 19 ---- .../contracts/DocumentScannerContract.kt | 102 ----------------- .../expo-module.config.json | 16 +-- .../index.ts | 5 + .../ios/RNMLKitTextRecognition.podspec | 29 ----- .../ios/RNMLKitTextRecognitionModule.swift | 34 ------ .../ios/RNMLKitTextRecord.swift | 78 ------------- .../ReactNativeMlkitBarcodeScanning.podspec | 23 ++++ ...eactNativeMlkitBarcodeScanningModule.swift | 48 ++++++++ .../ReactNativeMlkitBarcodeScanningView.swift | 38 +++++++ .../package.json | 47 -------- ... ReactNativeMlkitBarcodeScanning.types.ts} | 0 .../ReactNativeMlkitBarcodeScanningModule.ts | 10 ++ ...actNativeMlkitBarcodeScanningModule.web.ts | 19 ++++ .../ReactNativeMlkitBarcodeScanningView.tsx | 11 ++ ...eactNativeMlkitBarcodeScanningView.web.tsx | 15 +++ .../src/constants.ts | 1 - .../src/index.test.ts | 5 - .../src/index.ts | 21 ---- .../module/RNMLKitBarcodeScanningModule.ts | 5 - .../tsconfig.json | 8 -- 34 files changed, 291 insertions(+), 720 deletions(-) create mode 100644 docs/barcode-scanning/category.json create mode 100644 docs/barcode-scanning/index.md delete mode 100644 modules/react-native-mlkit-barcode-scanning/.eslintrc.js delete mode 100644 modules/react-native-mlkit-barcode-scanning/.gitignore delete mode 100644 modules/react-native-mlkit-barcode-scanning/.npmignore delete mode 100644 modules/react-native-mlkit-barcode-scanning/README.md create mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/expo/modules/mlkitbarcodescanning/ReactNativeMlkitBarcodeScanningModule.kt create mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/expo/modules/mlkitbarcodescanning/ReactNativeMlkitBarcodeScanningView.kt delete mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/DocumentScannerExceptions.kt delete mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerModule.kt delete mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerOptions.kt delete mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerResponse.kt delete mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/ContractUtils.kt delete mode 100644 modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/DocumentScannerContract.kt create mode 100644 modules/react-native-mlkit-barcode-scanning/index.ts delete mode 100644 modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognition.podspec delete mode 100644 modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognitionModule.swift delete mode 100644 modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecord.swift create mode 100644 modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanning.podspec create mode 100644 modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanningModule.swift create mode 100644 modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanningView.swift delete mode 100644 modules/react-native-mlkit-barcode-scanning/package.json rename modules/react-native-mlkit-barcode-scanning/src/{module/RNMLKitBarcodeScanning.types.ts => ReactNativeMlkitBarcodeScanning.types.ts} (100%) create mode 100644 modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningModule.ts create mode 100644 modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningModule.web.ts create mode 100644 modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningView.tsx create mode 100644 modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningView.web.tsx delete mode 100644 modules/react-native-mlkit-barcode-scanning/src/constants.ts delete mode 100644 modules/react-native-mlkit-barcode-scanning/src/index.test.ts delete mode 100644 modules/react-native-mlkit-barcode-scanning/src/index.ts delete mode 100644 modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanningModule.ts delete mode 100644 modules/react-native-mlkit-barcode-scanning/tsconfig.json diff --git a/docs/barcode-scanning/category.json b/docs/barcode-scanning/category.json new file mode 100644 index 00000000..333c8e38 --- /dev/null +++ b/docs/barcode-scanning/category.json @@ -0,0 +1,4 @@ +{ + "label": "Barcode Scanner", + "position": 100 + } \ No newline at end of file diff --git a/docs/barcode-scanning/index.md b/docs/barcode-scanning/index.md new file mode 100644 index 00000000..436bc90d --- /dev/null +++ b/docs/barcode-scanning/index.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 1 +--- + +# Barcode Scanner + +<> \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/.eslintrc.js b/modules/react-native-mlkit-barcode-scanning/.eslintrc.js deleted file mode 100644 index 8ef9bd99..00000000 --- a/modules/react-native-mlkit-barcode-scanning/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - root: true, - extends: ["universe/native", "universe/web"], - ignorePatterns: ["build"], - rules: { - "@typescript-eslint/array-type": "off", - "prettier/prettier": "off", - }, -}; diff --git a/modules/react-native-mlkit-barcode-scanning/.gitignore b/modules/react-native-mlkit-barcode-scanning/.gitignore deleted file mode 100644 index e64b91c9..00000000 --- a/modules/react-native-mlkit-barcode-scanning/.gitignore +++ /dev/null @@ -1,57 +0,0 @@ -# OSX -# -.DS_Store - -# VSCode -.vscode/ -jsconfig.json - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -project.xcworkspace - -# Android/IJ -# -.classpath -.cxx -.gradle -.idea -.project -.settings -local.properties -android.iml -android/app/libs -android/keystores/debug.keystore - -# Cocoapods -# -example/ios/Pods - -# Ruby -example/vendor/ - -# node.js -# -node_modules/ -npm-debug.log -yarn-debug.log -yarn-error.log - -# Expo -.expo/* diff --git a/modules/react-native-mlkit-barcode-scanning/.npmignore b/modules/react-native-mlkit-barcode-scanning/.npmignore deleted file mode 100644 index fa6abe7c..00000000 --- a/modules/react-native-mlkit-barcode-scanning/.npmignore +++ /dev/null @@ -1,11 +0,0 @@ -# Exclude all top-level hidden directories by convention -/.*/ - -__mocks__ -__tests__ - -/babel.config.js -/android/src/androidTest/ -/android/src/test/ -/android/build/ -/example/ diff --git a/modules/react-native-mlkit-barcode-scanning/README.md b/modules/react-native-mlkit-barcode-scanning/README.md deleted file mode 100644 index 4490d24e..00000000 --- a/modules/react-native-mlkit-barcode-scanning/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# react-native-mlkit-barcode-scanning - -MLKit Barcode Scanning for Expo - -# API documentation - -### [Check the friendly docs here! 📖](https://docs.infinite.red/react-native-mlkit/barcode-scanning) - -# Installation in managed Expo projects - -For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions -in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no -documentation available then this library is not yet usable within managed projects — it is likely to be included -in an upcoming Expo SDK release. - -# Installation in bare React Native projects - -For bare React Native projects, you must ensure that you -have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before -continuing. - -### Add the package to your npm dependencies - -``` -npm install @infinitered/react-native-mlkit-barcode-scanning -``` - -### Configure for iOS - -Run `npx pod-install` after installing the npm package. - -### Configure for Android - -# Contributing - -Contributions are very welcome! Please refer to guidelines described in -the [contributing guide](https://github.com/expo/expo#contributing). diff --git a/modules/react-native-mlkit-barcode-scanning/android/build.gradle b/modules/react-native-mlkit-barcode-scanning/android/build.gradle index 58c1308a..84441037 100644 --- a/modules/react-native-mlkit-barcode-scanning/android/build.gradle +++ b/modules/react-native-mlkit-barcode-scanning/android/build.gradle @@ -1,96 +1,43 @@ apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'maven-publish' -group = 'red.infinite.reactnativemlkit.documentscanner' -version = '0.5.0' - -buildscript { - def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") - if (expoModulesCorePlugin.exists()) { - apply from: expoModulesCorePlugin - applyKotlinExpoModulesCorePlugin() - } - - // Simple helper that allows the root project to override versions declared by this library. - ext.safeExtGet = { prop, fallback -> - rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback - } - - // Ensures backward compatibility - ext.getKotlinVersion = { - if (ext.has("kotlinVersion")) { - ext.kotlinVersion() - } else { - ext.safeExtGet("kotlinVersion", "1.8.10") +group = 'expo.modules.mlkitbarcodescanning' +version = '0.7.6' + +def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") +apply from: expoModulesCorePlugin +applyKotlinExpoModulesCorePlugin() +useCoreDependencies() +useExpoPublishing() + +// If you want to use the managed Android SDK versions from expo-modules-core, set this to true. +// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code. +// Most of the time, you may like to manage the Android SDK versions yourself. +def useManagedAndroidSdkVersions = false +if (useManagedAndroidSdkVersions) { + useDefaultAndroidSdkVersions() +} else { + buildscript { + // Simple helper that allows the root project to override versions declared by this library. + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } } - - repositories { - mavenCentral() - } - - dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") - } -} - -afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - } - } - repositories { - maven { - url = mavenLocal().url - } + project.android { + compileSdkVersion safeExtGet("compileSdkVersion", 36) + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 24) + targetSdkVersion safeExtGet("targetSdkVersion", 36) } } } android { - compileSdkVersion safeExtGet("compileSdkVersion", 34) - - def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION - if (agpVersion.tokenize('.')[0].toInteger() < 8) { - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.majorVersion - } - } - - namespace "red.infinite.reactnativemlkit.documentscanner" + namespace "expo.modules.mlkitbarcodescanning" defaultConfig { - minSdkVersion safeExtGet("minSdkVersion", 21) - targetSdkVersion safeExtGet("targetSdkVersion", 34) versionCode 1 - versionName "0.5.0" + versionName "0.7.6" } lintOptions { abortOnError false } - publishing { - singleVariant("release") { - withSourcesJar() - } - } -} - -repositories { - mavenCentral() -} - -dependencies { - implementation project(':expo-modules-core') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" - - implementation 'com.google.android.gms:play-services-mlkit-document-scanner:16.0.0-beta1' - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3" } diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/expo/modules/mlkitbarcodescanning/ReactNativeMlkitBarcodeScanningModule.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/expo/modules/mlkitbarcodescanning/ReactNativeMlkitBarcodeScanningModule.kt new file mode 100644 index 00000000..fb1f0889 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/expo/modules/mlkitbarcodescanning/ReactNativeMlkitBarcodeScanningModule.kt @@ -0,0 +1,50 @@ +package expo.modules.mlkitbarcodescanning + +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition +import java.net.URL + +class ReactNativeMlkitBarcodeScanningModule : Module() { + // Each module class must implement the definition function. The definition consists of components + // that describes the module's functionality and behavior. + // See https://docs.expo.dev/modules/module-api for more details about available components. + override fun definition() = ModuleDefinition { + // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. + // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. + // The module will be accessible from `requireNativeModule('ReactNativeMlkitBarcodeScanning')` in JavaScript. + Name("ReactNativeMlkitBarcodeScanning") + + // Defines constant property on the module. + Constant("PI") { + Math.PI + } + + // Defines event names that the module can send to JavaScript. + Events("onChange") + + // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread. + Function("hello") { + "Hello world! 👋" + } + + // Defines a JavaScript function that always returns a Promise and whose native code + // is by default dispatched on the different thread than the JavaScript runtime runs on. + AsyncFunction("setValueAsync") { value: String -> + // Send an event to JavaScript. + sendEvent("onChange", mapOf( + "value" to value + )) + } + + // Enables the module to be used as a native view. Definition components that are accepted as part of + // the view definition: Prop, Events. + View(ReactNativeMlkitBarcodeScanningView::class) { + // Defines a setter for the `url` prop. + Prop("url") { view: ReactNativeMlkitBarcodeScanningView, url: URL -> + view.webView.loadUrl(url.toString()) + } + // Defines an event that the view can send to JavaScript. + Events("onLoad") + } + } +} diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/expo/modules/mlkitbarcodescanning/ReactNativeMlkitBarcodeScanningView.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/expo/modules/mlkitbarcodescanning/ReactNativeMlkitBarcodeScanningView.kt new file mode 100644 index 00000000..a51536a6 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/expo/modules/mlkitbarcodescanning/ReactNativeMlkitBarcodeScanningView.kt @@ -0,0 +1,30 @@ +package expo.modules.mlkitbarcodescanning + +import android.content.Context +import android.webkit.WebView +import android.webkit.WebViewClient +import expo.modules.kotlin.AppContext +import expo.modules.kotlin.viewevent.EventDispatcher +import expo.modules.kotlin.views.ExpoView + +class ReactNativeMlkitBarcodeScanningView(context: Context, appContext: AppContext) : ExpoView(context, appContext) { + // Creates and initializes an event dispatcher for the `onLoad` event. + // The name of the event is inferred from the value and needs to match the event name defined in the module. + private val onLoad by EventDispatcher() + + // Defines a WebView that will be used as the root subview. + internal val webView = WebView(context).apply { + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String) { + // Sends an event to JavaScript. Triggers a callback defined on the view component in JavaScript. + onLoad(mapOf("url" to url)) + } + } + } + + init { + // Adds the WebView to the view hierarchy. + addView(webView) + } +} diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/DocumentScannerExceptions.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/DocumentScannerExceptions.kt deleted file mode 100644 index f358f4b7..00000000 --- a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/DocumentScannerExceptions.kt +++ /dev/null @@ -1,11 +0,0 @@ -package red.infinite.reactnativemlkit.documentscanner - -import androidx.core.net.toUri -import expo.modules.kotlin.exception.CodedException -import java.io.File - -internal class FailedToScanDocumentException : - CodedException("Failed to parse GmsDocumentScanner result") - -internal class MissingCurrentActivityException : - CodedException("Activity which was provided during module initialization is no longer available") \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerModule.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerModule.kt deleted file mode 100644 index 96f5a364..00000000 --- a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerModule.kt +++ /dev/null @@ -1,103 +0,0 @@ -package red.infinite.reactnativemlkit.documentscanner - -import android.util.Log -import android.os.OperationCanceledException -import expo.modules.kotlin.activityresult.AppContextActivityResultLauncher -import expo.modules.kotlin.exception.CodedException -import expo.modules.kotlin.functions.Coroutine -import expo.modules.kotlin.modules.Module -import expo.modules.kotlin.modules.ModuleDefinition -import expo.modules.kotlin.Promise -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - - -import red.infinite.reactnativemlkit.documentscanner.contracts.DocumentScannerContract -import red.infinite.reactnativemlkit.documentscanner.contracts.DocumentScannerContractOptions -import red.infinite.reactnativemlkit.documentscanner.contracts.DocumentScannerContractResult -import red.infinite.reactnativemlkit.documentscanner.contracts.PdfInfo - -import android.app.Activity - -class RNMLKitDocumentScannerModule : Module() { - override fun definition() = ModuleDefinition { - Name("RNMLKitDocumentScanner") - - // region JS API - - AsyncFunction("launchDocumentScannerAsync") Coroutine { options: RNMLKitDocumentScannerOptions -> - val contractOptions = options.toDocumentScannerContractOptions() - launchContract({ scannerLauncher.launch(contractOptions) }, options) - } - - // endregion - - - RegisterActivityContracts { - scannerLauncher = registerForActivityResult( - DocumentScannerContract(this@RNMLKitDocumentScannerModule) - ) { input, result -> handleResultUponActivityDestruction(result, input.options) } - } - } - - private lateinit var scannerLauncher: AppContextActivityResultLauncher - private var pendingDocumentScannerResult: PendingDocumentScannerResult? = null - - - private fun handleException(promise: Promise, e: Throwable? = null, message: String? = null) { - val errorMessage = message ?: "RNMLKitDocScan - Error: ${e?.message}" - Log.e("RNMLKitDocScan", "Error: $errorMessage", e) - promise.reject(CodedException(errorMessage, e)) - } - - /** - * Calls [scannerLauncher] and handles the result. - */ - private suspend fun launchContract( - scannerLauncher: suspend () -> DocumentScannerContractResult, - options: RNMLKitDocumentScannerOptions - ): Any { - return try { - var result = launchScanner(scannerLauncher) - return RNMLKitDocumentScannerResponse( - canceled = false, - pages = result.pages, - pdf = result.pdf - ) - } catch (cause: OperationCanceledException) { - return RNMLKitDocumentScannerResponse(canceled = true) - } - } - - /** - * Function that would store the results coming from 3-rd party Activity in case Android decides to - * destroy the launching application that is backgrounded. - */ - private fun handleResultUponActivityDestruction(result: DocumentScannerContractResult, options: RNMLKitDocumentScannerOptions) { - if (result is DocumentScannerContractResult.Success) { - pendingDocumentScannerResult = PendingDocumentScannerResult(result.pages, result.pdf, options) - } - } - - /** - * Launches document scanner - */ - private suspend fun launchScanner( - scannerLauncher: suspend () -> DocumentScannerContractResult - ): DocumentScannerContractResult.Success = withContext(Dispatchers.IO) { - when (val scannerResult = scannerLauncher()) { - is DocumentScannerContractResult.Success -> scannerResult - is DocumentScannerContractResult.Cancelled -> throw OperationCanceledException() - is DocumentScannerContractResult.Error -> throw FailedToScanDocumentException() - } - } -} - -/** - * Simple data structure to hold the data that has to be preserved after the Activity is destroyed. - */ -internal data class PendingDocumentScannerResult( - val pages: List? = null, - val pdf: PdfInfo? = null, - val options: RNMLKitDocumentScannerOptions -) diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerOptions.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerOptions.kt deleted file mode 100644 index c62cf49c..00000000 --- a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerOptions.kt +++ /dev/null @@ -1,40 +0,0 @@ -package red.infinite.reactnativemlkit.documentscanner - -import java.io.Serializable -import expo.modules.kotlin.records.Field -import expo.modules.kotlin.records.Record -import expo.modules.kotlin.types.Enumerable -import red.infinite.reactnativemlkit.documentscanner.contracts.DocumentScannerContractOptions - -/** - * A class representing the possible options for GmsDocumentScannerOptions for the JS side of things - * - * @see https://developers.google.com/android/reference/com/google/mlkit/vision/documentscanner/GmsDocumentScannerOptions.Builder#setScannerMode(int) - */ -internal class RNMLKitDocumentScannerOptions : Record, Serializable { - @Field - var pageLimit: Int = 1 - - @Field - var galleryImportAllowed: Boolean = true - - @Field - var scannerMode: ScannerMode = ScannerMode.FULL - - @Field - var resultFormats: ResultFormats = ResultFormats.JPEG - - fun toDocumentScannerContractOptions() = DocumentScannerContractOptions(this) -} - -internal enum class ScannerMode(val value: String) : Enumerable { - FULL("full"), - BASE_WITH_FILTER("base_with_filter"), - BASE("base") -} - -internal enum class ResultFormats(val value: String) : Enumerable { - ALL("all"), - PDF("pdf"), - JPEG("jpeg") -} \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerResponse.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerResponse.kt deleted file mode 100644 index f90c0c85..00000000 --- a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/RNMLKitDocumentScannerResponse.kt +++ /dev/null @@ -1,11 +0,0 @@ -package red.infinite.reactnativemlkit.documentscanner - -import expo.modules.kotlin.records.Record -import expo.modules.kotlin.records.Field -import red.infinite.reactnativemlkit.documentscanner.contracts.PdfInfo - -internal class RNMLKitDocumentScannerResponse( - @Field val canceled: Boolean = false, - @Field var pages: List? = null, - @Field var pdf: PdfInfo? = null -) : Record diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/ContractUtils.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/ContractUtils.kt deleted file mode 100644 index b176dded..00000000 --- a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/ContractUtils.kt +++ /dev/null @@ -1,19 +0,0 @@ -package red.infinite.reactnativemlkit.documentscanner.contracts - -import android.net.Uri -import expo.modules.kotlin.records.Record -import expo.modules.kotlin.records.Field - -/** - * Data required to be returned upon successful contract completion - */ -internal sealed class DocumentScannerContractResult private constructor() { - class Success(val pages: List? = null, val pdf: PdfInfo? = null) : DocumentScannerContractResult() - object Cancelled : DocumentScannerContractResult() - object Error : DocumentScannerContractResult() -} - -data class PdfInfo( - @Field var uri: String, - @Field var pageCount: Int -) : Record \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/DocumentScannerContract.kt b/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/DocumentScannerContract.kt deleted file mode 100644 index 29b290ae..00000000 --- a/modules/react-native-mlkit-barcode-scanning/android/src/main/java/red/infinite/reactnativemlkit/documentscanner/contracts/DocumentScannerContract.kt +++ /dev/null @@ -1,102 +0,0 @@ -package red.infinite.reactnativemlkit.documentscanner.contracts - -import expo.modules.kotlin.activityresult.AppContextActivityResultContract - -import red.infinite.reactnativemlkit.documentscanner.RNMLKitDocumentScannerOptions -import red.infinite.reactnativemlkit.documentscanner.MissingCurrentActivityException -import red.infinite.reactnativemlkit.documentscanner.ScannerMode -import red.infinite.reactnativemlkit.documentscanner.ResultFormats - -import android.app.Activity -import android.util.Log -import android.net.Uri -import android.content.ContentResolver -import android.content.Context -import android.content.Intent -import androidx.activity.result.ActivityResult -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.IntentSenderRequest -import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult - -import expo.modules.kotlin.exception.Exceptions -import expo.modules.kotlin.providers.AppContextProvider -import java.io.Serializable - -import com.google.mlkit.vision.documentscanner.GmsDocumentScanner -import com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions -import com.google.mlkit.vision.documentscanner.GmsDocumentScanning -import com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult -import com.google.android.gms.tasks.Tasks - -/** - * An [androidx.activity.result.contract.ActivityResultContract] to prompt the user to scan a document - */ -internal class DocumentScannerContract( - private val appContextProvider: AppContextProvider -) : AppContextActivityResultContract { - private val currentActivity - get() = appContextProvider.appContext.activityProvider?.currentActivity - ?: throw MissingCurrentActivityException() - - override fun createIntent(context: Context, input: DocumentScannerContractOptions): Intent { - val options = GmsDocumentScannerOptions.Builder() - .setGalleryImportAllowed(input.options.galleryImportAllowed) - .setPageLimit(input.options.pageLimit) - .setScannerMode( - when (input.options.scannerMode) { - ScannerMode.BASE -> { - GmsDocumentScannerOptions.SCANNER_MODE_BASE - } - - ScannerMode.BASE_WITH_FILTER -> { - GmsDocumentScannerOptions.SCANNER_MODE_BASE_WITH_FILTER - } - - else -> { - GmsDocumentScannerOptions.SCANNER_MODE_FULL - } - } - ) - - if (input.options.resultFormats == ResultFormats.PDF) { - options.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_PDF) - } else if (input.options.resultFormats == ResultFormats.JPEG) { - options.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG) - } else { - options.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG, GmsDocumentScannerOptions.RESULT_FORMAT_PDF) - } - - val scanner = GmsDocumentScanning.getClient(options.build()) - val intentSender = Tasks.await(scanner.getStartScanIntent(currentActivity)) - val request = IntentSenderRequest.Builder(intentSender).build() - - return StartIntentSenderForResult().createIntent(context, request) - } - - override fun parseResult(input: DocumentScannerContractOptions, resultCode: Int, intent: Intent?) = - if (resultCode == Activity.RESULT_CANCELED) { - DocumentScannerContractResult.Cancelled - } else { - if (resultCode == Activity.RESULT_OK) { - val result = GmsDocumentScanningResult.fromActivityResultIntent(intent) - - DocumentScannerContractResult.Success( - pages = result?.getPages()?.map { page -> - page.getImageUri().toString() - }, - pdf = result?.getPdf()?.let { pdf -> - PdfInfo( - uri = pdf.getUri().toString(), - pageCount = pdf.getPageCount() - ) - } - ) - } else { - DocumentScannerContractResult.Error - } - } -} - -internal data class DocumentScannerContractOptions( - val options: RNMLKitDocumentScannerOptions -) : Serializable \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/expo-module.config.json b/modules/react-native-mlkit-barcode-scanning/expo-module.config.json index 21004f71..cbb842f5 100644 --- a/modules/react-native-mlkit-barcode-scanning/expo-module.config.json +++ b/modules/react-native-mlkit-barcode-scanning/expo-module.config.json @@ -1,17 +1,9 @@ { - "platforms": [ - "ios", - "android", - "web" - ], - "ios": { - "modules": [ - "RNMLKitBarcodeScanningModule" - ] + "platforms": ["apple", "android", "web"], + "apple": { + "modules": ["ReactNativeMlkitBarcodeScanningModule"] }, "android": { - "modules": [ - "red.infinite.reactnativemlkit.barcodescanning.RNMLKitBarcodeScanningModule" - ] + "modules": ["expo.modules.mlkitbarcodescanning.ReactNativeMlkitBarcodeScanningModule"] } } diff --git a/modules/react-native-mlkit-barcode-scanning/index.ts b/modules/react-native-mlkit-barcode-scanning/index.ts new file mode 100644 index 00000000..191e46f0 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/index.ts @@ -0,0 +1,5 @@ +// Reexport the native module. On web, it will be resolved to ReactNativeMlkitBarcodeScanningModule.web.ts +// and on native platforms to ReactNativeMlkitBarcodeScanningModule.ts +export { default } from './src/ReactNativeMlkitBarcodeScanningModule'; +export { default as ReactNativeMlkitBarcodeScanningView } from './src/ReactNativeMlkitBarcodeScanningView'; +export * from './src/ReactNativeMlkitBarcodeScanning.types'; diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognition.podspec b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognition.podspec deleted file mode 100644 index cb805928..00000000 --- a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognition.podspec +++ /dev/null @@ -1,29 +0,0 @@ -require 'json' - -package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json'))) - -Pod::Spec.new do |s| - s.name = 'RNMLKitTextRecognition' - s.version = package['version'] - s.summary = package['description'] - s.description = package['description'] - s.license = package['license'] - s.author = package['author'] - s.homepage = package['homepage'] - s.platform = :ios, '15.1' - s.swift_version = '5.4' - s.source = { git: 'http://github.com/infinitered/react-native-mlkit' } - s.static_framework = true - - s.dependency 'ExpoModulesCore' - s.dependency 'RNMLKitCore' - s.dependency 'GoogleMLKit/TextRecognition' - - # Swift/Objective-C compatibility - s.pod_target_xcconfig = { - 'DEFINES_MODULE' => 'YES', - 'SWIFT_COMPILATION_MODE' => 'wholemodule' - } - - s.source_files = "**/*.{h,m,swift}" -end diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognitionModule.swift b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognitionModule.swift deleted file mode 100644 index 0357141d..00000000 --- a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecognitionModule.swift +++ /dev/null @@ -1,34 +0,0 @@ -import ExpoModulesCore -import MLKitCommon -import MLKitTextRecognition -import RNMLKitCore - -public class RNMLKitTextRecognitionModule: Module { - let ERROR_DOMAIN: String = "red.infinite.RNMLKit.RNMLKitTextRecognitionModuleErrorDomain" - - public func definition() -> ModuleDefinition { - Name("RNMLKitTextRecognition") - - AsyncFunction("recognizeText") { (imagePath: String, promise: Promise) in - let logger = Logger(logHandlers: [createOSLogHandler(category: Logger.EXPO_LOG_CATEGORY)]) - logger.info("RNMLKit", "recognize text: Recognizing text from image: \(imagePath) ") - - let options = TextRecognizerOptions() - let textRecognizer = TextRecognizer.textRecognizer(options:options) - - let image = try RNMLKitImage(imagePath: imagePath) - - Task { - do { - let result = try await textRecognizer.process(image.visionImage) - - promise.resolve(mapTextToRecord(result)) - } catch { - promise.reject( - NSError(domain: ERROR_DOMAIN, code: 1, userInfo: [NSLocalizedDescriptionKey: "Error occurred: \(error)"]) - ) - } - } - } - } -} diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecord.swift b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecord.swift deleted file mode 100644 index 7672854c..00000000 --- a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitTextRecord.swift +++ /dev/null @@ -1,78 +0,0 @@ -import ExpoModulesCore -import MLKitCommon -import MLKitTextRecognition - -struct RectRecord: Record { - @Field var left: Double = 0.0 - @Field var top: Double = 0.0 - @Field var right: Double = 0.0 - @Field var bottom: Double = 0.0 -} - -struct TextElementRecord: Record { - @Field var text: String = "" - @Field var frame: RectRecord = RectRecord() - @Field var recognizedLanguages: [String] = [] -} - -struct TextLineRecord: Record { - @Field var text: String = "" - @Field var frame: RectRecord = RectRecord() - @Field var recognizedLanguages: [String] = [] - @Field var elements: [TextElementRecord] = [] -} - -struct BlockRecord: Record { - @Field var text: String = "" - @Field var frame: RectRecord = RectRecord() - @Field var recognizedLanguages: [String] = [] - @Field var lines: [TextLineRecord] = [] -} - -struct TextRecord: Record { - @Field var text: String = "" - @Field var blocks: [BlockRecord] = [] -} - -func mapRectToRecord(_ rect: CGRect?) -> RectRecord { - guard let rect = rect else { return RectRecord() } - return RectRecord( - left: Double(rect.minX), - top: Double(rect.minY), - right: Double(rect.maxX), - bottom: Double(rect.maxY) - ) -} - -func mapElementToRecord(_ element: TextElement) -> TextElementRecord { - return TextElementRecord( - text: element.text, - frame: mapRectToRecord(element.frame), - recognizedLanguages: element.recognizedLanguages.map { $0.languageCode ?? "" } - ) -} - -func mapLineToRecord(_ line: TextLine) -> TextLineRecord { - return TextLineRecord( - text: line.text, - frame: mapRectToRecord(line.frame), - recognizedLanguages: line.recognizedLanguages.map { $0.languageCode ?? "" }, - elements: line.elements.map(mapElementToRecord) - ) -} - -func mapTextBlockToRecord(_ textBlock: TextBlock) -> BlockRecord { - return BlockRecord( - text: textBlock.text, - frame: mapRectToRecord(textBlock.frame), - recognizedLanguages: textBlock.recognizedLanguages.map { $0.languageCode ?? "" }, - lines: textBlock.lines.map(mapLineToRecord) - ) -} - -func mapTextToRecord(_ text: Text) -> TextRecord { - return TextRecord( - text: text.text, - blocks: text.blocks.map(mapTextBlockToRecord) - ) -} diff --git a/modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanning.podspec b/modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanning.podspec new file mode 100644 index 00000000..413b8a0f --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanning.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = 'ReactNativeMlkitBarcodeScanning' + s.version = '1.0.0' + s.summary = 'A sample project summary' + s.description = 'A sample project description' + s.author = '' + s.homepage = 'https://docs.expo.dev/modules/' + s.platforms = { + :ios => '15.1', + :tvos => '15.1' + } + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanningModule.swift b/modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanningModule.swift new file mode 100644 index 00000000..460b0239 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanningModule.swift @@ -0,0 +1,48 @@ +import ExpoModulesCore + +public class ReactNativeMlkitBarcodeScanningModule: Module { + // Each module class must implement the definition function. The definition consists of components + // that describes the module's functionality and behavior. + // See https://docs.expo.dev/modules/module-api for more details about available components. + public func definition() -> ModuleDefinition { + // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. + // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. + // The module will be accessible from `requireNativeModule('ReactNativeMlkitBarcodeScanning')` in JavaScript. + Name("ReactNativeMlkitBarcodeScanning") + + // Defines constant property on the module. + Constant("PI") { + Double.pi + } + + // Defines event names that the module can send to JavaScript. + Events("onChange") + + // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread. + Function("hello") { + return "Hello world! 👋" + } + + // Defines a JavaScript function that always returns a Promise and whose native code + // is by default dispatched on the different thread than the JavaScript runtime runs on. + AsyncFunction("setValueAsync") { (value: String) in + // Send an event to JavaScript. + self.sendEvent("onChange", [ + "value": value + ]) + } + + // Enables the module to be used as a native view. Definition components that are accepted as part of the + // view definition: Prop, Events. + View(ReactNativeMlkitBarcodeScanningView.self) { + // Defines a setter for the `url` prop. + Prop("url") { (view: ReactNativeMlkitBarcodeScanningView, url: URL) in + if view.webView.url != url { + view.webView.load(URLRequest(url: url)) + } + } + + Events("onLoad") + } + } +} diff --git a/modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanningView.swift b/modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanningView.swift new file mode 100644 index 00000000..5dbbdf56 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/ios/ReactNativeMlkitBarcodeScanningView.swift @@ -0,0 +1,38 @@ +import ExpoModulesCore +import WebKit + +// This view will be used as a native component. Make sure to inherit from `ExpoView` +// to apply the proper styling (e.g. border radius and shadows). +class ReactNativeMlkitBarcodeScanningView: ExpoView { + let webView = WKWebView() + let onLoad = EventDispatcher() + var delegate: WebViewDelegate? + + required init(appContext: AppContext? = nil) { + super.init(appContext: appContext) + clipsToBounds = true + delegate = WebViewDelegate { url in + self.onLoad(["url": url]) + } + webView.navigationDelegate = delegate + addSubview(webView) + } + + override func layoutSubviews() { + webView.frame = bounds + } +} + +class WebViewDelegate: NSObject, WKNavigationDelegate { + let onUrlChange: (String) -> Void + + init(onUrlChange: @escaping (String) -> Void) { + self.onUrlChange = onUrlChange + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation) { + if let url = webView.url { + onUrlChange(url.absoluteString) + } + } +} diff --git a/modules/react-native-mlkit-barcode-scanning/package.json b/modules/react-native-mlkit-barcode-scanning/package.json deleted file mode 100644 index 4dc86ac5..00000000 --- a/modules/react-native-mlkit-barcode-scanning/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@infinitered/react-native-mlkit-barcode-scanning", - "version": "5.0.0", - "description": "MLKit Barcode Scanning for Expo", - "main": "build/index.js", - "types": "build/index.d.ts", - "scripts": { - "build": "expo-module build", - "ci:build": "tsc", - "clean": "expo-module clean", - "lint": "expo-module lint", - "test": "../../scripts/test-module.sh", - "prepublishOnly": "expo-module prepublishOnly", - "expo-module": "expo-module" - }, - "keywords": [ - "react-native", - "expo", - "react-native-mlkit-document-scanner", - "RNMLKitDocumentScanner" - ], - "repository": "https://github.com/infinitered/react-native-mlkit", - "bugs": { - "url": "https://github.com/infinitered/react-native-mlkit/issues" - }, - "author": "Tyler Williams (https://github.com/coolsoftwaretyler)", - "license": "MIT", - "homepage": "https://docs.infinite.red/react-native-mlkit/barcode-scanning", - "dependencies": { - "@infinitered/react-native-mlkit-core": "5.0.0" - }, - "devDependencies": { - "@types/react": "~19.1.10", - "expo-module-scripts": "^3.4.1", - "expo-modules-core": "~3.0.25" - }, - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - }, - "expo": { - "autolinking": { - "nativeModulesDir": "../../modules" - } - } -} diff --git a/modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanning.types.ts b/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanning.types.ts similarity index 100% rename from modules/react-native-mlkit-barcode-scanning/src/module/RNMLKitBarcodeScanning.types.ts rename to modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanning.types.ts diff --git a/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningModule.ts b/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningModule.ts new file mode 100644 index 00000000..e975bfb0 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningModule.ts @@ -0,0 +1,10 @@ +import { NativeModule, requireNativeModule } from 'expo'; + +import { BarcodeScannerResult } from './ReactNativeMlkitBarcodeScanning.types'; + +declare class ReactNativeMlkitBarcodeScanningModule extends NativeModule { + process(imagePath: string): Promise; +} + +// This call loads the native module object from the JSI. +export default requireNativeModule('ReactNativeMlkitBarcodeScanning'); diff --git a/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningModule.web.ts b/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningModule.web.ts new file mode 100644 index 00000000..35dcd9ec --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningModule.web.ts @@ -0,0 +1,19 @@ +import { registerWebModule, NativeModule } from 'expo'; + +import { ChangeEventPayload } from './ReactNativeMlkitBarcodeScanning.types'; + +type ReactNativeMlkitBarcodeScanningModuleEvents = { + onChange: (params: ChangeEventPayload) => void; +} + +class ReactNativeMlkitBarcodeScanningModule extends NativeModule { + PI = Math.PI; + async setValueAsync(value: string): Promise { + this.emit('onChange', { value }); + } + hello() { + return 'Hello world! 👋'; + } +}; + +export default registerWebModule(ReactNativeMlkitBarcodeScanningModule, 'ReactNativeMlkitBarcodeScanningModule'); diff --git a/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningView.tsx b/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningView.tsx new file mode 100644 index 00000000..1dcb12c9 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningView.tsx @@ -0,0 +1,11 @@ +import { requireNativeView } from 'expo'; +import * as React from 'react'; + +import { ReactNativeMlkitBarcodeScanningViewProps } from './ReactNativeMlkitBarcodeScanning.types'; + +const NativeView: React.ComponentType = + requireNativeView('ReactNativeMlkitBarcodeScanning'); + +export default function ReactNativeMlkitBarcodeScanningView(props: ReactNativeMlkitBarcodeScanningViewProps) { + return ; +} diff --git a/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningView.web.tsx b/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningView.web.tsx new file mode 100644 index 00000000..0c84a98d --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/src/ReactNativeMlkitBarcodeScanningView.web.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +import { ReactNativeMlkitBarcodeScanningViewProps } from './ReactNativeMlkitBarcodeScanning.types'; + +export default function ReactNativeMlkitBarcodeScanningView(props: ReactNativeMlkitBarcodeScanningViewProps) { + return ( +
+