@unif/react-native-hms-scan 0.6.1 → 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.
Files changed (168) hide show
  1. package/README.md +24 -72
  2. package/android/src/main/AndroidManifest.xml +0 -9
  3. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanImage.kt +135 -0
  4. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanModule.kt +163 -147
  5. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanResultMapper.kt +17 -9
  6. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanView.kt +57 -19
  7. package/android/src/main/java/com/unif/reactnativehmsscan/HmsScanViewManager.kt +1 -1
  8. package/ios/HmsScanModule.mm +13 -37
  9. package/ios/HmsScanResultMapper.h +4 -3
  10. package/ios/HmsScanResultMapper.mm +31 -19
  11. package/ios/HmsScanView.h +1 -1
  12. package/ios/HmsScanView.mm +140 -25
  13. package/lib/module/HmsScanView/HmsScanView.js +66 -0
  14. package/lib/module/HmsScanView/HmsScanView.js.map +1 -0
  15. package/lib/module/HmsScanView/HmsScanView.web.js +26 -0
  16. package/lib/module/HmsScanView/HmsScanView.web.js.map +1 -0
  17. package/lib/module/HmsScanView/types.js +4 -0
  18. package/lib/module/HmsScanView/types.js.map +1 -0
  19. package/lib/module/HmsScanViewNativeComponent.ts +7 -5
  20. package/lib/module/NativeHmsScan.js +1 -1
  21. package/lib/module/NativeHmsScan.js.map +1 -1
  22. package/lib/module/ScanError.js +12 -0
  23. package/lib/module/ScanError.js.map +1 -0
  24. package/lib/module/Scanner/DeniedOverlay.js +1 -1
  25. package/lib/module/Scanner/DeniedOverlay.js.map +1 -1
  26. package/lib/module/Scanner/ResultFail.js +3 -2
  27. package/lib/module/Scanner/ResultFail.js.map +1 -1
  28. package/lib/module/Scanner/ResultFocus/ResultFocus.js +56 -0
  29. package/lib/module/Scanner/ResultFocus/ResultFocus.js.map +1 -0
  30. package/lib/module/Scanner/ResultFocus/styles.js +39 -0
  31. package/lib/module/Scanner/ResultFocus/styles.js.map +1 -0
  32. package/lib/module/Scanner/ResultFocus/types.js +4 -0
  33. package/lib/module/Scanner/ResultFocus/types.js.map +1 -0
  34. package/lib/module/Scanner/ScanHint.js +6 -3
  35. package/lib/module/Scanner/ScanHint.js.map +1 -1
  36. package/lib/module/Scanner/ScanToolbar.js +6 -3
  37. package/lib/module/Scanner/ScanToolbar.js.map +1 -1
  38. package/lib/module/Scanner/ScanTopBar.js +6 -3
  39. package/lib/module/Scanner/ScanTopBar.js.map +1 -1
  40. package/lib/module/Scanner/Scanner.js +9 -311
  41. package/lib/module/Scanner/Scanner.js.map +1 -1
  42. package/lib/module/Scanner/Scanner.web.js +29 -0
  43. package/lib/module/Scanner/Scanner.web.js.map +1 -0
  44. package/lib/module/Scanner/ScannerInner.js +310 -0
  45. package/lib/module/Scanner/ScannerInner.js.map +1 -0
  46. package/lib/module/Scanner/styles.js +11 -0
  47. package/lib/module/Scanner/styles.js.map +1 -0
  48. package/lib/module/Scanner/types.js +4 -0
  49. package/lib/module/Scanner/types.js.map +1 -0
  50. package/lib/module/constants.js +20 -0
  51. package/lib/module/constants.js.map +1 -0
  52. package/lib/module/decodeImage.js +29 -24
  53. package/lib/module/decodeImage.js.map +1 -1
  54. package/lib/module/decodeImage.web.js +7 -0
  55. package/lib/module/decodeImage.web.js.map +1 -0
  56. package/lib/module/errors.js +24 -0
  57. package/lib/module/errors.js.map +1 -0
  58. package/lib/module/format.js +53 -47
  59. package/lib/module/format.js.map +1 -1
  60. package/lib/module/index.js +5 -19
  61. package/lib/module/index.js.map +1 -1
  62. package/lib/module/index.web.js +8 -0
  63. package/lib/module/index.web.js.map +1 -0
  64. package/lib/module/mock.js +2 -23
  65. package/lib/module/mock.js.map +1 -1
  66. package/lib/module/permissions.js +24 -20
  67. package/lib/module/permissions.js.map +1 -1
  68. package/lib/module/permissions.web.js +10 -0
  69. package/lib/module/permissions.web.js.map +1 -0
  70. package/lib/module/types.js +1 -45
  71. package/lib/module/types.js.map +1 -1
  72. package/lib/typescript/src/HmsScanView/HmsScanView.d.ts +3 -0
  73. package/lib/typescript/src/HmsScanView/HmsScanView.d.ts.map +1 -0
  74. package/lib/typescript/src/HmsScanView/HmsScanView.web.d.ts +3 -0
  75. package/lib/typescript/src/HmsScanView/HmsScanView.web.d.ts.map +1 -0
  76. package/lib/typescript/src/HmsScanView/types.d.ts +12 -0
  77. package/lib/typescript/src/HmsScanView/types.d.ts.map +1 -0
  78. package/lib/typescript/src/HmsScanViewNativeComponent.d.ts +6 -4
  79. package/lib/typescript/src/HmsScanViewNativeComponent.d.ts.map +1 -1
  80. package/lib/typescript/src/NativeHmsScan.d.ts +3 -5
  81. package/lib/typescript/src/NativeHmsScan.d.ts.map +1 -1
  82. package/lib/typescript/src/ScanError.d.ts +7 -0
  83. package/lib/typescript/src/ScanError.d.ts.map +1 -0
  84. package/lib/typescript/src/Scanner/DeniedOverlay.d.ts.map +1 -1
  85. package/lib/typescript/src/Scanner/ResultFail.d.ts +2 -1
  86. package/lib/typescript/src/Scanner/ResultFail.d.ts.map +1 -1
  87. package/lib/typescript/src/Scanner/ResultFocus/ResultFocus.d.ts +4 -0
  88. package/lib/typescript/src/Scanner/ResultFocus/ResultFocus.d.ts.map +1 -0
  89. package/lib/typescript/src/Scanner/ResultFocus/styles.d.ts +36 -0
  90. package/lib/typescript/src/Scanner/ResultFocus/styles.d.ts.map +1 -0
  91. package/lib/typescript/src/Scanner/ResultFocus/types.d.ts +8 -0
  92. package/lib/typescript/src/Scanner/ResultFocus/types.d.ts.map +1 -0
  93. package/lib/typescript/src/Scanner/ScanHint.d.ts.map +1 -1
  94. package/lib/typescript/src/Scanner/ScanToolbar.d.ts.map +1 -1
  95. package/lib/typescript/src/Scanner/ScanTopBar.d.ts.map +1 -1
  96. package/lib/typescript/src/Scanner/Scanner.d.ts +2 -45
  97. package/lib/typescript/src/Scanner/Scanner.d.ts.map +1 -1
  98. package/lib/typescript/src/Scanner/Scanner.web.d.ts +3 -0
  99. package/lib/typescript/src/Scanner/Scanner.web.d.ts.map +1 -0
  100. package/lib/typescript/src/Scanner/ScannerInner.d.ts +3 -0
  101. package/lib/typescript/src/Scanner/ScannerInner.d.ts.map +1 -0
  102. package/lib/typescript/src/Scanner/styles.d.ts +7 -0
  103. package/lib/typescript/src/Scanner/styles.d.ts.map +1 -0
  104. package/lib/typescript/src/Scanner/types.d.ts +20 -0
  105. package/lib/typescript/src/Scanner/types.d.ts.map +1 -0
  106. package/lib/typescript/src/constants.d.ts +8 -0
  107. package/lib/typescript/src/constants.d.ts.map +1 -0
  108. package/lib/typescript/src/decodeImage.d.ts +2 -12
  109. package/lib/typescript/src/decodeImage.d.ts.map +1 -1
  110. package/lib/typescript/src/decodeImage.web.d.ts +3 -0
  111. package/lib/typescript/src/decodeImage.web.d.ts.map +1 -0
  112. package/lib/typescript/src/errors.d.ts +6 -0
  113. package/lib/typescript/src/errors.d.ts.map +1 -0
  114. package/lib/typescript/src/format.d.ts +15 -12
  115. package/lib/typescript/src/format.d.ts.map +1 -1
  116. package/lib/typescript/src/index.d.ts +6 -7
  117. package/lib/typescript/src/index.d.ts.map +1 -1
  118. package/lib/typescript/src/index.web.d.ts +9 -0
  119. package/lib/typescript/src/index.web.d.ts.map +1 -0
  120. package/lib/typescript/src/mock.d.ts +9 -8
  121. package/lib/typescript/src/mock.d.ts.map +1 -1
  122. package/lib/typescript/src/permissions.d.ts +4 -8
  123. package/lib/typescript/src/permissions.d.ts.map +1 -1
  124. package/lib/typescript/src/permissions.web.d.ts +4 -0
  125. package/lib/typescript/src/permissions.web.d.ts.map +1 -0
  126. package/lib/typescript/src/types.d.ts +31 -56
  127. package/lib/typescript/src/types.d.ts.map +1 -1
  128. package/package.json +9 -2
  129. package/src/HmsScanView/HmsScanView.tsx +67 -0
  130. package/src/HmsScanView/HmsScanView.web.tsx +22 -0
  131. package/src/HmsScanView/types.ts +17 -0
  132. package/src/HmsScanViewNativeComponent.ts +7 -5
  133. package/src/NativeHmsScan.ts +3 -5
  134. package/src/ScanError.ts +13 -0
  135. package/src/Scanner/DeniedOverlay.tsx +4 -2
  136. package/src/Scanner/ResultFail.tsx +23 -5
  137. package/src/Scanner/ResultFocus/ResultFocus.tsx +43 -0
  138. package/src/Scanner/ResultFocus/styles.ts +25 -0
  139. package/src/Scanner/ResultFocus/types.ts +8 -0
  140. package/src/Scanner/ScanHint.tsx +16 -11
  141. package/src/Scanner/ScanToolbar.tsx +35 -13
  142. package/src/Scanner/ScanTopBar.tsx +20 -12
  143. package/src/Scanner/Scanner.tsx +13 -436
  144. package/src/Scanner/Scanner.web.tsx +26 -0
  145. package/src/Scanner/ScannerInner.tsx +391 -0
  146. package/src/Scanner/styles.ts +6 -0
  147. package/src/Scanner/types.ts +36 -0
  148. package/src/constants.ts +55 -0
  149. package/src/decodeImage.ts +35 -29
  150. package/src/decodeImage.web.ts +8 -0
  151. package/src/errors.ts +26 -0
  152. package/src/format.ts +77 -76
  153. package/src/index.tsx +17 -32
  154. package/src/index.web.tsx +22 -0
  155. package/src/mock.ts +21 -29
  156. package/src/permissions.ts +33 -20
  157. package/src/permissions.web.ts +9 -0
  158. package/src/types.ts +38 -126
  159. package/lib/module/HmsScanView.js +0 -43
  160. package/lib/module/HmsScanView.js.map +0 -1
  161. package/lib/module/Scanner/ResultFocus.js +0 -171
  162. package/lib/module/Scanner/ResultFocus.js.map +0 -1
  163. package/lib/typescript/src/HmsScanView.d.ts +0 -33
  164. package/lib/typescript/src/HmsScanView.d.ts.map +0 -1
  165. package/lib/typescript/src/Scanner/ResultFocus.d.ts +0 -14
  166. package/lib/typescript/src/Scanner/ResultFocus.d.ts.map +0 -1
  167. package/src/HmsScanView.tsx +0 -87
  168. package/src/Scanner/ResultFocus.tsx +0 -122
