@symbiote-native/camera 0.0.0-stage → 0.1.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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +254 -2
  3. package/build/angular/camera-view/index.d.ts +43 -0
  4. package/build/angular/camera-view/index.js +101 -0
  5. package/build/angular/index.d.ts +3 -0
  6. package/build/angular/index.js +4 -0
  7. package/build/angular/services/camera-permissions.service.d.ts +8 -0
  8. package/build/angular/services/camera-permissions.service.js +82 -0
  9. package/build/core/android-barcode.d.ts +50 -0
  10. package/build/core/android-barcode.js +2 -0
  11. package/build/core/camera-api.d.ts +35 -0
  12. package/build/core/camera-api.js +68 -0
  13. package/build/core/camera-view-events.d.ts +3 -0
  14. package/build/core/camera-view-events.js +60 -0
  15. package/build/core/camera-view-handle.d.ts +26 -0
  16. package/build/core/camera-view-handle.js +46 -0
  17. package/build/core/camera-view.d.ts +12 -0
  18. package/build/core/camera-view.js +38 -0
  19. package/build/core/constants.d.ts +6 -0
  20. package/build/core/constants.js +7 -0
  21. package/build/core/index.d.ts +9 -0
  22. package/build/core/index.js +5 -0
  23. package/build/core/native-module.d.ts +28 -0
  24. package/build/core/native-module.js +3 -0
  25. package/build/core/picture-options.d.ts +9 -0
  26. package/build/core/picture-options.js +29 -0
  27. package/build/core/picture-ref.d.ts +2 -0
  28. package/build/core/picture-ref.js +3 -0
  29. package/build/core/props.d.ts +4 -0
  30. package/build/core/props.js +26 -0
  31. package/build/core/types.d.ts +170 -0
  32. package/build/core/types.js +1 -0
  33. package/build/react/camera-view.d.ts +8 -0
  34. package/build/react/camera-view.js +6 -0
  35. package/build/react/index.d.ts +5 -0
  36. package/build/react/index.js +4 -0
  37. package/build/react/use-camera-permissions.d.ts +9 -0
  38. package/build/react/use-camera-permissions.js +5 -0
  39. package/build/solid/camera-view.d.ts +33 -0
  40. package/build/solid/camera-view.js +4 -0
  41. package/build/solid/index.d.ts +4 -0
  42. package/build/solid/index.js +4 -0
  43. package/build/solid/use-camera-permissions.d.ts +10 -0
  44. package/build/solid/use-camera-permissions.js +5 -0
  45. package/build/svelte/camera-view.svelte +32 -0
  46. package/build/svelte/camera-view.svelte.d.ts +22 -0
  47. package/build/svelte/index.d.ts +4 -0
  48. package/build/svelte/index.js +4 -0
  49. package/build/svelte/use-camera-permissions.svelte.d.ts +9 -0
  50. package/build/svelte/use-camera-permissions.svelte.js +5 -0
  51. package/build/vue/camera-view.d.ts +2 -0
  52. package/build/vue/camera-view.js +4 -0
  53. package/build/vue/index.d.ts +4 -0
  54. package/build/vue/index.js +4 -0
  55. package/build/vue/use-camera-permissions.d.ts +10 -0
  56. package/build/vue/use-camera-permissions.js +5 -0
  57. package/build-ngc/angular/camera-view/index.d.ts +46 -0
  58. package/build-ngc/angular/camera-view/index.js +60 -0
  59. package/build-ngc/angular/camera-view/index.js.map +1 -0
  60. package/build-ngc/angular/index.d.ts +3 -0
  61. package/build-ngc/angular/index.js +5 -0
  62. package/build-ngc/angular/index.js.map +1 -0
  63. package/build-ngc/angular/services/camera-permissions.service.d.ts +13 -0
  64. package/build-ngc/angular/services/camera-permissions.service.js +28 -0
  65. package/build-ngc/angular/services/camera-permissions.service.js.map +1 -0
  66. package/build-ngc/core/android-barcode.d.ts +50 -0
  67. package/build-ngc/core/android-barcode.js +3 -0
  68. package/build-ngc/core/android-barcode.js.map +1 -0
  69. package/build-ngc/core/camera-api.d.ts +35 -0
  70. package/build-ngc/core/camera-api.js +69 -0
  71. package/build-ngc/core/camera-api.js.map +1 -0
  72. package/build-ngc/core/camera-view-events.d.ts +3 -0
  73. package/build-ngc/core/camera-view-events.js +61 -0
  74. package/build-ngc/core/camera-view-events.js.map +1 -0
  75. package/build-ngc/core/camera-view-handle.d.ts +26 -0
  76. package/build-ngc/core/camera-view-handle.js +47 -0
  77. package/build-ngc/core/camera-view-handle.js.map +1 -0
  78. package/build-ngc/core/camera-view.d.ts +12 -0
  79. package/build-ngc/core/camera-view.js +39 -0
  80. package/build-ngc/core/camera-view.js.map +1 -0
  81. package/build-ngc/core/constants.d.ts +6 -0
  82. package/build-ngc/core/constants.js +8 -0
  83. package/build-ngc/core/constants.js.map +1 -0
  84. package/build-ngc/core/index.d.ts +9 -0
  85. package/build-ngc/core/index.js +6 -0
  86. package/build-ngc/core/index.js.map +1 -0
  87. package/build-ngc/core/native-module.d.ts +28 -0
  88. package/build-ngc/core/native-module.js +4 -0
  89. package/build-ngc/core/native-module.js.map +1 -0
  90. package/build-ngc/core/picture-options.d.ts +9 -0
  91. package/build-ngc/core/picture-options.js +30 -0
  92. package/build-ngc/core/picture-options.js.map +1 -0
  93. package/build-ngc/core/picture-ref.d.ts +2 -0
  94. package/build-ngc/core/picture-ref.js +4 -0
  95. package/build-ngc/core/picture-ref.js.map +1 -0
  96. package/build-ngc/core/props.d.ts +4 -0
  97. package/build-ngc/core/props.js +27 -0
  98. package/build-ngc/core/props.js.map +1 -0
  99. package/build-ngc/core/types.d.ts +170 -0
  100. package/build-ngc/core/types.js +2 -0
  101. package/build-ngc/core/types.js.map +1 -0
  102. package/native-link.json +22 -0
  103. package/package.json +144 -3
  104. package/src/angular/camera-view/index.ts +95 -0
  105. package/src/angular/index.ts +8 -0
  106. package/src/angular/services/camera-permissions.service.ts +28 -0
  107. package/src/core/android-barcode.ts +51 -0
  108. package/src/core/camera-api.ts +99 -0
  109. package/src/core/camera-view-events.ts +108 -0
  110. package/src/core/camera-view-handle.ts +105 -0
  111. package/src/core/camera-view.ts +56 -0
  112. package/src/core/constants.ts +11 -0
  113. package/src/core/index.ts +57 -0
  114. package/src/core/native-module.ts +58 -0
  115. package/src/core/picture-options.ts +46 -0
  116. package/src/core/picture-ref.ts +4 -0
  117. package/src/core/props.ts +31 -0
  118. package/src/core/types.ts +212 -0
  119. package/src/react/camera-view.ts +17 -0
  120. package/src/react/index.ts +10 -0
  121. package/src/react/use-camera-permissions.ts +21 -0
  122. package/src/solid/camera-view.ts +9 -0
  123. package/src/solid/index.ts +9 -0
  124. package/src/solid/use-camera-permissions.ts +22 -0
  125. package/src/svelte/camera-view.svelte +32 -0
  126. package/src/svelte/index.ts +9 -0
  127. package/src/svelte/svelte-ambient.d.ts +3 -0
  128. package/src/svelte/svelte-compile.test-helper.ts +6 -0
  129. package/src/svelte/use-camera-permissions.svelte.ts +19 -0
  130. package/src/vue/camera-view.ts +8 -0
  131. package/src/vue/index.ts +9 -0
  132. package/src/vue/use-camera-permissions.ts +22 -0
