├── .watchmanconfig ├── .nvmrc ├── example ├── .watchmanconfig ├── jest.config.js ├── .bundle │ └── config ├── app.json ├── android │ ├── app │ │ ├── debug.keystore │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── values │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ └── drawable │ │ │ │ │ │ └── rn_edit_text_material.xml │ │ │ │ ├── java │ │ │ │ │ └── pushpendersingh │ │ │ │ │ │ └── reactnativescanner │ │ │ │ │ │ └── example │ │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ │ └── MainApplication.kt │ │ │ │ └── AndroidManifest.xml │ │ │ └── debug │ │ │ │ └── AndroidManifest.xml │ │ ├── proguard-rules.pro │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ ├── build.gradle │ ├── gradle.properties │ ├── gradlew.bat │ └── gradlew ├── ios │ ├── ReactNativeScannerExample │ │ ├── Images.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── PrivacyInfo.xcprivacy │ │ ├── AppDelegate.swift │ │ ├── Info.plist │ │ └── LaunchScreen.storyboard │ ├── ReactNativeScannerExample.xcworkspace │ │ └── contents.xcworkspacedata │ ├── .xcode.env │ ├── Podfile │ └── ReactNativeScannerExample.xcodeproj │ │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── ReactNativeScannerExample.xcscheme │ │ └── project.pbxproj ├── index.js ├── babel.config.js ├── metro.config.js ├── react-native.config.js ├── Gemfile ├── package.json ├── Gemfile.lock ├── README.md └── src │ └── App.tsx ├── .gitattributes ├── tsconfig.build.json ├── android ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── pushpendersingh │ │ └── reactnativescanner │ │ ├── ReactNativeScannerPackage.kt │ │ ├── ReactNativeScannerViewManager.kt │ │ ├── ReactNativeScannerView.kt │ │ ├── ReactNativeScannerModule.kt │ │ └── CameraManager.kt ├── gradle.properties └── build.gradle ├── ios ├── ReactNativeScanner-Bridging-Header.h ├── ReactNativeScanner.h ├── CameraViewManager.mm ├── ReactNativeScanner.mm └── CameraView.swift ├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── config.yml │ ├── feature_request.md │ └── bug_report.yml ├── workflows │ ├── semantic-pr.yml │ ├── versions.yml │ ├── stale.yml │ ├── triage.yaml │ ├── check-repro.yml │ └── ci.yml └── actions │ └── setup │ └── action.yml ├── babel.config.js ├── lefthook.yml ├── .editorconfig ├── .yarnrc.yml ├── src ├── ReactNativeScannerViewNativeComponent.ts ├── CameraView.tsx ├── NativeReactNativeScanner.ts ├── index.tsx └── __tests__ │ └── index.test.tsx ├── eslint.config.mjs ├── tsconfig.json ├── LICENSE ├── turbo.json ├── .gitignore ├── ReactNativeScanner.podspec ├── package.json ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── README.md /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.19.0 2 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /example/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | }; 4 | -------------------------------------------------------------------------------- /example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "exclude": ["example", "lib"] 4 | } 5 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactNativeScannerExample", 3 | "displayName": "ReactNativeScannerExample" 4 | } 5 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/debug.keystore -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ReactNativeScannerExample 3 | 4 | -------------------------------------------------------------------------------- /example/ios/ReactNativeScannerExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './src/App'; 3 | import { name as appName } from './app.json'; 4 | 5 | AppRegistry.registerComponent(appName, () => App); 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpender-singh-ap/react-native-scanner/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | ReactNativeScanner_kotlinVersion=2.0.21 2 | ReactNativeScanner_minSdkVersion=24 3 | ReactNativeScanner_targetSdkVersion=34 4 | ReactNativeScanner_compileSdkVersion=35 5 | ReactNativeScanner_ndkVersion=27.1.12297006 6 | -------------------------------------------------------------------------------- /ios/ReactNativeScanner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // ReactNativeScanner-Bridging-Header.h 3 | // ReactNativeScanner 4 | // 5 | // Created by Pushpender Singh 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💬 Question 3 | about: You need help with @pushpendersingh/react-native-scanner. 4 | title: '' 5 | labels: 'question' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Ask your Question 11 | 12 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | overrides: [ 3 | { 4 | exclude: /\/node_modules\//, 5 | presets: ['module:react-native-builder-bob/babel-preset'], 6 | }, 7 | { 8 | include: /\/node_modules\//, 9 | presets: ['module:@react-native/babel-preset'], 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | lint: 5 | glob: "*.{js,ts,jsx,tsx}" 6 | run: npx eslint {staged_files} 7 | types: 8 | glob: "*.{js,ts, jsx, tsx}" 9 | run: npx tsc 10 | commit-msg: 11 | parallel: true 12 | commands: 13 | commitlint: 14 | run: npx commitlint --edit 15 | -------------------------------------------------------------------------------- /example/ios/ReactNativeScannerExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ios/ReactNativeScanner.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @class CameraManager; 5 | 6 | @interface ReactNativeScanner : NativeReactNativeScannerSpecBase 7 | 8 | @property (nonatomic, strong, readonly) CameraManager *cameraManager; 9 | 10 | @end 11 | 12 | 13 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | nmHoistingLimits: workspaces 3 | 4 | plugins: 5 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 6 | spec: "@yarnpkg/plugin-interactive-tools" 7 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 8 | spec: "@yarnpkg/plugin-workspace-tools" 9 | 10 | yarnPath: .yarn/releases/yarn-3.6.1.cjs 11 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { getConfig } = require('react-native-builder-bob/babel-config'); 3 | const pkg = require('../package.json'); 4 | 5 | const root = path.resolve(__dirname, '..'); 6 | 7 | module.exports = getConfig( 8 | { 9 | presets: ['module:@react-native/babel-preset'], 10 | }, 11 | { root, pkg } 12 | ); 13 | -------------------------------------------------------------------------------- /.github/workflows/semantic-pr.yml: -------------------------------------------------------------------------------- 1 | name: "Semantic Pull Request" 2 | on: [pull_request] 3 | 4 | jobs: 5 | main: 6 | name: Validate PR title 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: amannn/action-semantic-pull-request@v4.5.0 10 | env: 11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 12 | with: 13 | validateSingleCommit: true 14 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } 2 | plugins { id("com.facebook.react.settings") } 3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } 4 | rootProject.name = 'pushpendersingh.reactnativescanner.example' 5 | include ':app' 6 | includeBuild('../node_modules/@react-native/gradle-plugin') 7 | -------------------------------------------------------------------------------- /src/ReactNativeScannerViewNativeComponent.ts: -------------------------------------------------------------------------------- 1 | import type { ViewProps, HostComponent } from 'react-native'; 2 | import { codegenNativeComponent } from 'react-native'; 3 | 4 | export interface NativeProps extends ViewProps { 5 | // Add any custom props here if needed in the future 6 | } 7 | 8 | export default codegenNativeComponent( 9 | 'ReactNativeScannerView' 10 | ) as HostComponent; 11 | -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Request 💡 4 | url: https://github.com/pushpender-singh-ap/react-native-scanner/discussions/new?category=ideas 5 | about: If you have a feature request, please create a new discussion on GitHub. 6 | - name: Discussions on GitHub 💬 7 | url: https://github.com/pushpender-singh-ap/react-native-scanner/discussions 8 | about: If this library works as promised but you need help, please ask questions there. 9 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { getDefaultConfig } = require('@react-native/metro-config'); 3 | const { withMetroConfig } = require('react-native-monorepo-config'); 4 | 5 | const root = path.resolve(__dirname, '..'); 6 | 7 | /** 8 | * Metro configuration 9 | * https://facebook.github.io/metro/docs/configuration 10 | * 11 | * @type {import('metro-config').MetroConfig} 12 | */ 13 | module.exports = withMetroConfig(getDefaultConfig(__dirname), { 14 | root, 15 | dirname: __dirname, 16 | }); 17 | -------------------------------------------------------------------------------- /src/CameraView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { ViewProps } from 'react-native'; 3 | import ReactNativeScannerViewNativeComponent from './ReactNativeScannerViewNativeComponent'; 4 | 5 | export interface CameraViewProps extends ViewProps { 6 | style?: ViewProps['style']; 7 | } 8 | 9 | export const CameraView = React.forwardRef( 10 | (props, ref) => { 11 | return ; 12 | } 13 | ); 14 | 15 | CameraView.displayName = 'CameraView'; 16 | -------------------------------------------------------------------------------- /example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /example/react-native.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pkg = require('../package.json'); 3 | 4 | module.exports = { 5 | project: { 6 | ios: { 7 | automaticPodsInstallation: true, 8 | }, 9 | }, 10 | dependencies: { 11 | [pkg.name]: { 12 | root: path.join(__dirname, '..'), 13 | platforms: { 14 | // Codegen script incorrectly fails without this 15 | // So we explicitly specify the platforms with empty object 16 | ios: {}, 17 | android: {}, 18 | }, 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /.github/workflows/versions.yml: -------------------------------------------------------------------------------- 1 | name: Check versions 2 | on: 3 | issues: 4 | types: [opened, edited] 5 | 6 | jobs: 7 | check-versions: 8 | if: ${{ github.event.label.name == 'bug' }} 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: react-navigation/check-versions-action@v1.0.0 12 | with: 13 | github-token: ${{ secrets.GITHUB_TOKEN }} 14 | required-packages: | 15 | react-native 16 | @pushpendersingh/react-native-scanner 17 | optional-packages: | 18 | npm 19 | yarn 20 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures. 7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' 9 | gem 'xcodeproj', '< 1.26.0' 10 | gem 'concurrent-ruby', '< 1.3.4' 11 | 12 | # Ruby 3.4.0 has removed some libraries from the standard library. 13 | gem 'bigdecimal' 14 | gem 'logger' 15 | gem 'benchmark' 16 | gem 'mutex_m' 17 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "36.0.0" 4 | minSdkVersion = 24 5 | compileSdkVersion = 36 6 | targetSdkVersion = 36 7 | ndkVersion = "27.1.12297006" 8 | kotlinVersion = "2.1.20" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🗣 Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'feature request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { fixupConfigRules } from '@eslint/compat'; 2 | import { FlatCompat } from '@eslint/eslintrc'; 3 | import js from '@eslint/js'; 4 | import prettier from 'eslint-plugin-prettier'; 5 | import { defineConfig } from 'eslint/config'; 6 | import path from 'node:path'; 7 | import { fileURLToPath } from 'node:url'; 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | allConfig: js.configs.all, 15 | }); 16 | 17 | export default defineConfig([ 18 | { 19 | extends: fixupConfigRules(compat.extends('@react-native', 'prettier')), 20 | plugins: { prettier }, 21 | rules: { 22 | 'react/react-in-jsx-scope': 'off', 23 | 'prettier/prettier': 'error', 24 | }, 25 | }, 26 | { 27 | ignores: ['node_modules/', 'lib/'], 28 | }, 29 | ]); 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "paths": { 5 | "@pushpendersingh/react-native-scanner": ["./src/index"] 6 | }, 7 | "allowUnreachableCode": false, 8 | "allowUnusedLabels": false, 9 | "customConditions": ["react-native-strict-api"], 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "jsx": "react-jsx", 13 | "lib": ["ESNext"], 14 | "module": "ESNext", 15 | "moduleResolution": "bundler", 16 | "noEmit": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "noImplicitReturns": true, 19 | "noImplicitUseStrict": false, 20 | "noStrictGenericChecks": false, 21 | "noUncheckedIndexedAccess": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "resolveJsonModule": true, 25 | "skipLibCheck": true, 26 | "strict": true, 27 | "target": "ESNext", 28 | "verbatimModuleSyntax": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/pushpendersingh/reactnativescanner/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package pushpendersingh.reactnativescanner.example 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | 8 | class MainActivity : ReactActivity() { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | override fun getMainComponentName(): String = "ReactNativeScannerExample" 15 | 16 | /** 17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 19 | */ 20 | override fun createReactActivityDelegate(): ReactActivityDelegate = 21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Pushpender Singh 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": [".nvmrc", ".yarnrc.yml"], 4 | "globalEnv": ["NODE_ENV"], 5 | "tasks": { 6 | "build:android": { 7 | "env": ["ANDROID_HOME", "ORG_GRADLE_PROJECT_newArchEnabled"], 8 | "inputs": [ 9 | "package.json", 10 | "android", 11 | "!android/build", 12 | "src/*.ts", 13 | "src/*.tsx", 14 | "example/package.json", 15 | "example/android", 16 | "!example/android/.gradle", 17 | "!example/android/build", 18 | "!example/android/app/build" 19 | ], 20 | "outputs": [] 21 | }, 22 | "build:ios": { 23 | "env": [ 24 | "RCT_NEW_ARCH_ENABLED", 25 | "RCT_USE_RN_DEP", 26 | "RCT_USE_PREBUILT_RNCORE" 27 | ], 28 | "inputs": [ 29 | "package.json", 30 | "*.podspec", 31 | "ios", 32 | "src/*.ts", 33 | "src/*.tsx", 34 | "example/package.json", 35 | "example/ios", 36 | "!example/ios/build", 37 | "!example/ios/Pods" 38 | ], 39 | "outputs": [] 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/ios/ReactNativeScannerExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/ios/ReactNativeScannerExample/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | CA92.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryFileTimestamp 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | C617.1 21 | 22 | 23 | 24 | NSPrivacyAccessedAPIType 25 | NSPrivacyAccessedAPICategorySystemBootTime 26 | NSPrivacyAccessedAPITypeReasons 27 | 28 | 35F9.1 29 | 30 | 31 | 32 | NSPrivacyCollectedDataTypes 33 | 34 | NSPrivacyTracking 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/CameraViewManager.mm: -------------------------------------------------------------------------------- 1 | // 2 | // CameraViewManager.mm 3 | // ReactNativeScanner 4 | // 5 | // Created by Pushpender Singh 6 | // 7 | 8 | #import "ReactNativeScanner.h" 9 | #import 10 | #import 11 | 12 | // Import the Swift bridging header 13 | #if __has_include() 14 | // For dynamic frameworks or when installed as a framework 15 | #import 16 | #else 17 | // For static libraries or when included directly in the project 18 | #import "ReactNativeScanner-Swift.h" 19 | #endif 20 | 21 | @interface ReactNativeScannerViewManager : RCTViewManager 22 | @end 23 | 24 | @implementation ReactNativeScannerViewManager 25 | 26 | RCT_EXPORT_MODULE(ReactNativeScannerView) 27 | 28 | - (UIView *)view { 29 | CameraView *cameraView = [[CameraView alloc] init]; 30 | 31 | ReactNativeScanner *scannerModule = 32 | [self.bridge moduleForClass:[ReactNativeScanner class]]; 33 | if (scannerModule && scannerModule.cameraManager) { 34 | [cameraView setCameraManager:scannerModule.cameraManager]; 35 | } 36 | 37 | return cameraView; 38 | } 39 | 40 | + (BOOL)requiresMainQueueSetup { 41 | return YES; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | **/.xcode.env.local 32 | 33 | # Android/IJ 34 | # 35 | .classpath 36 | .cxx 37 | .gradle 38 | .idea 39 | .project 40 | .settings 41 | local.properties 42 | android.iml 43 | 44 | # Cocoapods 45 | # 46 | example/ios/Pods 47 | 48 | # Ruby 49 | example/vendor/ 50 | 51 | # node.js 52 | # 53 | node_modules/ 54 | npm-debug.log 55 | yarn-debug.log 56 | yarn-error.log 57 | 58 | # BUCK 59 | buck-out/ 60 | \.buckd/ 61 | android/app/libs 62 | android/keystores/debug.keystore 63 | 64 | # Yarn 65 | .yarn/* 66 | !.yarn/patches 67 | !.yarn/plugins 68 | !.yarn/releases 69 | !.yarn/sdks 70 | !.yarn/versions 71 | 72 | # Expo 73 | .expo/ 74 | 75 | # Turborepo 76 | .turbo/ 77 | 78 | # generated by bob 79 | lib/ 80 | 81 | # React Native Codegen 82 | ios/generated 83 | android/generated 84 | 85 | # React Native Nitro Modules 86 | nitrogen/ 87 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup Node.js and install dependencies 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup Node.js 8 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 9 | with: 10 | node-version-file: .nvmrc 11 | 12 | - name: Restore dependencies 13 | id: yarn-cache 14 | uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 15 | with: 16 | path: | 17 | **/node_modules 18 | .yarn/install-state.gz 19 | key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }} 20 | restore-keys: | 21 | ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} 22 | ${{ runner.os }}-yarn- 23 | 24 | - name: Install dependencies 25 | if: steps.yarn-cache.outputs.cache-hit != 'true' 26 | run: yarn install --immutable 27 | shell: bash 28 | 29 | - name: Cache dependencies 30 | if: steps.yarn-cache.outputs.cache-hit != 'true' 31 | uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 32 | with: 33 | path: | 34 | **/node_modules 35 | .yarn/install-state.gz 36 | key: ${{ steps.yarn-cache.outputs.cache-primary-key }} 37 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Close stale issues and PRs 3 | on: 4 | schedule: 5 | - cron: '30 1 * * *' 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v3 12 | with: 13 | repo-token: ${{ secrets.GITHUB_TOKEN }} 14 | days-before-stale: 30 15 | days-before-close: 7 16 | any-of-labels: 'needs more info,needs repro,needs response' 17 | exempt-issue-labels: 'repro provided,keep open' 18 | exempt-pr-labels: 'keep open' 19 | stale-issue-label: 'stale' 20 | stale-pr-label: 'stale' 21 | stale-issue-message: 'Hello 👋, this issue has been open for more than a month without a repro or any activity. If the issue is still present in the latest version, please provide a repro or leave a comment within 7 days to keep it open, otherwise it will be closed automatically. If you found a solution or workaround for the issue, please comment here for others to find. If this issue is critical for you, please consider sending a pull request to fix it.' 22 | stale-pr-message: 'Hello 👋, this pull request has been open for more than a month with no activity on it. If you think this is still necessary with the latest version, please comment and ping a maintainer to get this reviewed, otherwise it will be closed automatically in 7 days.' 23 | -------------------------------------------------------------------------------- /ReactNativeScanner.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "ReactNativeScanner" 7 | s.version = package["version"] 8 | s.summary = package["description"] 9 | s.homepage = package["homepage"] 10 | s.license = package["license"] 11 | s.authors = package["author"] 12 | 13 | s.platforms = { :ios => min_ios_version_supported } 14 | s.source = { :git => "https://github.com/pushpender-singh-ap/react-native-scanner.git", :tag => "#{s.version}" } 15 | 16 | s.source_files = "ios/**/*.{h,m,mm,swift}" 17 | s.private_header_files = "ios/**/*.h" 18 | 19 | # Enable modules and set the Swift bridging header to allow Swift and Objective-C to interoperate 20 | s.pod_target_xcconfig = { 21 | # Enables the use of modules (i.e., frameworks) in the generated Xcode project 22 | 'DEFINES_MODULE' => 'YES', 23 | 24 | # Sets the name of the generated Swift header for Objective-C code to use 25 | 'SWIFT_OBJC_INTERFACE_HEADER_NAME' => 'ReactNativeScanner-Swift.h' 26 | } 27 | 28 | # The name of the module that will be generated for this pod 29 | s.module_name = s.name 30 | 31 | # Swift support 32 | s.swift_version = '5.0' 33 | 34 | # Frameworks 35 | s.frameworks = 'AVFoundation', 'Vision' 36 | 37 | install_modules_dependencies(s) 38 | end 39 | -------------------------------------------------------------------------------- /src/NativeReactNativeScanner.ts: -------------------------------------------------------------------------------- 1 | import type { CodegenTypes, TurboModule } from 'react-native'; 2 | import { TurboModuleRegistry } from 'react-native'; 3 | 4 | export type BarcodeResult = { 5 | data: string; 6 | type: string; 7 | bounds?: { 8 | width: number; 9 | height: number; 10 | origin: { 11 | topLeft: { x: number; y: number }; 12 | bottomLeft: { x: number; y: number }; 13 | bottomRight: { x: number; y: number }; 14 | topRight: { x: number; y: number }; 15 | }; 16 | }; 17 | }; 18 | 19 | export type BarcodeScannedEvent = BarcodeResult[]; 20 | 21 | export interface Spec extends TurboModule { 22 | // Start scanning - results will be emitted via events 23 | startScanning(): Promise; 24 | 25 | // Stop scanning 26 | stopScanning(): Promise; 27 | 28 | // Enable flashlight/torch 29 | enableFlashlight(): Promise; 30 | 31 | // Disable flashlight/torch 32 | disableFlashlight(): Promise; 33 | 34 | // Release camera resources 35 | releaseCamera(): Promise; 36 | 37 | // Check if camera permission is granted 38 | hasCameraPermission(): Promise; 39 | 40 | // Request camera permission 41 | requestCameraPermission(): Promise; 42 | 43 | readonly onBarcodeScanned: CodegenTypes.EventEmitter; 44 | } 45 | 46 | export default TurboModuleRegistry.getEnforcing('ReactNativeScanner'); 47 | -------------------------------------------------------------------------------- /example/ios/ReactNativeScannerExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import React 3 | import React_RCTAppDelegate 4 | import ReactAppDependencyProvider 5 | 6 | @main 7 | class AppDelegate: UIResponder, UIApplicationDelegate { 8 | var window: UIWindow? 9 | 10 | var reactNativeDelegate: ReactNativeDelegate? 11 | var reactNativeFactory: RCTReactNativeFactory? 12 | 13 | func application( 14 | _ application: UIApplication, 15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil 16 | ) -> Bool { 17 | let delegate = ReactNativeDelegate() 18 | let factory = RCTReactNativeFactory(delegate: delegate) 19 | delegate.dependencyProvider = RCTAppDependencyProvider() 20 | 21 | reactNativeDelegate = delegate 22 | reactNativeFactory = factory 23 | 24 | window = UIWindow(frame: UIScreen.main.bounds) 25 | 26 | factory.startReactNative( 27 | withModuleName: "ReactNativeScannerExample", 28 | in: window, 29 | launchOptions: launchOptions 30 | ) 31 | 32 | return true 33 | } 34 | } 35 | 36 | class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { 37 | override func sourceURL(for bridge: RCTBridge) -> URL? { 38 | self.bundleURL() 39 | } 40 | 41 | override func bundleURL() -> URL? { 42 | #if DEBUG 43 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") 44 | #else 45 | Bundle.main.url(forResource: "main", withExtension: "jsbundle") 46 | #endif 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pushpendersingh/react-native-scanner-example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", 10 | "build:ios": "react-native build-ios --mode Debug", 11 | "xcode": "xed -b ios", 12 | "pod": "npx pod-install", 13 | "ios-16": "react-native run-ios --simulator='iPhone 16'", 14 | "clean": "watchman watch-del-all" 15 | }, 16 | "dependencies": { 17 | "@react-native/new-app-screen": "0.81.1", 18 | "react": "19.1.0", 19 | "react-native": "0.81.1", 20 | "react-native-permissions": "^5.4.2", 21 | "react-native-safe-area-context": "^5.5.2" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.25.2", 25 | "@babel/preset-env": "^7.25.3", 26 | "@babel/runtime": "^7.25.0", 27 | "@react-native-community/cli": "20.0.0", 28 | "@react-native-community/cli-platform-android": "20.0.0", 29 | "@react-native-community/cli-platform-ios": "20.0.0", 30 | "@react-native/babel-preset": "0.81.1", 31 | "@react-native/metro-config": "0.81.1", 32 | "@react-native/typescript-config": "0.81.1", 33 | "@types/react": "^19.1.0", 34 | "react-native-builder-bob": "^0.40.13", 35 | "react-native-monorepo-config": "^0.1.9" 36 | }, 37 | "engines": { 38 | "node": ">=20" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerPackage.kt: -------------------------------------------------------------------------------- 1 | package com.pushpendersingh.reactnativescanner 2 | 3 | import com.facebook.react.BaseReactPackage 4 | import com.facebook.react.bridge.NativeModule 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.module.model.ReactModuleInfo 7 | import com.facebook.react.module.model.ReactModuleInfoProvider 8 | import com.facebook.react.uimanager.ViewManager 9 | import java.util.HashMap 10 | 11 | class ReactNativeScannerPackage : BaseReactPackage() { 12 | override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { 13 | return if (name == ReactNativeScannerModule.NAME) { 14 | ReactNativeScannerModule(reactContext) 15 | } else { 16 | null 17 | } 18 | } 19 | 20 | override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { 21 | return ReactModuleInfoProvider { 22 | val moduleInfos: MutableMap = HashMap() 23 | moduleInfos[ReactNativeScannerModule.NAME] = ReactModuleInfo( 24 | ReactNativeScannerModule.NAME, 25 | ReactNativeScannerModule.NAME, 26 | false, // canOverrideExistingModule 27 | false, // needsEagerInit 28 | false, // isCxxModule 29 | true // isTurboModule 30 | ) 31 | moduleInfos 32 | } 33 | } 34 | 35 | override fun createViewManagers(reactContext: ReactApplicationContext): List> { 36 | return listOf(ReactNativeScannerViewManager()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/pushpendersingh/reactnativescanner/example/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package pushpendersingh.reactnativescanner.example 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative 8 | import com.facebook.react.ReactNativeHost 9 | import com.facebook.react.ReactPackage 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | 13 | class MainApplication : Application(), ReactApplication { 14 | 15 | override val reactNativeHost: ReactNativeHost = 16 | object : DefaultReactNativeHost(this) { 17 | override fun getPackages(): List = 18 | PackageList(this).packages.apply { 19 | // Packages that cannot be autolinked yet can be added manually here, for example: 20 | // add(MyReactNativePackage()) 21 | } 22 | 23 | override fun getJSMainModuleName(): String = "index" 24 | 25 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 26 | 27 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 28 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 29 | } 30 | 31 | override val reactHost: ReactHost 32 | get() = getDefaultReactHost(applicationContext, reactNativeHost) 33 | 34 | override fun onCreate() { 35 | super.onCreate() 36 | loadReactNative(this) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/triage.yaml: -------------------------------------------------------------------------------- 1 | name: Triage 2 | on: 3 | issues: 4 | types: [labeled] 5 | 6 | jobs: 7 | needs-more-info: 8 | runs-on: ubuntu-latest 9 | if: github.event.label.name == 'needs more info' 10 | steps: 11 | - uses: actions/checkout@master 12 | - uses: actions/github@v1.0.0 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | with: 16 | args: comment "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can, to be able to help. Make sure to at least provide - Current behaviour, Expected behaviour, A way to reproduce the issue with minimal code (link to [snack.expo.dev](https://snack.expo.dev)) or a repo on GitHub, and the information about your environment (such as the platform of the device, versions of all the packages etc.)." 17 | 18 | needs-repro: 19 | runs-on: ubuntu-latest 20 | if: github.event.label.name == 'needs repro' 21 | steps: 22 | - uses: actions/checkout@master 23 | - uses: actions/github@v1.0.0 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | args: comment "Hey! Thanks for opening the issue. Can you provide a minimal repro which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible. The easiest way to provide a repro is on [snack.expo.dev](https://snack.expo.dev). If it's not possible to repro it on [snack.expo.dev](https://snack.expo.dev), then you can also provide the repro in a GitHub repository." 28 | -------------------------------------------------------------------------------- /example/ios/ReactNativeScannerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ReactNativeScannerExample 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSAllowsLocalNetworking 32 | 33 | 34 | NSLocationWhenInUseUsageDescription 35 | 36 | RCTNewArchEnabled 37 | 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIRequiredDeviceCapabilities 41 | 42 | arm64 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | NSCameraUsageDescription 53 | We need access to your camera to scan barcodes. 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | ENV['RCT_NEW_ARCH_ENABLED'] = '1' 2 | 3 | def node_require(script) 4 | # Resolve script with node to allow for hoisting 5 | require Pod::Executable.execute_command('node', ['-p', 6 | "require.resolve( 7 | '#{script}', 8 | {paths: [process.argv[1]]}, 9 | )", __dir__]).strip 10 | end 11 | 12 | # Use it to require both react-native's and this package's scripts: 13 | node_require('react-native/scripts/react_native_pods.rb') 14 | node_require('react-native-permissions/scripts/setup.rb') 15 | 16 | platform :ios, min_ios_version_supported 17 | prepare_react_native_project! 18 | 19 | # ⬇️ uncomment the permissions you need 20 | setup_permissions([ 21 | # 'AppTrackingTransparency', 22 | # 'Bluetooth', 23 | # 'Calendars', 24 | # 'CalendarsWriteOnly', 25 | 'Camera', 26 | # 'Contacts', 27 | # 'FaceID', 28 | # 'LocationAccuracy', 29 | # 'LocationAlways', 30 | # 'LocationWhenInUse', 31 | # 'MediaLibrary', 32 | # 'Microphone', 33 | # 'Motion', 34 | # 'Notifications', 35 | # 'PhotoLibrary', 36 | # 'PhotoLibraryAddOnly', 37 | # 'Reminders', 38 | # 'Siri', 39 | # 'SpeechRecognition', 40 | # 'StoreKit', 41 | ]) 42 | 43 | linkage = ENV['USE_FRAMEWORKS'] 44 | if linkage != nil 45 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 46 | use_frameworks! :linkage => linkage.to_sym 47 | end 48 | 49 | target 'ReactNativeScannerExample' do 50 | config = use_native_modules! 51 | 52 | use_react_native!( 53 | :path => config[:reactNativePath], 54 | # An absolute path to your application root. 55 | :app_path => "#{Pod::Config.instance.installation_root}/.." 56 | ) 57 | 58 | post_install do |installer| 59 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 60 | react_native_post_install( 61 | installer, 62 | config[:reactNativePath], 63 | :mac_catalyst_enabled => false, 64 | # :ccache_enabled => true 65 | ) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt: -------------------------------------------------------------------------------- 1 | package com.pushpendersingh.reactnativescanner 2 | 3 | import android.view.View 4 | import com.facebook.react.bridge.ReadableArray 5 | import com.facebook.react.module.annotations.ReactModule 6 | import com.facebook.react.uimanager.SimpleViewManager 7 | import com.facebook.react.uimanager.ThemedReactContext 8 | import com.facebook.react.uimanager.annotations.ReactProp 9 | 10 | @ReactModule(name = ReactNativeScannerViewManager.NAME) 11 | class ReactNativeScannerViewManager : SimpleViewManager() { 12 | 13 | private var cameraManager: CameraManager? = null 14 | 15 | override fun getName(): String { 16 | return NAME 17 | } 18 | 19 | override fun createViewInstance(reactContext: ThemedReactContext): ReactNativeScannerView { 20 | val view = ReactNativeScannerView(reactContext) 21 | 22 | // Get or create camera manager from the module 23 | val scannerModule = reactContext.catalystInstance 24 | ?.getNativeModule("ReactNativeScanner") as? ReactNativeScannerModule 25 | 26 | cameraManager = scannerModule?.getCameraManager() 27 | cameraManager?.let { view.setCameraManager(it) } 28 | 29 | return view 30 | } 31 | 32 | @ReactProp(name = "active") 33 | fun setActive(view: ReactNativeScannerView, active: Boolean) { 34 | // Could be used to pause/resume scanning 35 | } 36 | 37 | override fun getExportedCustomDirectEventTypeConstants(): Map? { 38 | return mapOf( 39 | "onBarcodeScanned" to mapOf("registrationName" to "onBarcodeScanned"), 40 | "onError" to mapOf("registrationName" to "onError") 41 | ) 42 | } 43 | 44 | override fun onDropViewInstance(view: ReactNativeScannerView) { 45 | super.onDropViewInstance(view) 46 | // View cleanup is handled in the view's onDetachedFromWindow 47 | } 48 | 49 | companion object { 50 | const val NAME = "ReactNativeScannerView" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Use this property to specify which architecture you want to build. 26 | # You can also override it from the CLI using 27 | # ./gradlew -PreactNativeArchitectures=x86_64 28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 29 | 30 | # Use this property to enable support to the new architecture. 31 | # This will allow you to use TurboModules and the Fabric render in 32 | # your application. You should enable this flag either if you want 33 | # to write custom TurboModules/Fabric components OR use libraries that 34 | # are providing them. 35 | newArchEnabled=true 36 | 37 | # Use this property to enable or disable the Hermes JS engine. 38 | # If set to false, you will be using JSC instead. 39 | hermesEnabled=true 40 | 41 | # Use this property to enable edge-to-edge display support. 42 | # This allows your app to draw behind system bars for an immersive UI. 43 | # Note: Only works with ReactActivity and should not be used with custom Activity. 44 | edgeToEdgeEnabled=false 45 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.getExtOrDefault = {name -> 3 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeScanner_' + name] 4 | } 5 | 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath "com.android.tools.build:gradle:8.7.2" 13 | // noinspection DifferentKotlinGradleVersion 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}" 15 | } 16 | } 17 | 18 | 19 | apply plugin: "com.android.library" 20 | apply plugin: "kotlin-android" 21 | 22 | apply plugin: "com.facebook.react" 23 | 24 | def getExtOrIntegerDefault(name) { 25 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ReactNativeScanner_" + name]).toInteger() 26 | } 27 | 28 | android { 29 | namespace "com.pushpendersingh.reactnativescanner" 30 | 31 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") 32 | 33 | defaultConfig { 34 | minSdkVersion getExtOrIntegerDefault("minSdkVersion") 35 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") 36 | } 37 | 38 | buildFeatures { 39 | buildConfig true 40 | } 41 | 42 | buildTypes { 43 | release { 44 | minifyEnabled false 45 | } 46 | } 47 | 48 | lintOptions { 49 | disable "GradleCompatible" 50 | } 51 | 52 | compileOptions { 53 | sourceCompatibility JavaVersion.VERSION_1_8 54 | targetCompatibility JavaVersion.VERSION_1_8 55 | } 56 | 57 | sourceSets { 58 | main { 59 | java.srcDirs += [ 60 | "generated/java", 61 | "generated/jni" 62 | ] 63 | } 64 | } 65 | } 66 | 67 | repositories { 68 | mavenCentral() 69 | google() 70 | } 71 | 72 | def kotlin_version = getExtOrDefault("kotlinVersion") 73 | 74 | dependencies { 75 | implementation "com.facebook.react:react-android" 76 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 77 | 78 | def camerax_version = "1.5.0" 79 | // Core CameraX libraries 80 | implementation "androidx.camera:camera-core:${camerax_version}" 81 | implementation "androidx.camera:camera-camera2:${camerax_version}" 82 | implementation "androidx.camera:camera-lifecycle:${camerax_version}" 83 | implementation "androidx.camera:camera-view:${camerax_version}" 84 | 85 | // MLKit Barcode scanning 86 | implementation 'com.google.mlkit:barcode-scanning:17.3.0' 87 | } 88 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug report 2 | description: Report a reproducible bug or regression in this library. 3 | labels: [bug] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | # Bug report 9 | 10 | 👋 Hi! 11 | 12 | **Please fill the following carefully before opening a new issue ❗** 13 | *(Your issue may be closed if it doesn't provide the required pieces of information)* 14 | - type: checkboxes 15 | attributes: 16 | label: Before submitting a new issue 17 | description: Please perform simple checks first. 18 | options: 19 | - label: I tested using the latest version of the library, as the bug might be already fixed. 20 | required: true 21 | - label: I tested using a [supported version](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md) of react native. 22 | required: true 23 | - label: I checked for possible duplicate issues, with possible answers. 24 | required: true 25 | - type: textarea 26 | id: summary 27 | attributes: 28 | label: Bug summary 29 | description: | 30 | Provide a clear and concise description of what the bug is. 31 | If needed, you can also provide other samples: error messages / stack traces, screenshots, gifs, etc. 32 | validations: 33 | required: true 34 | - type: input 35 | id: library-version 36 | attributes: 37 | label: Library version 38 | description: What version of the library are you using? 39 | placeholder: "x.x.x" 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: react-native-info 44 | attributes: 45 | label: Environment info 46 | description: Run `react-native info` in your terminal and paste the results here. 47 | render: shell 48 | validations: 49 | required: true 50 | - type: textarea 51 | id: steps-to-reproduce 52 | attributes: 53 | label: Steps to reproduce 54 | description: | 55 | You must provide a clear list of steps and code to reproduce the problem. 56 | value: | 57 | 1. … 58 | 2. … 59 | validations: 60 | required: true 61 | - type: input 62 | id: reproducible-example 63 | attributes: 64 | label: Reproducible example repository 65 | description: Please provide a link to a repository on GitHub with a reproducible example. 66 | validations: 67 | required: true 68 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import NativeReactNativeScanner from './NativeReactNativeScanner'; 2 | 3 | // Types 4 | export type BarcodeType = 5 | | 'QR_CODE' 6 | | 'AZTEC' 7 | | 'CODE_128' 8 | | 'CODE_39' 9 | | 'CODE_93' 10 | | 'CODABAR' 11 | | 'DATA_MATRIX' 12 | | 'EAN_13' 13 | | 'EAN_8' 14 | | 'ITF' 15 | | 'PDF417' 16 | | 'UPC_A' 17 | | 'UPC_E' 18 | | 'UNKNOWN'; 19 | 20 | export interface BarcodeResult { 21 | data: string; 22 | type: BarcodeType; 23 | bounds?: { 24 | width: number; 25 | height: number; 26 | origin: { 27 | topLeft: { x: number; y: number }; 28 | bottomLeft: { x: number; y: number }; 29 | bottomRight: { x: number; y: number }; 30 | topRight: { x: number; y: number }; 31 | }; 32 | }; 33 | } 34 | 35 | export type BarcodeScannerCallback = (results: BarcodeResult[]) => void; 36 | 37 | // Scanner API 38 | export class BarcodeScanner { 39 | private static listener: any = null; 40 | 41 | static async startScanning(callback: BarcodeScannerCallback): Promise { 42 | // Remove existing listener 43 | if (this.listener) { 44 | this.listener.remove(); 45 | this.listener = null; 46 | } 47 | 48 | this.listener = NativeReactNativeScanner.onBarcodeScanned((event) => { 49 | callback(event as unknown as BarcodeResult[]); 50 | }); 51 | 52 | // Start scanning 53 | return NativeReactNativeScanner.startScanning(); 54 | } 55 | 56 | static async stopScanning(): Promise { 57 | if (this.listener) { 58 | this.listener.remove(); 59 | this.listener = null; 60 | } 61 | return NativeReactNativeScanner.stopScanning(); 62 | } 63 | 64 | static async enableFlashlight(): Promise { 65 | return NativeReactNativeScanner.enableFlashlight(); 66 | } 67 | 68 | static async disableFlashlight(): Promise { 69 | return NativeReactNativeScanner.disableFlashlight(); 70 | } 71 | 72 | static async releaseCamera(): Promise { 73 | if (this.listener) { 74 | this.listener.remove(); 75 | this.listener = null; 76 | } 77 | return NativeReactNativeScanner.releaseCamera(); 78 | } 79 | 80 | static async hasCameraPermission(): Promise { 81 | return NativeReactNativeScanner.hasCameraPermission(); 82 | } 83 | 84 | static async requestCameraPermission(): Promise { 85 | return NativeReactNativeScanner.requestCameraPermission(); 86 | } 87 | } 88 | 89 | // Export camera view 90 | export { CameraView } from './CameraView'; 91 | export type { CameraViewProps } from './CameraView'; 92 | 93 | // Export default 94 | export default BarcodeScanner; 95 | -------------------------------------------------------------------------------- /.github/workflows/check-repro.yml: -------------------------------------------------------------------------------- 1 | name: Check for repro 2 | on: 3 | issues: 4 | types: [opened, edited] 5 | issue_comment: 6 | types: [created, edited] 7 | 8 | jobs: 9 | check-repro: 10 | if: ${{ github.event.label.name == 'bug' }} 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/github-script@v3 14 | with: 15 | github-token: ${{ secrets.GITHUB_TOKEN }} 16 | script: | 17 | const user = context.payload.sender.login; 18 | const body = context.payload.comment 19 | ? context.payload.comment.body 20 | : context.payload.issue.body; 21 | const regex = new RegExp( 22 | `https?:\\/\\/((github\\.com\\/${user}\\/[^/]+\\/?[\\s\\n]+)|(snack\\.expo\\.dev\\/.+))`, 23 | 'gm' 24 | ); 25 | if (regex.test(body)) { 26 | await github.issues.addLabels({ 27 | issue_number: context.issue.number, 28 | owner: context.repo.owner, 29 | repo: context.repo.repo, 30 | labels: ['repro provided'], 31 | }); 32 | try { 33 | await github.issues.removeLabel({ 34 | issue_number: context.issue.number, 35 | owner: context.repo.owner, 36 | repo: context.repo.repo, 37 | name: 'needs repro', 38 | }); 39 | } catch (error) { 40 | if (!/Label does not exist/.test(error.message)) { 41 | throw error; 42 | } 43 | } 44 | } else { 45 | if (context.eventName !== 'issues') { 46 | return; 47 | } 48 | const body = "Hey! Thanks for opening the issue. The issue doesn't seem to contain a link to a repro (a [snack.expo.dev](https://snack.expo.dev) link or link to a GitHub repo under your username).\n\nCan you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? A repro will help us debug the issue faster. Please try to keep the repro as small as possible and make sure that we can run it without additional setup."; 49 | const comments = await github.issues.listComments({ 50 | issue_number: context.issue.number, 51 | owner: context.repo.owner, 52 | repo: context.repo.repo, 53 | }); 54 | if (comments.data.some(comment => comment.body === body)) { 55 | return; 56 | } 57 | await github.issues.createComment({ 58 | issue_number: context.issue.number, 59 | owner: context.repo.owner, 60 | repo: context.repo.repo, 61 | body, 62 | }); 63 | await github.issues.addLabels({ 64 | issue_number: context.issue.number, 65 | owner: context.repo.owner, 66 | repo: context.repo.repo, 67 | labels: ['needs repro'], 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.7) 5 | base64 6 | nkf 7 | rexml 8 | activesupport (7.2.2.2) 9 | base64 10 | benchmark (>= 0.3) 11 | bigdecimal 12 | concurrent-ruby (~> 1.0, >= 1.3.1) 13 | connection_pool (>= 2.2.5) 14 | drb 15 | i18n (>= 1.6, < 2) 16 | logger (>= 1.4.2) 17 | minitest (>= 5.1) 18 | securerandom (>= 0.3) 19 | tzinfo (~> 2.0, >= 2.0.5) 20 | addressable (2.8.7) 21 | public_suffix (>= 2.0.2, < 7.0) 22 | algoliasearch (1.27.5) 23 | httpclient (~> 2.8, >= 2.8.3) 24 | json (>= 1.5.1) 25 | atomos (0.1.3) 26 | base64 (0.3.0) 27 | benchmark (0.4.1) 28 | bigdecimal (3.2.3) 29 | claide (1.1.0) 30 | cocoapods (1.15.2) 31 | addressable (~> 2.8) 32 | claide (>= 1.0.2, < 2.0) 33 | cocoapods-core (= 1.15.2) 34 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 35 | cocoapods-downloader (>= 2.1, < 3.0) 36 | cocoapods-plugins (>= 1.0.0, < 2.0) 37 | cocoapods-search (>= 1.0.0, < 2.0) 38 | cocoapods-trunk (>= 1.6.0, < 2.0) 39 | cocoapods-try (>= 1.1.0, < 2.0) 40 | colored2 (~> 3.1) 41 | escape (~> 0.0.4) 42 | fourflusher (>= 2.3.0, < 3.0) 43 | gh_inspector (~> 1.0) 44 | molinillo (~> 0.8.0) 45 | nap (~> 1.0) 46 | ruby-macho (>= 2.3.0, < 3.0) 47 | xcodeproj (>= 1.23.0, < 2.0) 48 | cocoapods-core (1.15.2) 49 | activesupport (>= 5.0, < 8) 50 | addressable (~> 2.8) 51 | algoliasearch (~> 1.0) 52 | concurrent-ruby (~> 1.1) 53 | fuzzy_match (~> 2.0.4) 54 | nap (~> 1.0) 55 | netrc (~> 0.11) 56 | public_suffix (~> 4.0) 57 | typhoeus (~> 1.0) 58 | cocoapods-deintegrate (1.0.5) 59 | cocoapods-downloader (2.1) 60 | cocoapods-plugins (1.0.0) 61 | nap 62 | cocoapods-search (1.0.1) 63 | cocoapods-trunk (1.6.0) 64 | nap (>= 0.8, < 2.0) 65 | netrc (~> 0.11) 66 | cocoapods-try (1.2.0) 67 | colored2 (3.1.2) 68 | concurrent-ruby (1.3.3) 69 | connection_pool (2.5.4) 70 | drb (2.2.3) 71 | escape (0.0.4) 72 | ethon (0.15.0) 73 | ffi (>= 1.15.0) 74 | ffi (1.17.2) 75 | fourflusher (2.3.1) 76 | fuzzy_match (2.0.4) 77 | gh_inspector (1.1.3) 78 | httpclient (2.9.0) 79 | mutex_m 80 | i18n (1.14.7) 81 | concurrent-ruby (~> 1.0) 82 | json (2.15.0) 83 | logger (1.7.0) 84 | minitest (5.25.5) 85 | molinillo (0.8.0) 86 | mutex_m (0.3.0) 87 | nanaimo (0.3.0) 88 | nap (1.1.0) 89 | netrc (0.11.0) 90 | nkf (0.2.0) 91 | public_suffix (4.0.7) 92 | rexml (3.4.4) 93 | ruby-macho (2.5.1) 94 | securerandom (0.4.1) 95 | typhoeus (1.5.0) 96 | ethon (>= 0.9.0, < 0.16.0) 97 | tzinfo (2.0.6) 98 | concurrent-ruby (~> 1.0) 99 | xcodeproj (1.25.1) 100 | CFPropertyList (>= 2.3.3, < 4.0) 101 | atomos (~> 0.1.3) 102 | claide (>= 1.0.2, < 2.0) 103 | colored2 (~> 3.1) 104 | nanaimo (~> 0.3.0) 105 | rexml (>= 3.3.6, < 4.0) 106 | 107 | PLATFORMS 108 | ruby 109 | 110 | DEPENDENCIES 111 | activesupport (>= 6.1.7.5, != 7.1.0) 112 | benchmark 113 | bigdecimal 114 | cocoapods (>= 1.13, != 1.15.1, != 1.15.0) 115 | concurrent-ruby (< 1.3.4) 116 | logger 117 | mutex_m 118 | xcodeproj (< 1.26.0) 119 | 120 | RUBY VERSION 121 | ruby 3.4.4p34 122 | 123 | BUNDLED WITH 124 | 2.6.9 125 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @REM Copyright (c) Meta Platforms, Inc. and affiliates. 2 | @REM 3 | @REM This source code is licensed under the MIT license found in the 4 | @REM LICENSE file in the root directory of this source tree. 5 | 6 | @rem 7 | @rem Copyright 2015 the original author or authors. 8 | @rem 9 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 10 | @rem you may not use this file except in compliance with the License. 11 | @rem You may obtain a copy of the License at 12 | @rem 13 | @rem https://www.apache.org/licenses/LICENSE-2.0 14 | @rem 15 | @rem Unless required by applicable law or agreed to in writing, software 16 | @rem distributed under the License is distributed on an "AS IS" BASIS, 17 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | @rem See the License for the specific language governing permissions and 19 | @rem limitations under the License. 20 | @rem 21 | @rem SPDX-License-Identifier: Apache-2.0 22 | @rem 23 | 24 | @if "%DEBUG%"=="" @echo off 25 | @rem ########################################################################## 26 | @rem 27 | @rem Gradle startup script for Windows 28 | @rem 29 | @rem ########################################################################## 30 | 31 | @rem Set local scope for the variables with windows NT shell 32 | if "%OS%"=="Windows_NT" setlocal 33 | 34 | set DIRNAME=%~dp0 35 | if "%DIRNAME%"=="" set DIRNAME=. 36 | @rem This is normally unused 37 | set APP_BASE_NAME=%~n0 38 | set APP_HOME=%DIRNAME% 39 | 40 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 41 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 42 | 43 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 44 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 45 | 46 | @rem Find java.exe 47 | if defined JAVA_HOME goto findJavaFromJavaHome 48 | 49 | set JAVA_EXE=java.exe 50 | %JAVA_EXE% -version >NUL 2>&1 51 | if %ERRORLEVEL% equ 0 goto execute 52 | 53 | echo. 1>&2 54 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 55 | echo. 1>&2 56 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 57 | echo location of your Java installation. 1>&2 58 | 59 | goto fail 60 | 61 | :findJavaFromJavaHome 62 | set JAVA_HOME=%JAVA_HOME:"=% 63 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 64 | 65 | if exist "%JAVA_EXE%" goto execute 66 | 67 | echo. 1>&2 68 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 69 | echo. 1>&2 70 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 71 | echo location of your Java installation. 1>&2 72 | 73 | goto fail 74 | 75 | :execute 76 | @rem Setup the command line 77 | 78 | set CLASSPATH= 79 | 80 | 81 | @rem Execute Gradle 82 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 83 | 84 | :end 85 | @rem End local scope for the variables with windows NT shell 86 | if %ERRORLEVEL% equ 0 goto mainEnd 87 | 88 | :fail 89 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 90 | rem the _cmd.exe /c_ return code! 91 | set EXIT_CODE=%ERRORLEVEL% 92 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 93 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 94 | exit /b %EXIT_CODE% 95 | 96 | :mainEnd 97 | if "%OS%"=="Windows_NT" endlocal 98 | 99 | :omega 100 | -------------------------------------------------------------------------------- /android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt: -------------------------------------------------------------------------------- 1 | package com.pushpendersingh.reactnativescanner 2 | 3 | import android.content.Context 4 | import android.view.Choreographer 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.FrameLayout 8 | import androidx.camera.view.PreviewView 9 | import com.facebook.react.bridge.ReactContext 10 | 11 | class ReactNativeScannerView(context: Context) : FrameLayout(context) { 12 | 13 | private val previewView: PreviewView 14 | private var cameraManager: CameraManager? = null 15 | private var layoutCallback: Choreographer.FrameCallback? = null 16 | 17 | init { 18 | // Create PreviewView 19 | previewView = PreviewView(context).apply { 20 | layoutParams = ViewGroup.LayoutParams( 21 | ViewGroup.LayoutParams.MATCH_PARENT, 22 | ViewGroup.LayoutParams.MATCH_PARENT 23 | ) 24 | // Set implementation mode for better compatibility 25 | implementationMode = PreviewView.ImplementationMode.COMPATIBLE 26 | } 27 | addView(previewView) 28 | 29 | // Setup layout hack for proper rendering 30 | setupLayoutHack() 31 | } 32 | 33 | private fun setupLayoutHack() { 34 | layoutCallback = object : Choreographer.FrameCallback { 35 | override fun doFrame(frameTimeNanos: Long) { 36 | manuallyLayoutChildren() 37 | viewTreeObserver.dispatchOnGlobalLayout() 38 | Choreographer.getInstance().postFrameCallback(this) 39 | } 40 | } 41 | layoutCallback?.let { Choreographer.getInstance().postFrameCallback(it) } 42 | } 43 | 44 | private fun removeLayoutCallback() { 45 | layoutCallback?.let { 46 | Choreographer.getInstance().removeFrameCallback(it) 47 | layoutCallback = null 48 | } 49 | } 50 | 51 | private fun manuallyLayoutChildren() { 52 | for (i in 0 until childCount) { 53 | val child = getChildAt(i) 54 | child.measure( 55 | MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), 56 | MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY) 57 | ) 58 | child.layout(0, 0, child.measuredWidth, child.measuredHeight) 59 | } 60 | } 61 | 62 | fun getPreviewView(): PreviewView { 63 | return previewView 64 | } 65 | 66 | fun setCameraManager(manager: CameraManager) { 67 | this.cameraManager = manager 68 | // Bind the preview view to the camera manager 69 | manager.bindPreviewView(previewView) 70 | } 71 | 72 | override fun onAttachedToWindow() { 73 | super.onAttachedToWindow() 74 | // Restart layout callback when view is reattached 75 | setupLayoutHack() 76 | // Rebind the preview view to camera manager when reattached 77 | cameraManager?.bindPreviewView(previewView) 78 | } 79 | 80 | override fun onDetachedFromWindow() { 81 | super.onDetachedFromWindow() 82 | // Remove Choreographer callback to prevent memory leak 83 | removeLayoutCallback() 84 | // Clean up camera when view is detached 85 | cameraManager?.releaseCamera() 86 | } 87 | 88 | override fun requestLayout() { 89 | super.requestLayout() 90 | post(measureAndLayout) 91 | } 92 | 93 | private val measureAndLayout = Runnable { 94 | measure( 95 | MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 96 | MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) 97 | ) 98 | layout(left, top, right, bottom) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /example/ios/ReactNativeScannerExample.xcodeproj/xcshareddata/xcschemes/ReactNativeScannerExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). 2 | 3 | # Getting Started 4 | 5 | > **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding. 6 | 7 | ## Step 1: Start Metro 8 | 9 | First, you will need to run **Metro**, the JavaScript build tool for React Native. 10 | 11 | To start the Metro dev server, run the following command from the root of your React Native project: 12 | 13 | ```sh 14 | # Using npm 15 | npm start 16 | 17 | # OR using Yarn 18 | yarn start 19 | ``` 20 | 21 | ## Step 2: Build and run your app 22 | 23 | With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app: 24 | 25 | ### Android 26 | 27 | ```sh 28 | # Using npm 29 | npm run android 30 | 31 | # OR using Yarn 32 | yarn android 33 | ``` 34 | 35 | ### iOS 36 | 37 | For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps). 38 | 39 | The first time you create a new project, run the Ruby bundler to install CocoaPods itself: 40 | 41 | ```sh 42 | bundle install 43 | ``` 44 | 45 | Then, and every time you update your native dependencies, run: 46 | 47 | ```sh 48 | bundle exec pod install 49 | ``` 50 | 51 | For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html). 52 | 53 | ```sh 54 | # Using npm 55 | npm run ios 56 | 57 | # OR using Yarn 58 | yarn ios 59 | ``` 60 | 61 | If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device. 62 | 63 | This is one way to run your app — you can also build it directly from Android Studio or Xcode. 64 | 65 | ## Step 3: Modify your app 66 | 67 | Now that you have successfully run the app, let's make changes! 68 | 69 | Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh). 70 | 71 | When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload: 72 | 73 | - **Android**: Press the R key twice or select **"Reload"** from the **Dev Menu**, accessed via Ctrl + M (Windows/Linux) or Cmd ⌘ + M (macOS). 74 | - **iOS**: Press R in iOS Simulator. 75 | 76 | ## Congratulations! :tada: 77 | 78 | You've successfully run and modified your React Native App. :partying_face: 79 | 80 | ### Now what? 81 | 82 | - If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). 83 | - If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started). 84 | 85 | # Troubleshooting 86 | 87 | If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. 88 | 89 | # Learn More 90 | 91 | To learn more about React Native, take a look at the following resources: 92 | 93 | - [React Native Website](https://reactnative.dev) - learn more about React Native. 94 | - [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. 95 | - [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. 96 | - [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. 97 | - [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. 98 | -------------------------------------------------------------------------------- /ios/ReactNativeScanner.mm: -------------------------------------------------------------------------------- 1 | #import "ReactNativeScanner.h" 2 | #import 3 | 4 | #if __has_include("ReactNativeScanner-Swift.h") 5 | #import "ReactNativeScanner-Swift.h" 6 | #else 7 | #import 8 | #endif 9 | 10 | @implementation ReactNativeScanner 11 | @synthesize cameraManager = _cameraManager; 12 | 13 | - (instancetype)init { 14 | if (self = [super init]) { 15 | _cameraManager = [[CameraManager alloc] init]; 16 | } 17 | return self; 18 | } 19 | 20 | // Expose cameraManager for CameraViewManager 21 | - (CameraManager *)cameraManager { 22 | return _cameraManager; 23 | } 24 | 25 | - (void)startScanning:(RCTPromiseResolveBlock)resolve 26 | reject:(RCTPromiseRejectBlock)reject { 27 | if (![_cameraManager hasCameraPermission]) { 28 | reject(@"PERMISSION_DENIED", @"Camera permission not granted", nil); 29 | return; 30 | } 31 | 32 | NSError *error = nil; 33 | @try { 34 | [_cameraManager 35 | startScanningWithCallback:^(NSArray *result) { 36 | [self emitOnBarcodeScanned:result]; 37 | } 38 | error:&error]; 39 | if (error) { 40 | reject(@"START_SCANNING_ERROR", error.localizedDescription, error); 41 | } else { 42 | resolve(nil); 43 | } 44 | } @catch (NSException *exception) { 45 | reject(@"START_SCANNING_ERROR", exception.reason, nil); 46 | } 47 | } 48 | 49 | - (void)stopScanning:(RCTPromiseResolveBlock)resolve 50 | reject:(RCTPromiseRejectBlock)reject { 51 | @try { 52 | [_cameraManager stopScanning]; 53 | resolve(nil); 54 | } @catch (NSException *exception) { 55 | reject(@"STOP_SCANNING_ERROR", exception.reason, nil); 56 | } 57 | } 58 | 59 | - (void)enableFlashlight:(RCTPromiseResolveBlock)resolve 60 | reject:(RCTPromiseRejectBlock)reject { 61 | @try { 62 | [_cameraManager enableFlashlight]; 63 | resolve(nil); 64 | } @catch (NSException *exception) { 65 | reject(@"FLASHLIGHT_ERROR", exception.reason, nil); 66 | } 67 | } 68 | 69 | - (void)disableFlashlight:(RCTPromiseResolveBlock)resolve 70 | reject:(RCTPromiseRejectBlock)reject { 71 | @try { 72 | [_cameraManager disableFlashlight]; 73 | resolve(nil); 74 | } @catch (NSException *exception) { 75 | reject(@"FLASHLIGHT_ERROR", exception.reason, nil); 76 | } 77 | } 78 | 79 | - (void)releaseCamera:(RCTPromiseResolveBlock)resolve 80 | reject:(RCTPromiseRejectBlock)reject { 81 | @try { 82 | [_cameraManager releaseCamera]; 83 | resolve(nil); 84 | } @catch (NSException *exception) { 85 | reject(@"RELEASE_CAMERA_ERROR", exception.reason, nil); 86 | } 87 | } 88 | 89 | - (void)hasCameraPermission:(RCTPromiseResolveBlock)resolve 90 | reject:(RCTPromiseRejectBlock)reject { 91 | BOOL hasPermission = [_cameraManager hasCameraPermission]; 92 | resolve(@(hasPermission)); 93 | } 94 | 95 | - (void)requestCameraPermission:(RCTPromiseResolveBlock)resolve 96 | reject:(RCTPromiseRejectBlock)reject { 97 | [_cameraManager requestCameraPermissionWithCompletion:^(BOOL granted) { 98 | resolve(@(granted)); 99 | }]; 100 | } 101 | 102 | - (void)invalidate { 103 | // Capture strong reference to camera manager first to ensure it stays alive 104 | // during cleanup 105 | CameraManager *cameraManager = _cameraManager; 106 | 107 | if (cameraManager) { 108 | [cameraManager releaseCamera]; 109 | } 110 | } 111 | 112 | - (std::shared_ptr)getTurboModule: 113 | (const facebook::react::ObjCTurboModule::InitParams &)params 114 | { 115 | return std::make_shared(params); 116 | } 117 | 118 | + (NSString *)moduleName 119 | { 120 | return @"ReactNativeScanner"; 121 | } 122 | 123 | @end 124 | 125 | -------------------------------------------------------------------------------- /example/ios/ReactNativeScannerExample/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pushpendersingh/react-native-scanner", 3 | "version": "3.0.0", 4 | "description": "A QR code & Barcode Scanner for React Native Projects.", 5 | "main": "./lib/module/index.js", 6 | "types": "./lib/typescript/src/index.d.ts", 7 | "exports": { 8 | ".": { 9 | "source": "./src/index.tsx", 10 | "types": "./lib/typescript/src/index.d.ts", 11 | "default": "./lib/module/index.js" 12 | }, 13 | "./package.json": "./package.json" 14 | }, 15 | "files": [ 16 | "src", 17 | "lib", 18 | "android", 19 | "ios", 20 | "cpp", 21 | "*.podspec", 22 | "react-native.config.js", 23 | "!ios/build", 24 | "!android/build", 25 | "!android/gradle", 26 | "!android/gradlew", 27 | "!android/gradlew.bat", 28 | "!android/local.properties", 29 | "!**/__tests__", 30 | "!**/__fixtures__", 31 | "!**/__mocks__", 32 | "!**/.*" 33 | ], 34 | "scripts": { 35 | "example": "yarn workspace @pushpendersingh/react-native-scanner-example", 36 | "test": "jest", 37 | "typecheck": "tsc", 38 | "lint": "eslint \"**/*.{js,ts,tsx}\"", 39 | "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", 40 | "prepare": "bob build", 41 | "release": "release-it --only-version" 42 | }, 43 | "keywords": [ 44 | "react-native", 45 | "ios", 46 | "android" 47 | ], 48 | "repository": { 49 | "type": "git", 50 | "url": "git+https://github.com/pushpender-singh-ap/react-native-scanner.git" 51 | }, 52 | "author": "Pushpender Singh (https://github.com/pushpender-singh-ap)", 53 | "license": "MIT", 54 | "bugs": { 55 | "url": "https://github.com/pushpender-singh-ap/react-native-scanner/issues" 56 | }, 57 | "homepage": "https://github.com/pushpender-singh-ap/react-native-scanner#readme", 58 | "publishConfig": { 59 | "registry": "https://registry.npmjs.org/" 60 | }, 61 | "devDependencies": { 62 | "@commitlint/config-conventional": "^19.8.1", 63 | "@eslint/compat": "^1.3.2", 64 | "@eslint/eslintrc": "^3.3.1", 65 | "@eslint/js": "^9.35.0", 66 | "@evilmartians/lefthook": "^1.12.3", 67 | "@react-native-community/cli": "20.0.1", 68 | "@react-native/babel-preset": "0.81.1", 69 | "@react-native/eslint-config": "^0.81.1", 70 | "@release-it/conventional-changelog": "^10.0.1", 71 | "@types/jest": "^29.5.14", 72 | "@types/react": "^19.1.0", 73 | "commitlint": "^19.8.1", 74 | "del-cli": "^6.0.0", 75 | "eslint": "^9.35.0", 76 | "eslint-config-prettier": "^10.1.8", 77 | "eslint-plugin-prettier": "^5.5.4", 78 | "jest": "^29.7.0", 79 | "prettier": "^3.6.2", 80 | "react": "19.1.0", 81 | "react-native": "0.81.1", 82 | "react-native-builder-bob": "^0.40.13", 83 | "release-it": "^19.0.4", 84 | "turbo": "^2.5.6", 85 | "typescript": "^5.9.2" 86 | }, 87 | "peerDependencies": { 88 | "react": "*", 89 | "react-native": "*" 90 | }, 91 | "workspaces": [ 92 | "example" 93 | ], 94 | "packageManager": "yarn@3.6.1", 95 | "jest": { 96 | "preset": "react-native", 97 | "modulePathIgnorePatterns": [ 98 | "/example/node_modules", 99 | "/lib/" 100 | ] 101 | }, 102 | "commitlint": { 103 | "extends": [ 104 | "@commitlint/config-conventional" 105 | ] 106 | }, 107 | "release-it": { 108 | "git": { 109 | "commitMessage": "chore: release ${version}", 110 | "tagName": "v${version}" 111 | }, 112 | "npm": { 113 | "publish": true 114 | }, 115 | "github": { 116 | "release": true 117 | }, 118 | "plugins": { 119 | "@release-it/conventional-changelog": { 120 | "preset": { 121 | "name": "angular" 122 | } 123 | } 124 | } 125 | }, 126 | "prettier": { 127 | "quoteProps": "consistent", 128 | "singleQuote": true, 129 | "tabWidth": 2, 130 | "trailingComma": "es5", 131 | "useTabs": false 132 | }, 133 | "react-native-builder-bob": { 134 | "source": "src", 135 | "output": "lib", 136 | "targets": [ 137 | [ 138 | "module", 139 | { 140 | "esm": true 141 | } 142 | ], 143 | [ 144 | "typescript", 145 | { 146 | "project": "tsconfig.build.json" 147 | } 148 | ] 149 | ] 150 | }, 151 | "codegenConfig": { 152 | "name": "ReactNativeScannerSpec", 153 | "type": "modules", 154 | "jsSrcsDir": "src", 155 | "android": { 156 | "javaPackageName": "com.pushpendersingh.reactnativescanner" 157 | } 158 | }, 159 | "create-react-native-library": { 160 | "languages": "kotlin-objc", 161 | "type": "turbo-module", 162 | "version": "0.54.3" 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "org.jetbrains.kotlin.android" 3 | apply plugin: "com.facebook.react" 4 | 5 | /** 6 | * This is the configuration block to customize your React Native Android app. 7 | * By default you don't need to apply any configuration, just uncomment the lines you need. 8 | */ 9 | react { 10 | /* Folders */ 11 | // The root of your project, i.e. where "package.json" lives. Default is '../..' 12 | // root = file("../../") 13 | // The folder where the react-native NPM package is. Default is ../../node_modules/react-native 14 | // reactNativeDir = file("../../node_modules/react-native") 15 | // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen 16 | // codegenDir = file("../../node_modules/@react-native/codegen") 17 | // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js 18 | // cliFile = file("../../node_modules/react-native/cli.js") 19 | 20 | /* Variants */ 21 | // The list of variants to that are debuggable. For those we're going to 22 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 23 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 24 | // debuggableVariants = ["liteDebug", "prodDebug"] 25 | 26 | /* Bundling */ 27 | // A list containing the node command and its flags. Default is just 'node'. 28 | // nodeExecutableAndArgs = ["node"] 29 | // 30 | // The command to run when bundling. By default is 'bundle' 31 | // bundleCommand = "ram-bundle" 32 | // 33 | // The path to the CLI configuration file. Default is empty. 34 | // bundleConfig = file(../rn-cli.config.js) 35 | // 36 | // The name of the generated asset file containing your JS bundle 37 | // bundleAssetName = "MyApplication.android.bundle" 38 | // 39 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 40 | // entryFile = file("../js/MyApplication.android.js") 41 | // 42 | // A list of extra flags to pass to the 'bundle' commands. 43 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 44 | // extraPackagerArgs = [] 45 | 46 | /* Hermes Commands */ 47 | // The hermes compiler command to run. By default it is 'hermesc' 48 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 49 | // 50 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 51 | // hermesFlags = ["-O", "-output-source-map"] 52 | 53 | /* Autolinking */ 54 | autolinkLibrariesWithApp() 55 | } 56 | 57 | /** 58 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 59 | */ 60 | def enableProguardInReleaseBuilds = false 61 | 62 | /** 63 | * The preferred build flavor of JavaScriptCore (JSC) 64 | * 65 | * For example, to use the international variant, you can use: 66 | * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` 67 | * 68 | * The international variant includes ICU i18n library and necessary data 69 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 70 | * give correct results when using with locales other than en-US. Note that 71 | * this variant is about 6MiB larger per architecture than default. 72 | */ 73 | def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' 74 | 75 | android { 76 | ndkVersion rootProject.ext.ndkVersion 77 | buildToolsVersion rootProject.ext.buildToolsVersion 78 | compileSdk rootProject.ext.compileSdkVersion 79 | 80 | namespace "pushpendersingh.reactnativescanner.example" 81 | defaultConfig { 82 | applicationId "pushpendersingh.reactnativescanner.example" 83 | minSdkVersion rootProject.ext.minSdkVersion 84 | targetSdkVersion rootProject.ext.targetSdkVersion 85 | versionCode 1 86 | versionName "1.0" 87 | } 88 | signingConfigs { 89 | debug { 90 | storeFile file('debug.keystore') 91 | storePassword 'android' 92 | keyAlias 'androiddebugkey' 93 | keyPassword 'android' 94 | } 95 | } 96 | buildTypes { 97 | debug { 98 | signingConfig signingConfigs.debug 99 | } 100 | release { 101 | // Caution! In production, you need to generate your own keystore file. 102 | // see https://reactnative.dev/docs/signed-apk-android. 103 | signingConfig signingConfigs.debug 104 | minifyEnabled enableProguardInReleaseBuilds 105 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 106 | } 107 | } 108 | } 109 | 110 | dependencies { 111 | // The version of react-native is set by the React Native Gradle Plugin 112 | implementation("com.facebook.react:react-android") 113 | 114 | if (hermesEnabled.toBoolean()) { 115 | implementation("com.facebook.react:hermes-android") 116 | } else { 117 | implementation jscFlavor 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | merge_group: 10 | types: 11 | - checks_requested 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | lint: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 24 | 25 | - name: Setup 26 | uses: ./.github/actions/setup 27 | 28 | - name: Lint files 29 | run: yarn lint 30 | 31 | - name: Typecheck files 32 | run: yarn typecheck 33 | 34 | test: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 40 | 41 | - name: Setup 42 | uses: ./.github/actions/setup 43 | 44 | - name: Run unit tests 45 | run: yarn test --maxWorkers=2 --coverage 46 | 47 | build-library: 48 | runs-on: ubuntu-latest 49 | 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 53 | 54 | - name: Setup 55 | uses: ./.github/actions/setup 56 | 57 | - name: Build package 58 | run: yarn prepare 59 | 60 | build-android: 61 | runs-on: ubuntu-latest 62 | 63 | env: 64 | TURBO_CACHE_DIR: .turbo/android 65 | 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 69 | 70 | - name: Setup 71 | uses: ./.github/actions/setup 72 | 73 | - name: Cache turborepo for Android 74 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 75 | with: 76 | path: ${{ env.TURBO_CACHE_DIR }} 77 | key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }} 78 | restore-keys: | 79 | ${{ runner.os }}-turborepo-android- 80 | 81 | - name: Check turborepo cache for Android 82 | run: | 83 | TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") 84 | 85 | if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then 86 | echo "turbo_cache_hit=1" >> $GITHUB_ENV 87 | fi 88 | 89 | - name: Install JDK 90 | if: env.turbo_cache_hit != 1 91 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 92 | with: 93 | distribution: 'zulu' 94 | java-version: '17' 95 | 96 | - name: Finalize Android SDK 97 | if: env.turbo_cache_hit != 1 98 | run: | 99 | /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null" 100 | 101 | - name: Cache Gradle 102 | if: env.turbo_cache_hit != 1 103 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 104 | with: 105 | path: | 106 | ~/.gradle/wrapper 107 | ~/.gradle/caches 108 | key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} 109 | restore-keys: | 110 | ${{ runner.os }}-gradle- 111 | 112 | - name: Build example for Android 113 | env: 114 | JAVA_OPTS: "-XX:MaxHeapSize=6g" 115 | run: | 116 | yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" 117 | 118 | build-ios: 119 | runs-on: macos-latest 120 | 121 | env: 122 | XCODE_VERSION: 16.3 123 | TURBO_CACHE_DIR: .turbo/ios 124 | RCT_USE_RN_DEP: 1 125 | RCT_USE_PREBUILT_RNCORE: 1 126 | 127 | steps: 128 | - name: Checkout 129 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 130 | 131 | - name: Setup 132 | uses: ./.github/actions/setup 133 | 134 | - name: Cache turborepo for iOS 135 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 136 | with: 137 | path: ${{ env.TURBO_CACHE_DIR }} 138 | key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }} 139 | restore-keys: | 140 | ${{ runner.os }}-turborepo-ios- 141 | 142 | - name: Check turborepo cache for iOS 143 | run: | 144 | TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") 145 | 146 | if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then 147 | echo "turbo_cache_hit=1" >> $GITHUB_ENV 148 | fi 149 | 150 | - name: Use appropriate Xcode version 151 | if: env.turbo_cache_hit != 1 152 | uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 153 | with: 154 | xcode-version: ${{ env.XCODE_VERSION }} 155 | 156 | - name: Install cocoapods 157 | if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true' 158 | run: | 159 | cd example 160 | bundle install 161 | bundle exec pod repo update --verbose 162 | bundle exec pod install --project-directory=ios 163 | 164 | - name: Build example for iOS 165 | run: | 166 | yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" 167 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome, no matter how large or small! 4 | 5 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md). 6 | 7 | ## Development workflow 8 | 9 | This project is a monorepo managed using [Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains the following packages: 10 | 11 | - The library package in the root directory. 12 | - An example app in the `example/` directory. 13 | 14 | To get started with the project, make sure you have the correct version of [Node.js](https://nodejs.org/) installed. See the [`.nvmrc`](./.nvmrc) file for the version used in this project. 15 | 16 | Run `yarn` in the root directory to install the required dependencies for each package: 17 | 18 | ```sh 19 | yarn 20 | ``` 21 | 22 | > Since the project relies on Yarn workspaces, you cannot use [`npm`](https://github.com/npm/cli) for development without manually migrating. 23 | 24 | The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make. 25 | 26 | It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app. 27 | 28 | If you want to use Android Studio or XCode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/ReactNativeScannerExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > @pushpendersingh/react-native-scanner`. 29 | 30 | To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `pushpendersingh-react-native-scanner` under `Android`. 31 | 32 | You can use various commands from the root directory to work with the project. 33 | 34 | To start the packager: 35 | 36 | ```sh 37 | yarn example start 38 | ``` 39 | 40 | To run the example app on Android: 41 | 42 | ```sh 43 | yarn example android 44 | ``` 45 | 46 | To run the example app on iOS: 47 | 48 | ```sh 49 | yarn example ios 50 | ``` 51 | 52 | To confirm that the app is running with the new architecture, you can check the Metro logs for a message like this: 53 | 54 | ```sh 55 | Running "ReactNativeScannerExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1} 56 | ``` 57 | 58 | Note the `"fabric":true` and `"concurrentRoot":true` properties. 59 | 60 | Make sure your code passes TypeScript and ESLint. Run the following to verify: 61 | 62 | ```sh 63 | yarn typecheck 64 | yarn lint 65 | ``` 66 | 67 | To fix formatting errors, run the following: 68 | 69 | ```sh 70 | yarn lint --fix 71 | ``` 72 | 73 | Remember to add tests for your change if possible. Run the unit tests by: 74 | 75 | ```sh 76 | yarn test 77 | ``` 78 | 79 | ### Commit message convention 80 | 81 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages: 82 | 83 | - `fix`: bug fixes, e.g. fix crash due to deprecated method. 84 | - `feat`: new features, e.g. add new method to the module. 85 | - `refactor`: code refactor, e.g. migrate from class components to hooks. 86 | - `docs`: changes into documentation, e.g. add usage example for the module. 87 | - `test`: adding or updating tests, e.g. add integration tests using detox. 88 | - `chore`: tooling changes, e.g. change CI config. 89 | 90 | Our pre-commit hooks verify that your commit message matches this format when committing. 91 | 92 | ### Linting and tests 93 | 94 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/) 95 | 96 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. 97 | 98 | Our pre-commit hooks verify that the linter and tests pass when committing. 99 | 100 | ### Publishing to npm 101 | 102 | We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc. 103 | 104 | To publish new versions, run the following: 105 | 106 | ```sh 107 | yarn release 108 | ``` 109 | 110 | ### Scripts 111 | 112 | The `package.json` file contains various scripts for common tasks: 113 | 114 | - `yarn`: setup project by installing dependencies. 115 | - `yarn typecheck`: type-check files with TypeScript. 116 | - `yarn lint`: lint files with ESLint. 117 | - `yarn test`: run unit tests with Jest. 118 | - `yarn example start`: start the Metro server for the example app. 119 | - `yarn example android`: run the example app on Android. 120 | - `yarn example ios`: run the example app on iOS. 121 | 122 | ### Sending a pull request 123 | 124 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). 125 | 126 | When you're sending a pull request: 127 | 128 | - Prefer small pull requests focused on one change. 129 | - Verify that linters and tests are passing. 130 | - Review the documentation to make sure it looks good. 131 | - Follow the pull request template when opening a pull request. 132 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. 133 | -------------------------------------------------------------------------------- /android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerModule.kt: -------------------------------------------------------------------------------- 1 | package com.pushpendersingh.reactnativescanner 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.pm.PackageManager 6 | import androidx.core.app.ActivityCompat 7 | import androidx.core.content.ContextCompat 8 | import com.facebook.react.bridge.Promise 9 | import com.facebook.react.bridge.ReactApplicationContext 10 | import com.facebook.react.bridge.ReactMethod 11 | import com.facebook.react.bridge.WritableArray 12 | import com.facebook.react.module.annotations.ReactModule 13 | import com.facebook.react.modules.core.PermissionAwareActivity 14 | import com.facebook.react.modules.core.PermissionListener 15 | import com.pushpendersingh.reactnativescanner.NativeReactNativeScannerSpec 16 | 17 | @ReactModule(name = ReactNativeScannerModule.NAME) 18 | class ReactNativeScannerModule(reactContext: ReactApplicationContext) : 19 | NativeReactNativeScannerSpec(reactContext) { 20 | 21 | private val cameraManager: CameraManager = CameraManager(reactContext) 22 | private var permissionPromise: Promise? = null 23 | 24 | private val permissionListener = PermissionListener { requestCode, permissions, grantResults -> 25 | if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { 26 | // Validate that we're handling the correct permission 27 | val permissionGranted = permissions.isNotEmpty() && 28 | permissions[0] == Manifest.permission.CAMERA && 29 | grantResults.isNotEmpty() && 30 | grantResults[0] == PackageManager.PERMISSION_GRANTED 31 | 32 | permissionPromise?.resolve(permissionGranted) 33 | permissionPromise = null 34 | return@PermissionListener true 35 | } 36 | false 37 | } 38 | 39 | override fun getName(): String { 40 | return NAME 41 | } 42 | 43 | // Expose camera manager for ViewManager 44 | fun getCameraManager(): CameraManager { 45 | return cameraManager 46 | } 47 | 48 | @ReactMethod 49 | override fun startScanning(promise: Promise) { 50 | try { 51 | if (!cameraManager.hasCameraPermission()) { 52 | promise.reject("PERMISSION_DENIED", "Camera permission not granted") 53 | return 54 | } 55 | 56 | cameraManager.startScanning { result -> 57 | emitOnBarcodeScanned(result) 58 | } 59 | promise.resolve(null) 60 | } catch (e: Exception) { 61 | promise.reject("START_SCANNING_ERROR", e.message, e) 62 | } 63 | } 64 | 65 | @ReactMethod 66 | override fun stopScanning(promise: Promise) { 67 | try { 68 | cameraManager.stopScanning() 69 | promise.resolve(null) 70 | } catch (e: Exception) { 71 | promise.reject("STOP_SCANNING_ERROR", e.message, e) 72 | } 73 | } 74 | 75 | @ReactMethod 76 | override fun enableFlashlight(promise: Promise) { 77 | try { 78 | cameraManager.enableFlashlight() 79 | promise.resolve(null) 80 | } catch (e: Exception) { 81 | promise.reject("FLASHLIGHT_ERROR", e.message, e) 82 | } 83 | } 84 | 85 | @ReactMethod 86 | override fun disableFlashlight(promise: Promise) { 87 | try { 88 | cameraManager.disableFlashlight() 89 | promise.resolve(null) 90 | } catch (e: Exception) { 91 | promise.reject("FLASHLIGHT_ERROR", e.message, e) 92 | } 93 | } 94 | 95 | @ReactMethod 96 | override fun releaseCamera(promise: Promise) { 97 | try { 98 | cameraManager.releaseCamera() 99 | promise.resolve(null) 100 | } catch (e: Exception) { 101 | promise.reject("RELEASE_CAMERA_ERROR", e.message, e) 102 | } 103 | } 104 | 105 | @ReactMethod 106 | override fun hasCameraPermission(promise: Promise) { 107 | try { 108 | val hasPermission = cameraManager.hasCameraPermission() 109 | promise.resolve(hasPermission) 110 | } catch (e: Exception) { 111 | promise.reject("PERMISSION_CHECK_ERROR", e.message, e) 112 | } 113 | } 114 | 115 | @ReactMethod 116 | override fun requestCameraPermission(promise: Promise) { 117 | try { 118 | val currentActivity = reactApplicationContext.currentActivity 119 | if (currentActivity == null) { 120 | promise.reject("NO_ACTIVITY", "Current activity is null") 121 | return 122 | } 123 | 124 | if (cameraManager.hasCameraPermission()) { 125 | promise.resolve(true) 126 | return 127 | } 128 | 129 | // Check if there's already a pending permission request 130 | if (permissionPromise != null) { 131 | promise.reject( 132 | "PERMISSION_REQUEST_IN_PROGRESS", 133 | "A camera permission request is already in progress" 134 | ) 135 | return 136 | } 137 | 138 | // Store promise to be resolved in permission callback 139 | permissionPromise = promise 140 | 141 | // Request permission using PermissionAwareActivity 142 | val permissionAwareActivity = currentActivity as? PermissionAwareActivity 143 | if (permissionAwareActivity != null) { 144 | permissionAwareActivity.requestPermissions( 145 | arrayOf(Manifest.permission.CAMERA), 146 | CAMERA_PERMISSION_REQUEST_CODE, 147 | permissionListener 148 | ) 149 | } else { 150 | promise.reject("NO_PERMISSION_AWARE_ACTIVITY", "Current activity does not implement PermissionAwareActivity") 151 | permissionPromise = null 152 | } 153 | } catch (e: Exception) { 154 | promise.reject("PERMISSION_REQUEST_ERROR", e.message, e) 155 | permissionPromise = null 156 | } 157 | } 158 | 159 | override fun invalidate() { 160 | super.invalidate() 161 | permissionPromise = null 162 | cameraManager.releaseCamera() 163 | } 164 | 165 | companion object { 166 | const val NAME = "ReactNativeScanner" 167 | private const val CAMERA_PERMISSION_REQUEST_CODE = 100 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /ios/CameraView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraView.swift 3 | // ReactNativeScanner 4 | // 5 | // Created by Pushpender Singh 6 | // 7 | 8 | import UIKit 9 | import AVFoundation 10 | 11 | @objc(CameraView) 12 | public class CameraView: UIView { 13 | 14 | private var previewLayer: AVCaptureVideoPreviewLayer? 15 | private var cameraManager: CameraManager? 16 | private var currentVideoOrientation: AVCaptureVideoOrientation = .portrait 17 | 18 | public override init(frame: CGRect) { 19 | super.init(frame: frame) 20 | setupView() 21 | setupOrientationObserver() 22 | } 23 | 24 | required init?(coder: NSCoder) { 25 | super.init(coder: coder) 26 | setupView() 27 | setupOrientationObserver() 28 | } 29 | 30 | private func setupView() { 31 | backgroundColor = .black 32 | 33 | // Create preview layer 34 | previewLayer = AVCaptureVideoPreviewLayer() 35 | if let previewLayer = previewLayer { 36 | previewLayer.videoGravity = .resizeAspectFill 37 | previewLayer.frame = bounds 38 | layer.addSublayer(previewLayer) 39 | } 40 | } 41 | 42 | private func setupOrientationObserver() { 43 | // Enable device orientation notifications 44 | UIDevice.current.beginGeneratingDeviceOrientationNotifications() 45 | 46 | NotificationCenter.default.addObserver( 47 | self, 48 | selector: #selector(handleOrientationChange), 49 | name: UIDevice.orientationDidChangeNotification, 50 | object: nil 51 | ) 52 | } 53 | 54 | @objc private func handleOrientationChange() { 55 | updateVideoOrientation() 56 | } 57 | 58 | @objc public func setCameraManager(_ manager: CameraManager) { 59 | self.cameraManager = manager 60 | 61 | // CHANGE: Subscribe to CameraManager's onSessionReady callback. 62 | // Reason: CameraView owns the preview layer and binds it to the session on the main thread, 63 | // keeping UI work on main and avoiding concurrency violations. 64 | manager.onSessionReady = { [weak self] session in 65 | guard let self = self else { return } 66 | DispatchQueue.main.async { [weak self] in 67 | guard let self = self else { return } 68 | if let previewLayer = self.previewLayer { 69 | previewLayer.session = session 70 | self.updateVideoOrientation() 71 | print("✅ Preview layer bound to session from onSessionReady callback") 72 | } else { 73 | print("⚠️ Preview layer missing when session became ready") 74 | } 75 | } 76 | } 77 | 78 | // Use non-blocking callback version 79 | // Reason: Avoids blocking main thread with semaphore 80 | manager.getCurrentSession { [weak self] existingSession in 81 | guard let self = self, let previewLayer = self.previewLayer else { return } 82 | if let existingSession = existingSession { 83 | previewLayer.session = existingSession 84 | self.updateVideoOrientation() 85 | print("✅ Preview layer bound to existing session") 86 | } 87 | } 88 | 89 | print("✅ Camera manager set on CameraView") 90 | } 91 | 92 | public override func layoutSubviews() { 93 | super.layoutSubviews() 94 | previewLayer?.frame = bounds 95 | } 96 | 97 | private func updateVideoOrientation() { 98 | guard let connection = previewLayer?.connection, connection.isVideoOrientationSupported else { return } 99 | 100 | let deviceOrientation = UIDevice.current.orientation 101 | 102 | let videoOrientation: AVCaptureVideoOrientation 103 | 104 | switch deviceOrientation { 105 | case .portrait: 106 | videoOrientation = .portrait 107 | case .landscapeRight: 108 | videoOrientation = .landscapeLeft 109 | case .landscapeLeft: 110 | videoOrientation = .landscapeRight 111 | case .portraitUpsideDown: 112 | videoOrientation = .portraitUpsideDown 113 | default: 114 | // For .faceUp, .faceDown, .unknown, or during transitions, 115 | // fall back to the interface orientation from the window scene 116 | if let windowScene = window?.windowScene { 117 | switch windowScene.interfaceOrientation { 118 | case .portrait: 119 | videoOrientation = .portrait 120 | case .landscapeRight: 121 | videoOrientation = .landscapeRight 122 | case .landscapeLeft: 123 | videoOrientation = .landscapeLeft 124 | case .portraitUpsideDown: 125 | videoOrientation = .portraitUpsideDown 126 | default: 127 | videoOrientation = .portrait 128 | } 129 | } else { 130 | videoOrientation = .portrait 131 | } 132 | } 133 | 134 | // Only update if orientation actually changed 135 | guard videoOrientation != currentVideoOrientation else { return } 136 | 137 | currentVideoOrientation = videoOrientation 138 | connection.videoOrientation = videoOrientation 139 | } 140 | 141 | deinit { 142 | // Remove orientation observer and stop generating notifications 143 | NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil) 144 | UIDevice.current.endGeneratingDeviceOrientationNotifications() 145 | 146 | // CHANGE: Clear session on teardown to avoid retaining references. 147 | previewLayer?.session = nil 148 | previewLayer?.removeFromSuperlayer() 149 | previewLayer = nil 150 | 151 | // Clear the onSessionReady callback to prevent potential retain cycles 152 | cameraManager?.onSessionReady = nil 153 | cameraManager = nil 154 | 155 | print("🗑️ CameraView deallocated") 156 | } 157 | } 158 | 159 | -------------------------------------------------------------------------------- /src/__tests__/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { BarcodeScanner, type BarcodeResult, type BarcodeType } from '../index'; 2 | import NativeReactNativeScanner from '../NativeReactNativeScanner'; 3 | 4 | // Mock the native module 5 | jest.mock('../NativeReactNativeScanner', () => ({ 6 | onBarcodeScanned: jest.fn(), 7 | startScanning: jest.fn(), 8 | stopScanning: jest.fn(), 9 | enableFlashlight: jest.fn(), 10 | disableFlashlight: jest.fn(), 11 | releaseCamera: jest.fn(), 12 | hasCameraPermission: jest.fn(), 13 | requestCameraPermission: jest.fn(), 14 | })); 15 | 16 | describe('BarcodeScanner', () => { 17 | let mockRemoveListener: jest.Mock; 18 | 19 | beforeEach(() => { 20 | jest.clearAllMocks(); 21 | mockRemoveListener = jest.fn(); 22 | // Reset the static listener property 23 | (BarcodeScanner as any).listener = null; 24 | }); 25 | 26 | // Test 1: Start scanning with callback 27 | test('should start scanning and set up barcode listener', async () => { 28 | const mockCallback = jest.fn(); 29 | const mockBarcodeResult: BarcodeResult = { 30 | data: 'QR_CODE_DATA', 31 | type: 'QR_CODE' as BarcodeType, 32 | }; 33 | const mockListener = { remove: mockRemoveListener }; 34 | 35 | (NativeReactNativeScanner.onBarcodeScanned as jest.Mock).mockReturnValue( 36 | mockListener 37 | ); 38 | (NativeReactNativeScanner.startScanning as jest.Mock).mockResolvedValue( 39 | undefined 40 | ); 41 | 42 | await BarcodeScanner.startScanning(mockCallback); 43 | 44 | expect(NativeReactNativeScanner.onBarcodeScanned).toHaveBeenCalled(); 45 | expect(NativeReactNativeScanner.startScanning).toHaveBeenCalled(); 46 | 47 | // Simulate barcode detection 48 | const eventCallback = ( 49 | NativeReactNativeScanner.onBarcodeScanned as jest.Mock 50 | ).mock.calls[0][0]; 51 | eventCallback(mockBarcodeResult); 52 | 53 | expect(mockCallback).toHaveBeenCalledWith(mockBarcodeResult); 54 | }); 55 | 56 | // Test 2: Stop scanning 57 | test('should stop scanning and remove listener', async () => { 58 | const mockCallback = jest.fn(); 59 | const mockListener = { remove: mockRemoveListener }; 60 | 61 | (NativeReactNativeScanner.onBarcodeScanned as jest.Mock).mockReturnValue( 62 | mockListener 63 | ); 64 | (NativeReactNativeScanner.startScanning as jest.Mock).mockResolvedValue( 65 | undefined 66 | ); 67 | 68 | await BarcodeScanner.startScanning(mockCallback); 69 | await BarcodeScanner.stopScanning(); 70 | 71 | expect(mockRemoveListener).toHaveBeenCalled(); 72 | expect(NativeReactNativeScanner.stopScanning).toHaveBeenCalled(); 73 | }); 74 | 75 | // Test 3: Enable flashlight 76 | test('should enable flashlight', async () => { 77 | await BarcodeScanner.enableFlashlight(); 78 | 79 | expect(NativeReactNativeScanner.enableFlashlight).toHaveBeenCalled(); 80 | }); 81 | 82 | // Test 4: Disable flashlight 83 | test('should disable flashlight', async () => { 84 | await BarcodeScanner.disableFlashlight(); 85 | 86 | expect(NativeReactNativeScanner.disableFlashlight).toHaveBeenCalled(); 87 | }); 88 | 89 | // Test 5: Check camera permission 90 | test('should check camera permission', async () => { 91 | ( 92 | NativeReactNativeScanner.hasCameraPermission as jest.Mock 93 | ).mockResolvedValue(true); 94 | 95 | const hasPermission = await BarcodeScanner.hasCameraPermission(); 96 | 97 | expect(hasPermission).toBe(true); 98 | expect(NativeReactNativeScanner.hasCameraPermission).toHaveBeenCalled(); 99 | }); 100 | 101 | // Test 6: Request camera permission 102 | test('should request camera permission', async () => { 103 | ( 104 | NativeReactNativeScanner.requestCameraPermission as jest.Mock 105 | ).mockResolvedValue(true); 106 | 107 | const permissionGranted = await BarcodeScanner.requestCameraPermission(); 108 | 109 | expect(permissionGranted).toBe(true); 110 | expect(NativeReactNativeScanner.requestCameraPermission).toHaveBeenCalled(); 111 | }); 112 | 113 | // Test 7: Release camera 114 | test('should release camera and clean up listener', async () => { 115 | const mockCallback = jest.fn(); 116 | const mockListener = { remove: mockRemoveListener }; 117 | 118 | (NativeReactNativeScanner.onBarcodeScanned as jest.Mock).mockReturnValue( 119 | mockListener 120 | ); 121 | (NativeReactNativeScanner.startScanning as jest.Mock).mockResolvedValue( 122 | undefined 123 | ); 124 | 125 | await BarcodeScanner.startScanning(mockCallback); 126 | await BarcodeScanner.releaseCamera(); 127 | 128 | expect(mockRemoveListener).toHaveBeenCalled(); 129 | expect(NativeReactNativeScanner.releaseCamera).toHaveBeenCalled(); 130 | }); 131 | 132 | // Test 8: Handle multiple barcode detections 133 | test('should handle multiple barcode detections', async () => { 134 | const mockCallback = jest.fn(); 135 | const mockListener = { remove: mockRemoveListener }; 136 | 137 | (NativeReactNativeScanner.onBarcodeScanned as jest.Mock).mockReturnValue( 138 | mockListener 139 | ); 140 | (NativeReactNativeScanner.startScanning as jest.Mock).mockResolvedValue( 141 | undefined 142 | ); 143 | 144 | await BarcodeScanner.startScanning(mockCallback); 145 | 146 | const eventCallback = ( 147 | NativeReactNativeScanner.onBarcodeScanned as jest.Mock 148 | ).mock.calls[0][0]; 149 | 150 | // First barcode 151 | const firstBarcode: BarcodeResult = { 152 | data: 'BARCODE_1', 153 | type: 'QR_CODE' as BarcodeType, 154 | }; 155 | eventCallback(firstBarcode); 156 | 157 | // Second barcode 158 | const secondBarcode: BarcodeResult = { 159 | data: 'BARCODE_2', 160 | type: 'CODE_128' as BarcodeType, 161 | }; 162 | eventCallback(secondBarcode); 163 | 164 | expect(mockCallback).toHaveBeenCalledTimes(2); 165 | expect(mockCallback).toHaveBeenNthCalledWith(1, firstBarcode); 166 | expect(mockCallback).toHaveBeenNthCalledWith(2, secondBarcode); 167 | }); 168 | 169 | // Test 9: Handle different barcode types 170 | test('should handle different barcode types', async () => { 171 | const mockCallback = jest.fn(); 172 | const mockListener = { remove: mockRemoveListener }; 173 | 174 | (NativeReactNativeScanner.onBarcodeScanned as jest.Mock).mockReturnValue( 175 | mockListener 176 | ); 177 | (NativeReactNativeScanner.startScanning as jest.Mock).mockResolvedValue( 178 | undefined 179 | ); 180 | 181 | await BarcodeScanner.startScanning(mockCallback); 182 | 183 | const eventCallback = ( 184 | NativeReactNativeScanner.onBarcodeScanned as jest.Mock 185 | ).mock.calls[0][0]; 186 | 187 | const barcodeTypes: BarcodeType[] = [ 188 | 'QR_CODE', 189 | 'CODE_128', 190 | 'EAN_13', 191 | 'PDF417', 192 | ]; 193 | 194 | barcodeTypes.forEach((type, index) => { 195 | const barcode: BarcodeResult = { 196 | data: `DATA_${type}`, 197 | type: type, 198 | }; 199 | eventCallback(barcode); 200 | expect(mockCallback).toHaveBeenNthCalledWith(index + 1, barcode); 201 | }); 202 | }); 203 | 204 | // Test 10: Request camera permission denied 205 | test('should handle camera permission denied', async () => { 206 | ( 207 | NativeReactNativeScanner.requestCameraPermission as jest.Mock 208 | ).mockResolvedValue(false); 209 | 210 | const permissionGranted = await BarcodeScanner.requestCameraPermission(); 211 | 212 | expect(permissionGranted).toBe(false); 213 | expect(NativeReactNativeScanner.requestCameraPermission).toHaveBeenCalled(); 214 | }); 215 | }); 216 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { 3 | Alert, 4 | Platform, 5 | Text, 6 | Button, 7 | View, 8 | StyleSheet, 9 | ScrollView, 10 | TouchableOpacity, 11 | } from 'react-native'; 12 | 13 | import BarcodeScanner, { 14 | type BarcodeResult, 15 | CameraView, 16 | } from '@pushpendersingh/react-native-scanner'; 17 | import { SafeAreaView } from 'react-native-safe-area-context'; 18 | 19 | export default function App() { 20 | const [isCameraPermissionGranted, setIsCameraPermissionGranted] = 21 | useState(false); 22 | const [isScanning, setIsScanning] = useState(false); 23 | const [scannedData, setScannedData] = useState(null); 24 | const [flashEnabled, setFlashEnabled] = useState(false); 25 | const [scanHistory, setScanHistory] = useState([]); 26 | 27 | useEffect(() => { 28 | checkCameraPermission(); 29 | return () => { 30 | // Cleanup on unmount 31 | if (isScanning) { 32 | BarcodeScanner.stopScanning(); 33 | } 34 | }; 35 | // eslint-disable-next-line react-hooks/exhaustive-deps 36 | }, []); 37 | 38 | const handleBarcodeScanned = (results: BarcodeResult[]) => { 39 | console.log('Barcode / QR Code scanned:', results); 40 | if (results.length > 0) { 41 | setScannedData(results[0] ?? null); 42 | setScanHistory((prev) => [...results, ...prev].slice(0, 20)); // Keep last 20 scans 43 | } 44 | }; 45 | 46 | const startScanning = async () => { 47 | try { 48 | await BarcodeScanner.startScanning(handleBarcodeScanned); 49 | setIsScanning(true); 50 | console.log('Scanning started'); 51 | } catch (error: any) { 52 | console.error('Error starting scanner:', error); 53 | Alert.alert('Error', error.message || 'Failed to start scanning'); 54 | } 55 | }; 56 | 57 | const stopScanning = async () => { 58 | try { 59 | await BarcodeScanner.stopScanning(); 60 | setIsScanning(false); 61 | console.log('Scanning stopped'); 62 | } catch (error: any) { 63 | console.error('Error stopping scanner:', error); 64 | } 65 | }; 66 | 67 | const toggleFlashlight = async () => { 68 | try { 69 | if (flashEnabled) { 70 | await BarcodeScanner.disableFlashlight(); 71 | setFlashEnabled(false); 72 | console.log('Flashlight disabled'); 73 | } else { 74 | await BarcodeScanner.enableFlashlight(); 75 | setFlashEnabled(true); 76 | console.log('Flashlight enabled'); 77 | } 78 | } catch (error: any) { 79 | console.error('Error toggling flashlight:', error); 80 | Alert.alert('Error', 'Could not toggle flashlight'); 81 | } 82 | }; 83 | 84 | const releaseCamera = async () => { 85 | try { 86 | await BarcodeScanner.releaseCamera(); 87 | setIsScanning(false); 88 | setFlashEnabled(false); 89 | console.log('Camera released'); 90 | } catch (error: any) { 91 | console.error('Error releasing camera:', error); 92 | } 93 | }; 94 | 95 | const clearHistory = () => { 96 | setScanHistory([]); 97 | setScannedData(null); 98 | }; 99 | 100 | const checkCameraPermission = async () => { 101 | try { 102 | // Check if permission is already granted 103 | const hasPermission = await BarcodeScanner.hasCameraPermission(); 104 | 105 | if (hasPermission) { 106 | setIsCameraPermissionGranted(true); 107 | return; 108 | } 109 | 110 | // Request permission if not granted 111 | const granted = await BarcodeScanner.requestCameraPermission(); 112 | 113 | if (granted) { 114 | setIsCameraPermissionGranted(true); 115 | Alert.alert('Success', 'Camera permission granted!'); 116 | } else { 117 | setIsCameraPermissionGranted(false); 118 | Alert.alert( 119 | 'Permission Denied', 120 | 'Camera permission is required to scan barcodes and QR codes.', 121 | [ 122 | { text: 'Cancel', style: 'cancel' }, 123 | { text: 'Try Again', onPress: checkCameraPermission }, 124 | ] 125 | ); 126 | } 127 | } catch (error: any) { 128 | console.error('Error checking camera permission:', error); 129 | Alert.alert('Error', 'Failed to check camera permission'); 130 | } 131 | }; 132 | 133 | if (!isCameraPermissionGranted) { 134 | return ( 135 | 136 | 137 | 138 | 📷 Camera Permission Required 139 | 140 | 141 | This app needs camera access to scan barcodes and QR codes. 142 | 143 |