package/src/format.ts CHANGED
@@ -1,93 +1,94 @@
1
- import {
2
- ALL_BARCODE_FORMATS,
3
- type BarcodeContentType,
4
- type BarcodeFormat,
5
- type ScanCornerPoint,
6
- type ScanResult,
1
+ import { SCAN_FORMAT_SET, SCAN_CONTENT_TYPE_SET } from './constants';
2
+ import { ScanError } from './ScanError';
3
+ import type {
4
+ RequestedScanFormat,
5
+ ScanContentType,
6
+ ScanFormat,
7
+ ScanPoint,
8
+ ScanResult,
7
9
  } from './types';
8
-
9
- const FORMAT_SET = new Set<string>([...ALL_BARCODE_FORMATS, 'UNKNOWN']);
10
-
11
- const CONTENT_TYPES: readonly BarcodeContentType[] = [
12
- 'TEXT',
13
- 'URL',
14
- 'EMAIL',
15
- 'PHONE',
16
- 'SMS',
17
- 'WIFI',
18
- 'CONTACT',
19
- 'EVENT',
20
- 'LOCATION',
21
- 'DRIVER',
22
- 'ISBN',
23
- 'ARTICLE',
24
- 'OTHER',
25
- ];
26
- const CONTENT_SET = new Set<string>(CONTENT_TYPES);
27
-
28
- /** 把任意字符串安全收敛成 BarcodeFormat(未知归 UNKNOWN)。 */
29
- export function coerceFormat(value: unknown): BarcodeFormat {
30
- return typeof value === 'string' && FORMAT_SET.has(value)
31
- ? (value as BarcodeFormat)
10
+ export function coerceFormat(value: unknown): ScanFormat {
11
+ return typeof value === 'string' && SCAN_FORMAT_SET.has(value)
12
+ ? (value as ScanFormat)
32
13
  : 'UNKNOWN';
33
14
  }
34
-
35
- /** 把任意字符串安全收敛成 BarcodeContentType(未知/缺省 → undefined)。 */
36
- export function coerceContentType(value: unknown): BarcodeContentType | undefined {
37
- return typeof value === 'string' && CONTENT_SET.has(value)
38
- ? (value as BarcodeContentType)
15
+ export function coerceContentType(value: unknown): ScanContentType | undefined {
16
+ return typeof value === 'string' && SCAN_CONTENT_TYPE_SET.has(value)
17
+ ? (value as ScanContentType)
39
18
  : undefined;
40
19
  }
41
-
42
- /** formats[] → 逗号分隔 CSV(传给原生);空/未传 → ''(= 全部码制)。 */
43
- export function formatsToCsv(formats?: readonly BarcodeFormat[]): string {
44
- return formats && formats.length > 0 ? formats.join(',') : '';
20
+ /** Empty means no extra filter. Order and duplicates do not change the filter. */
21
+ export function formatsToCsv(value?: readonly RequestedScanFormat[]): string {
22
+ if (value === undefined) return '';
23
+ if (
24
+ !Array.isArray(value) ||
25
+ [...value].some(
26
+ (item) =>
27
+ typeof item !== 'string' ||
28
+ item === 'UNKNOWN' ||
29
+ !SCAN_FORMAT_SET.has(item)
30
+ )
31
+ ) {
32
+ throw new ScanError({
33
+ reason: 'invalid_input',
34
+ message: 'formats 包含无效码制',
35
+ });
36
+ }
37
+ return [...new Set(value)].sort().join(',');
45
38
  }
46
-
47
- function coerceCornerPoints(value: unknown): ScanCornerPoint[] | undefined {
39
+ export function formatConfiguration(value?: readonly RequestedScanFormat[]) {
40
+ try {
41
+ return { csv: formatsToCsv(value), error: undefined };
42
+ } catch (error) {
43
+ return { csv: '', error: error as ScanError };
44
+ }
45
+ }
46
+ function cornerPoints(value: unknown): readonly ScanPoint[] | undefined {
48
47
  if (!Array.isArray(value)) return undefined;
49
48
  const points = value
50
49
  .filter(
51
- (p): p is { x: number; y: number } =>
52
- !!p && typeof p.x === 'number' && typeof p.y === 'number'
50
+ (p): p is ScanPoint =>
51
+ !!p &&
52
+ typeof p.x === 'number' &&
53
+ Number.isFinite(p.x) &&
54
+ typeof p.y === 'number' &&
55
+ Number.isFinite(p.y)
53
56
  )
54
57
  .map((p) => ({ x: p.x, y: p.y }));
55
- return points.length > 0 ? points : undefined;
58
+ return points.length ? points : undefined;
56
59
  }
57
-
58
- function coerceResult(raw: unknown): ScanResult | null {
59
- if (!raw || typeof raw !== 'object') return null;
60
- const r = raw as Record<string, unknown>;
61
- // 没有有效 value 的命中无意义,丢弃
62
- if (typeof r.value !== 'string' || r.value.length === 0) return null;
63
- const result: ScanResult = {
64
- value: r.value,
65
- format: coerceFormat(r.format),
66
- };
67
- const contentType = coerceContentType(r.contentType);
68
- if (contentType) result.contentType = contentType;
69
- const cornerPoints = coerceCornerPoints(r.cornerPoints);
70
- if (cornerPoints) result.cornerPoints = cornerPoints;
71
- return result;
60
+ function invalidResponse(): never {
61
+ throw new ScanError({
62
+ reason: 'invalid_response',
63
+ message: '原生扫码结果不符合约定',
64
+ });
72
65
  }
73
-
74
- /**
75
- * 解析原生回传的 JSON 字符串 → 强类型 ScanResult[]。
76
- * 任何脏数据都被安全过滤;解析失败返回空数组而非抛错。
77
- */
78
- export function parseResultsJson(json: string): ScanResult[] {
79
- if (!json) return [];
80
- let parsed: unknown;
66
+ /** Malformed bridge data is distinct from a successfully decoded empty batch. */
67
+ export function parseResultsJson(json: unknown): readonly ScanResult[] {
68
+ let data: unknown;
69
+ if (typeof json !== 'string') return invalidResponse();
81
70
  try {
82
- parsed = JSON.parse(json);
71
+ data = JSON.parse(json);
83
72
  } catch {
84
- return [];
85
- }
86
- if (!Array.isArray(parsed)) return [];
87
- const out: ScanResult[] = [];
88
- for (const item of parsed) {
89
- const r = coerceResult(item);
90
- if (r) out.push(r);
73
+ return invalidResponse();
91
74
  }
92
- return out;
75
+ if (!Array.isArray(data)) return invalidResponse();
76
+ return data.map((raw) => {
77
+ if (
78
+ !raw ||
79
+ typeof raw !== 'object' ||
80
+ typeof raw.value !== 'string' ||
81
+ typeof raw.format !== 'string'
82
+ )
83
+ return invalidResponse();
84
+ const result: ScanResult = {
85
+ value: raw.value,
86
+ format: coerceFormat(raw.format),
87
+ };
88
+ const contentType = coerceContentType(raw.contentType);
89
+ const points = cornerPoints(raw.cornerPoints);
90
+ if (contentType !== undefined) result.contentType = contentType;
91
+ if (points !== undefined) result.cornerPoints = points;
92
+ return result;
93
+ });
93
94
  }
package/src/index.tsx CHANGED
@@ -1,37 +1,22 @@
1
- // @unif/react-native-hms-scan —— 华为 HMS 统一扫码服务 RN bridge(Fabric 新架构)
2
- // 能力:定制视图扫码(底层 <HmsScanView> + 成品 <Scanner> 扫一扫页)+ 图片识别(decodeImage)+ 相机权限。
3
- // 文档站:https://unif-design.github.io/react-native-hms-scan/(版本随发版自动部署)。
4
-
5
- // ── 类型与常量 ──
6
- export {
7
- ALL_BARCODE_FORMATS,
8
- HmsScanError,
9
- } from './types';
1
+ export { ScanError } from './ScanError';
10
2
  export type {
11
- BarcodeFormat,
12
- BarcodeContentType,
13
- ScanCornerPoint,
3
+ ScanFormat,
4
+ RequestedScanFormat,
5
+ ScanContentType,
6
+ ScanPoint,
14
7
  ScanResult,
15
- ScanError,
16
- DecodeImageOptions,
17
- CameraPermissionStatus,
18
- HmsScanErrorCode,
19
- ScanProduct,
8
+ ScanFailure,
9
+ DecodeScanImageInput,
10
+ ScanCameraPermission,
11
+ ScanTorchState,
12
+ ScannerImage,
20
13
  } from './types';
21
-
22
- // ── 码制工具 ──
23
- export { coerceFormat, coerceContentType, formatsToCsv } from './format';
24
-
25
- // ── 底层 headless 相机组件 ──
26
- export { HmsScanView } from './HmsScanView';
27
- export type { HmsScanViewProps, TorchStatus } from './HmsScanView';
28
-
29
- // ── 图片识别 ──
14
+ export { HmsScanView } from './HmsScanView/HmsScanView';
15
+ export type { HmsScanViewProps } from './HmsScanView/types';
30
16
  export { decodeImage } from './decodeImage';
31
-
32
- // ── 相机权限 ──
33
- export { getCameraPermissionStatus, requestCameraPermission } from './permissions';
34
-
35
- // ── 成品扫一扫页(聚焦款,浅色) ──
17
+ export {
18
+ getCameraPermissionStatus,
19
+ requestCameraPermission,
20
+ } from './permissions';
36
21
  export { Scanner } from './Scanner/Scanner';
37
- export type { ScannerProps } from './Scanner/Scanner';
22
+ export type { ScannerProps } from './Scanner/types';
@@ -0,0 +1,22 @@
1
+ export { ScanError } from './ScanError';
2
+ export type {
3
+ ScanFormat,
4
+ RequestedScanFormat,
5
+ ScanContentType,
6
+ ScanPoint,
7
+ ScanResult,
8
+ ScanFailure,
9
+ DecodeScanImageInput,
10
+ ScanCameraPermission,
11
+ ScanTorchState,
12
+ ScannerImage,
13
+ } from './types';
14
+ export { HmsScanView } from './HmsScanView/HmsScanView.web';
15
+ export type { HmsScanViewProps } from './HmsScanView/types';
16
+ export { decodeImage } from './decodeImage.web';
17
+ export {
18
+ getCameraPermissionStatus,
19
+ requestCameraPermission,
20
+ } from './permissions.web';
21
+ export { Scanner } from './Scanner/Scanner.web';
22
+ export type { ScannerProps } from './Scanner/types';
package/src/mock.ts CHANGED
@@ -1,43 +1,35 @@
1
- // Jest mock for @unif/react-native-hms-scan —— 供消费者在测试中替换本库,
2
- // 避免 jest 环境加载 HmsScan TurboModule / HmsScanView Fabric 组件而崩溃。
3
- //
4
- // 用法(消费者 jest setup 或单个测试文件):
5
- //
6
- // jest.mock('@unif/react-native-hms-scan', () =>
7
- // require('@unif/react-native-hms-scan/mock')
8
- // );
9
- //
10
- // 替换后 decodeImage / 权限方法都是 jest.fn;<HmsScanView> / <Scanner> 渲染为 null。
11
- // 按需覆盖单次返回:
12
- // (decodeImage as jest.Mock).mockResolvedValueOnce([{ value: '690...', format: 'EAN_13' }]);
13
-
14
1
  import type { FC } from 'react';
15
2
  import type {
16
- CameraPermissionStatus,
17
- DecodeImageOptions,
3
+ DecodeScanImageInput,
4
+ ScanCameraPermission,
18
5
  ScanResult,
19
6
  } from './types';
20
- import type { HmsScanViewProps } from './HmsScanView';
21
- import type { ScannerProps } from './Scanner/Scanner';
22
-
23
- // 纯 JS 类型 / 常量 / Error / 码制工具:保留真实实现(不碰原生)。
24
- export * from './types';
25
- export { coerceFormat, coerceContentType, formatsToCsv } from './format';
7
+ import type { HmsScanViewProps } from './HmsScanView/types';
8
+ import type { ScannerProps } from './Scanner/types';
26
9
 
27
- // ── 图片识别 ──
10
+ export { ScanError } from './ScanError';
11
+ export type {
12
+ ScanFormat,
13
+ RequestedScanFormat,
14
+ ScanContentType,
15
+ ScanPoint,
16
+ ScanResult,
17
+ ScanFailure,
18
+ DecodeScanImageInput,
19
+ ScanCameraPermission,
20
+ ScanTorchState,
21
+ ScannerImage,
22
+ } from './types';
23
+ export type { HmsScanViewProps, ScannerProps };
28
24
  export const decodeImage = jest.fn(
29
- (_uri: string, _options?: DecodeImageOptions): Promise<ScanResult[]> =>
25
+ (_input: Readonly<DecodeScanImageInput>): Promise<readonly ScanResult[]> =>
30
26
  Promise.resolve([])
31
27
  );
32
-
33
- // ── 相机权限 ──(默认已授权)
34
28
  export const getCameraPermissionStatus = jest.fn(
35
- (): Promise<CameraPermissionStatus> => Promise.resolve('granted')
29
+ (): Promise<ScanCameraPermission> => Promise.resolve('granted')
36
30
  );
37
31
  export const requestCameraPermission = jest.fn(
38
- (): Promise<CameraPermissionStatus> => Promise.resolve('granted')
32
+ (): Promise<ScanCameraPermission> => Promise.resolve('granted')
39
33
  );
40
-
41
- // ── 组件 ──(渲染 null,避免触碰原生)
42
34
  export const HmsScanView: FC<HmsScanViewProps> = () => null;
43
35
  export const Scanner: FC<ScannerProps> = () => null;
@@ -1,27 +1,40 @@
1
1
  import NativeHmsScan from './NativeHmsScan';
2
- import type { CameraPermissionStatus } from './types';
2
+ import { scanError } from './errors';
3
+ import { ScanError } from './ScanError';
4
+ import type { ScanCameraPermission } from './types';
3
5
 
4
- function normalize(status: string): CameraPermissionStatus {
5
- switch (status) {
6
- case 'granted':
7
- case 'denied':
8
- case 'blocked':
9
- case 'undetermined':
6
+ async function permission(request: boolean): Promise<ScanCameraPermission> {
7
+ if (!NativeHmsScan)
8
+ throw new ScanError({
9
+ reason: 'unavailable',
10
+ message: 'HMS Scan 原生模块未安装',
11
+ });
12
+ try {
13
+ const status = await (request
14
+ ? NativeHmsScan.requestCameraPermission()
15
+ : NativeHmsScan.getCameraPermissionStatus());
16
+ if (
17
+ status === 'granted' ||
18
+ status === 'denied' ||
19
+ status === 'blocked' ||
20
+ status === 'undetermined'
21
+ )
10
22
  return status;
11
- default:
12
- return 'undetermined';
23
+ throw new ScanError({
24
+ reason: 'invalid_response',
25
+ message: '原生相机权限状态无法识别',
26
+ });
27
+ } catch (error) {
28
+ throw scanError(error, {
29
+ reason: 'unavailable',
30
+ message: '读取相机权限失败',
31
+ });
13
32
  }
14
33
  }
15
-
16
- /** 查询当前相机权限状态(不弹窗)。 */
17
- export async function getCameraPermissionStatus(): Promise<CameraPermissionStatus> {
18
- return normalize(await NativeHmsScan.getCameraPermissionStatus());
34
+ /** Reads the actual status without presenting a system permission prompt. */
35
+ export function getCameraPermissionStatus(): Promise<ScanCameraPermission> {
36
+ return permission(false);
19
37
  }
20
-
21
- /**
22
- * 发起相机权限请求(必要时弹系统授权框),返回请求后的状态。
23
- * 若用户已永久拒绝(blocked),需引导去系统设置开启。
24
- */
25
- export async function requestCameraPermission(): Promise<CameraPermissionStatus> {
26
- return normalize(await NativeHmsScan.requestCameraPermission());
38
+ export function requestCameraPermission(): Promise<ScanCameraPermission> {
39
+ return permission(true);
27
40
  }
@@ -0,0 +1,9 @@
1
+ import { unsupportedScanError } from './errors';
2
+ import type { ScanCameraPermission } from './types';
3
+
4
+ export async function getCameraPermissionStatus(): Promise<ScanCameraPermission> {
5
+ throw unsupportedScanError();
6
+ }
7
+ export async function requestCameraPermission(): Promise<ScanCameraPermission> {
8
+ throw unsupportedScanError();
9
+ }
package/src/types.ts CHANGED
@@ -1,144 +1,56 @@
1
- // ──────────────────────────────────────────────────────────────
2
- // @unif/react-native-hms-scan —— 公共类型
3
- //
4
- // 这些是纯 JS/TS 类型与常量(不触碰原生),index 与 mock 都从这里导出。
5
- // 码制 / 内容类型在原生侧(Android HmsScan.*_SCAN_TYPE / getScanTypeForm,
6
- // iOS HMSScanFormatTypeCode / sceneType)已统一映射成下面的字符串枚举再回 JS。
7
- // ──────────────────────────────────────────────────────────────
1
+ import type { SCAN_FORMATS, SCAN_CONTENT_TYPES } from './constants';
8
2
 
9
- /** 统一码制枚举(两端归一)。 */
10
- export type BarcodeFormat =
11
- | 'QR_CODE'
12
- | 'AZTEC'
13
- | 'DATA_MATRIX'
14
- | 'PDF417'
15
- | 'CODABAR'
16
- | 'CODE_39'
17
- | 'CODE_93'
18
- | 'CODE_128'
19
- | 'EAN_8'
20
- | 'EAN_13'
21
- | 'UPC_A'
22
- | 'UPC_E'
23
- | 'ITF14'
24
- | 'MULTI_FUNCTIONAL'
25
- | 'UNKNOWN';
3
+ export type ScanFormat = (typeof SCAN_FORMATS)[number];
4
+ export type RequestedScanFormat = Exclude<ScanFormat, 'UNKNOWN'>;
26
5
 
27
- /** 所有可用码制(不含 UNKNOWN)。传给原生即"识别全部码制"。 */
28
- export const ALL_BARCODE_FORMATS: readonly BarcodeFormat[] = [
29
- 'QR_CODE',
30
- 'AZTEC',
31
- 'DATA_MATRIX',
32
- 'PDF417',
33
- 'CODABAR',
34
- 'CODE_39',
35
- 'CODE_93',
36
- 'CODE_128',
37
- 'EAN_8',
38
- 'EAN_13',
39
- 'UPC_A',
40
- 'UPC_E',
41
- 'ITF14',
42
- 'MULTI_FUNCTIONAL',
43
- ] as const;
6
+ export type ScanContentType = (typeof SCAN_CONTENT_TYPES)[number];
44
7
 
45
- /**
46
- * 码内容语义类型(尽力归一)。
47
- * Android 来自 HmsScan.getScanTypeForm(),iOS 来自 sceneType(精度有限,未知归 OTHER)。
48
- */
49
- export type BarcodeContentType =
50
- | 'TEXT'
51
- | 'URL'
52
- | 'EMAIL'
53
- | 'PHONE'
54
- | 'SMS'
55
- | 'WIFI'
56
- | 'CONTACT'
57
- | 'EVENT'
58
- | 'LOCATION'
59
- | 'DRIVER'
60
- | 'ISBN'
61
- | 'ARTICLE'
62
- | 'OTHER';
63
-
64
- /** 取景框/解码命中的角点(图像坐标系,单位 px)。 */
65
- export interface ScanCornerPoint {
8
+ export interface ScanPoint {
66
9
  x: number;
67
10
  y: number;
68
11
  }
69
-
70
- /** 一次扫码/解码命中的结果。 */
71
12
  export interface ScanResult {
72
- /** 原始解码文本(Android getOriginalValue / iOS text)。 */
13
+ /** Unmodified decoded text. */
73
14
  value: string;
74
- /** 码制。 */
75
- format: BarcodeFormat;
76
- /** 内容语义类型(可能缺省)。 */
77
- contentType?: BarcodeContentType;
78
- /** 条码四角点(可能缺省)。 */
79
- cornerPoints?: ScanCornerPoint[];
15
+ format: ScanFormat;
16
+ contentType?: ScanContentType;
17
+ /** Coordinates in the recognition image, not screen coordinates. */
18
+ cornerPoints?: readonly ScanPoint[];
80
19
  }
81
-
82
- /** decodeImage 选项。 */
83
- export interface DecodeImageOptions {
84
- /** 限定识别码制;不传 = 全部。 */
85
- formats?: readonly BarcodeFormat[];
86
- }
87
-
88
- /** `<HmsScanView>` / `<Scanner>` 通过回调上报的普通错误对象。 */
89
- export interface ScanError {
90
- code: string;
20
+ export interface ScanFailure {
21
+ reason:
22
+ | 'invalid_input'
23
+ | 'permission_denied'
24
+ | 'image_unavailable'
25
+ | 'decode_failed'
26
+ | 'invalid_response'
27
+ | 'unavailable'
28
+ | 'unsupported';
91
29
  message: string;
30
+ sourceCode?: string;
92
31
  }
93
-
94
- /** 相机权限状态。 */
95
- export type CameraPermissionStatus =
32
+ export interface DecodeScanImageInput {
33
+ /** A readable local file URI, borrowed until this operation settles. */
34
+ uri: string;
35
+ formats?: readonly RequestedScanFormat[];
36
+ }
37
+ export type ScanCameraPermission =
96
38
  | 'granted'
97
39
  | 'denied'
98
40
  | 'blocked'
99
41
  | 'undetermined';
100
-
101
- /** 错误码。 */
102
- export type HmsScanErrorCode =
103
- | 'E_NO_CAMERA_PERMISSION'
104
- | 'E_NO_READ_PERMISSION'
105
- | 'E_CAMERA_INIT'
106
- | 'E_DECODE_FAILED'
107
- | 'E_IMAGE_LOAD_FAILED'
108
- | 'E_NO_RESULT'
109
- | 'E_UNKNOWN';
110
-
111
- /** 库统一抛出的错误类型。 */
112
- export class HmsScanError extends Error {
113
- readonly code: HmsScanErrorCode;
114
- constructor(code: HmsScanErrorCode, message?: string) {
115
- super(message ?? code);
116
- this.name = 'HmsScanError';
117
- this.code = code;
118
- // 兼容 TS 继承内建类的 instanceof
119
- Object.setPrototypeOf(this, HmsScanError.prototype);
120
- }
42
+ export interface ScanTorchState {
43
+ on: boolean;
44
+ available?: boolean;
45
+ lowLight?: boolean;
46
+ }
47
+ export interface ScannerImage {
48
+ uri: string;
49
+ onSourceReleased?(): void;
121
50
  }
122
51
 
123
- /**
124
- * 业务层商品信息——`<Scanner>` 扫到条码后,由宿主通过 resolveProduct 解析返回,
125
- * 用于聚焦款"浮层确认卡"展示。仅 name 必填,其余可缺省。
126
- */
127
- export interface ScanProduct {
128
- /** 商品名(必填)。 */
129
- name: string;
130
- /** 品牌(如 "统一")。 */
131
- brand?: string;
132
- /** 字母牌字符;缺省时取 brand 或 name 的首字。 */
133
- brandChar?: string;
134
- /** 条码;缺省时取扫到的 value。 */
135
- barcode?: string;
136
- /** 规格(如 "500ml × 15 瓶/箱")。 */
137
- spec?: string;
138
- /** 库存短描述(如 "充足")。 */
139
- stockShort?: string;
140
- /** 价格展示串(如 "¥5.50")。 */
141
- price?: string;
142
- /** 价格下方副标题,默认 "建议零售"。 */
143
- priceCaption?: string;
52
+ /** Native rejection payload; internal to the capability boundary. */
53
+ export interface NativeScanFailure {
54
+ code?: unknown;
55
+ message?: unknown;
144
56
  }
@@ -1,43 +0,0 @@
1
- "use strict";
2
-
3
- import { forwardRef } from 'react';
4
- import NativeHmsScanView from './HmsScanViewNativeComponent';
5
- import { formatsToCsv, parseResultsJson } from "./format.js";
6
-
7
- /** 平台相关的暗光提示 / 手电状态。 */
8
- import { jsx as _jsx } from "react/jsx-runtime";
9
- /**
10
- * 底层 headless 扫码相机组件:只渲染相机预览并发出扫码结果事件。
11
- * 取景框 / 扫描线 / 手电按钮等 UI 由上层(如 `<Scanner>`)用普通 RN 视图叠加绘制。
12
- *
13
- * Android = 华为 RemoteView,iOS = HmsCustomScanViewController(Scan SDK-Plus / ScanKitFrameWork)。
14
- */
15
- export const HmsScanView = /*#__PURE__*/forwardRef(function HmsScanView({
16
- formats,
17
- continuous = true,
18
- paused = false,
19
- torch = false,
20
- onScanResult,
21
- onScanError,
22
- onTorchStatus,
23
- ...viewProps
24
- }, ref) {
25
- return /*#__PURE__*/_jsx(NativeHmsScanView, {
26
- ...viewProps,
27
- ref: ref,
28
- formatsCsv: formatsToCsv(formats),
29
- continuous: continuous,
30
- paused: paused,
31
- torch: torch,
32
- onScanResult: onScanResult ? e => onScanResult(parseResultsJson(e.nativeEvent.resultsJson)) : undefined,
33
- onScanError: onScanError ? e => onScanError({
34
- code: e.nativeEvent.code,
35
- message: e.nativeEvent.message
36
- }) : undefined,
37
- onTorchStatus: onTorchStatus ? e => onTorchStatus({
38
- available: e.nativeEvent.available,
39
- on: e.nativeEvent.on
40
- }) : undefined
41
- });
42
- });
43
- //# sourceMappingURL=HmsScanView.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["forwardRef","NativeHmsScanView","formatsToCsv","parseResultsJson","jsx","_jsx","HmsScanView","formats","continuous","paused","torch","onScanResult","onScanError","onTorchStatus","viewProps","ref","formatsCsv","e","nativeEvent","resultsJson","undefined","code","message","available","on"],"sourceRoot":"../../src","sources":["HmsScanView.tsx"],"mappings":";;AAAA,SAASA,UAAU,QAA2B,OAAO;AAErD,OAAOC,iBAAiB,MAAM,8BAA8B;AAC5D,SAASC,YAAY,EAAEC,gBAAgB,QAAQ,aAAU;;AAGzD;AAAA,SAAAC,GAAA,IAAAC,IAAA;AAyBA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,WAAW,gBAAGN,UAAU,CAGnC,SAASM,WAAWA,CACpB;EACEC,OAAO;EACPC,UAAU,GAAG,IAAI;EACjBC,MAAM,GAAG,KAAK;EACdC,KAAK,GAAG,KAAK;EACbC,YAAY;EACZC,WAAW;EACXC,aAAa;EACb,GAAGC;AACL,CAAC,EACDC,GAAG,EACH;EACA,oBACEV,IAAA,CAACJ,iBAAiB;IAAA,GACZa,SAAS;IACbC,GAAG,EAAEA,GAAI;IACTC,UAAU,EAAEd,YAAY,CAACK,OAAO,CAAE;IAClCC,UAAU,EAAEA,UAAW;IACvBC,MAAM,EAAEA,MAAO;IACfC,KAAK,EAAEA,KAAM;IACbC,YAAY,EACVA,YAAY,GACPM,CAAC,IAAKN,YAAY,CAACR,gBAAgB,CAACc,CAAC,CAACC,WAAW,CAACC,WAAW,CAAC,CAAC,GAChEC,SACL;IACDR,WAAW,EACTA,WAAW,GACNK,CAAC,IACAL,WAAW,CAAC;MACVS,IAAI,EAAEJ,CAAC,CAACC,WAAW,CAACG,IAAI;MACxBC,OAAO,EAAEL,CAAC,CAACC,WAAW,CAACI;IACzB,CAAC,CAAC,GACJF,SACL;IACDP,aAAa,EACXA,aAAa,GACRI,CAAC,IACAJ,aAAa,CAAC;MACZU,SAAS,EAAEN,CAAC,CAACC,WAAW,CAACK,SAAS;MAClCC,EAAE,EAAEP,CAAC,CAACC,WAAW,CAACM;IACpB,CAAC,CAAC,GACJJ;EACL,CACF,CAAC;AAEN,CAAC,CAAC","ignoreList":[]}