@unif/react-native-hms-scan 0.1.0 → 0.2.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/README.md +13 -114
- package/ReactNativeHmsScan.podspec +14 -6
- package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanModule.kt +38 -28
- package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanResultMapper.kt +64 -58
- package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanView.kt +54 -26
- package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanViewManager.kt +21 -12
- package/android/src/main/java/com/unif/reactnativehmsscan/ReactNativeHmsScanPackage.kt +17 -21
- package/ios/HmsScanModule.mm +26 -40
- package/ios/HmsScanResultMapper.mm +97 -83
- package/ios/HmsScanView.mm +23 -40
- package/lib/module/index.js +2 -2
- package/package.json +32 -6
- package/src/index.tsx +2 -2
|
@@ -5,8 +5,8 @@ import com.facebook.react.uimanager.SimpleViewManager
|
|
|
5
5
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
6
|
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
7
7
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
8
|
-
import com.facebook.react.viewmanagers.HmsScanViewManagerInterface
|
|
9
8
|
import com.facebook.react.viewmanagers.HmsScanViewManagerDelegate
|
|
9
|
+
import com.facebook.react.viewmanagers.HmsScanViewManagerInterface
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Fabric ViewManager for "HmsScanView". Delegates prop dispatch to the
|
|
@@ -17,7 +17,6 @@ import com.facebook.react.viewmanagers.HmsScanViewManagerDelegate
|
|
|
17
17
|
class HmsScanViewManager :
|
|
18
18
|
SimpleViewManager<HmsScanView>(),
|
|
19
19
|
HmsScanViewManagerInterface<HmsScanView> {
|
|
20
|
-
|
|
21
20
|
private val mDelegate: ViewManagerDelegate<HmsScanView> =
|
|
22
21
|
HmsScanViewManagerDelegate(this)
|
|
23
22
|
|
|
@@ -25,26 +24,37 @@ class HmsScanViewManager :
|
|
|
25
24
|
|
|
26
25
|
override fun getName(): String = NAME
|
|
27
26
|
|
|
28
|
-
public override fun createViewInstance(context: ThemedReactContext): HmsScanView =
|
|
29
|
-
HmsScanView(context)
|
|
27
|
+
public override fun createViewInstance(context: ThemedReactContext): HmsScanView = HmsScanView(context)
|
|
30
28
|
|
|
31
29
|
@ReactProp(name = "formatsCsv")
|
|
32
|
-
override fun setFormatsCsv(
|
|
30
|
+
override fun setFormatsCsv(
|
|
31
|
+
view: HmsScanView?,
|
|
32
|
+
value: String?,
|
|
33
|
+
) {
|
|
33
34
|
view?.setFormatsCsv(value)
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
@ReactProp(name = "continuous", defaultBoolean = true)
|
|
37
|
-
override fun setContinuous(
|
|
38
|
+
override fun setContinuous(
|
|
39
|
+
view: HmsScanView?,
|
|
40
|
+
value: Boolean,
|
|
41
|
+
) {
|
|
38
42
|
view?.setContinuous(value)
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
@ReactProp(name = "paused", defaultBoolean = false)
|
|
42
|
-
override fun setPaused(
|
|
46
|
+
override fun setPaused(
|
|
47
|
+
view: HmsScanView?,
|
|
48
|
+
value: Boolean,
|
|
49
|
+
) {
|
|
43
50
|
view?.setPaused(value)
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
@ReactProp(name = "torch", defaultBoolean = false)
|
|
47
|
-
override fun setTorch(
|
|
54
|
+
override fun setTorch(
|
|
55
|
+
view: HmsScanView?,
|
|
56
|
+
value: Boolean,
|
|
57
|
+
) {
|
|
48
58
|
view?.setTorch(value)
|
|
49
59
|
}
|
|
50
60
|
|
|
@@ -52,13 +62,12 @@ class HmsScanViewManager :
|
|
|
52
62
|
* Map internal Fabric event names to the JS prop callback names declared in
|
|
53
63
|
* HmsScanViewNativeComponent.ts.
|
|
54
64
|
*/
|
|
55
|
-
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>
|
|
56
|
-
|
|
65
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> =
|
|
66
|
+
mapOf(
|
|
57
67
|
"topScanResult" to mapOf("registrationName" to "onScanResult"),
|
|
58
68
|
"topScanError" to mapOf("registrationName" to "onScanError"),
|
|
59
|
-
"topTorchStatus" to mapOf("registrationName" to "onTorchStatus")
|
|
69
|
+
"topTorchStatus" to mapOf("registrationName" to "onTorchStatus"),
|
|
60
70
|
)
|
|
61
|
-
}
|
|
62
71
|
|
|
63
72
|
companion object {
|
|
64
73
|
const val NAME = "HmsScanView"
|
|
@@ -16,34 +16,30 @@ import com.facebook.react.uimanager.ViewManager
|
|
|
16
16
|
* getReactModuleInfoProvider,视图走 createViewManagers + listOf(ViewManager)。
|
|
17
17
|
*/
|
|
18
18
|
class ReactNativeHmsScanPackage : BaseReactPackage() {
|
|
19
|
-
|
|
20
19
|
override fun getModule(
|
|
21
20
|
name: String,
|
|
22
|
-
reactContext: ReactApplicationContext
|
|
23
|
-
): NativeModule?
|
|
24
|
-
|
|
21
|
+
reactContext: ReactApplicationContext,
|
|
22
|
+
): NativeModule? =
|
|
23
|
+
if (name == HmsScanModule.NAME) {
|
|
25
24
|
HmsScanModule(reactContext)
|
|
26
25
|
} else {
|
|
27
26
|
null
|
|
28
27
|
}
|
|
29
|
-
}
|
|
30
28
|
|
|
31
|
-
override fun getReactModuleInfoProvider() =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
override fun getReactModuleInfoProvider() =
|
|
30
|
+
ReactModuleInfoProvider {
|
|
31
|
+
mapOf(
|
|
32
|
+
HmsScanModule.NAME to
|
|
33
|
+
ReactModuleInfo(
|
|
34
|
+
name = HmsScanModule.NAME,
|
|
35
|
+
className = HmsScanModule.NAME,
|
|
36
|
+
canOverrideExistingModule = false,
|
|
37
|
+
needsEagerInit = false,
|
|
38
|
+
isCxxModule = false,
|
|
39
|
+
isTurboModule = true,
|
|
40
|
+
),
|
|
40
41
|
)
|
|
41
|
-
|
|
42
|
-
}
|
|
42
|
+
}
|
|
43
43
|
|
|
44
|
-
override fun createViewManagers(
|
|
45
|
-
reactContext: ReactApplicationContext
|
|
46
|
-
): List<ViewManager<*, *>> {
|
|
47
|
-
return listOf(HmsScanViewManager())
|
|
48
|
-
}
|
|
44
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = listOf(HmsScanViewManager())
|
|
49
45
|
}
|
package/ios/HmsScanModule.mm
CHANGED
|
@@ -24,34 +24,27 @@ RCT_EXPORT_MODULE(HmsScan)
|
|
|
24
24
|
|
|
25
25
|
// AVAuthorizationStatus -> our CameraPermissionStatus string.
|
|
26
26
|
// authorized -> granted; notDetermined -> undetermined; denied/restricted -> blocked.
|
|
27
|
-
+ (NSString *)stringForAuthorizationStatus:(AVAuthorizationStatus)status
|
|
28
|
-
{
|
|
27
|
+
+ (NSString *)stringForAuthorizationStatus:(AVAuthorizationStatus)status {
|
|
29
28
|
switch (status) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
case AVAuthorizationStatusAuthorized:
|
|
30
|
+
return @"granted";
|
|
31
|
+
case AVAuthorizationStatusNotDetermined:
|
|
32
|
+
return @"undetermined";
|
|
33
|
+
case AVAuthorizationStatusDenied:
|
|
34
|
+
case AVAuthorizationStatusRestricted:
|
|
35
|
+
return @"blocked";
|
|
36
|
+
default:
|
|
37
|
+
return @"undetermined";
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
- (void)getCameraPermissionStatus:(RCTPromiseResolveBlock)resolve
|
|
43
|
-
|
|
44
|
-
{
|
|
45
|
-
AVAuthorizationStatus status =
|
|
46
|
-
[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
|
41
|
+
- (void)getCameraPermissionStatus:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
42
|
+
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
|
47
43
|
resolve([HmsScanModule stringForAuthorizationStatus:status]);
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
- (void)requestCameraPermission:(RCTPromiseResolveBlock)resolve
|
|
51
|
-
|
|
52
|
-
{
|
|
53
|
-
AVAuthorizationStatus current =
|
|
54
|
-
[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
|
46
|
+
- (void)requestCameraPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
47
|
+
AVAuthorizationStatus current = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
|
55
48
|
|
|
56
49
|
// Only notDetermined can present the system prompt. For an already-resolved
|
|
57
50
|
// state, return it directly (requestAccess would still call back immediately,
|
|
@@ -63,12 +56,12 @@ RCT_EXPORT_MODULE(HmsScan)
|
|
|
63
56
|
|
|
64
57
|
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
|
|
65
58
|
completionHandler:^(BOOL granted) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
// Re-read the authoritative status rather than inferring from `granted`,
|
|
60
|
+
// so restricted/denied are reported correctly.
|
|
61
|
+
AVAuthorizationStatus updated =
|
|
62
|
+
[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
|
63
|
+
resolve([HmsScanModule stringForAuthorizationStatus:updated]);
|
|
64
|
+
}];
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
#pragma mark - Image loading
|
|
@@ -76,8 +69,7 @@ RCT_EXPORT_MODULE(HmsScan)
|
|
|
76
69
|
// Load a UIImage from a local URI: file:// , an absolute/relative file path,
|
|
77
70
|
// or a data: URI. Remote URLs and Photos (ph:// / assets-library://) are not
|
|
78
71
|
// supported here -> returns nil so the caller rejects E_IMAGE_LOAD_FAILED.
|
|
79
|
-
+ (nullable UIImage *)imageForURI:(NSString *)uri
|
|
80
|
-
{
|
|
72
|
+
+ (nullable UIImage *)imageForURI:(NSString *)uri {
|
|
81
73
|
if (uri.length == 0) {
|
|
82
74
|
return nil;
|
|
83
75
|
}
|
|
@@ -121,13 +113,11 @@ RCT_EXPORT_MODULE(HmsScan)
|
|
|
121
113
|
- (void)decodeImage:(NSString *)uri
|
|
122
114
|
formatsCsv:(NSString *)formatsCsv
|
|
123
115
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
124
|
-
reject:(RCTPromiseRejectBlock)reject
|
|
125
|
-
{
|
|
116
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
126
117
|
UIImage *image = [HmsScanModule imageForURI:uri];
|
|
127
118
|
if (image == nil) {
|
|
128
119
|
reject(kErrImageLoadFailed,
|
|
129
|
-
[NSString stringWithFormat:@"无法从 URI 加载图片(仅支持 file:// / 绝对路径 / data:):%@",
|
|
130
|
-
uri ?: @"(nil)"],
|
|
120
|
+
[NSString stringWithFormat:@"无法从 URI 加载图片(仅支持 file:// / 绝对路径 / data:):%@", uri ?: @"(nil)"],
|
|
131
121
|
nil);
|
|
132
122
|
return;
|
|
133
123
|
}
|
|
@@ -135,8 +125,7 @@ RCT_EXPORT_MODULE(HmsScan)
|
|
|
135
125
|
@try {
|
|
136
126
|
unsigned int formatType = [HmsScanResultMapper scanFormatTypeFromCsv:formatsCsv];
|
|
137
127
|
// photoMode/Photo=YES: still-image (album) decode path, per HUAWEI guidance.
|
|
138
|
-
HmsScanOptions *options =
|
|
139
|
-
[[HmsScanOptions alloc] initWithScanFormatType:formatType Photo:YES];
|
|
128
|
+
HmsScanOptions *options = [[HmsScanOptions alloc] initWithScanFormatType:formatType Photo:YES];
|
|
140
129
|
|
|
141
130
|
NSArray *raw = [HmsBitMap multiDecodeBitMapForImage:image withOptions:options];
|
|
142
131
|
NSString *json = [HmsScanResultMapper jsonStringFromHuaweiArray:raw];
|
|
@@ -144,17 +133,14 @@ RCT_EXPORT_MODULE(HmsScan)
|
|
|
144
133
|
// JS parseResultsJson handles the empty case.
|
|
145
134
|
resolve(json);
|
|
146
135
|
} @catch (NSException *exception) {
|
|
147
|
-
reject(kErrDecodeFailed,
|
|
148
|
-
exception.reason ?: @"HUAWEI Scan Kit 解码图片时发生异常",
|
|
149
|
-
nil);
|
|
136
|
+
reject(kErrDecodeFailed, exception.reason ?: @"HUAWEI Scan Kit 解码图片时发生异常", nil);
|
|
150
137
|
}
|
|
151
138
|
}
|
|
152
139
|
|
|
153
140
|
#pragma mark - TurboModule
|
|
154
141
|
|
|
155
142
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
156
|
-
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
157
|
-
{
|
|
143
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
158
144
|
return std::make_shared<facebook::react::NativeHmsScanSpecJSI>(params);
|
|
159
145
|
}
|
|
160
146
|
|
|
@@ -20,10 +20,9 @@
|
|
|
20
20
|
// Maps one of our BarcodeFormat tokens to the matching HMSScanFormatTypeCode bit.
|
|
21
21
|
// Returns 0 for tokens HUAWEI iOS does not support (e.g. MULTI_FUNCTIONAL) or
|
|
22
22
|
// UNKNOWN, so they contribute nothing to the OR-ed mask.
|
|
23
|
-
+ (unsigned int)bitForFormatToken:(NSString *)token
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
|
|
23
|
+
+ (unsigned int)bitForFormatToken:(NSString *)token {
|
|
24
|
+
NSString *t =
|
|
25
|
+
[[token stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
|
|
27
26
|
if (t.length == 0) {
|
|
28
27
|
return 0;
|
|
29
28
|
}
|
|
@@ -32,20 +31,20 @@
|
|
|
32
31
|
static dispatch_once_t onceToken;
|
|
33
32
|
dispatch_once(&onceToken, ^{
|
|
34
33
|
map = @{
|
|
35
|
-
@"QR_CODE": @(QR_CODE),
|
|
36
|
-
@"AZTEC": @(AZTEC),
|
|
37
|
-
@"DATA_MATRIX": @(DATA_MATRIX),
|
|
38
|
-
@"PDF417": @(PDF_417),
|
|
39
|
-
@"CODABAR": @(CODABAR),
|
|
40
|
-
@"CODE_39": @(CODE_39),
|
|
41
|
-
@"CODE_93": @(CODE_93),
|
|
42
|
-
@"CODE_128": @(CODE_128),
|
|
43
|
-
@"EAN_8": @(EAN_8),
|
|
44
|
-
@"EAN_13": @(EAN_13),
|
|
45
|
-
@"UPC_A": @(UPC_A),
|
|
46
|
-
@"UPC_E": @(UPC_E),
|
|
34
|
+
@"QR_CODE" : @(QR_CODE),
|
|
35
|
+
@"AZTEC" : @(AZTEC),
|
|
36
|
+
@"DATA_MATRIX" : @(DATA_MATRIX),
|
|
37
|
+
@"PDF417" : @(PDF_417),
|
|
38
|
+
@"CODABAR" : @(CODABAR),
|
|
39
|
+
@"CODE_39" : @(CODE_39),
|
|
40
|
+
@"CODE_93" : @(CODE_93),
|
|
41
|
+
@"CODE_128" : @(CODE_128),
|
|
42
|
+
@"EAN_8" : @(EAN_8),
|
|
43
|
+
@"EAN_13" : @(EAN_13),
|
|
44
|
+
@"UPC_A" : @(UPC_A),
|
|
45
|
+
@"UPC_E" : @(UPC_E),
|
|
47
46
|
// Our ITF14 maps to HUAWEI's ITF bit.
|
|
48
|
-
@"ITF14": @(ITF),
|
|
47
|
+
@"ITF14" : @(ITF),
|
|
49
48
|
// MULTI_FUNCTIONAL / UNKNOWN have no HUAWEI iOS equivalent -> 0.
|
|
50
49
|
};
|
|
51
50
|
});
|
|
@@ -54,12 +53,10 @@
|
|
|
54
53
|
return bit ? (unsigned int)bit.unsignedIntValue : 0u;
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
+ (unsigned int)scanFormatTypeFromCsv:(NSString *)csv
|
|
58
|
-
|
|
59
|
-
NSString *trimmed = [csv stringByTrimmingCharactersInSet:
|
|
60
|
-
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
56
|
+
+ (unsigned int)scanFormatTypeFromCsv:(NSString *)csv {
|
|
57
|
+
NSString *trimmed = [csv stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
61
58
|
if (trimmed.length == 0) {
|
|
62
|
-
return (unsigned int)ALL;
|
|
59
|
+
return (unsigned int)ALL; // empty string == all formats
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
unsigned int mask = 0u;
|
|
@@ -77,16 +74,14 @@
|
|
|
77
74
|
// We tolerate it being delivered as an NSNumber (bit value) OR an NSString
|
|
78
75
|
// (either a numeric string or already a token like "QR_CODE"), since the
|
|
79
76
|
// public headers do not pin the type and demos have shown both shapes.
|
|
80
|
-
+ (NSString *)formatStringFromValue:(id)formatValue
|
|
81
|
-
{
|
|
77
|
+
+ (NSString *)formatStringFromValue:(id)formatValue {
|
|
82
78
|
// Case 1: numeric (NSNumber, or numeric NSString) -> match the enum bit.
|
|
83
79
|
NSInteger code = NSNotFound;
|
|
84
80
|
if ([formatValue isKindOfClass:[NSNumber class]]) {
|
|
85
81
|
code = [(NSNumber *)formatValue integerValue];
|
|
86
82
|
} else if ([formatValue isKindOfClass:[NSString class]]) {
|
|
87
83
|
NSString *s = (NSString *)formatValue;
|
|
88
|
-
NSString *trimmed = [s stringByTrimmingCharactersInSet:
|
|
89
|
-
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
84
|
+
NSString *trimmed = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
90
85
|
// Pure-integer string?
|
|
91
86
|
NSScanner *scanner = [NSScanner scannerWithString:trimmed];
|
|
92
87
|
NSInteger parsed = 0;
|
|
@@ -95,15 +90,28 @@
|
|
|
95
90
|
} else {
|
|
96
91
|
// Case 2: already a token string -> normalize known spellings.
|
|
97
92
|
NSString *upper = [trimmed uppercaseString];
|
|
98
|
-
if ([upper isEqualToString:@"PDF_417"])
|
|
99
|
-
|
|
93
|
+
if ([upper isEqualToString:@"PDF_417"])
|
|
94
|
+
return @"PDF417";
|
|
95
|
+
if ([upper isEqualToString:@"ITF"])
|
|
96
|
+
return @"ITF14";
|
|
100
97
|
static NSSet<NSString *> *known = nil;
|
|
101
98
|
static dispatch_once_t onceToken;
|
|
102
99
|
dispatch_once(&onceToken, ^{
|
|
103
100
|
known = [NSSet setWithArray:@[
|
|
104
|
-
@"QR_CODE",
|
|
105
|
-
@"
|
|
106
|
-
@"
|
|
101
|
+
@"QR_CODE",
|
|
102
|
+
@"AZTEC",
|
|
103
|
+
@"DATA_MATRIX",
|
|
104
|
+
@"PDF417",
|
|
105
|
+
@"CODABAR",
|
|
106
|
+
@"CODE_39",
|
|
107
|
+
@"CODE_93",
|
|
108
|
+
@"CODE_128",
|
|
109
|
+
@"EAN_8",
|
|
110
|
+
@"EAN_13",
|
|
111
|
+
@"UPC_A",
|
|
112
|
+
@"UPC_E",
|
|
113
|
+
@"ITF14",
|
|
114
|
+
@"MULTI_FUNCTIONAL",
|
|
107
115
|
]];
|
|
108
116
|
});
|
|
109
117
|
return [known containsObject:upper] ? upper : @"UNKNOWN";
|
|
@@ -114,20 +122,34 @@
|
|
|
114
122
|
|
|
115
123
|
// Map enum bit -> our string. Compare against the global enum constants.
|
|
116
124
|
switch (code) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
case QR_CODE:
|
|
126
|
+
return @"QR_CODE";
|
|
127
|
+
case AZTEC:
|
|
128
|
+
return @"AZTEC";
|
|
129
|
+
case DATA_MATRIX:
|
|
130
|
+
return @"DATA_MATRIX";
|
|
131
|
+
case PDF_417:
|
|
132
|
+
return @"PDF417";
|
|
133
|
+
case CODABAR:
|
|
134
|
+
return @"CODABAR";
|
|
135
|
+
case CODE_39:
|
|
136
|
+
return @"CODE_39";
|
|
137
|
+
case CODE_93:
|
|
138
|
+
return @"CODE_93";
|
|
139
|
+
case CODE_128:
|
|
140
|
+
return @"CODE_128";
|
|
141
|
+
case EAN_8:
|
|
142
|
+
return @"EAN_8";
|
|
143
|
+
case EAN_13:
|
|
144
|
+
return @"EAN_13";
|
|
145
|
+
case UPC_A:
|
|
146
|
+
return @"UPC_A";
|
|
147
|
+
case UPC_E:
|
|
148
|
+
return @"UPC_E";
|
|
149
|
+
case ITF:
|
|
150
|
+
return @"ITF14";
|
|
151
|
+
default:
|
|
152
|
+
return @"UNKNOWN";
|
|
131
153
|
}
|
|
132
154
|
}
|
|
133
155
|
|
|
@@ -136,12 +158,11 @@
|
|
|
136
158
|
// HUAWEI iOS does not publish a sceneType enum in the headers; precision is
|
|
137
159
|
// limited. We map a few well-known scene tokens/codes and otherwise return nil
|
|
138
160
|
// so `contentType` is omitted (the JS contract treats unknown as undefined).
|
|
139
|
-
+ (nullable NSString *)contentTypeFromSceneType:(id)sceneType
|
|
140
|
-
{
|
|
161
|
+
+ (nullable NSString *)contentTypeFromSceneType:(id)sceneType {
|
|
141
162
|
NSString *token = nil;
|
|
142
163
|
if ([sceneType isKindOfClass:[NSString class]]) {
|
|
143
|
-
token = [[(NSString *)sceneType stringByTrimmingCharactersInSet:
|
|
144
|
-
|
|
164
|
+
token = [[(NSString *)sceneType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]
|
|
165
|
+
uppercaseString];
|
|
145
166
|
} else if ([sceneType isKindOfClass:[NSNumber class]]) {
|
|
146
167
|
// No documented numeric scene mapping -> leave unknown.
|
|
147
168
|
return nil;
|
|
@@ -156,32 +177,31 @@
|
|
|
156
177
|
static dispatch_once_t onceToken;
|
|
157
178
|
dispatch_once(&onceToken, ^{
|
|
158
179
|
map = @{
|
|
159
|
-
@"TEXT": @"TEXT",
|
|
160
|
-
@"URL": @"URL",
|
|
161
|
-
@"URI": @"URL",
|
|
162
|
-
@"EMAIL": @"EMAIL",
|
|
163
|
-
@"PHONE": @"PHONE",
|
|
164
|
-
@"TEL": @"PHONE",
|
|
165
|
-
@"SMS": @"SMS",
|
|
166
|
-
@"WIFI": @"WIFI",
|
|
167
|
-
@"CONTACT": @"CONTACT",
|
|
168
|
-
@"CONTACT_INFO": @"CONTACT",
|
|
169
|
-
@"EVENT": @"EVENT",
|
|
170
|
-
@"LOCATION": @"LOCATION",
|
|
171
|
-
@"GEO": @"LOCATION",
|
|
172
|
-
@"DRIVER": @"DRIVER",
|
|
173
|
-
@"ISBN": @"ISBN",
|
|
174
|
-
@"ARTICLE": @"ARTICLE",
|
|
175
|
-
@"PRODUCT": @"ARTICLE",
|
|
180
|
+
@"TEXT" : @"TEXT",
|
|
181
|
+
@"URL" : @"URL",
|
|
182
|
+
@"URI" : @"URL",
|
|
183
|
+
@"EMAIL" : @"EMAIL",
|
|
184
|
+
@"PHONE" : @"PHONE",
|
|
185
|
+
@"TEL" : @"PHONE",
|
|
186
|
+
@"SMS" : @"SMS",
|
|
187
|
+
@"WIFI" : @"WIFI",
|
|
188
|
+
@"CONTACT" : @"CONTACT",
|
|
189
|
+
@"CONTACT_INFO" : @"CONTACT",
|
|
190
|
+
@"EVENT" : @"EVENT",
|
|
191
|
+
@"LOCATION" : @"LOCATION",
|
|
192
|
+
@"GEO" : @"LOCATION",
|
|
193
|
+
@"DRIVER" : @"DRIVER",
|
|
194
|
+
@"ISBN" : @"ISBN",
|
|
195
|
+
@"ARTICLE" : @"ARTICLE",
|
|
196
|
+
@"PRODUCT" : @"ARTICLE",
|
|
176
197
|
};
|
|
177
198
|
});
|
|
178
|
-
return map[token];
|
|
199
|
+
return map[token]; // nil when unrecognized -> contentType omitted
|
|
179
200
|
}
|
|
180
201
|
|
|
181
202
|
#pragma mark - ResultPoint -> cornerPoints
|
|
182
203
|
|
|
183
|
-
+ (nullable NSNumber *)numberFromPointDict:(NSDictionary *)dict keys:(NSArray<NSString *> *)keys
|
|
184
|
-
{
|
|
204
|
+
+ (nullable NSNumber *)numberFromPointDict:(NSDictionary *)dict keys:(NSArray<NSString *> *)keys {
|
|
185
205
|
for (NSString *key in keys) {
|
|
186
206
|
id v = dict[key];
|
|
187
207
|
if ([v isKindOfClass:[NSNumber class]]) {
|
|
@@ -196,8 +216,7 @@
|
|
|
196
216
|
|
|
197
217
|
// HUAWEI delivers `ResultPoint` as an array of dicts with `posX`/`posY` numbers.
|
|
198
218
|
// We tolerate a couple of key spellings just in case.
|
|
199
|
-
+ (nullable NSArray<NSDictionary *> *)cornerPointsFromResultPoint:(id)resultPoint
|
|
200
|
-
{
|
|
219
|
+
+ (nullable NSArray<NSDictionary *> *)cornerPointsFromResultPoint:(id)resultPoint {
|
|
201
220
|
if (![resultPoint isKindOfClass:[NSArray class]]) {
|
|
202
221
|
return nil;
|
|
203
222
|
}
|
|
@@ -210,7 +229,7 @@
|
|
|
210
229
|
NSNumber *x = [self numberFromPointDict:p keys:@[ @"posX", @"x", @"X" ]];
|
|
211
230
|
NSNumber *y = [self numberFromPointDict:p keys:@[ @"posY", @"y", @"Y" ]];
|
|
212
231
|
if (x != nil && y != nil) {
|
|
213
|
-
[points addObject:@{
|
|
232
|
+
[points addObject:@{@"x" : x, @"y" : y}];
|
|
214
233
|
}
|
|
215
234
|
}
|
|
216
235
|
return points.count > 0 ? [points copy] : nil;
|
|
@@ -218,8 +237,7 @@
|
|
|
218
237
|
|
|
219
238
|
#pragma mark - Dict -> ScanResult
|
|
220
239
|
|
|
221
|
-
+ (nullable NSString *)valueFromHuaweiDict:(NSDictionary *)dict
|
|
222
|
-
{
|
|
240
|
+
+ (nullable NSString *)valueFromHuaweiDict:(NSDictionary *)dict {
|
|
223
241
|
// Primary key is `text`; tolerate a couple of alternates defensively.
|
|
224
242
|
for (NSString *key in @[ @"text", @"originalValue", @"showText", @"value" ]) {
|
|
225
243
|
id v = dict[key];
|
|
@@ -230,15 +248,14 @@
|
|
|
230
248
|
return nil;
|
|
231
249
|
}
|
|
232
250
|
|
|
233
|
-
+ (nullable NSDictionary *)scanResultFromHuaweiDict:(NSDictionary *)dict
|
|
234
|
-
{
|
|
251
|
+
+ (nullable NSDictionary *)scanResultFromHuaweiDict:(NSDictionary *)dict {
|
|
235
252
|
if (![dict isKindOfClass:[NSDictionary class]]) {
|
|
236
253
|
return nil;
|
|
237
254
|
}
|
|
238
255
|
|
|
239
256
|
NSString *value = [self valueFromHuaweiDict:dict];
|
|
240
257
|
if (value == nil) {
|
|
241
|
-
return nil;
|
|
258
|
+
return nil; // no usable decoded text -> drop
|
|
242
259
|
}
|
|
243
260
|
|
|
244
261
|
NSMutableDictionary *out = [NSMutableDictionary dictionary];
|
|
@@ -258,8 +275,7 @@
|
|
|
258
275
|
return [out copy];
|
|
259
276
|
}
|
|
260
277
|
|
|
261
|
-
+ (NSArray<NSDictionary *> *)scanResultsFromHuaweiArray:(NSArray *)array
|
|
262
|
-
{
|
|
278
|
+
+ (NSArray<NSDictionary *> *)scanResultsFromHuaweiArray:(NSArray *)array {
|
|
263
279
|
NSMutableArray<NSDictionary *> *results = [NSMutableArray array];
|
|
264
280
|
if ([array isKindOfClass:[NSArray class]]) {
|
|
265
281
|
for (id element in array) {
|
|
@@ -274,8 +290,7 @@
|
|
|
274
290
|
|
|
275
291
|
#pragma mark - JSON
|
|
276
292
|
|
|
277
|
-
+ (NSString *)jsonStringFromScanResults:(NSArray<NSDictionary *> *)results
|
|
278
|
-
{
|
|
293
|
+
+ (NSString *)jsonStringFromScanResults:(NSArray<NSDictionary *> *)results {
|
|
279
294
|
NSArray *safe = [results isKindOfClass:[NSArray class]] ? results : @[];
|
|
280
295
|
if (![NSJSONSerialization isValidJSONObject:safe]) {
|
|
281
296
|
return @"[]";
|
|
@@ -289,8 +304,7 @@
|
|
|
289
304
|
return json ?: @"[]";
|
|
290
305
|
}
|
|
291
306
|
|
|
292
|
-
+ (NSString *)jsonStringFromHuaweiArray:(NSArray *)array
|
|
293
|
-
{
|
|
307
|
+
+ (NSString *)jsonStringFromHuaweiArray:(NSArray *)array {
|
|
294
308
|
return [self jsonStringFromScanResults:[self scanResultsFromHuaweiArray:array]];
|
|
295
309
|
}
|
|
296
310
|
|