@symbiote-native/camera 0.0.0-stage → 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -2
- package/build/angular/camera-view/index.d.ts +43 -0
- package/build/angular/camera-view/index.js +101 -0
- package/build/angular/index.d.ts +3 -0
- package/build/angular/index.js +4 -0
- package/build/angular/services/camera-permissions.service.d.ts +8 -0
- package/build/angular/services/camera-permissions.service.js +82 -0
- package/build/core/android-barcode.d.ts +50 -0
- package/build/core/android-barcode.js +2 -0
- package/build/core/camera-api.d.ts +35 -0
- package/build/core/camera-api.js +68 -0
- package/build/core/camera-view-events.d.ts +3 -0
- package/build/core/camera-view-events.js +60 -0
- package/build/core/camera-view-handle.d.ts +26 -0
- package/build/core/camera-view-handle.js +40 -0
- package/build/core/camera-view.d.ts +12 -0
- package/build/core/camera-view.js +38 -0
- package/build/core/constants.d.ts +6 -0
- package/build/core/constants.js +7 -0
- package/build/core/index.d.ts +9 -0
- package/build/core/index.js +5 -0
- package/build/core/native-module.d.ts +28 -0
- package/build/core/native-module.js +3 -0
- package/build/core/picture-options.d.ts +9 -0
- package/build/core/picture-options.js +29 -0
- package/build/core/picture-ref.d.ts +2 -0
- package/build/core/picture-ref.js +3 -0
- package/build/core/props.d.ts +4 -0
- package/build/core/props.js +26 -0
- package/build/core/types.d.ts +170 -0
- package/build/core/types.js +1 -0
- package/build/react/camera-view.d.ts +8 -0
- package/build/react/camera-view.js +6 -0
- package/build/react/index.d.ts +5 -0
- package/build/react/index.js +4 -0
- package/build/react/use-camera-permissions.d.ts +9 -0
- package/build/react/use-camera-permissions.js +5 -0
- package/build/solid/camera-view.d.ts +33 -0
- package/build/solid/camera-view.js +4 -0
- package/build/solid/index.d.ts +4 -0
- package/build/solid/index.js +4 -0
- package/build/solid/use-camera-permissions.d.ts +10 -0
- package/build/solid/use-camera-permissions.js +5 -0
- package/build/svelte/camera-view.svelte +32 -0
- package/build/svelte/camera-view.svelte.d.ts +22 -0
- package/build/svelte/index.d.ts +4 -0
- package/build/svelte/index.js +4 -0
- package/build/svelte/use-camera-permissions.svelte.d.ts +9 -0
- package/build/svelte/use-camera-permissions.svelte.js +5 -0
- package/build/vue/camera-view.d.ts +2 -0
- package/build/vue/camera-view.js +4 -0
- package/build/vue/index.d.ts +4 -0
- package/build/vue/index.js +4 -0
- package/build/vue/use-camera-permissions.d.ts +10 -0
- package/build/vue/use-camera-permissions.js +5 -0
- package/build-ngc/angular/camera-view/index.d.ts +46 -0
- package/build-ngc/angular/camera-view/index.js +60 -0
- package/build-ngc/angular/camera-view/index.js.map +1 -0
- package/build-ngc/angular/index.d.ts +3 -0
- package/build-ngc/angular/index.js +5 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/services/camera-permissions.service.d.ts +13 -0
- package/build-ngc/angular/services/camera-permissions.service.js +28 -0
- package/build-ngc/angular/services/camera-permissions.service.js.map +1 -0
- package/build-ngc/core/android-barcode.d.ts +50 -0
- package/build-ngc/core/android-barcode.js +3 -0
- package/build-ngc/core/android-barcode.js.map +1 -0
- package/build-ngc/core/camera-api.d.ts +35 -0
- package/build-ngc/core/camera-api.js +69 -0
- package/build-ngc/core/camera-api.js.map +1 -0
- package/build-ngc/core/camera-view-events.d.ts +3 -0
- package/build-ngc/core/camera-view-events.js +61 -0
- package/build-ngc/core/camera-view-events.js.map +1 -0
- package/build-ngc/core/camera-view-handle.d.ts +26 -0
- package/build-ngc/core/camera-view-handle.js +41 -0
- package/build-ngc/core/camera-view-handle.js.map +1 -0
- package/build-ngc/core/camera-view.d.ts +12 -0
- package/build-ngc/core/camera-view.js +39 -0
- package/build-ngc/core/camera-view.js.map +1 -0
- package/build-ngc/core/constants.d.ts +6 -0
- package/build-ngc/core/constants.js +8 -0
- package/build-ngc/core/constants.js.map +1 -0
- package/build-ngc/core/index.d.ts +9 -0
- package/build-ngc/core/index.js +6 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +28 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/picture-options.d.ts +9 -0
- package/build-ngc/core/picture-options.js +30 -0
- package/build-ngc/core/picture-options.js.map +1 -0
- package/build-ngc/core/picture-ref.d.ts +2 -0
- package/build-ngc/core/picture-ref.js +4 -0
- package/build-ngc/core/picture-ref.js.map +1 -0
- package/build-ngc/core/props.d.ts +4 -0
- package/build-ngc/core/props.js +27 -0
- package/build-ngc/core/props.js.map +1 -0
- package/build-ngc/core/types.d.ts +170 -0
- package/build-ngc/core/types.js +2 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +22 -0
- package/package.json +144 -3
- package/src/angular/camera-view/index.ts +95 -0
- package/src/angular/index.ts +8 -0
- package/src/angular/services/camera-permissions.service.ts +28 -0
- package/src/core/android-barcode.ts +48 -0
- package/src/core/camera-api.ts +99 -0
- package/src/core/camera-view-events.ts +103 -0
- package/src/core/camera-view-handle.ts +93 -0
- package/src/core/camera-view.ts +56 -0
- package/src/core/constants.ts +11 -0
- package/src/core/index.ts +57 -0
- package/src/core/native-module.ts +57 -0
- package/src/core/picture-options.ts +46 -0
- package/src/core/picture-ref.ts +4 -0
- package/src/core/props.ts +31 -0
- package/src/core/types.ts +223 -0
- package/src/react/camera-view.ts +17 -0
- package/src/react/index.ts +7 -0
- package/src/react/use-camera-permissions.ts +21 -0
- package/src/solid/camera-view.ts +9 -0
- package/src/solid/index.ts +6 -0
- package/src/solid/use-camera-permissions.ts +22 -0
- package/src/svelte/camera-view.svelte +32 -0
- package/src/svelte/index.ts +9 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +6 -0
- package/src/svelte/use-camera-permissions.svelte.ts +21 -0
- package/src/vue/camera-view.ts +5 -0
- package/src/vue/index.ts +6 -0
- package/src/vue/use-camera-permissions.ts +22 -0
|
@@ -0,0 +1,48 @@
|
|
|
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'> & { number?: string; phoneNumberType?: string })
|
|
44
|
+
| (IAndroidBarcodeKind<'wifi'> & {
|
|
45
|
+
ssid?: string;
|
|
46
|
+
password?: string;
|
|
47
|
+
encryptionType?: string;
|
|
48
|
+
});
|
|
@@ -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,103 @@
|
|
|
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> = (value): value is ICameraMountError =>
|
|
19
|
+
hasField(value, 'message', 'string');
|
|
20
|
+
|
|
21
|
+
const isOrientation: IGuard<ICameraResponsiveOrientation> = (
|
|
22
|
+
value,
|
|
23
|
+
): value is ICameraResponsiveOrientation =>
|
|
24
|
+
hasField(value, 'orientation', 'string');
|
|
25
|
+
|
|
26
|
+
const isLenses: IGuard<ICameraAvailableLenses> = (
|
|
27
|
+
value,
|
|
28
|
+
): value is ICameraAvailableLenses =>
|
|
29
|
+
Array.isArray(Reflect.get(Object(value), 'lenses'));
|
|
30
|
+
|
|
31
|
+
const isScanningResult: IGuard<ICameraBarcodeScanningResult> = (
|
|
32
|
+
value,
|
|
33
|
+
): value is ICameraBarcodeScanningResult =>
|
|
34
|
+
hasField(value, 'type', 'string') && hasField(value, 'data', 'string');
|
|
35
|
+
|
|
36
|
+
function isPictureSaved(
|
|
37
|
+
value: unknown,
|
|
38
|
+
): value is { data: ICameraCapturedPicture; id: number } {
|
|
39
|
+
return hasField(value, 'id', 'number') && hasField(value, 'data', 'object');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Hands the callback the payload inside the native event, and only when it has the expected shape
|
|
43
|
+
function forward<TPayload>(
|
|
44
|
+
props: object,
|
|
45
|
+
name: string,
|
|
46
|
+
isPayload: IGuard<TPayload>,
|
|
47
|
+
) {
|
|
48
|
+
return (event: ISymbioteEvent): void => {
|
|
49
|
+
const callback: unknown = Reflect.get(props, name);
|
|
50
|
+
if (typeof callback === 'function' && isPayload(event.nativeEvent)) {
|
|
51
|
+
callback(event.nativeEvent);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type ICameraViewEvents = (props: object) => Record<string, unknown>;
|
|
57
|
+
|
|
58
|
+
/** One per view, the throttle of the barcode events belongs to the view */
|
|
59
|
+
export function createCameraViewEvents(): ICameraViewEvents {
|
|
60
|
+
const lastScans = new Map<string, { json: string; at: number }>();
|
|
61
|
+
|
|
62
|
+
// Equal events of one type inside the window are the same scan seen again
|
|
63
|
+
const throttledScan =
|
|
64
|
+
(callback: (result: ICameraBarcodeScanningResult) => void) =>
|
|
65
|
+
(event: ISymbioteEvent): void => {
|
|
66
|
+
const result = event.nativeEvent;
|
|
67
|
+
if (!isScanningResult(result)) return;
|
|
68
|
+
const json = JSON.stringify(result);
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const last = lastScans.get(result.type);
|
|
71
|
+
if (last?.json === json && now - last.at < BARCODE_EVENT_THROTTLE_MS) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
callback(result);
|
|
75
|
+
lastScans.set(result.type, { json, at: now });
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return props => {
|
|
79
|
+
const onBarcodeScanned: unknown = Reflect.get(props, 'onBarcodeScanned');
|
|
80
|
+
const onCameraReady: unknown = Reflect.get(props, 'onCameraReady');
|
|
81
|
+
return {
|
|
82
|
+
onCameraReady: () => {
|
|
83
|
+
if (typeof onCameraReady === 'function') onCameraReady();
|
|
84
|
+
},
|
|
85
|
+
onMountError: forward(props, 'onMountError', isMountError),
|
|
86
|
+
onBarcodeScanned:
|
|
87
|
+
typeof onBarcodeScanned === 'function'
|
|
88
|
+
? throttledScan(result => onBarcodeScanned(result))
|
|
89
|
+
: undefined,
|
|
90
|
+
onAvailableLensesChanged: forward(props, 'onAvailableLensesChanged', isLenses),
|
|
91
|
+
onResponsiveOrientationChanged: forward(
|
|
92
|
+
props,
|
|
93
|
+
'onResponsiveOrientationChanged',
|
|
94
|
+
isOrientation,
|
|
95
|
+
),
|
|
96
|
+
onPictureSaved: (event: ISymbioteEvent): void => {
|
|
97
|
+
if (isPictureSaved(event.nativeEvent)) {
|
|
98
|
+
handlePictureSaved({ nativeEvent: event.nativeEvent });
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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 { ensurePictureOptions, ensureRecordingOptions } from './picture-options';
|
|
8
|
+
import type {
|
|
9
|
+
ICameraCapturedPicture,
|
|
10
|
+
ICameraPictureOptions,
|
|
11
|
+
ICameraRecordingOptions,
|
|
12
|
+
ICameraSupportedFeatures,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
export type ICameraViewHandle = {
|
|
16
|
+
/** Waits for `onCameraReady` first. With `pictureRef` it resolves to a native image reference */
|
|
17
|
+
takePictureAsync(
|
|
18
|
+
options: ICameraPictureOptions & { pictureRef: true },
|
|
19
|
+
): Promise<ICameraPictureRef | undefined>;
|
|
20
|
+
takePictureAsync(
|
|
21
|
+
options?: ICameraPictureOptions,
|
|
22
|
+
): Promise<ICameraCapturedPicture | undefined>;
|
|
23
|
+
/** Resolves when `stopRecording` runs, a limit is reached or the preview stops */
|
|
24
|
+
recordAsync(
|
|
25
|
+
options?: ICameraRecordingOptions,
|
|
26
|
+
): Promise<{ uri: string } | undefined>;
|
|
27
|
+
/** Pauses or resumes the recording, iOS 18 and later on iOS */
|
|
28
|
+
toggleRecordingAsync(): Promise<void>;
|
|
29
|
+
stopRecording(): void;
|
|
30
|
+
pausePreview(): Promise<void>;
|
|
31
|
+
resumePreview(): Promise<void>;
|
|
32
|
+
getAvailablePictureSizesAsync(): Promise<string[]>;
|
|
33
|
+
/** Lenses exist on iOS only */
|
|
34
|
+
getAvailableLensesAsync(): Promise<string[]>;
|
|
35
|
+
getSupportedFeatures(): ICameraSupportedFeatures;
|
|
36
|
+
/** The host node of the mounted view, `createCameraTextureAsync` of a GL view takes it */
|
|
37
|
+
getHostNode(): ISymbioteNode | null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const callViewFunction = defineExpoViewMethods(
|
|
41
|
+
requireNativeModule,
|
|
42
|
+
CAMERA_MODULE_NAME,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export function createCameraViewHandle(
|
|
46
|
+
getNode: () => ISymbioteNode | null | undefined,
|
|
47
|
+
): ICameraViewHandle {
|
|
48
|
+
// Like upstream's `ref.current?.`, a view that has not committed yet answers nothing
|
|
49
|
+
function call<TResult>(
|
|
50
|
+
method: string,
|
|
51
|
+
args: readonly unknown[] = [],
|
|
52
|
+
): TResult | undefined {
|
|
53
|
+
const node = getNode();
|
|
54
|
+
if (!node || getNativeTag(node) === undefined) return undefined;
|
|
55
|
+
return callViewFunction<TResult>(node, method, args);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function takePictureAsync(
|
|
59
|
+
options: ICameraPictureOptions & { pictureRef: true },
|
|
60
|
+
): Promise<ICameraPictureRef | undefined>;
|
|
61
|
+
async function takePictureAsync(
|
|
62
|
+
options?: ICameraPictureOptions,
|
|
63
|
+
): Promise<ICameraCapturedPicture | undefined>;
|
|
64
|
+
async function takePictureAsync(options?: ICameraPictureOptions) {
|
|
65
|
+
const pictureOptions = ensurePictureOptions(options);
|
|
66
|
+
// The picture ref is an iOS feature, Android answers a plain picture
|
|
67
|
+
if (Platform.select({ ios: Boolean(options?.pictureRef), default: false })) {
|
|
68
|
+
return call<Promise<ICameraPictureRef>>('takePictureRef', [pictureOptions]);
|
|
69
|
+
}
|
|
70
|
+
return call<Promise<ICameraCapturedPicture>>('takePicture', [pictureOptions]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
takePictureAsync,
|
|
75
|
+
recordAsync: async options =>
|
|
76
|
+
call<Promise<{ uri: string }>>('record', [ensureRecordingOptions(options)]),
|
|
77
|
+
toggleRecordingAsync: async () => call<Promise<void>>('toggleRecording'),
|
|
78
|
+
stopRecording: () => {
|
|
79
|
+
call('stopRecording');
|
|
80
|
+
},
|
|
81
|
+
pausePreview: async () => call<Promise<void>>('pausePreview'),
|
|
82
|
+
resumePreview: async () => call<Promise<void>>('resumePreview'),
|
|
83
|
+
getAvailablePictureSizesAsync: async () =>
|
|
84
|
+
(await call<Promise<string[]>>('getAvailablePictureSizes')) ?? [],
|
|
85
|
+
getAvailableLensesAsync: async () =>
|
|
86
|
+
(await call<Promise<string[]>>('getAvailableLenses')) ?? [],
|
|
87
|
+
getSupportedFeatures: () => ({
|
|
88
|
+
isModernBarcodeScannerAvailable: expoCamera.isModernBarcodeScannerAvailable,
|
|
89
|
+
toggleRecordingAsyncAvailable: expoCamera.toggleRecordingAsyncAvailable,
|
|
90
|
+
}),
|
|
91
|
+
getHostNode: () => getNode() ?? null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Ядро `CameraView`: нативный view камеры, чьи функции вызываются через `ViewPrototypes`
|
|
2
|
+
import { requireNativeViewManager } from 'expo-modules-core';
|
|
3
|
+
import { el } from '@symbiote-native/components';
|
|
4
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
5
|
+
import { defineExpoNativeView, isDevBuild } from '@symbiote-native/engine';
|
|
6
|
+
import type { ISymbioteNode } from '@symbiote-native/engine';
|
|
7
|
+
import { createCameraViewEvents } from './camera-view-events';
|
|
8
|
+
import { createCameraViewHandle } from './camera-view-handle';
|
|
9
|
+
import type { ICameraViewHandle } from './camera-view-handle';
|
|
10
|
+
import { CAMERA_MODULE_NAME } from './constants';
|
|
11
|
+
import { ensureNativeProps } from './props';
|
|
12
|
+
|
|
13
|
+
// Регистрация идёт при рендере: побочный эффект барреля теряется в release
|
|
14
|
+
const cameraView = defineExpoNativeView(
|
|
15
|
+
requireNativeViewManager,
|
|
16
|
+
CAMERA_MODULE_NAME,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const cameraViewName = cameraView.name;
|
|
20
|
+
|
|
21
|
+
export const ensureCameraViewRegistered = cameraView.ensureRegistered;
|
|
22
|
+
|
|
23
|
+
// What upstream's `defaultProps` gave the class component
|
|
24
|
+
const DEFAULT_VIEW_PROPS = {
|
|
25
|
+
zoom: 0,
|
|
26
|
+
facing: 'back',
|
|
27
|
+
enableTorch: false,
|
|
28
|
+
mode: 'picture',
|
|
29
|
+
flash: 'off',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ICameraView = {
|
|
33
|
+
handle: ICameraViewHandle;
|
|
34
|
+
/** `null` means the view cannot register and the caller renders nothing */
|
|
35
|
+
render(props: object): IDescriptor | null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** One per mounted view, `getNode` answers the host node once it exists */
|
|
39
|
+
export function createCameraView(
|
|
40
|
+
getNode: () => ISymbioteNode | null | undefined,
|
|
41
|
+
): ICameraView {
|
|
42
|
+
const toEvents = createCameraViewEvents();
|
|
43
|
+
return {
|
|
44
|
+
handle: createCameraViewHandle(getNode),
|
|
45
|
+
render: props => {
|
|
46
|
+
if (!ensureCameraViewRegistered()) {
|
|
47
|
+
if (isDevBuild()) console.warn("'CameraView' is not available.");
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return el(cameraViewName(), {
|
|
51
|
+
...ensureNativeProps({ ...DEFAULT_VIEW_PROPS, ...props }),
|
|
52
|
+
...toEvents(props),
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Apart from `native-module`, which resolves the module on import: the view needs the name too
|
|
2
|
+
|
|
3
|
+
export const CAMERA_MODULE_NAME = 'ExpoCamera';
|
|
4
|
+
|
|
5
|
+
/** The label upstream puts into its `UnavailabilityError` */
|
|
6
|
+
export const CAMERA_PACKAGE_NAME = 'expo-camera';
|
|
7
|
+
|
|
8
|
+
/** Equal barcode events of one type inside this window are dropped */
|
|
9
|
+
export const BARCODE_EVENT_THROTTLE_MS = 500;
|
|
10
|
+
|
|
11
|
+
export const MODERN_SCANNER_EVENT = 'onModernBarcodeScanned';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Camera,
|
|
3
|
+
cameraPermissionMethods,
|
|
4
|
+
dismissScanner,
|
|
5
|
+
getAvailableVideoCodecsAsync,
|
|
6
|
+
isCameraAvailableAsync,
|
|
7
|
+
isModernBarcodeScannerAvailable,
|
|
8
|
+
launchScanner,
|
|
9
|
+
microphonePermissionMethods,
|
|
10
|
+
onModernBarcodeScanned,
|
|
11
|
+
scanFromURLAsync,
|
|
12
|
+
} from './camera-api';
|
|
13
|
+
export {
|
|
14
|
+
cameraViewName,
|
|
15
|
+
createCameraView,
|
|
16
|
+
ensureCameraViewRegistered,
|
|
17
|
+
type ICameraView,
|
|
18
|
+
} from './camera-view';
|
|
19
|
+
export type { ICameraViewHandle } from './camera-view-handle';
|
|
20
|
+
export { CAMERA_MODULE_NAME } from './constants';
|
|
21
|
+
export type { ICameraPictureRef } from './native-module';
|
|
22
|
+
export { PictureRef } from './picture-ref';
|
|
23
|
+
export type { IAndroidBarcode } from './android-barcode';
|
|
24
|
+
export type {
|
|
25
|
+
ICameraAvailableLenses,
|
|
26
|
+
ICameraBarcodeBounds,
|
|
27
|
+
ICameraBarcodePoint,
|
|
28
|
+
ICameraBarcodeScanningResult,
|
|
29
|
+
ICameraBarcodeSettings,
|
|
30
|
+
ICameraBarcodeType,
|
|
31
|
+
ICameraCapturedPicture,
|
|
32
|
+
ICameraFlashMode,
|
|
33
|
+
ICameraFocusMode,
|
|
34
|
+
ICameraMode,
|
|
35
|
+
ICameraMountError,
|
|
36
|
+
ICameraOrientation,
|
|
37
|
+
ICameraPhotoResult,
|
|
38
|
+
ICameraPictureOptions,
|
|
39
|
+
ICameraRatio,
|
|
40
|
+
ICameraRecordingOptions,
|
|
41
|
+
ICameraResponsiveOrientation,
|
|
42
|
+
ICameraSavePictureOptions,
|
|
43
|
+
ICameraScanningOptions,
|
|
44
|
+
ICameraScanningResult,
|
|
45
|
+
ICameraSupportedFeatures,
|
|
46
|
+
ICameraType,
|
|
47
|
+
ICameraVideoCodec,
|
|
48
|
+
ICameraVideoQuality,
|
|
49
|
+
ICameraVideoStabilization,
|
|
50
|
+
ICameraViewProps,
|
|
51
|
+
} from './types';
|
|
52
|
+
export {
|
|
53
|
+
PermissionStatus,
|
|
54
|
+
type EventSubscription,
|
|
55
|
+
type PermissionExpiration,
|
|
56
|
+
type PermissionResponse,
|
|
57
|
+
} from 'expo-modules-core';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
import type {
|
|
3
|
+
EventSubscription,
|
|
4
|
+
PermissionResponse,
|
|
5
|
+
SharedRef,
|
|
6
|
+
} from 'expo-modules-core';
|
|
7
|
+
import { CAMERA_MODULE_NAME } from './constants';
|
|
8
|
+
import type {
|
|
9
|
+
ICameraBarcodeScanningResult,
|
|
10
|
+
ICameraBarcodeType,
|
|
11
|
+
ICameraPhotoResult,
|
|
12
|
+
ICameraSavePictureOptions,
|
|
13
|
+
ICameraScanningOptions,
|
|
14
|
+
ICameraScanningResult,
|
|
15
|
+
ICameraVideoCodec,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
/** A reference to a native instance of an image, other Expo packages accept it as is */
|
|
19
|
+
export type ICameraPictureRef = SharedRef<'image'> & {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
/** Saves the image into the cache directory */
|
|
23
|
+
savePictureAsync(options?: ICameraSavePictureOptions): Promise<ICameraPhotoResult>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ICameraPictureRefClass = abstract new (
|
|
27
|
+
...args: never[]
|
|
28
|
+
) => ICameraPictureRef;
|
|
29
|
+
|
|
30
|
+
// `Type` and `FlashMode` map the friendly prop values to what the native view takes
|
|
31
|
+
export type ICameraNativeModule = {
|
|
32
|
+
Picture: ICameraPictureRefClass;
|
|
33
|
+
readonly isModernBarcodeScannerAvailable: boolean;
|
|
34
|
+
readonly toggleRecordingAsyncAvailable: boolean;
|
|
35
|
+
readonly Type: Record<string, string>;
|
|
36
|
+
readonly FlashMode: Record<string, string>;
|
|
37
|
+
isAvailableAsync?(): Promise<boolean>;
|
|
38
|
+
launchScanner(options?: ICameraScanningOptions): Promise<void>;
|
|
39
|
+
dismissScanner(): Promise<void>;
|
|
40
|
+
scanFromURLAsync(
|
|
41
|
+
url: string,
|
|
42
|
+
barcodeTypes?: ICameraBarcodeType[],
|
|
43
|
+
): Promise<ICameraBarcodeScanningResult[]>;
|
|
44
|
+
getCameraPermissionsAsync(): Promise<PermissionResponse>;
|
|
45
|
+
requestCameraPermissionsAsync(): Promise<PermissionResponse>;
|
|
46
|
+
getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
|
|
47
|
+
requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
|
|
48
|
+
getAvailableVideoCodecsAsync?(): Promise<ICameraVideoCodec[]>;
|
|
49
|
+
addListener(
|
|
50
|
+
eventName: string,
|
|
51
|
+
listener: (event: ICameraScanningResult) => void,
|
|
52
|
+
): EventSubscription;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const expoCamera = requireNativeModule<ICameraNativeModule>(
|
|
56
|
+
CAMERA_MODULE_NAME,
|
|
57
|
+
);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ICameraCapturedPicture,
|
|
3
|
+
ICameraPictureOptions,
|
|
4
|
+
ICameraRecordingOptions,
|
|
5
|
+
} from './types';
|
|
6
|
+
|
|
7
|
+
const MIRROR_DEPRECATED =
|
|
8
|
+
'The `mirror` option is deprecated. Please use the `mirror` prop on the `CameraView` instead.';
|
|
9
|
+
|
|
10
|
+
// The callbacks wait here for the native event that carries their picture
|
|
11
|
+
const pictureSavedCallbacks = new Map<
|
|
12
|
+
number,
|
|
13
|
+
(picture: ICameraCapturedPicture) => void
|
|
14
|
+
>();
|
|
15
|
+
let nextPictureId = 1;
|
|
16
|
+
|
|
17
|
+
export function ensurePictureOptions(
|
|
18
|
+
options?: ICameraPictureOptions,
|
|
19
|
+
): ICameraPictureOptions {
|
|
20
|
+
if (!options || typeof options !== 'object') return {};
|
|
21
|
+
if (options.mirror) console.warn(MIRROR_DEPRECATED);
|
|
22
|
+
const { onPictureSaved, ...rest } = options;
|
|
23
|
+
const withQuality = { ...rest, quality: options.quality ?? 1 };
|
|
24
|
+
if (!onPictureSaved) return withQuality;
|
|
25
|
+
const id = nextPictureId++;
|
|
26
|
+
pictureSavedCallbacks.set(id, onPictureSaved);
|
|
27
|
+
return { ...withQuality, id, fastMode: true };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ensureRecordingOptions(
|
|
31
|
+
options: ICameraRecordingOptions = {},
|
|
32
|
+
): ICameraRecordingOptions {
|
|
33
|
+
if (options.mirror) console.warn(MIRROR_DEPRECATED);
|
|
34
|
+
return options;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function handlePictureSaved({
|
|
38
|
+
nativeEvent,
|
|
39
|
+
}: {
|
|
40
|
+
nativeEvent: { data: ICameraCapturedPicture; id: number };
|
|
41
|
+
}): void {
|
|
42
|
+
const callback = pictureSavedCallbacks.get(nativeEvent.id);
|
|
43
|
+
if (!callback) return;
|
|
44
|
+
pictureSavedCallbacks.delete(nativeEvent.id);
|
|
45
|
+
callback(nativeEvent.data);
|
|
46
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { expoCamera } from './native-module';
|
|
2
|
+
|
|
3
|
+
type INativeProps = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
// Values under these keys become native options, the tables come from the native module
|
|
6
|
+
function conversionTableFor(key: string): Record<string, unknown> | undefined {
|
|
7
|
+
if (key === 'type') return expoCamera.Type;
|
|
8
|
+
if (key === 'flash') return expoCamera.FlashMode;
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function convertNativeProps(props: object): INativeProps {
|
|
13
|
+
return Object.fromEntries(
|
|
14
|
+
Object.entries(props).map(([key, value]) => {
|
|
15
|
+
const table = conversionTableFor(key);
|
|
16
|
+
return [key, typeof value === 'string' && table ? table[value] : value];
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The props the native view takes, built from the friendly ones of the view */
|
|
22
|
+
export function ensureNativeProps(props: object): INativeProps {
|
|
23
|
+
const { poster: _poster, ...native } = convertNativeProps(props);
|
|
24
|
+
return {
|
|
25
|
+
...native,
|
|
26
|
+
barcodeScannerEnabled: Boolean(Reflect.get(props, 'onBarcodeScanned')),
|
|
27
|
+
flashMode: Reflect.get(props, 'flash') ?? 'off',
|
|
28
|
+
mute: Reflect.get(props, 'mute') ?? false,
|
|
29
|
+
autoFocus: Reflect.get(props, 'autofocus') ?? 'off',
|
|
30
|
+
};
|
|
31
|
+
}
|