package/package.json CHANGED
@@ -1,6 +1,147 @@
1
1
  {
2
2
  "name": "@symbiote-native/camera",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "expo-camera wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. The camera preview, pictures, video recording and barcode scanning.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
9
+ "directory": "packages/camera"
10
+ },
11
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/camera#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
14
+ },
15
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
16
+ "keywords": [
17
+ "react-native",
18
+ "symbiote-native",
19
+ "expo-camera",
20
+ "react",
21
+ "vue",
22
+ "svelte",
23
+ "solid",
24
+ "angular",
25
+ "camera"
26
+ ],
27
+ "type": "module",
28
+ "main": "./build/core/index.js",
29
+ "module": "./build/core/index.js",
30
+ "types": "./build/core/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./build/core/index.d.ts",
34
+ "default": "./build/core/index.js"
35
+ },
36
+ "./vue": {
37
+ "types": "./build/vue/index.d.ts",
38
+ "default": "./build/vue/index.js"
39
+ },
40
+ "./react": {
41
+ "types": "./build/react/index.d.ts",
42
+ "default": "./build/react/index.js"
43
+ },
44
+ "./svelte": "./src/svelte/index.ts",
45
+ "./solid": {
46
+ "types": "./build/solid/index.d.ts",
47
+ "default": "./build/solid/index.js"
48
+ },
49
+ "./angular": {
50
+ "types": "./build-ngc/angular/index.d.ts",
51
+ "react-native": "./build-ngc/angular/index.js",
52
+ "default": "./src/angular/index.ts"
53
+ }
54
+ },
55
+ "files": [
56
+ "src",
57
+ "build",
58
+ "build-ngc",
59
+ "native-link.json",
60
+ "!src/**/*.test.*",
61
+ "!src/**/*.spec.*",
62
+ "!src/**/*.detox.*"
63
+ ],
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
67
+ "dependencies": {
68
+ "expo-camera": "57.0.6",
69
+ "expo-modules-core": "57.0.20",
70
+ "@symbiote-native/components": "3.2.0"
71
+ },
72
+ "peerDependencies": {
73
+ "@angular/core": ">=20",
74
+ "@vue/runtime-core": "^3.5.13",
75
+ "react": ">=19.0.0",
76
+ "react-native": ">=0.86",
77
+ "solid-js": ">=1.9.0",
78
+ "svelte": ">=5.56.0",
79
+ "vue": ">=3.5.0",
80
+ "@symbiote-native/angular": "^3.3.0",
81
+ "@symbiote-native/engine": "^1.6.0",
82
+ "@symbiote-native/react": "^3.3.0",
83
+ "@symbiote-native/solid": "^3.2.0",
84
+ "@symbiote-native/svelte": "^3.2.0",
85
+ "@symbiote-native/vue": "^3.3.0"
86
+ },
87
+ "peerDependenciesMeta": {
88
+ "@symbiote-native/angular": {
89
+ "optional": true
90
+ },
91
+ "@symbiote-native/react": {
92
+ "optional": true
93
+ },
94
+ "@symbiote-native/solid": {
95
+ "optional": true
96
+ },
97
+ "@symbiote-native/svelte": {
98
+ "optional": true
99
+ },
100
+ "@symbiote-native/vue": {
101
+ "optional": true
102
+ },
103
+ "@angular/core": {
104
+ "optional": true
105
+ },
106
+ "@vue/runtime-core": {
107
+ "optional": true
108
+ },
109
+ "react": {
110
+ "optional": true
111
+ },
112
+ "solid-js": {
113
+ "optional": true
114
+ },
115
+ "svelte": {
116
+ "optional": true
117
+ },
118
+ "vue": {
119
+ "optional": true
120
+ }
121
+ },
122
+ "devDependencies": {
123
+ "@angular/compiler": "~22.0.8",
124
+ "@angular/compiler-cli": "~22.0.8",
125
+ "@angular/core": "~22.0.8",
126
+ "@types/node": "^26.0.0",
127
+ "@types/react": "^19.2.0",
128
+ "@vue/runtime-core": "^3.5.13",
129
+ "react": "19.2.3",
130
+ "solid-js": "^1.9.14",
131
+ "svelte": "^5.56.0",
132
+ "typescript": "~6.0.0",
133
+ "@symbiote-native/angular": "3.3.0",
134
+ "@symbiote-native/engine": "1.6.0",
135
+ "@symbiote-native/react": "3.3.0",
136
+ "@symbiote-native/solid": "3.2.0",
137
+ "@symbiote-native/svelte": "3.2.0",
138
+ "@symbiote-native/test-utils": "0.4.7",
139
+ "@symbiote-native/vue": "3.3.0"
140
+ },
141
+ "scripts": {
142
+ "typecheck": "tsc --build",
143
+ "clean": "rm -rf build-ngc",
144
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
145
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
146
+ }
6
147
  }
