raxon-barcode-scanner 0.1.0 → 0.1.2
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/build/RaxonBarcodeScannerModule.web.js +1 -1
- package/build/RaxonBarcodeScannerModule.web.js.map +1 -1
- package/expo-module.config.json +4 -1
- package/ios/RaxonBarcodeScanner-Bridging-Header.h +1 -0
- package/ios/RaxonBarcodeScanner.podspec +21 -0
- package/ios/RaxonBarcodeScannerModule.swift +328 -0
- package/package.json +6 -1
- package/src/RaxonBarcodeScannerModule.web.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { registerWebModule, NativeModule } from 'expo';
|
|
2
2
|
class RaxonBarcodeScannerModule extends NativeModule {
|
|
3
3
|
startListening(_options) {
|
|
4
|
-
console.warn('raxon-barcode-scanner is only supported on Android.');
|
|
4
|
+
console.warn('raxon-barcode-scanner is only supported on iOS and Android.');
|
|
5
5
|
}
|
|
6
6
|
stopListening() {
|
|
7
7
|
// no-op
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RaxonBarcodeScannerModule.web.js","sourceRoot":"","sources":["../src/RaxonBarcodeScannerModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAOvD,MAAM,yBAA0B,SAAQ,YAA6C;IACnF,cAAc,CAAC,QAAgC;QAC7C,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"RaxonBarcodeScannerModule.web.js","sourceRoot":"","sources":["../src/RaxonBarcodeScannerModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAOvD,MAAM,yBAA0B,SAAQ,YAA6C;IACnF,cAAc,CAAC,QAAgC;QAC7C,OAAO,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IAC9E,CAAC;IAED,aAAa;QACX,QAAQ;IACV,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,yBAAyB,EAAE,qBAAqB,CAAC,CAAC","sourcesContent":["import { registerWebModule, NativeModule } from 'expo';\n\nimport {\n BarcodeScannerOptions,\n RaxonBarcodeScannerModuleEvents,\n} from './RaxonBarcodeScanner.types';\n\nclass RaxonBarcodeScannerModule extends NativeModule<RaxonBarcodeScannerModuleEvents> {\n startListening(_options?: BarcodeScannerOptions): void {\n console.warn('raxon-barcode-scanner is only supported on iOS and Android.');\n }\n\n stopListening(): void {\n // no-op\n }\n}\n\nexport default registerWebModule(RaxonBarcodeScannerModule, 'RaxonBarcodeScanner');\n"]}
|
package/expo-module.config.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#import <ExpoModulesCore/ExpoModulesCore.h>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'RaxonBarcodeScanner'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.description = package['description']
|
|
10
|
+
s.license = package['license']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.homepage = package['homepage']
|
|
13
|
+
s.platform = :ios, '13.4'
|
|
14
|
+
s.swift_version = '5.4'
|
|
15
|
+
s.source = { git: 'https://github.com/raxonltd/raxon-barcode-scanner.git' }
|
|
16
|
+
s.static_framework = true
|
|
17
|
+
|
|
18
|
+
s.dependency 'ExpoModulesCore'
|
|
19
|
+
|
|
20
|
+
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
|
21
|
+
end
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
// MARK: - RaxonBarcodeScannerModule
|
|
5
|
+
|
|
6
|
+
/// iOS için Bluetooth ve HID barkod okuyucu desteği sağlayan Expo modülü.
|
|
7
|
+
/// Harici klavye olarak davranan Bluetooth barkod okuyucuların tuş vuruşlarını yakalar.
|
|
8
|
+
public final class RaxonBarcodeScannerModule: Module {
|
|
9
|
+
private var isListening = false
|
|
10
|
+
private var captureKeyboard = true
|
|
11
|
+
private var keyboardHandler: ExternalKeyboardHandler?
|
|
12
|
+
|
|
13
|
+
public required init(appContext: AppContext) {
|
|
14
|
+
super.init(appContext: appContext)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public func definition() -> ModuleDefinition {
|
|
18
|
+
Name("RaxonBarcodeScanner")
|
|
19
|
+
|
|
20
|
+
Events("onBarcodeScanned")
|
|
21
|
+
|
|
22
|
+
Function("startListening") { (options: [String: Any]?) in
|
|
23
|
+
self.startListening(options: options)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Function("stopListening") {
|
|
27
|
+
self.stopListening()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
OnDestroy {
|
|
31
|
+
self.stopListening()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private func startListening(options: [String: Any]?) {
|
|
36
|
+
guard !isListening else {
|
|
37
|
+
stopListening()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
captureKeyboard = options?["captureKeyboard"] as? Bool ?? true
|
|
41
|
+
|
|
42
|
+
if captureKeyboard {
|
|
43
|
+
setupKeyboardCapture()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
isListening = true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private func stopListening() {
|
|
50
|
+
teardownKeyboardCapture()
|
|
51
|
+
isListening = false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private func setupKeyboardCapture() {
|
|
55
|
+
guard let viewController = appContext?.currentViewController else {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if #available(iOS 13.4, *) {
|
|
60
|
+
keyboardHandler = ExternalKeyboardHandler(
|
|
61
|
+
viewController: viewController,
|
|
62
|
+
onBarcodeScanned: { [weak self] code in
|
|
63
|
+
self?.sendEvent("onBarcodeScanned", ["code": code])
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
keyboardHandler?.startListening()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private func teardownKeyboardCapture() {
|
|
71
|
+
keyboardHandler?.stopListening()
|
|
72
|
+
keyboardHandler = nil
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// MARK: - ExternalKeyboardHandler
|
|
77
|
+
|
|
78
|
+
/// iOS 13.4+ için harici klavye (Bluetooth barkod okuyucu) girişini yakalayan sınıf.
|
|
79
|
+
/// pressesBegan/pressesEnded API'sini kullanarak fiziksel klavye tuşlarını yakalar.
|
|
80
|
+
@available(iOS 13.4, *)
|
|
81
|
+
private final class ExternalKeyboardHandler {
|
|
82
|
+
private weak var viewController: UIViewController?
|
|
83
|
+
private let onBarcodeScanned: (String) -> Void
|
|
84
|
+
private var keyBuffer: String = ""
|
|
85
|
+
private var lastKeyTime: TimeInterval = 0
|
|
86
|
+
private let bufferTimeout: TimeInterval = 1.0
|
|
87
|
+
|
|
88
|
+
// Terminatör tuş kodları
|
|
89
|
+
private let terminatorKeyCodes: Set<Int> = [
|
|
90
|
+
36, // Return/Enter
|
|
91
|
+
76, // Numpad Enter
|
|
92
|
+
48, // Tab
|
|
93
|
+
52, // Numpad Equal (bazı okuyucularda)
|
|
94
|
+
67, // Numpad Asterisk (bazı okuyucularda)
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
init(viewController: UIViewController, onBarcodeScanned: @escaping (String) -> Void) {
|
|
98
|
+
self.viewController = viewController
|
|
99
|
+
self.onBarcodeScanned = onBarcodeScanned
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func startListening() {
|
|
103
|
+
// pressesBegan/pressesEnded metodlarını sarmalamak için swizzling kullanacağız
|
|
104
|
+
swizzlePressesMethods()
|
|
105
|
+
|
|
106
|
+
// Ayrıca UIResponder chain'den gelen tuş olaylarını da dinle
|
|
107
|
+
setupKeyCommandHandling()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func stopListening() {
|
|
111
|
+
unswizzlePressesMethods()
|
|
112
|
+
keyBuffer = ""
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private func setupKeyCommandHandling() {
|
|
116
|
+
// UIKeyCommand kullanarak bazı özel tuşları yakalayabiliriz
|
|
117
|
+
// Ancak tüm tuşları yakalamak için pressesBegan/pressesEnded daha iyi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// MARK: - Tuş İşleme
|
|
121
|
+
|
|
122
|
+
func handlePress(_ press: UIPress) -> Bool {
|
|
123
|
+
guard let key = press.key else { return false }
|
|
124
|
+
|
|
125
|
+
let keyCode = key.keyCode.rawValue
|
|
126
|
+
let now = Date().timeIntervalSince1970
|
|
127
|
+
|
|
128
|
+
// Zaman aşımı kontrolü - tuşlar arası 1 saniyeden fazla varsa tamponu sıfırla
|
|
129
|
+
if now - lastKeyTime > bufferTimeout {
|
|
130
|
+
keyBuffer = ""
|
|
131
|
+
}
|
|
132
|
+
lastKeyTime = now
|
|
133
|
+
|
|
134
|
+
// Terminatör tuş kontrolü
|
|
135
|
+
if isTerminator(keyCode) {
|
|
136
|
+
if !keyBuffer.isEmpty {
|
|
137
|
+
let code = keyBuffer
|
|
138
|
+
keyBuffer = ""
|
|
139
|
+
onBarcodeScanned(code)
|
|
140
|
+
return true // Tuşu yut
|
|
141
|
+
}
|
|
142
|
+
// Tampon boşken terminatörü normal davranışa bırak
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Karakter tuşu mu?
|
|
147
|
+
if let char = characterFromKey(key) {
|
|
148
|
+
keyBuffer.append(char)
|
|
149
|
+
return true // Tuşu yut (UI'ya ulaşmasını engelle)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private func isTerminator(_ keyCode: Int) -> Bool {
|
|
156
|
+
return terminatorKeyCodes.contains(keyCode)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private func characterFromKey(_ key: UIKey) -> Character? {
|
|
160
|
+
// Karakter önceliği:
|
|
161
|
+
// 1. modifiersiz karakter (temel ASCII)
|
|
162
|
+
// 2. modifiers varsa ve shift varsa büyük harf
|
|
163
|
+
|
|
164
|
+
var char: Character?
|
|
165
|
+
|
|
166
|
+
// UIKey.characters ile dene
|
|
167
|
+
if let chars = key.characters, !chars.isEmpty {
|
|
168
|
+
char = chars.first
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// UIKey.charactersIgnoringModifiers ile dene (Shift/Ctrl/Alt olmadan)
|
|
172
|
+
if char == nil, let chars = key.charactersIgnoringModifiers, !chars.isEmpty {
|
|
173
|
+
char = chars.first
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Eğer hala nil ise, keyCode'dan çevir
|
|
177
|
+
if char == nil {
|
|
178
|
+
char = characterFromKeyCode(key.keyCode.rawValue)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return char
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private func characterFromKeyCode(_ keyCode: Int) -> Character? {
|
|
185
|
+
// iOS fiziksel klavye key kodlarından karakter dönüşümü
|
|
186
|
+
// Bluetooth barkod okuyucular genellikle standart USB HID key kodları kullanır
|
|
187
|
+
let mapping: [Int: Character] = [
|
|
188
|
+
// Rakamlar (ana klavye)
|
|
189
|
+
23: "0", 22: "1", 26: "2", 20: "3", 25: "4",
|
|
190
|
+
29: "5", 27: "6", 24: "7", 28: "8", 21: "9",
|
|
191
|
+
|
|
192
|
+
// Numpad rakamlar
|
|
193
|
+
98: "0", 89: "1", 90: "2", 91: "3", 92: "4",
|
|
194
|
+
93: "5", 94: "6", 95: "7", 96: "8", 97: "9",
|
|
195
|
+
|
|
196
|
+
// Harfler (ana klavye)
|
|
197
|
+
12: "q", 13: "w", 14: "e", 15: "r", 17: "t",
|
|
198
|
+
16: "y", 32: "u", 34: "i", 31: "o", 35: "p",
|
|
199
|
+
0: "a", 1: "s", 2: "d", 3: "f", 5: "g",
|
|
200
|
+
4: "h", 38: "k", 37: "j", 40: "l", 6: "z",
|
|
201
|
+
7: "x", 8: "c", 9: "v", 11: "b", 45: "n",
|
|
202
|
+
46: "m",
|
|
203
|
+
|
|
204
|
+
// Özel karakterler
|
|
205
|
+
51: "\u{0008}", // Backspace (silme)
|
|
206
|
+
117: "\u{007F}", // Delete
|
|
207
|
+
42: ",", 43: "-", 44: ".", 47: "/",
|
|
208
|
+
39: "'", 33: "[", 30: "]", 41: ";", 50: "`",
|
|
209
|
+
115: "\u{001B}", // Escape
|
|
210
|
+
|
|
211
|
+
// Boşluk
|
|
212
|
+
49: " ",
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
return mapping[keyCode]
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// MARK: - Method Swizzling
|
|
219
|
+
|
|
220
|
+
private func swizzlePressesMethods() {
|
|
221
|
+
guard let viewController = viewController else { return }
|
|
222
|
+
|
|
223
|
+
let originalPressesBeganSelector = #selector(UIViewController.pressesBegan(_:with:))
|
|
224
|
+
let swizzledPressesBeganSelector = #selector(UIViewController.raxon_pressesBegan(_:with:))
|
|
225
|
+
|
|
226
|
+
let originalPressesEndedSelector = #selector(UIViewController.pressesEnded(_:with:))
|
|
227
|
+
let swizzledPressesEndedSelector = #selector(UIViewController.raxon_pressesEnded(_:with:))
|
|
228
|
+
|
|
229
|
+
swizzleMethod(
|
|
230
|
+
for: UIViewController.self,
|
|
231
|
+
originalSelector: originalPressesBeganSelector,
|
|
232
|
+
swizzledSelector: swizzledPressesBeganSelector
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
swizzleMethod(
|
|
236
|
+
for: UIViewController.self,
|
|
237
|
+
originalSelector: originalPressesEndedSelector,
|
|
238
|
+
swizzledSelector: swizzledPressesEndedSelector
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
// Bu view controller'a weak referans ile handler'a erişim sağla
|
|
242
|
+
objc_setAssociatedObject(
|
|
243
|
+
viewController,
|
|
244
|
+
&AssociatedKeys.keyboardHandler,
|
|
245
|
+
self,
|
|
246
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private func unswizzlePressesMethods() {
|
|
251
|
+
// Method swizzling geri alınamaz ama handler'ı temizleyebiliriz
|
|
252
|
+
if let viewController = viewController {
|
|
253
|
+
objc_setAssociatedObject(
|
|
254
|
+
viewController,
|
|
255
|
+
&AssociatedKeys.keyboardHandler,
|
|
256
|
+
nil,
|
|
257
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private func swizzleMethod(
|
|
263
|
+
for classType: AnyClass,
|
|
264
|
+
originalSelector: Selector,
|
|
265
|
+
swizzledSelector: Selector
|
|
266
|
+
) {
|
|
267
|
+
guard let originalMethod = class_getInstanceMethod(classType, originalSelector),
|
|
268
|
+
let swizzledMethod = class_getInstanceMethod(classType, swizzledSelector) else {
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let didAddMethod = class_addMethod(
|
|
273
|
+
classType,
|
|
274
|
+
originalSelector,
|
|
275
|
+
method_getImplementation(swizzledMethod),
|
|
276
|
+
method_getTypeEncoding(swizzledMethod)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
if didAddMethod {
|
|
280
|
+
class_replaceMethod(
|
|
281
|
+
classType,
|
|
282
|
+
swizzledSelector,
|
|
283
|
+
method_getImplementation(originalMethod),
|
|
284
|
+
method_getTypeEncoding(originalMethod)
|
|
285
|
+
)
|
|
286
|
+
} else {
|
|
287
|
+
method_exchangeImplementations(originalMethod, swizzledMethod)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// MARK: - AssociatedKeys
|
|
293
|
+
|
|
294
|
+
private enum AssociatedKeys {
|
|
295
|
+
static var keyboardHandler: UInt8 = 0
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// MARK: - UIViewController Extension
|
|
299
|
+
|
|
300
|
+
@available(iOS 13.4, *)
|
|
301
|
+
extension UIViewController {
|
|
302
|
+
@objc dynamic func raxon_pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
|
303
|
+
// Orijinal implementasyonu çağır (swizzling yüzünden bu aslında orijinal)
|
|
304
|
+
raxon_pressesBegan(presses, with: event)
|
|
305
|
+
|
|
306
|
+
// Handler varsa tuşları işle
|
|
307
|
+
if let handler = objc_getAssociatedObject(self, &AssociatedKeys.keyboardHandler)
|
|
308
|
+
as? ExternalKeyboardHandler {
|
|
309
|
+
for press in presses {
|
|
310
|
+
_ = handler.handlePress(press)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@objc dynamic func raxon_pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
|
316
|
+
// Orijinal implementasyonu çağır
|
|
317
|
+
raxon_pressesEnded(presses, with: event)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// MARK: - UIKey Extensions
|
|
322
|
+
|
|
323
|
+
@available(iOS 13.4, *)
|
|
324
|
+
extension UIKey {
|
|
325
|
+
var keyCode: UIKeyboardHIDUsage {
|
|
326
|
+
return self.keyCode
|
|
327
|
+
}
|
|
328
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "raxon-barcode-scanner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Zebra and enterprise barcode scanner hook for React Native Expo apps",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"android/build.gradle",
|
|
39
39
|
"android/src",
|
|
40
|
+
"ios/**/*.h",
|
|
41
|
+
"ios/**/*.m",
|
|
42
|
+
"ios/**/*.mm",
|
|
43
|
+
"ios/**/*.swift",
|
|
44
|
+
"ios/*.podspec",
|
|
40
45
|
"build",
|
|
41
46
|
"expo-module.config.json",
|
|
42
47
|
"src"
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
class RaxonBarcodeScannerModule extends NativeModule<RaxonBarcodeScannerModuleEvents> {
|
|
9
9
|
startListening(_options?: BarcodeScannerOptions): void {
|
|
10
|
-
console.warn('raxon-barcode-scanner is only supported on Android.');
|
|
10
|
+
console.warn('raxon-barcode-scanner is only supported on iOS and Android.');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
stopListening(): void {
|