react-native-fabric-barcode-scanner 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/android/build.gradle +68 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/rncustomscanner/BarcodeAnalyzer.kt +56 -0
- package/android/src/main/java/com/rncustomscanner/BarcodeFormatMapper.kt +59 -0
- package/android/src/main/java/com/rncustomscanner/CameraPermissionHelper.kt +140 -0
- package/android/src/main/java/com/rncustomscanner/RNCustomScannerPackage.kt +44 -0
- package/android/src/main/java/com/rncustomscanner/ScannerView.kt +245 -0
- package/android/src/main/java/com/rncustomscanner/events/BarcodeScannedEvent.kt +29 -0
- package/android/src/newarch/java/com/rncustomscanner/ScannerModule.kt +103 -0
- package/android/src/newarch/java/com/rncustomscanner/ScannerViewManager.kt +61 -0
- package/android/src/oldarch/java/com/rncustomscanner/ScannerModule.kt +109 -0
- package/android/src/oldarch/java/com/rncustomscanner/ScannerViewManager.kt +54 -0
- package/ios/RNCustomScanner-Bridging-Header.h +3 -0
- package/ios/RNScannerView.swift +132 -0
- package/ios/ScannerModule.mm +65 -0
- package/ios/ScannerModule.swift +105 -0
- package/ios/ScannerViewManager.mm +67 -0
- package/package.json +71 -0
- package/react-native-fabric-barcode-scanner.podspec +28 -0
- package/react-native.config.js +8 -0
- package/src/NativeScannerModule.ts +21 -0
- package/src/ScannerViewNativeComponent.ts +28 -0
- package/src/index.tsx +66 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
private final class MetadataOutputHandler: NSObject, AVCaptureMetadataOutputObjectsDelegate {
|
|
5
|
+
var onBarcodeScanned: ((NSDictionary) -> Void)?
|
|
6
|
+
|
|
7
|
+
func metadataOutput(_ output: AVCaptureMetadataOutput,
|
|
8
|
+
didOutput metadataObjects: [AVMetadataObject],
|
|
9
|
+
from connection: AVCaptureConnection) {
|
|
10
|
+
for object in metadataObjects {
|
|
11
|
+
guard let readable = object as? AVMetadataMachineReadableCodeObject,
|
|
12
|
+
let value = readable.stringValue else { continue }
|
|
13
|
+
onBarcodeScanned?([
|
|
14
|
+
"value": value,
|
|
15
|
+
"type": RNScannerHelper.name(for: readable.type),
|
|
16
|
+
"timestamp": Int32(Date().timeIntervalSince1970 * 1000)
|
|
17
|
+
] as NSDictionary)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@objc(RNScannerView)
|
|
23
|
+
public class RNScannerView: UIView {
|
|
24
|
+
|
|
25
|
+
private let session = AVCaptureSession()
|
|
26
|
+
private var previewLayer: AVCaptureVideoPreviewLayer?
|
|
27
|
+
private let metadataOutput = AVCaptureMetadataOutput()
|
|
28
|
+
private let metadataHandler = MetadataOutputHandler()
|
|
29
|
+
private let sessionQueue = DispatchQueue(label: "com.rncustomscanner.session")
|
|
30
|
+
private var isSessionConfigured = false
|
|
31
|
+
|
|
32
|
+
@objc public var isActive: Bool = true {
|
|
33
|
+
didSet {
|
|
34
|
+
sessionQueue.async {
|
|
35
|
+
if !self.isSessionConfigured {
|
|
36
|
+
self.configureSessionIfNeeded()
|
|
37
|
+
} else {
|
|
38
|
+
self.updateSessionState()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
@objc public var torchOn: Bool = false { didSet { RNScannerHelper().setTorchEnabled(torchOn) } }
|
|
44
|
+
@objc public var scanFormats: [String] = RNScannerHelper.supportedFormats { didSet { applyMetadataFilters() } }
|
|
45
|
+
|
|
46
|
+
// Wired up by ScannerViewManager to emit the Fabric event
|
|
47
|
+
@objc public var onBarcodeScanned: ((NSDictionary) -> Void)? {
|
|
48
|
+
didSet { metadataHandler.onBarcodeScanned = onBarcodeScanned }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public override init(frame: CGRect) {
|
|
52
|
+
super.init(frame: frame)
|
|
53
|
+
sessionQueue.async {
|
|
54
|
+
self.configureSessionIfNeeded()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
public required init?(coder: NSCoder) { fatalError("init(coder:) not implemented") }
|
|
58
|
+
|
|
59
|
+
// Only builds the AVCaptureSession if permission is already granted.
|
|
60
|
+
// Requesting permission is entirely JS's responsibility via
|
|
61
|
+
// ScannerModule.requestCameraPermission() — this view never triggers
|
|
62
|
+
// the system permission dialog itself.
|
|
63
|
+
private func configureSessionIfNeeded() {
|
|
64
|
+
guard !isSessionConfigured else {
|
|
65
|
+
updateSessionState()
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
buildSessionGraph()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private func buildSessionGraph() {
|
|
75
|
+
session.beginConfiguration()
|
|
76
|
+
guard let device = AVCaptureDevice.default(for: .video),
|
|
77
|
+
let input = try? AVCaptureDeviceInput(device: device),
|
|
78
|
+
session.canAddInput(input) else {
|
|
79
|
+
session.commitConfiguration()
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
session.addInput(input)
|
|
83
|
+
guard session.canAddOutput(metadataOutput) else {
|
|
84
|
+
session.commitConfiguration()
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
session.addOutput(metadataOutput)
|
|
88
|
+
metadataOutput.setMetadataObjectsDelegate(metadataHandler, queue: DispatchQueue.main)
|
|
89
|
+
session.commitConfiguration()
|
|
90
|
+
isSessionConfigured = true
|
|
91
|
+
|
|
92
|
+
DispatchQueue.main.async { [weak self] in
|
|
93
|
+
guard let self = self else { return }
|
|
94
|
+
self.applyMetadataFilters()
|
|
95
|
+
let layer = AVCaptureVideoPreviewLayer(session: self.session)
|
|
96
|
+
layer.videoGravity = .resizeAspectFill
|
|
97
|
+
layer.frame = self.bounds
|
|
98
|
+
self.layer.addSublayer(layer)
|
|
99
|
+
self.previewLayer = layer
|
|
100
|
+
self.updateSessionState()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private func applyMetadataFilters() {
|
|
105
|
+
let requested = scanFormats.compactMap { RNScannerHelper.avFormat(for: $0) }
|
|
106
|
+
let available = metadataOutput.availableMetadataObjectTypes
|
|
107
|
+
metadataOutput.metadataObjectTypes = requested.filter { available.contains($0) }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private func updateSessionState() {
|
|
111
|
+
sessionQueue.async {
|
|
112
|
+
guard self.isSessionConfigured else { return }
|
|
113
|
+
if self.isActive && !self.session.isRunning {
|
|
114
|
+
self.session.startRunning()
|
|
115
|
+
} else if !self.isActive && self.session.isRunning {
|
|
116
|
+
self.session.stopRunning()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public override func layoutSubviews() {
|
|
122
|
+
super.layoutSubviews()
|
|
123
|
+
previewLayer?.frame = bounds
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public override func removeFromSuperview() {
|
|
127
|
+
sessionQueue.async { [session] in
|
|
128
|
+
if session.isRunning { session.stopRunning() }
|
|
129
|
+
}
|
|
130
|
+
super.removeFromSuperview()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#import "RNCustomScannerSpec.h"
|
|
2
|
+
#import "RNCustomScanner-Swift.h"
|
|
3
|
+
|
|
4
|
+
using namespace facebook::react;
|
|
5
|
+
|
|
6
|
+
@interface ScannerModule : NativeScannerModuleSpecBase <NativeScannerModuleSpec>
|
|
7
|
+
@end
|
|
8
|
+
|
|
9
|
+
@implementation ScannerModule {
|
|
10
|
+
RNScannerHelper *_helper;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
RCT_EXPORT_MODULE(ScannerModule)
|
|
14
|
+
|
|
15
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
16
|
+
return NO;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
- (instancetype)init {
|
|
20
|
+
if (self = [super init]) {
|
|
21
|
+
_helper = [RNScannerHelper new];
|
|
22
|
+
}
|
|
23
|
+
return self;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- (void)checkCameraPermission:(RCTPromiseResolveBlock)resolve
|
|
27
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
28
|
+
resolve([_helper checkCameraPermission]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
- (void)requestCameraPermission:(RCTPromiseResolveBlock)resolve
|
|
32
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
33
|
+
[_helper requestCameraPermission:^(NSString *result) {
|
|
34
|
+
resolve(result);
|
|
35
|
+
}];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
- (void)setTorchEnabled:(BOOL)enabled {
|
|
39
|
+
[_helper setTorchEnabled:enabled];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
- (void)openCameraSettings {
|
|
43
|
+
[_helper openCameraSettings];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
- (facebook::react::ModuleConstants<JS::NativeScannerModule::Constants>)getConstants {
|
|
47
|
+
std::vector<NSString *> formats;
|
|
48
|
+
for (NSString *f in RNScannerHelper.supportedFormats) {
|
|
49
|
+
formats.push_back(f);
|
|
50
|
+
}
|
|
51
|
+
return facebook::react::typedConstants<JS::NativeScannerModule::Constants>({
|
|
52
|
+
.SUPPORTED_FORMATS = formats
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
- (facebook::react::ModuleConstants<JS::NativeScannerModule::Constants>)constantsToExport {
|
|
57
|
+
return [self getConstants];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
61
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
62
|
+
return std::make_shared<facebook::react::NativeScannerModuleSpecJSI>(params);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Foundation
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
@objc(RNScannerHelper)
|
|
6
|
+
public class RNScannerHelper: NSObject {
|
|
7
|
+
|
|
8
|
+
@objc public static var supportedFormats: [String] {
|
|
9
|
+
return ["qr", "ean13", "ean8", "upce", "code128", "code39",
|
|
10
|
+
"code93", "pdf417", "aztec", "datamatrix", "itf14", "interleaved2of5"]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// NOTE: intentionally NOT @objc — AVMetadataObject.ObjectType does not
|
|
14
|
+
// bridge cleanly to Objective-C in the generated -Swift.h header and
|
|
15
|
+
// causes "expected a type" build errors if exposed. These are internal
|
|
16
|
+
// Swift-only helpers; call them only from other Swift code.
|
|
17
|
+
internal static func avFormat(for name: String) -> AVMetadataObject.ObjectType? {
|
|
18
|
+
switch name {
|
|
19
|
+
case "qr": return .qr
|
|
20
|
+
case "ean13": return .ean13
|
|
21
|
+
case "ean8": return .ean8
|
|
22
|
+
case "upce": return .upce
|
|
23
|
+
case "code128": return .code128
|
|
24
|
+
case "code39": return .code39
|
|
25
|
+
case "code93": return .code93
|
|
26
|
+
case "pdf417": return .pdf417
|
|
27
|
+
case "aztec": return .aztec
|
|
28
|
+
case "datamatrix": return .dataMatrix
|
|
29
|
+
case "itf14": return .itf14
|
|
30
|
+
case "interleaved2of5": return .interleaved2of5
|
|
31
|
+
default: return nil
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
internal static func name(for avType: AVMetadataObject.ObjectType) -> String {
|
|
36
|
+
switch avType {
|
|
37
|
+
case .qr: return "qr"
|
|
38
|
+
case .ean13: return "ean13"
|
|
39
|
+
case .ean8: return "ean8"
|
|
40
|
+
case .upce: return "upce"
|
|
41
|
+
case .code128: return "code128"
|
|
42
|
+
case .code39: return "code39"
|
|
43
|
+
case .code93: return "code93"
|
|
44
|
+
case .pdf417: return "pdf417"
|
|
45
|
+
case .aztec: return "aztec"
|
|
46
|
+
case .dataMatrix: return "datamatrix"
|
|
47
|
+
case .itf14: return "itf14"
|
|
48
|
+
case .interleaved2of5: return "interleaved2of5"
|
|
49
|
+
default: return "unknown"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public override init() {
|
|
54
|
+
super.init()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@objc public func checkCameraPermission() -> String {
|
|
58
|
+
switch AVCaptureDevice.authorizationStatus(for: .video) {
|
|
59
|
+
case .authorized: return "granted"
|
|
60
|
+
case .denied: return "blocked"
|
|
61
|
+
case .restricted: return "restricted"
|
|
62
|
+
case .notDetermined: return "not-determined"
|
|
63
|
+
@unknown default: return "not-determined"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@objc public func requestCameraPermission(_ completion: @escaping (String) -> Void) {
|
|
68
|
+
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
69
|
+
|
|
70
|
+
switch status {
|
|
71
|
+
case .authorized:
|
|
72
|
+
completion("granted")
|
|
73
|
+
case .denied:
|
|
74
|
+
completion("blocked")
|
|
75
|
+
case .restricted:
|
|
76
|
+
completion("restricted")
|
|
77
|
+
case .notDetermined:
|
|
78
|
+
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
79
|
+
DispatchQueue.main.async {
|
|
80
|
+
completion(granted ? "granted" : "blocked")
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
@unknown default:
|
|
84
|
+
completion("not-determined")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@objc public func openCameraSettings() {
|
|
89
|
+
DispatchQueue.main.async {
|
|
90
|
+
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
|
|
91
|
+
UIApplication.shared.open(url)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@objc public func setTorchEnabled(_ enabled: Bool) {
|
|
96
|
+
guard let device = AVCaptureDevice.default(for: .video), device.hasTorch else { return }
|
|
97
|
+
do {
|
|
98
|
+
try device.lockForConfiguration()
|
|
99
|
+
device.torchMode = enabled ? .on : .off
|
|
100
|
+
device.unlockForConfiguration()
|
|
101
|
+
} catch {
|
|
102
|
+
// Best-effort: torch failures are non-fatal for scanning
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#import "RNCustomScanner-Swift.h"
|
|
2
|
+
#import <React/RCTViewComponentView.h>
|
|
3
|
+
#import <react/renderer/components/RNCustomScannerSpec/ComponentDescriptors.h>
|
|
4
|
+
#import <react/renderer/components/RNCustomScannerSpec/EventEmitters.h>
|
|
5
|
+
#import <react/renderer/components/RNCustomScannerSpec/Props.h>
|
|
6
|
+
#import <react/renderer/components/RNCustomScannerSpec/RCTComponentViewHelpers.h>
|
|
7
|
+
|
|
8
|
+
using namespace facebook::react;
|
|
9
|
+
|
|
10
|
+
@interface ScannerView : RCTViewComponentView <RCTScannerViewViewProtocol>
|
|
11
|
+
@end
|
|
12
|
+
|
|
13
|
+
@implementation ScannerView {
|
|
14
|
+
RNScannerView *_scannerView;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
+ (facebook::react::ComponentDescriptorProvider)componentDescriptorProvider {
|
|
18
|
+
return concreteComponentDescriptorProvider<ScannerViewComponentDescriptor>();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
22
|
+
if (self = [super initWithFrame:frame]) {
|
|
23
|
+
static const auto defaultProps = std::make_shared<const ScannerViewProps>();
|
|
24
|
+
_props = defaultProps;
|
|
25
|
+
|
|
26
|
+
_scannerView = [[RNScannerView alloc] initWithFrame:self.bounds];
|
|
27
|
+
__weak ScannerView *weakSelf = self;
|
|
28
|
+
_scannerView.onBarcodeScanned = ^(NSDictionary<NSString *, id> *payload) {
|
|
29
|
+
[weakSelf emitBarcodeScanned:payload];
|
|
30
|
+
};
|
|
31
|
+
self.contentView = _scannerView;
|
|
32
|
+
}
|
|
33
|
+
return self;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps {
|
|
37
|
+
const auto &newProps = *std::static_pointer_cast<const ScannerViewProps>(props);
|
|
38
|
+
|
|
39
|
+
_scannerView.isActive = newProps.isActive;
|
|
40
|
+
_scannerView.torchOn = newProps.torchOn;
|
|
41
|
+
|
|
42
|
+
if (!newProps.scanFormats.empty()) {
|
|
43
|
+
NSMutableArray<NSString *> *formats = [NSMutableArray new];
|
|
44
|
+
for (const auto &format : newProps.scanFormats) {
|
|
45
|
+
[formats addObject:[NSString stringWithUTF8String:format.c_str()]];
|
|
46
|
+
}
|
|
47
|
+
_scannerView.scanFormats = formats;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[super updateProps:props oldProps:oldProps];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
- (void)emitBarcodeScanned:(NSDictionary<NSString *, id> *)payload {
|
|
54
|
+
if (!_eventEmitter) return;
|
|
55
|
+
const auto emitter = std::static_pointer_cast<const ScannerViewEventEmitter>(_eventEmitter);
|
|
56
|
+
emitter->onBarcodeScanned(ScannerViewEventEmitter::OnBarcodeScanned{
|
|
57
|
+
.value = std::string([payload[@"value"] UTF8String]),
|
|
58
|
+
.type = std::string([payload[@"type"] UTF8String]),
|
|
59
|
+
.timestamp = [payload[@"timestamp"] intValue]
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@end
|
|
64
|
+
|
|
65
|
+
Class<RCTComponentViewProtocol> ScannerViewCls(void) {
|
|
66
|
+
return ScannerView.class;
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-fabric-barcode-scanner",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "High-performance native barcode scanner for React Native with TurboModule + Fabric (New Architecture) support on iOS and Android",
|
|
5
|
+
"main": "src/index.tsx",
|
|
6
|
+
"types": "src/index.tsx",
|
|
7
|
+
"source": "src/index.tsx",
|
|
8
|
+
"react-native": "src/index.tsx",
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"ios",
|
|
12
|
+
"android",
|
|
13
|
+
"react-native-fabric-barcode-scanner.podspec",
|
|
14
|
+
"react-native.config.js",
|
|
15
|
+
"!ios/build",
|
|
16
|
+
"!android/build",
|
|
17
|
+
"!android/.cxx",
|
|
18
|
+
"!android/.gradle"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"react-native",
|
|
25
|
+
"barcode",
|
|
26
|
+
"scanner",
|
|
27
|
+
"qr",
|
|
28
|
+
"qr-code",
|
|
29
|
+
"camera",
|
|
30
|
+
"turbo-module",
|
|
31
|
+
"fabric",
|
|
32
|
+
"new-architecture",
|
|
33
|
+
"mlkit",
|
|
34
|
+
"camerax"
|
|
35
|
+
],
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/Rajubarde12/react-native-fabric-barcode-scanner.git"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/Rajubarde12/react-native-fabric-barcode-scanner/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/Rajubarde12/react-native-fabric-barcode-scanner#readme",
|
|
44
|
+
"author": "Rajubarde12 (https://github.com/Rajubarde12)",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public",
|
|
48
|
+
"registry": "https://registry.npmjs.org/"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"react": "*",
|
|
52
|
+
"react-native": ">=0.74.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/react": "^19.0.0",
|
|
56
|
+
"typescript": "^5.0.0"
|
|
57
|
+
},
|
|
58
|
+
"codegenConfig": {
|
|
59
|
+
"name": "RNCustomScannerSpec",
|
|
60
|
+
"type": "all",
|
|
61
|
+
"jsSrcsDir": "src",
|
|
62
|
+
"ios": {
|
|
63
|
+
"componentProvider": {
|
|
64
|
+
"ScannerView": "ScannerView"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"android": {
|
|
68
|
+
"javaPackageName": "com.rncustomscanner"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "react-native-fabric-barcode-scanner"
|
|
7
|
+
s.module_name = "RNCustomScanner"
|
|
8
|
+
s.version = package["version"]
|
|
9
|
+
s.summary = package["description"]
|
|
10
|
+
s.homepage = package["homepage"]
|
|
11
|
+
s.license = package["license"]
|
|
12
|
+
s.authors = package["author"]
|
|
13
|
+
s.platforms = { :ios => "13.4" }
|
|
14
|
+
repo_url = package["repository"]["url"].sub(/^git\+/, "")
|
|
15
|
+
s.source = { :git => repo_url, :tag => "v#{s.version}" }
|
|
16
|
+
s.static_framework = true
|
|
17
|
+
|
|
18
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
19
|
+
|
|
20
|
+
s.dependency "React-Core"
|
|
21
|
+
|
|
22
|
+
s.pod_target_xcconfig = {
|
|
23
|
+
"DEFINES_MODULE" => "YES",
|
|
24
|
+
"SWIFT_VERSION" => "5.0"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
install_modules_dependencies(s)
|
|
28
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export type CameraPermissionStatus =
|
|
5
|
+
| 'granted'
|
|
6
|
+
| 'not-determined'
|
|
7
|
+
| 'denied'
|
|
8
|
+
| 'blocked'
|
|
9
|
+
| 'restricted';
|
|
10
|
+
|
|
11
|
+
export interface Spec extends TurboModule {
|
|
12
|
+
checkCameraPermission(): Promise<CameraPermissionStatus>;
|
|
13
|
+
requestCameraPermission(): Promise<CameraPermissionStatus>;
|
|
14
|
+
openCameraSettings(): void;
|
|
15
|
+
setTorchEnabled(enabled: boolean): void;
|
|
16
|
+
getConstants(): {
|
|
17
|
+
SUPPORTED_FORMATS: string[];
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('ScannerModule');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ViewProps, HostComponent } from 'react-native';
|
|
2
|
+
import { codegenNativeComponent } from 'react-native';
|
|
3
|
+
import type {
|
|
4
|
+
Int32,
|
|
5
|
+
WithDefault,
|
|
6
|
+
BubblingEventHandler,
|
|
7
|
+
} from 'react-native/Libraries/Types/CodegenTypes';
|
|
8
|
+
|
|
9
|
+
export interface BarcodeScannedEvent {
|
|
10
|
+
value: string;
|
|
11
|
+
type: string; // e.g. 'qr', 'ean13', 'code128', 'pdf417', 'upc-e', etc.
|
|
12
|
+
timestamp: Int32;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface NativeProps extends ViewProps {
|
|
16
|
+
isActive?: WithDefault<boolean, true>;
|
|
17
|
+
torchOn?: WithDefault<boolean, false>;
|
|
18
|
+
|
|
19
|
+
// comma-free array prop: list of formats to actively decode
|
|
20
|
+
// e.g. ['qr','ean13','ean8','upc-a','upc-e','code128','code39','pdf417','aztec','datamatrix']
|
|
21
|
+
scanFormats?: string[];
|
|
22
|
+
|
|
23
|
+
onBarcodeScanned?: BubblingEventHandler<BarcodeScannedEvent>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default codegenNativeComponent<NativeProps>(
|
|
27
|
+
'ScannerView',
|
|
28
|
+
) as HostComponent<NativeProps>;
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import ScannerModule from './NativeScannerModule';
|
|
2
|
+
import type { CameraPermissionStatus } from './NativeScannerModule';
|
|
3
|
+
import ScannerViewNative from './ScannerViewNativeComponent';
|
|
4
|
+
import type { BarcodeScannedEvent } from './ScannerViewNativeComponent';
|
|
5
|
+
|
|
6
|
+
export const ScannerView = ScannerViewNative;
|
|
7
|
+
export type { BarcodeScannedEvent, CameraPermissionStatus };
|
|
8
|
+
|
|
9
|
+
export interface CameraPermissionState {
|
|
10
|
+
status: CameraPermissionStatus;
|
|
11
|
+
canRequest: boolean;
|
|
12
|
+
canOpenSettings: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isCameraPermissionGranted(
|
|
16
|
+
status: CameraPermissionStatus,
|
|
17
|
+
): boolean {
|
|
18
|
+
return status === 'granted';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function canRequestCameraPermission(
|
|
22
|
+
status: CameraPermissionStatus,
|
|
23
|
+
): boolean {
|
|
24
|
+
return status === 'not-determined' || status === 'denied';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function shouldOpenCameraSettings(
|
|
28
|
+
status: CameraPermissionStatus,
|
|
29
|
+
): boolean {
|
|
30
|
+
return status === 'blocked' || status === 'restricted';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function getCameraPermissionState(): Promise<CameraPermissionState> {
|
|
34
|
+
const status = await ScannerModule.checkCameraPermission();
|
|
35
|
+
return {
|
|
36
|
+
status,
|
|
37
|
+
canRequest: canRequestCameraPermission(status),
|
|
38
|
+
canOpenSettings: shouldOpenCameraSettings(status),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function checkCameraPermission(): Promise<CameraPermissionStatus> {
|
|
43
|
+
return ScannerModule.checkCameraPermission();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function requestCameraPermission(): Promise<CameraPermissionStatus> {
|
|
47
|
+
const current = await ScannerModule.checkCameraPermission();
|
|
48
|
+
|
|
49
|
+
if (current === 'granted') {
|
|
50
|
+
return 'granted';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!canRequestCameraPermission(current)) {
|
|
54
|
+
return current;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return ScannerModule.requestCameraPermission();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function openCameraSettings(): void {
|
|
61
|
+
ScannerModule.openCameraSettings();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function setTorchEnabled(enabled: boolean): void {
|
|
65
|
+
ScannerModule.setTorchEnabled(enabled);
|
|
66
|
+
}
|