@@ -0,0 +1,95 @@
1
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { DescriptorOutlet, NativeViewBase } from '@symbiote-native/angular';
3
+ import type { IDescriptor } from '@symbiote-native/components';
4
+ import { createCameraView } from '../../core';
5
+ import type { ICameraViewHandle, ICameraViewProps } from '../../core';
6
+
7
+ // One list is the inputs of the component and the props it hands to the native view
8
+ const CAMERA_VIEW_INPUTS = [
9
+ 'facing',
10
+ 'flash',
11
+ 'zoom',
12
+ 'mode',
13
+ 'mute',
14
+ 'mirror',
15
+ 'autofocus',
16
+ 'active',
17
+ 'videoQuality',
18
+ 'videoBitrate',
19
+ 'animateShutter',
20
+ 'pictureSize',
21
+ 'selectedLens',
22
+ 'enableTorch',
23
+ 'videoStabilizationMode',
24
+ 'barcodeScannerSettings',
25
+ 'responsiveOrientationWhenOrientationLocked',
26
+ 'ratio',
27
+ 'onCameraReady',
28
+ 'onMountError',
29
+ 'onBarcodeScanned',
30
+ 'onResponsiveOrientationChanged',
31
+ 'onAvailableLensesChanged',
32
+ ] as const;
33
+
34
+ /** Angular twin of `expo-camera`'s `CameraView` */
35
+ @Component({
36
+ selector: 'CameraView',
37
+ standalone: true,
38
+ imports: [DescriptorOutlet],
39
+ inputs: [...CAMERA_VIEW_INPUTS],
40
+ changeDetection: ChangeDetectionStrategy.OnPush,
41
+ template: `@if (descriptor; as node) {
42
+ <symbiote-descriptor-outlet [node]="node" />
43
+ }`,
44
+ })
45
+ export class CameraView extends NativeViewBase {
46
+ // The inputs are named by `inputs`, these only type them for the template
47
+ declare facing?: ICameraViewProps['facing'];
48
+ declare flash?: ICameraViewProps['flash'];
49
+ declare zoom?: ICameraViewProps['zoom'];
50
+ declare mode?: ICameraViewProps['mode'];
51
+ declare mute?: ICameraViewProps['mute'];
52
+ declare mirror?: ICameraViewProps['mirror'];
53
+ declare autofocus?: ICameraViewProps['autofocus'];
54
+ declare active?: ICameraViewProps['active'];
55
+ declare videoQuality?: ICameraViewProps['videoQuality'];
56
+ declare videoBitrate?: ICameraViewProps['videoBitrate'];
57
+ declare animateShutter?: ICameraViewProps['animateShutter'];
58
+ declare pictureSize?: ICameraViewProps['pictureSize'];
59
+ declare selectedLens?: ICameraViewProps['selectedLens'];
60
+ declare enableTorch?: ICameraViewProps['enableTorch'];
61
+ declare videoStabilizationMode?: ICameraViewProps['videoStabilizationMode'];
62
+ declare barcodeScannerSettings?: ICameraViewProps['barcodeScannerSettings'];
63
+ declare responsiveOrientationWhenOrientationLocked?: ICameraViewProps['responsiveOrientationWhenOrientationLocked'];
64
+ declare ratio?: ICameraViewProps['ratio'];
65
+ declare onCameraReady?: ICameraViewProps['onCameraReady'];
66
+ declare onMountError?: ICameraViewProps['onMountError'];
67
+ declare onBarcodeScanned?: ICameraViewProps['onBarcodeScanned'];
68
+ declare onResponsiveOrientationChanged?: ICameraViewProps['onResponsiveOrientationChanged'];
69
+ declare onAvailableLensesChanged?: ICameraViewProps['onAvailableLensesChanged'];
70
+
71
+ private readonly view = createCameraView(() => this.hostNode());
72
+
73
+ // The functions of the view are copied onto the component, a `@ViewChild` reaches them
74
+ declare readonly takePictureAsync: ICameraViewHandle['takePictureAsync'];
75
+ declare readonly recordAsync: ICameraViewHandle['recordAsync'];
76
+ declare readonly toggleRecordingAsync: ICameraViewHandle['toggleRecordingAsync'];
77
+ declare readonly stopRecording: ICameraViewHandle['stopRecording'];
78
+ declare readonly pausePreview: ICameraViewHandle['pausePreview'];
79
+ declare readonly resumePreview: ICameraViewHandle['resumePreview'];
80
+ declare readonly getAvailablePictureSizesAsync: ICameraViewHandle['getAvailablePictureSizesAsync'];
81
+ declare readonly getAvailableLensesAsync: ICameraViewHandle['getAvailableLensesAsync'];
82
+ declare readonly getSupportedFeatures: ICameraViewHandle['getSupportedFeatures'];
83
+ declare readonly getHostNode: ICameraViewHandle['getHostNode'];
84
+
85
+ constructor() {
86
+ super();
87
+ Object.assign(this, this.view.handle);
88
+ }
89
+
90
+ protected override readonly propNames = CAMERA_VIEW_INPUTS;
91
+
92
+ protected override renderView(props: object): IDescriptor | null {
93
+ return this.view.render(props);
94
+ }
95
+ }
@@ -0,0 +1,8 @@
1
+ // @symbiote-native/camera/angular: камера, съёмка, запись видео и сканирование кодов на общем ядре
2
+
3
+ export { CameraView } from './camera-view';
4
+ export {
5
+ CameraPermissionsService,
6
+ MicrophonePermissionsService,
7
+ } from './services/camera-permissions.service';
8
+ export * from '../core';
@@ -0,0 +1,28 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { PermissionsServiceBase } from '@symbiote-native/angular';
3
+ import {
4
+ cameraPermissionMethods,
5
+ microphonePermissionMethods,
6
+ } from '../../core/camera-api';
7
+ import type { PermissionResponse } from 'expo-modules-core';
8
+
9
+ // Angular twin of `../../react`'s camera permission hooks
10
+ @Injectable({ providedIn: 'root' })
11
+ export class CameraPermissionsService extends PermissionsServiceBase<PermissionResponse> {
12
+ constructor() {
13
+ super(
14
+ cameraPermissionMethods.getMethod,
15
+ cameraPermissionMethods.requestMethod,
16
+ );
17
+ }
18
+ }
19
+
20
+ @Injectable({ providedIn: 'root' })
21
+ export class MicrophonePermissionsService extends PermissionsServiceBase<PermissionResponse> {
22
+ constructor() {
23
+ super(
24
+ microphonePermissionMethods.getMethod,
25
+ microphonePermissionMethods.requestMethod,
26
+ );
27
+ }
28
+ }
@@ -0,0 +1,51 @@
1
+ // What Google's code scanner adds to a result, Android only
2
+
3
+ type IAndroidBarcodeKind<TType extends string> = { type: TType };
4
+
5
+ export type IAndroidBarcode =
6
+ | (IAndroidBarcodeKind<'contactInfo'> & {
7
+ firstName?: string;
8
+ middleName?: string;
9
+ lastName?: string;
10
+ title?: string;
11
+ organization?: string;
12
+ email?: string;
13
+ phone?: string;
14
+ url?: string;
15
+ address?: string;
16
+ })
17
+ | (IAndroidBarcodeKind<'geoPoint'> & { lat: string; lng: string })
18
+ | (IAndroidBarcodeKind<'sms'> & { phoneNumber?: string; message?: string })
19
+ | (IAndroidBarcodeKind<'url'> & { url?: string })
20
+ | (IAndroidBarcodeKind<'calendarEvent'> & {
21
+ summary?: string;
22
+ description?: string;
23
+ location?: string;
24
+ start?: string;
25
+ end?: string;
26
+ })
27
+ | (IAndroidBarcodeKind<'driverLicense'> & {
28
+ firstName?: string;
29
+ middleName?: string;
30
+ lastName?: string;
31
+ licenseNumber?: string;
32
+ expiryDate?: string;
33
+ issueDate?: string;
34
+ addressStreet?: string;
35
+ addressCity?: string;
36
+ addressState?: string;
37
+ })
38
+ | (IAndroidBarcodeKind<'email'> & {
39
+ address?: string;
40
+ subject?: string;
41
+ body?: string;
42
+ })
43
+ | (IAndroidBarcodeKind<'phone'> & {
44
+ number?: string;
45
+ phoneNumberType?: string;
46
+ })
47
+ | (IAndroidBarcodeKind<'wifi'> & {
48
+ ssid?: string;
49
+ password?: string;
50
+ encryptionType?: string;
51
+ });
@@ -0,0 +1,99 @@
1
+ import { UnavailabilityError } from 'expo-modules-core';
2
+ import type { EventSubscription, PermissionResponse } from 'expo-modules-core';
3
+ import { CAMERA_PACKAGE_NAME, MODERN_SCANNER_EVENT } from './constants';
4
+ import { expoCamera } from './native-module';
5
+ import type {
6
+ ICameraBarcodeScanningResult,
7
+ ICameraBarcodeType,
8
+ ICameraScanningOptions,
9
+ ICameraScanningResult,
10
+ ICameraVideoCodec,
11
+ } from './types';
12
+
13
+ /** Whether the device has a camera, the permissions are a separate question */
14
+ export async function isCameraAvailableAsync(): Promise<boolean> {
15
+ if (!expoCamera.isAvailableAsync) {
16
+ throw new UnavailabilityError(CAMERA_PACKAGE_NAME, 'isAvailableAsync');
17
+ }
18
+ return expoCamera.isAvailableAsync();
19
+ }
20
+
21
+ /** iOS only */
22
+ export async function getAvailableVideoCodecsAsync(): Promise<
23
+ ICameraVideoCodec[]
24
+ > {
25
+ if (!expoCamera.getAvailableVideoCodecsAsync) {
26
+ throw new UnavailabilityError('Camera', 'getAvailableVideoCodecsAsync');
27
+ }
28
+ return expoCamera.getAvailableVideoCodecsAsync();
29
+ }
30
+
31
+ /** Whether the device has `DataScannerViewController` (iOS 16+) or Google's code scanner */
32
+ export function isModernBarcodeScannerAvailable(): boolean {
33
+ return expoCamera.isModernBarcodeScannerAvailable;
34
+ }
35
+
36
+ /** A device without the modern scanner does nothing here */
37
+ export async function launchScanner(
38
+ options: ICameraScanningOptions = { barcodeTypes: [] },
39
+ ): Promise<void> {
40
+ if (expoCamera.isModernBarcodeScannerAvailable) {
41
+ await expoCamera.launchScanner(options);
42
+ }
43
+ }
44
+
45
+ /** Dismisses the scanner on iOS, on Android it closes itself after a scan */
46
+ export async function dismissScanner(): Promise<void> {
47
+ if (expoCamera.isModernBarcodeScannerAvailable) {
48
+ await expoCamera.dismissScanner();
49
+ }
50
+ }
51
+
52
+ export function onModernBarcodeScanned(
53
+ listener: (event: ICameraScanningResult) => void,
54
+ ): EventSubscription {
55
+ return expoCamera.addListener(MODERN_SCANNER_EVENT, listener);
56
+ }
57
+
58
+ /** Only QR codes are scanned on iOS */
59
+ export async function scanFromURLAsync(
60
+ url: string,
61
+ barcodeTypes: ICameraBarcodeType[] = ['qr'],
62
+ ): Promise<ICameraBarcodeScanningResult[]> {
63
+ return expoCamera.scanFromURLAsync(url, barcodeTypes);
64
+ }
65
+
66
+ async function getCameraPermissionsAsync(): Promise<PermissionResponse> {
67
+ return expoCamera.getCameraPermissionsAsync();
68
+ }
69
+
70
+ async function requestCameraPermissionsAsync(): Promise<PermissionResponse> {
71
+ return expoCamera.requestCameraPermissionsAsync();
72
+ }
73
+
74
+ async function getMicrophonePermissionsAsync(): Promise<PermissionResponse> {
75
+ return expoCamera.getMicrophonePermissionsAsync();
76
+ }
77
+
78
+ async function requestMicrophonePermissionsAsync(): Promise<PermissionResponse> {
79
+ return expoCamera.requestMicrophonePermissionsAsync();
80
+ }
81
+
82
+ export const Camera = {
83
+ getCameraPermissionsAsync,
84
+ requestCameraPermissionsAsync,
85
+ getMicrophonePermissionsAsync,
86
+ requestMicrophonePermissionsAsync,
87
+ scanFromURLAsync,
88
+ };
89
+
90
+ // What every adapter's `createPermissionHook` binds, `useCameraPermissions` and its twin
91
+ export const cameraPermissionMethods = {
92
+ getMethod: getCameraPermissionsAsync,
93
+ requestMethod: requestCameraPermissionsAsync,
94
+ };
95
+
96
+ export const microphonePermissionMethods = {
97
+ getMethod: getMicrophonePermissionsAsync,
98
+ requestMethod: requestMicrophonePermissionsAsync,
99
+ };
@@ -0,0 +1,108 @@
1
+ import type { ISymbioteEvent } from '@symbiote-native/engine';
2
+ import { BARCODE_EVENT_THROTTLE_MS } from './constants';
3
+ import { handlePictureSaved } from './picture-options';
4
+ import type {
5
+ ICameraAvailableLenses,
6
+ ICameraBarcodeScanningResult,
7
+ ICameraCapturedPicture,
8
+ ICameraMountError,
9
+ ICameraResponsiveOrientation,
10
+ } from './types';
11
+
12
+ type IGuard<TPayload> = (value: unknown) => value is TPayload;
13
+
14
+ function hasField(value: unknown, key: string, kind: string): boolean {
15
+ return typeof Reflect.get(Object(value), key) === kind;
16
+ }
17
+
18
+ const isMountError: IGuard<ICameraMountError> = (
19
+ value,
20
+ ): value is ICameraMountError => hasField(value, 'message', 'string');
21
+
22
+ const isOrientation: IGuard<ICameraResponsiveOrientation> = (
23
+ value,
24
+ ): value is ICameraResponsiveOrientation =>
25
+ hasField(value, 'orientation', 'string');
26
+
27
+ const isLenses: IGuard<ICameraAvailableLenses> = (
28
+ value,
29
+ ): value is ICameraAvailableLenses =>
30
+ Array.isArray(Reflect.get(Object(value), 'lenses'));
31
+
32
+ const isScanningResult: IGuard<ICameraBarcodeScanningResult> = (
33
+ value,
34
+ ): value is ICameraBarcodeScanningResult =>
35
+ hasField(value, 'type', 'string') && hasField(value, 'data', 'string');
36
+
37
+ function isPictureSaved(
38
+ value: unknown,
39
+ ): value is { data: ICameraCapturedPicture; id: number } {
40
+ return hasField(value, 'id', 'number') && hasField(value, 'data', 'object');
41
+ }
42
+
43
+ // Hands the callback the payload inside the native event, and only when it has the expected shape
44
+ function forward<TPayload>(
45
+ props: object,
46
+ name: string,
47
+ isPayload: IGuard<TPayload>,
48
+ ) {
49
+ return (event: ISymbioteEvent): void => {
50
+ const callback: unknown = Reflect.get(props, name);
51
+ if (typeof callback === 'function' && isPayload(event.nativeEvent)) {
52
+ callback(event.nativeEvent);
53
+ }
54
+ };
55
+ }
56
+
57
+ export type ICameraViewEvents = (props: object) => Record<string, unknown>;
58
+
59
+ /** One per view, the throttle of the barcode events belongs to the view */
60
+ export function createCameraViewEvents(): ICameraViewEvents {
61
+ const lastScans = new Map<string, { json: string; at: number }>();
62
+
63
+ // Equal events of one type inside the window are the same scan seen again
64
+ const throttledScan =
65
+ (callback: (result: ICameraBarcodeScanningResult) => void) =>
66
+ (event: ISymbioteEvent): void => {
67
+ const result = event.nativeEvent;
68
+ if (!isScanningResult(result)) return;
69
+ const json = JSON.stringify(result);
70
+ const now = Date.now();
71
+ const last = lastScans.get(result.type);
72
+ if (last?.json === json && now - last.at < BARCODE_EVENT_THROTTLE_MS) {
73
+ return;
74
+ }
75
+ callback(result);
76
+ lastScans.set(result.type, { json, at: now });
77
+ };
78
+
79
+ return props => {
80
+ const onBarcodeScanned: unknown = Reflect.get(props, 'onBarcodeScanned');
81
+ const onCameraReady: unknown = Reflect.get(props, 'onCameraReady');
82
+ return {
83
+ onCameraReady: () => {
84
+ if (typeof onCameraReady === 'function') onCameraReady();
85
+ },
86
+ onMountError: forward(props, 'onMountError', isMountError),
87
+ onBarcodeScanned:
88
+ typeof onBarcodeScanned === 'function'
89
+ ? throttledScan(result => onBarcodeScanned(result))
90
+ : undefined,
91
+ onAvailableLensesChanged: forward(
92
+ props,
93
+ 'onAvailableLensesChanged',
94
+ isLenses,
95
+ ),
96
+ onResponsiveOrientationChanged: forward(
97
+ props,
98
+ 'onResponsiveOrientationChanged',
99
+ isOrientation,
100
+ ),
101
+ onPictureSaved: (event: ISymbioteEvent): void => {
102
+ if (isPictureSaved(event.nativeEvent)) {
103
+ handlePictureSaved({ nativeEvent: event.nativeEvent });
104
+ }
105
+ },
106
+ };
107
+ };
108
+ }
@@ -0,0 +1,105 @@
1
+ import { Platform, requireNativeModule } from 'expo-modules-core';
2
+ import { defineExpoViewMethods, getNativeTag } from '@symbiote-native/engine';
3
+ import type { ISymbioteNode } from '@symbiote-native/engine';
4
+ import { CAMERA_MODULE_NAME } from './constants';
5
+ import { expoCamera } from './native-module';
6
+ import type { ICameraPictureRef } from './native-module';
7
+ import {
8
+ ensurePictureOptions,
9
+ ensureRecordingOptions,
10
+ } from './picture-options';
11
+ import type {
12
+ ICameraCapturedPicture,
13
+ ICameraPictureOptions,
14
+ ICameraRecordingOptions,
15
+ ICameraSupportedFeatures,
16
+ } from './types';
17
+
18
+ export type ICameraViewHandle = {
19
+ /** Waits for `onCameraReady` first. With `pictureRef` it resolves to a native image reference */
20
+ takePictureAsync(
21
+ options: ICameraPictureOptions & { pictureRef: true },
22
+ ): Promise<ICameraPictureRef | undefined>;
23
+ takePictureAsync(
24
+ options?: ICameraPictureOptions,
25
+ ): Promise<ICameraCapturedPicture | undefined>;
26
+ /** Resolves when `stopRecording` runs, a limit is reached or the preview stops */
27
+ recordAsync(
28
+ options?: ICameraRecordingOptions,
29
+ ): Promise<{ uri: string } | undefined>;
30
+ /** Pauses or resumes the recording, iOS 18 and later on iOS */
31
+ toggleRecordingAsync(): Promise<void>;
32
+ stopRecording(): void;
33
+ pausePreview(): Promise<void>;
34
+ resumePreview(): Promise<void>;
35
+ getAvailablePictureSizesAsync(): Promise<string[]>;
36
+ /** Lenses exist on iOS only */
37
+ getAvailableLensesAsync(): Promise<string[]>;
38
+ getSupportedFeatures(): ICameraSupportedFeatures;
39
+ /** The host node of the mounted view, `createCameraTextureAsync` of a GL view takes it */
40
+ getHostNode(): ISymbioteNode | null;
41
+ };
42
+
43
+ const callViewFunction = defineExpoViewMethods(
44
+ requireNativeModule,
45
+ CAMERA_MODULE_NAME,
46
+ );
47
+
48
+ export function createCameraViewHandle(
49
+ getNode: () => ISymbioteNode | null | undefined,
50
+ ): ICameraViewHandle {
51
+ // Like upstream's `ref.current?.`, a view that has not committed yet answers nothing
52
+ function call<TResult>(
53
+ method: string,
54
+ args: readonly unknown[] = [],
55
+ ): TResult | undefined {
56
+ const node = getNode();
57
+ if (!node || getNativeTag(node) === undefined) return undefined;
58
+ return callViewFunction<TResult>(node, method, args);
59
+ }
60
+
61
+ async function takePictureAsync(
62
+ options: ICameraPictureOptions & { pictureRef: true },
63
+ ): Promise<ICameraPictureRef | undefined>;
64
+ async function takePictureAsync(
65
+ options?: ICameraPictureOptions,
66
+ ): Promise<ICameraCapturedPicture | undefined>;
67
+ async function takePictureAsync(options?: ICameraPictureOptions) {
68
+ const pictureOptions = ensurePictureOptions(options);
69
+ // The picture ref is an iOS feature, Android answers a plain picture
70
+ if (
71
+ Platform.select({ ios: Boolean(options?.pictureRef), default: false })
72
+ ) {
73
+ return call<Promise<ICameraPictureRef>>('takePictureRef', [
74
+ pictureOptions,
75
+ ]);
76
+ }
77
+ return call<Promise<ICameraCapturedPicture>>('takePicture', [
78
+ pictureOptions,
79
+ ]);
80
+ }
81
+
82
+ return {
83
+ takePictureAsync,
84
+ recordAsync: async options =>
85
+ call<Promise<{ uri: string }>>('record', [
86
+ ensureRecordingOptions(options),
87
+ ]),
88
+ toggleRecordingAsync: async () => call<Promise<void>>('toggleRecording'),
89
+ stopRecording: () => {
90
+ call('stopRecording');
91
+ },
92
+ pausePreview: async () => call<Promise<void>>('pausePreview'),
93
+ resumePreview: async () => call<Promise<void>>('resumePreview'),
94
+ getAvailablePictureSizesAsync: async () =>
95
+ (await call<Promise<string[]>>('getAvailablePictureSizes')) ?? [],
96
+ getAvailableLensesAsync: async () =>
97
+ (await call<Promise<string[]>>('getAvailableLenses')) ?? [],
98
+ getSupportedFeatures: () => ({
99
+ isModernBarcodeScannerAvailable:
100
+ expoCamera.isModernBarcodeScannerAvailable,
101
+ toggleRecordingAsyncAvailable: expoCamera.toggleRecordingAsyncAvailable,
102
+ }),
103
+ getHostNode: () => getNode() ?? null,
104
+ };
105
+ }