From ec02338661ef2adbd9c61c56757782321787a5c6 Mon Sep 17 00:00:00 2001 From: dingchenjie Date: Sat, 12 Sep 2026 15:38:56 +0800 Subject: [PATCH] Adapt to the RN0.84 framework and upgrade the baseline to 4.2.1, version: 4.0.0-beta.1 Signed-off-by: dingchenjie --- CHANGELOG.md | 5 + COMMITTERS.md | 7 + OAT.xml | 75 + .../README.OpenSource => README.OpenSource | 6 +- README.md | 403 +- README_en.md | 398 ++ harmony/reactNativeCamera.har | Bin 47753 -> 50413 bytes harmony/reactNativeCamera/index.ets | 32 +- harmony/reactNativeCamera/oh-package.json5 | 4 +- .../src/main/cpp/ComponentDescriptors.h | 32 +- .../src/main/cpp/EventEmitters.cpp | 32 +- .../src/main/cpp/EventEmitters.h | 32 +- .../src/main/cpp/FaceNativeTurboModule.cpp | 32 +- .../src/main/cpp/FaceNativeTurboModule.h | 32 +- .../cpp/NativeCameraComponentDescriptor.h | 32 +- .../cpp/NativeCameraEventEmitRequestHandler.h | 32 +- .../src/main/cpp/NativeCameraJSIBinder.h | 34 +- .../src/main/cpp/NativeCameraPackage.h | 32 +- .../reactNativeCamera/src/main/cpp/Props.cpp | 34 +- .../reactNativeCamera/src/main/cpp/Props.h | 34 +- .../src/main/cpp/RNCCameraModule.cpp | 4 + .../src/main/cpp/ShadowNodes.cpp | 32 +- .../src/main/cpp/ShadowNodes.h | 32 +- .../reactNativeCamera/src/main/cpp/States.cpp | 32 +- .../reactNativeCamera/src/main/cpp/States.h | 32 +- .../src/main/ets/FaceDectorPackage.ets | 32 +- .../src/main/ets/FaceDectorTurboModule.ts | 32 +- .../reactNativeCamera/src/main/ets/Logger.ts | 32 +- .../src/main/ets/ReactCameraView.ets | 110 +- .../src/main/ets/ReactNativeCameraPackage.ets | 54 +- .../main/ets/ReactNativeCameraTurboModule.ts | 162 +- .../src/main/ets/core/CameraConfig.ts | 18 +- .../src/main/ets/core/CameraDeviceInfo.ts | 22 +- .../src/main/ets/core/CameraEnumBox.ts | 21 +- .../src/main/ets/service/CameraManager.ts | 16 +- .../src/main/ets/service/CameraSession.ts | 105 +- .../src/main/ets/service/ScanSession.ts | 16 +- .../src/main/ets/types/AuthType.ts | 32 +- .../src/main/ets/types/CameraError.ts | 16 +- .../main/ets/types/ReactNativeCameraProps.ets | 34 +- .../src/main/ets/types/TempCameraProp.ts | 34 +- .../src/main/ets/types/TemporaryFile.ts | 16 +- .../src/main/ets/types/VideoFile.ts | 16 +- .../main/ets/types/VisionCameraModuleSpec.ts | 16 +- .../main/ets/types/VisionCameraViewSpec.ts | 16 +- .../src/main/ets/utils/Logger.ts | 16 +- .../src/main/ets/utils/OrientationUtils.ets | 32 +- .../src/main/ets/utils/PermissionUtils.ts | 16 +- .../src/main/ets/utils/Utils.ts | 16 +- harmony/reactNativeCamera/ts.ets | 32 +- jest.config.js | 15 + jest/__tests__/constants-defaultprops-test.js | 104 + jest/__tests__/events-test.js | 91 + jest/__tests__/facedetector-test.js | 34 + jest/__tests__/index-test.js | 60 + jest/__tests__/platform-routing-test.js | 108 + jest/babel.config.js | 26 + jest/coverage/clover.xml | 280 ++ jest/coverage/coverage-final.json | 4 + .../coverage/lcov-report/FaceDetector.js.html | 265 ++ jest/coverage/lcov-report/RNCamera.js.html | 3262 +++++++++++++++++ jest/coverage/lcov-report/base.css | 224 ++ jest/coverage/lcov-report/block-navigation.js | 87 + jest/coverage/lcov-report/favicon.png | Bin 0 -> 445 bytes jest/coverage/lcov-report/index.html | 146 + jest/coverage/lcov-report/index.js.html | 103 + jest/coverage/lcov-report/prettify.css | 1 + jest/coverage/lcov-report/prettify.js | 2 + .../lcov-report/sort-arrow-sprite.png | Bin 0 -> 138 bytes jest/coverage/lcov-report/sorter.js | 210 ++ jest/coverage/lcov.info | 605 +++ jest/helpers/nativeCameraMock.js | 36 + jest/helpers/nativeCameraModuleMock.js | 31 + jest/helpers/nativeFaceDetectorMock.js | 18 + jest/helpers/propTypesMock.js | 46 + jest/helpers/reactMock.js | 47 + jest/helpers/reactNativeMock.js | 77 + jest/jest.config.js | 94 + jest/jest.setup.js | 22 + package.json | 18 +- src/NativeCamera.ts | 2 + src/NativeCameraModule.ts | 4 + src/RNCamera.js | 92 +- src/index.js | 4 +- types/index.d.ts | 26 +- 85 files changed, 7861 insertions(+), 615 deletions(-) create mode 100644 COMMITTERS.md create mode 100644 OAT.xml rename harmony/reactNativeCamera/README.OpenSource => README.OpenSource (72%) create mode 100644 README_en.md create mode 100644 jest.config.js create mode 100644 jest/__tests__/constants-defaultprops-test.js create mode 100644 jest/__tests__/events-test.js create mode 100644 jest/__tests__/facedetector-test.js create mode 100644 jest/__tests__/index-test.js create mode 100644 jest/__tests__/platform-routing-test.js create mode 100644 jest/babel.config.js create mode 100644 jest/coverage/clover.xml create mode 100644 jest/coverage/coverage-final.json create mode 100644 jest/coverage/lcov-report/FaceDetector.js.html create mode 100644 jest/coverage/lcov-report/RNCamera.js.html create mode 100644 jest/coverage/lcov-report/base.css create mode 100644 jest/coverage/lcov-report/block-navigation.js create mode 100644 jest/coverage/lcov-report/favicon.png create mode 100644 jest/coverage/lcov-report/index.html create mode 100644 jest/coverage/lcov-report/index.js.html create mode 100644 jest/coverage/lcov-report/prettify.css create mode 100644 jest/coverage/lcov-report/prettify.js create mode 100644 jest/coverage/lcov-report/sort-arrow-sprite.png create mode 100644 jest/coverage/lcov-report/sorter.js create mode 100644 jest/coverage/lcov.info create mode 100644 jest/helpers/nativeCameraMock.js create mode 100644 jest/helpers/nativeCameraModuleMock.js create mode 100644 jest/helpers/nativeFaceDetectorMock.js create mode 100644 jest/helpers/propTypesMock.js create mode 100644 jest/helpers/reactMock.js create mode 100644 jest/helpers/reactNativeMock.js create mode 100644 jest/jest.config.js create mode 100644 jest/jest.setup.js diff --git a/CHANGELOG.md b/CHANGELOG.md index b2f8c00cf..d667ec7be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ [**THE CHANGELOG OF FURTHER VERSIONS (STARTING WITH 1.4.0) IS MAINTAINED WITH GITHUB RELEASES AND CAN BE FOUND HERE**](https://github.com/react-native-community/react-native-camera/releases) + +#### 4.0.0-beta.1 + +* Adapt to the RN0.84 framework and upgrade the baseline to 4.2.1 + #### 3.42.0 * release version 3.42.0 diff --git a/COMMITTERS.md b/COMMITTERS.md new file mode 100644 index 000000000..c2ee75d65 --- /dev/null +++ b/COMMITTERS.md @@ -0,0 +1,7 @@ +## Committers列表 + +### 以下是此项目的committer人员 + +不区分先后顺序 + +- [xiafeng_xf_admin](https://gitee.com/xiafeng_xf_admin) \ No newline at end of file diff --git a/OAT.xml b/OAT.xml new file mode 100644 index 000000000..3b9f3f8a0 --- /dev/null +++ b/OAT.xml @@ -0,0 +1,75 @@ + + + + LICENSE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/harmony/reactNativeCamera/README.OpenSource b/README.OpenSource similarity index 72% rename from harmony/reactNativeCamera/README.OpenSource rename to README.OpenSource index c87a179b4..a1ef097c5 100644 --- a/harmony/reactNativeCamera/README.OpenSource +++ b/README.OpenSource @@ -2,9 +2,9 @@ { "Name": "react-native-camera", "License": "MIT License", - "License File": "https://github.com/react-native-camera/react-native-camera/blob/v3.40.0/LICENSE", - "Version Number": "3.40.0", - "Owner" : "react-native-camera" + "License File": "https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE", + "Version Number": "v4.2.1", + "Owner" : "ding_chengjie@hoperun.com", "Upstream URL": "https://github.com/react-native-camera/react-native-camera", "Description": "A Camera component for React Native. Also supports barcode scanning!" } diff --git a/README.md b/README.md index d09eeb1c3..4636a80c1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,402 @@ -# @react-native-ohos/react-native-camera +> 模板版本:v0.4.2 -This project is based on [react-native-camera](https://github.com/react-native-camera/react-native-camera) +

+

react-native-camera

+

-## Documentation +本项目基于 [react-native-camera](https://github.com/react-native-camera/react-native-camera/tree/v4.2.1) 开发。 -- [中文 ](https://gitcode.com/CPF-RN/usage-docs/blob/master/zh-cn/react-native-camera.md) +请到三方库的 Releases 发布地址查看配套的版本信息:[@react-native-ohos/react-native-camera Releases](https://github.com/react-native-oh-library/react-native-camera/releases)。对于未发布到npm的旧版本,请参考[安装指南](./tgz-usage.md)安装tgz包。 -- [英文 ](https://gitcode.com/CPF-RN/usage-docs/blob/master/en/react-native-camera.md) +| 三方库名称 | 三方库版本(npm地址) | 发布信息 | 支持RN版本 | Autolink | 编译API版本 | 社区基线版本 | 源码地址 | +| - | - | - | - | - | - | - | - | +| @react-native-ohos/react-native-camera | [~4.0.0 (开发中)](https://www.npmjs.com/package/@react-native-ohos/react-native-camera) | [Releases](https://github.com/react-native-oh-library/react-native-camera/releases) | 0.84.* | 是 | API12+ | v4.2.1 | [sig](https://github.com/react-native-oh-library/react-native-camera/tree/sig) | +| @react-native-ohos/react-native-camera | [~3.42.0](https://www.npmjs.com/package/@react-native-ohos/react-native-camera) | [Releases](https://github.com/react-native-oh-library/react-native-camera/releases) | 0.82.* | 是 | API12+ | v3.40.0 | [br_rnoh0.82](https://github.com/react-native-oh-library/react-native-camera/tree/br_rnoh0.82) | +| @react-native-ohos/react-native-camera | [~3.41.1](https://www.npmjs.com/package/@react-native-ohos/react-native-camera) | [Releases](https://github.com/react-native-oh-library/react-native-camera/releases) | 0.77.* | 否 | API12+ | v3.40.0 | [br_rnoh0.77](https://github.com/react-native-oh-library/react-native-camera/tree/br_rnoh0.77) | +| @react-native-ohos/react-native-camera | [~3.40.1](https://www.npmjs.com/package/@react-native-ohos/react-native-camera) | [Releases](https://github.com/react-native-oh-library/react-native-camera/releases) | 0.72.* | 否 | API12+ | v3.40.0 | [br_rnoh0.72](https://github.com/react-native-oh-library/react-native-camera/tree/br_rnoh0.72) | -## License +## 简介 -This library is licensed under [The MIT License (MIT)](https://github.com/react-native-camera/react-native-camera/blob/master/LICENSE) +react-native-camera 是 React Native 的相机组件,同时支持条码扫描。
+本仓库为 `@react-native-ohos/react-native-camera`,当前版本(4.0.0-beta.1)已适配 RN 0.84 框架,并将社区基线升级至 react-native-camera v4.2.1:JS 层采用 codegen spec(NativeCamera / NativeCameraModule / NativeFaceDetector)接入,鸿蒙侧基于 Camera Kit / AVRecorder 实现预览、拍照、录像与扫码能力。 + +## 下载安装 + +进入到工程目录并输入以下命令: + +**npm** + +```bash +npm install @react-native-ohos/react-native-camera +``` + +**yarn** + +```bash +yarn add @react-native-ohos/react-native-camera +``` + +## Link + +| | 是否支持autolink | RN框架版本 | +| - | - | - | +| ~4.0.0 | 是 | 0.84 | + +使用AutoLink的工程需要根据该文档配置,Autolink框架指导文档:https://gitcode.com/openharmony-sig/ohos_react_native/blob/master/docs/zh-cn/Autolinking.md + +如您使用的版本支持 Autolink,并且工程已接入 Autolink,可跳过ManualLink配置。 +
+ ManualLink: 此步骤为手动配置原生依赖项的指导 + +首先需要使用 DevEco Studio 打开项目里的 HarmonyOS 工程 `harmony`。 + +### 1. Overrides RN SDK + +为了让工程依赖同一个版本的 RN SDK,需要在工程根目录的 `oh-package.json5` 添加 overrides 字段,指向工程需要使用的 RN SDK 版本。替换的版本既可以是一个具体的版本号,也可以是一个模糊版本,还可以是本地存在的 HAR 包或源码目录。 + +```json +{ + "overrides": { + "@rnoh/react-native-openharmony": "file:./react_native_openharmony.har" + } +} +``` + +### 2. 引入原生端代码 + +方法一:通过 har 包引入(推荐) + +> [!TIP] har 包位于三方库安装路径的 `harmony` 文件夹下。 + +打开 `entry/oh-package.json5`,添加以下依赖 + +```json +"dependencies": { + "@react-native-ohos/react-native-camera": "file:../../node_modules/@react-native-ohos/react-native-camera/harmony/reactNativeCamera.har" + } +``` + +点击右上角的 `sync` 按钮 + +或者在命令行终端执行: + +```bash +cd entry +ohpm install +``` + +方法二:直接链接源码 + +> [!TIP] 如需使用直接链接源码,请参考[直接链接源码说明](https://gitcode.com/CPF-RN/usage-docs/blob/master/zh-cn/link-source-code.md) + +### 3. 配置 CMakeLists 和引入 NativeCameraPackage + +打开 `entry/src/main/cpp/CMakeLists.txt`,添加: + +```diff ++ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules") + +# RNOH_BEGIN: manual_package_linking_1 ++ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-camera/src/main/cpp" ./reactNativeCamera) +# RNOH_END: manual_package_linking_1 + +# RNOH_BEGIN: manual_package_linking_2 ++ target_link_libraries(rnoh_app PUBLIC rnoh_native_camera) +# RNOH_END: manual_package_linking_2 +``` + +打开 `entry/src/main/cpp/PackageProvider.cpp`,添加: + +```diff +#include "RNOH/PackageProvider.h" ++ #include "NativeCameraPackage.h" + +using namespace rnoh; + +std::vector> PackageProvider::getPackages(Package::Context ctx) { + return { ++ std::make_shared(ctx) + }; +} +``` + +### 4. 在 ArkTS 侧引入 ReactNativeCameraPackage + +打开 `entry/src/main/ets/RNPackagesFactory.ts`,添加: + +```diff + ... ++ import ReactNativeCameraPackage from '@react-native-ohos/react-native-camera'; + +export function createRNPackages(ctx: RNPackageContext): RNPackage[] { + return [ ++ new ReactNativeCameraPackage(ctx) + ]; +} +``` + +> [!TIP] 本库的 ArkTS 组件(ReactCameraView)由 `ReactNativeCameraPackage` 内部的组件 builder 注册(`createWrappedCustomRNComponentBuilderByComponentNameMap`)自动提供,宿主无需在 `buildCustomRNComponent` 或 `arkTsComponentNames` 中额外配置。 +
+ +### 运行 + +点击右上角的 `sync` 按钮 + +或者在命令行终端执行: + +```bash +cd entry +ohpm install +``` + +然后编译、运行即可。 + +## 约束与限制 + +### 兼容性 + +要使用此库,需要使用正确的 React-Native 和 RNOH 版本。另外,还需要使用配套的 DevEco Studio 和 手机 ROM。 + +在以下版本验证通过: + +1. RNOH: 0.84.2; SDK: HarmonyOS 6.0.1 Release SDK; IDE: DevEco Studio 6.0.1 Release; ROM: 6.0.0.120 SP7; + +### 编译运行API要求 + +> [!TIP] 当前三方库所有版本均已实现版本隔离,支持在 `API12+` 工程编译,及 `API12+` ROM运行。 + +> [!TIP] 以下功能依赖特定版本的API,使用 `低于指定API版本的工程编译` 或 `低于指定API版本的ROM运行` 均可能导致部分功能受限。 +1. `whiteBalance` 属性需要在支持 `API20+` 的工程编译,并在支持 `API20+` 的 ROM 上运行,方可生效。 + +### 权限要求 + +#### 在 entry 目录下的 module.json5 中添加权限 + +打开 `entry/src/main/module.json5`,添加: + +```diff +... +"requestPermissions": [ ++ { ++ "name": "ohos.permission.CAMERA", ++ "reason": "$string:camera_reason", ++ "usedScene": { ++ "abilities": [ ++ "EntryAbility" ++ ], ++ "when":"inuse" ++ } ++ }, ++ { ++ "name": "ohos.permission.MICROPHONE", ++ "reason": "$string:microphone_reason", ++ "usedScene": { ++ "abilities": [ ++ "EntryAbility" ++ ], ++ "when":"inuse" ++ } ++ }, +] +``` + +#### 在 entry 目录下添加申请以上权限的原因 + +打开 `entry/src/main/resources/base/element/string.json`,添加: + +```diff +... +{ + "string": [ ++ { ++ "name": "camera_reason", ++ "value": "使用相机" ++ }, ++ { ++ "name": "microphone_reason", ++ "value": "使用麦克风" ++ }, + ] +} +``` + +## 使用示例 + +下面的代码展示了这个库的基本使用场景: + +> [!WARNING] 使用时 import 的库名不变。 + +```js +import * as React from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { RNCamera } from "@react-native-ohos/react-native-camera"; + +export function CameraExample() { + const cameraRef = React.useRef(null); + + const takePicture = () => { + const camera = cameraRef.current; + if (!camera) { + return; + } + camera + .takePictureAsync({ quality: 0.5 }) + .then((photoResult: any) => { + console.log('takepicture result:' + (photoResult?.path ?? photoResult?.uri)); + }) + .catch((error: unknown) => { + console.warn('takepicture failed:', error); + }); + }; + + return ( + + + + 拍照 + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1 }, + cameraPreview: { width: '100%', aspectRatio: 56 / 100 }, + actionBtn: { + margin: 10, + padding: 10, + backgroundColor: '#1E90FF', + borderRadius: 8, + alignItems: 'center', + }, + actionBtnText: { color: '#fff', fontSize: 16 }, +}); +``` + +## 接口说明 + +> [!TIP] "Platform"列表示该属性在原三方库上支持的平台。 + +> [!TIP] "HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性;no 则表示不支持;partially 表示部分支持。使用方法跨平台一致,效果对标 iOS 或 Android 的效果。 + +### RNCamera + +| Name | Description | Type | 函数签名 | Required | Platform | HarmonyOS Support | +| ---- | ----------- | ---- | -------- | -------- |----------|-------------------| +| children | 设置子组件 | View | | no | iOS/Android | yes | +| type | 选择摄像头 | string | | no | iOS/Android | yes | +| cameraId | 精确指定摄像头设备(getCameraIdsAsync 返回的 id) | string | | no | iOS/Android | yes | +| flashMode | 设置闪光灯 | string | | no | iOS/Android | yes | +| exposure | 设置曝光 | number | | no | iOS/Android | yes | +| autoFocus | 设置自动对焦 | string | | no | iOS/Android | yes | +| whiteBalance | 白平衡 | string | | no | iOS/Android | yes | +| captureAudio | 是否开启录音 | boolean | | no | iOS/Android | yes | +| zoom | 设置缩放比例 | number | | no | iOS/Android | yes | +| focusDepth | 非自动对焦时,设置对焦值 | number | | no | iOS/Android | yes | +| maxZoom | 设置最大缩放比例 | number | | no | iOS/Android | yes | +| pictureSize | 设置默认图片分辨率 | string | | no | iOS/Android | yes | +| playSoundOnRecord | 录制开始/结束时播放系统提示音 | boolean | | no | Android | no | +| onCameraReady | 相机就绪回调,无参数 | function | `() => void` | no | iOS/Android | yes | +| onBarCodeRead | 接收 barcode 扫描数据回调;event 含 data(条码内容)、type(码型)、bounds(位置)等 | function | `(event: BarCodeReadEvent) => void` | no | iOS/Android | yes | +| onFacesDetected | 接收人脸识别数据回调;response.faces 为人脸数组(harmony 暂不支持) | function | `(response: {faces: Face[]}) => void` | no | iOS/Android | no | +| onDoubleTap | 双击预览回调;origin 为触点坐标 {x, y}(组件相对坐标) | function | `(origin: Point) => void` | no | iOS/Android | yes | +| notAuthorizedView | 未授权显示的ui | View | | no | Android | no | +| pendingAuthorizationView | 正在授权显示的ui | View | | no | Android | no | +| ratio | 相机比例 | string | | no | Android | no | +| defaultVideoQuality | 默认视频分辨率 | string | | no | iOS/Android | yes | +| autoFocusPointOfInterest | 设置自动对焦点(x/y) | Point | | no | iOS/Android | no | +| useNativeZoom | 启用原生捏合缩放手势 | boolean | | no | iOS/Android | yes | +| onStatusChange | 相机状态变化回调;event 含 cameraStatus、recordAudioPermissionStatus | function | `(event: {cameraStatus, recordAudioPermissionStatus}) => void` | no | iOS/Android | yes | +| onMountError | 相机异常回调;error.message 为错误信息 | function | `(error: {message}) => void` | no | iOS/Android | yes | +| onPictureTaken | 拍照完成回调,无参数 | function | `() => void` | no | iOS/Android | yes | +| onRecordingStart | 录像开始回调;event.nativeEvent 含 uri、videoOrientation、deviceOrientation | function | `(event: {nativeEvent: {uri, videoOrientation, deviceOrientation}}) => void` | no | iOS/Android | yes | +| onRecordingEnd | 录像结束回调,无参数 | function | `() => void` | no | iOS/Android | yes | +| videoStabilizationMode | 视频防抖模式 | string | | no | iOS | no | +| barCodeTypes | 条码识别类型数组 | BarCodeType[] | | no | iOS/Android | no | +| detectedImageInEvent | 扫码回调携带原始图像 | boolean | | no | iOS/Android | no | +| rectOfInterest | 限定条码识别区域 | RectOfInterest | | no | iOS/Android | no | +| googleVisionBarcodeType | 条码类型(Google Vision) | enum | | no | Android | no | +| googleVisionBarcodeMode | 条码识别模式(Google Vision) | enum | | no | Android | no | +| onGoogleVisionBarcodesDetected | Google 条码识别回调 | function | `(event: {type, barcodes, target}) => void` | no | Android | no | +| onFaceDetectionError | 人脸检测错误回调 | function | `(response: {isOperational}) => void` | no | iOS/Android | no | +| faceDetectionMode | 人脸检测模式 | string | | no | iOS/Android | no | +| faceDetectionLandmarks | 人脸特征点检测 | string | | no | iOS/Android | no | +| faceDetectionClassifications | 人脸分类检测 | string | | no | iOS/Android | no | +| trackingEnabled | 人脸跟踪 | boolean | | no | iOS/Android | no | +| onTextRecognized | 文字识别回调 | function | `(response: {textBlocks}) => void` | no | iOS/Android | no | +| onTap | 单击预览回调;origin 为触点坐标 {x, y} | function | `(origin: Point) => void` | no | iOS | no | +| onAudioInterrupted | 音频会话中断回调,无参数 | function | `() => void` | no | iOS | no | +| onAudioConnected | 音频会话连接回调,无参数 | function | `() => void` | no | iOS | no | +| onSubjectAreaChanged | 主体区域变化回调 | function | `(event: {nativeEvent: {prevPoint: {x, y}}}) => void` | no | iOS | no | +| useCamera2Api | 使用 Android Camera2 API | boolean | | no | Android | no | +| playSoundOnCapture | 拍照快门音 | boolean | | no | Android | no | +| permissionDialogTitle | 权限弹窗标题(已废弃) | string | | no | Android | no | +| permissionDialogMessage | 权限弹窗文案(已废弃) | string | | no | Android | no | +| androidCameraPermissionOptions | 相机权限弹窗配置 | object | | no | Android | no | +| androidRecordAudioPermissionOptions | 录音权限弹窗配置 | object | | no | Android | no | +| cameraViewDimensions | 扫码区域尺寸(Android) | object | | no | Android | no | +| keepAudioSession | 卸载时不释放音频会话 | boolean | | no | iOS | no | + +### camera + +| Name | Description | Type | 函数签名 | Required | Platform | HarmonyOS Support | +| ---- | ----------- | ---- | -------- | -------- | --------|-------------------| +| takePictureAsync | 照相;options 可选:quality(0~1)、imageType(jpeg/png)、path 等;resolve 出 TakePictureResponse(width、height、path、pictureOrientation、deviceOrientation) | function | `(options?: TakePictureOptions) => Promise` | no | iOS/Android | yes | +| recordAsync | 开始录像;options 可选:quality、maxDuration、maxFileSize、mute、path 等;resolve 出 RecordResponse(uri、videoOrientation、deviceOrientation、isRecordingInterrupted) | function | `(options?: RecordOptions) => Promise` | no | iOS/Android | yes | +| stopRecording | 停止录像,无参数,无返回值;harmony 停录后有转码延迟,待 recordAsync resolve | function | `() => void` | no | iOS/Android | yes | +| pauseRecording | 暂停录像,无参数,无返回值;仅录像中有效 | function | `() => void` | no | iOS/Android | yes | +| resumeRecording | 恢复录像,无参数,无返回值;仅暂停后有效 | function | `() => void` | no | iOS/Android | yes | +| pausePreview | 暂停预览(画面冻结),无参数,无返回值 | function | `() => void` | no | iOS/Android | yes | +| resumePreview | 恢复预览,无参数,无返回值 | function | `() => void` | no | iOS/Android | yes | +| getCameraIdsAsync | 获取可用摄像头列表;resolve 出 CameraDeviceInfo[],成员:{ id: string(设备 id,可设给 cameraId)、type: number(摄像头类型)、deviceType?: string } | function | `() => Promise` | no | iOS/Android | yes | +| getAvailablePictureSizes | 获取可用图片分辨率;resolve 出 string[],如 "1920x1080" | function | `() => Promise` | no | iOS/Android | yes | +| getSupportedPreviewFpsRange | 获取支持的帧率范围;resolve 出 string[],每项格式 "min,max" | function | `() => Promise` | no | iOS/Android | yes | +| checkIfVideoIsValid | 校验录像文件是否为有效视频;path 为视频文件路径;返回 true=有效、false=文件不存在或非视频 | function | `(path: string) => Promise` | no | iOS/Android | yes | +| hasTorch | 查询设备是否支持闪光灯;返回 true/false | function | `() => Promise` | no | iOS/Android | yes | +| refreshAuthorizationStatus | 重新请求相机/麦克风权限;resolve 后可读取最新授权状态 | function | `() => Promise` | no | iOS/Android | yes | +| getSupportedRatiosAsync | 获取支持的预览宽高比;resolve 出 string[],如 "4:3"、"16:9" | function | `() => Promise` | no | Android | yes | + +## 遗留问题 + +- [ ] onFacesDetected 人脸检测暂不支持 [issue#8](https://github.com/react-native-oh-library/react-native-camera/issues/8) + + +## 其他 + +- notAuthorizedView、pendingAuthorizationView、ratio 是 Android 独有属性;harmony 已提供授权时的系统弹窗。 +- playSoundOnRecord 源于 Android 语义(MediaActionSound),OHOS 相机无快门音开关对等 API,当前为 no-op,属性可正常传入不影响其他功能。 +- checkIfVideoIsValid 失败语义平台差异:harmony 对"文件不存在/元数据解析失败"返回 false;Android 上游对加载失败保守返回 true。 + +## 目录结构 + +```` +/react-native-camera # 项目根目录 +├── harmony # 鸿蒙适配代码 +│ └── reactNativeCamera.har # har包 +│ └── reactNativeCamera # 鸿蒙适配核心代码 +│ └─ index.ets # 鸿蒙适配代码入口 +│ └─ src/main/ets # ArkTS 实现层 +│ └─ src/main/cpp # C++ 层(NativeCameraPackage/Props/RNCCameraModule 等) +├── src # RN 代码 +│ └─ index.js # 入口文件 +│ └─ RNCamera.js / FaceDetector.js # 组件与能力封装 +│ └─ NativeCamera.ts / NativeCameraModule.ts / NativeFaceDetector.ts # codegen spec +├── types # TS 类型定义 +├── README.md # 中文安装使用方法 +├── README_en.md # 英文安装使用方法 +```` + +## 贡献代码 + +使用过程中发现任何问题都可以提交 [Issue](https://github.com/react-native-oh-library/react-native-camera/issues),当然,也非常欢迎提交 [PR](https://github.com/react-native-oh-library/react-native-camera/pulls) 。 + +## 开源协议 + +本项目基于 [MIT License](https://github.com/react-native-camera/react-native-camera/blob/master/LICENSE) ,请自由地享受和参与开源。 diff --git a/README_en.md b/README_en.md new file mode 100644 index 000000000..179d3d71b --- /dev/null +++ b/README_en.md @@ -0,0 +1,398 @@ +> Document Template: v0.4.2 + +

+

react-native-camera

+

+ +This project is based on [react-native-camera](https://github.com/react-native-camera/react-native-camera/tree/v4.2.1). + +Please check the matching version information on the Releases page: [@react-native-ohos/react-native-camera Releases](https://github.com/react-native-oh-library/react-native-camera/releases). For older versions not published to npm, please refer to the [Installation Guide](./tgz-usage.md) to install tgz packages. + +| Name | Version(Npm Address) | Release Information | Supported RN Version | Supported Autolink | Compile API Version | Community Baseline Version | Source code address | +| - | - | - | - | - | - | - | - | +| @react-native-ohos/react-native-camera | [~4.0.0 (In development)](https://www.npmjs.com/package/@react-native-ohos/react-native-camera) | [Releases](https://github.com/react-native-oh-library/react-native-camera/releases) | 0.84.* | Yes | API12+ | v4.2.1 | [sig](https://github.com/react-native-oh-library/react-native-camera/tree/sig) | +| @react-native-ohos/react-native-camera | [~3.42.0](https://www.npmjs.com/package/@react-native-ohos/react-native-camera) | [Releases](https://github.com/react-native-oh-library/react-native-camera/releases) | 0.82.* | Yes | API12+ | v3.40.0 | [br_rnoh0.82](https://github.com/react-native-oh-library/react-native-camera/tree/br_rnoh0.82) | +| @react-native-ohos/react-native-camera | [~3.41.1](https://www.npmjs.com/package/@react-native-ohos/react-native-camera) | [Releases](https://github.com/react-native-oh-library/react-native-camera/releases) | 0.77.* | No | API12+ | v3.40.0 | [br_rnoh0.77](https://github.com/react-native-oh-library/react-native-camera/tree/br_rnoh0.77) | +| @react-native-ohos/react-native-camera | [~3.40.1](https://www.npmjs.com/package/@react-native-ohos/react-native-camera) | [Releases](https://github.com/react-native-oh-library/react-native-camera/releases) | 0.72.* | No | API12+ | v3.40.0 | [br_rnoh0.72](https://github.com/react-native-oh-library/react-native-camera/tree/br_rnoh0.72) | + +## Introduction + +react-native-camera is a camera component for React Native, which also reads barcodes.
+This repository hosts `@react-native-ohos/react-native-camera`. The current version (4.0.0-beta.1) adapts to the RN 0.84 framework and upgrades the community baseline to react-native-camera v4.2.1: the JS layer integrates via codegen specs (NativeCamera / NativeCameraModule / NativeFaceDetector), and the HarmonyOS side implements preview, photo capture, video recording and barcode scanning based on Camera Kit / AVRecorder. + +## Installation + +Go to the project directory and execute the following instruction: + +**npm** + +```bash +npm install @react-native-ohos/react-native-camera +``` + +**yarn** + +```bash +yarn add @react-native-ohos/react-native-camera +``` + +## Link + +| | Supported Autolink | RN framework version | +| - | - | - | +| ~4.0.0 | Yes | 0.84 | + +Projects using AutoLink should configure according to this document. Autolink framework guide: https://gitcode.com/openharmony-sig/ohos_react_native/blob/master/docs/en/Autolinking.md + +If the version you use supports Autolink and your project has integrated Autolink, you can skip the ManualLink configuration. +
+ ManualLink: guidance for manually configuring native dependencies + +First, open the `harmony` directory of the project with DevEco Studio. + +### 1. Overrides RN SDK + +To keep the project on a single RN SDK version, add an `overrides` field in the project root `oh-package.json5`, pointing to the RN SDK version required by the project. + +```json +{ + "overrides": { + "@rnoh/react-native-openharmony": "file:./react_native_openharmony.har" + } +} +``` + +### 2. Introducing Native Code + +Method 1: via har package (recommended) + +> [!TIP] The har package is located in the `harmony` folder of the library installation path. + +Open `entry/oh-package.json5` and add the following dependency: + +```json +"dependencies": { + "@react-native-ohos/react-native-camera": "file:../../node_modules/@react-native-ohos/react-native-camera/harmony/reactNativeCamera.har" + } +``` + +Click the `sync` button in the top-right corner, or run: + +```bash +cd entry +ohpm install +``` + +Method 2: direct source linking + +> [!TIP] For direct source linking, please refer to [link-source-code](https://gitcode.com/CPF-RN/usage-docs/blob/master/en/link-source-code.md) + +### 3. Configuring CMakeLists and Introducing NativeCameraPackage + +Open `entry/src/main/cpp/CMakeLists.txt` and add: + +```diff ++ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules") + +# RNOH_BEGIN: manual_package_linking_1 ++ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-camera/src/main/cpp" ./reactNativeCamera) +# RNOH_END: manual_package_linking_1 + +# RNOH_BEGIN: manual_package_linking_2 ++ target_link_libraries(rnoh_app PUBLIC rnoh_native_camera) +# RNOH_END: manual_package_linking_2 +``` + +Open `entry/src/main/cpp/PackageProvider.cpp` and add: + +```diff +#include "RNOH/PackageProvider.h" ++ #include "NativeCameraPackage.h" + +using namespace rnoh; + +std::vector> PackageProvider::getPackages(Package::Context ctx) { + return { ++ std::make_shared(ctx) + }; +} +``` + +### 4. Introducing ReactNativeCameraPackage on the ArkTS side + +Open `entry/src/main/ets/RNPackagesFactory.ts` and add: + +```diff + ... ++ import ReactNativeCameraPackage from '@react-native-ohos/react-native-camera'; + +export function createRNPackages(ctx: RNPackageContext): RNPackage[] { + return [ ++ new ReactNativeCameraPackage(ctx) + ]; +} +``` + +> [!TIP] The ArkTS component (ReactCameraView) is provided automatically by the component builder registry inside `ReactNativeCameraPackage` (`createWrappedCustomRNComponentBuilderByComponentNameMap`); the host does not need extra configuration in `buildCustomRNComponent` or `arkTsComponentNames`. +
+ +### Running + +Click the `sync` button in the top-right corner, or run: + +```bash +cd entry +ohpm install +``` + +Then build and run. + +## Constraints + +### Compatibility + +To use this library, a correct React-Native and RNOH version is required, together with matching DevEco Studio and device ROM. + +Verified on: + +1. RNOH: 0.84.2; SDK: HarmonyOS 6.0.1 Release SDK; IDE: DevEco Studio 6.0.1 Release; ROM: 6.0.0.120 SP7; + +### Compilation and Runtime API Requirements + +> [!TIP] All versions of this library are version-isolated and support compiling with `API12+` projects and running on `API12+` ROMs. + +> [!TIP] The following features depend on specific API versions; compiling with projects or running on ROMs below the specified API version may cause partial feature limitation. +1. The `whiteBalance` property requires a project compiled with `API20+` and a ROM supporting `API20+` to take effect. + +### Permission Requirements + +#### Add permissions in `module.json5` under the `entry` directory + +Open `entry/src/main/module.json5` and add: + +```diff +... +"requestPermissions": [ ++ { ++ "name": "ohos.permission.CAMERA", ++ "reason": "$string:camera_reason", ++ "usedScene": { ++ "abilities": [ ++ "EntryAbility" ++ ], ++ "when":"inuse" ++ } ++ }, ++ { ++ "name": "ohos.permission.MICROPHONE", ++ "reason": "$string:microphone_reason", ++ "usedScene": { ++ "abilities": [ ++ "EntryAbility" ++ ], ++ "when":"inuse" ++ } ++ }, +] +``` + +#### Add the permission rationale strings under the `entry` directory + +Open `entry/src/main/resources/base/element/string.json` and add: + +```diff +... +{ + "string": [ ++ { ++ "name": "camera_reason", ++ "value": "Use the camera" ++ }, ++ { ++ "name": "microphone_reason", ++ "value": "Use the microphone" ++ }, + ] +} +``` + +## Example + +The following code shows the basic usage of this library: + +> [!WARNING] The import name stays unchanged. + +```js +import * as React from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { RNCamera } from "@react-native-ohos/react-native-camera"; + +export function CameraExample() { + const cameraRef = React.useRef(null); + + const takePicture = () => { + const camera = cameraRef.current; + if (!camera) { + return; + } + camera + .takePictureAsync({ quality: 0.5 }) + .then((photoResult: any) => { + console.log('takepicture result:' + (photoResult?.path ?? photoResult?.uri)); + }) + .catch((error: unknown) => { + console.warn('takepicture failed:', error); + }); + }; + + return ( + + + + Take Photo + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1 }, + cameraPreview: { width: '100%', aspectRatio: 56 / 100 }, + actionBtn: { + margin: 10, + padding: 10, + backgroundColor: '#1E90FF', + borderRadius: 8, + alignItems: 'center', + }, + actionBtnText: { color: '#fff', fontSize: 16 }, +}); +``` + +## Properties + +> [!TIP] The "Platform" column indicates the platforms supported by the property in the original third-party library. + +> [!TIP] In the "HarmonyOS Support" column, yes means the property is supported on HarmonyOS; no means not supported; partially means partially supported. Usage is consistent across platforms, benchmarked against iOS or Android behavior. + +### RNCamera + +| Name | Description | Type | Function Signature | Required | Platform | HarmonyOS Support | +| ---- | ----------- | ---- | ------------------ | -------- |----------|-------------------| +| children | Set child components | View | | no | iOS/Android | yes | +| type | Select camera | string | | no | iOS/Android | yes | +| cameraId | Precisely specify the camera device (the id returned by getCameraIdsAsync) | string | | no | iOS/Android | yes | +| flashMode | Set flash mode | string | | no | iOS/Android | yes | +| exposure | Set exposure | number | | no | iOS/Android | yes | +| autoFocus | Set auto focus | string | | no | iOS/Android | yes | +| whiteBalance | White balance | string | | no | iOS/Android | yes | +| captureAudio | Enable audio recording | boolean | | no | iOS/Android | yes | +| zoom | Set zoom ratio | number | | no | iOS/Android | yes | +| focusDepth | Set focus value in non-auto-focus mode | number | | no | iOS/Android | yes | +| maxZoom | Set max zoom ratio | number | | no | iOS/Android | yes | +| pictureSize | Set default picture resolution | string | | no | iOS/Android | yes | +| playSoundOnRecord | Play system sound at recording start/stop | boolean | | no | Android | no | +| onCameraReady | Camera ready callback, no arguments | function | `() => void` | no | iOS/Android | yes | +| onBarCodeRead | Barcode scanning callback; event contains data (barcode content), type (barcode format), bounds (position) | function | `(event: BarCodeReadEvent) => void` | no | iOS/Android | yes | +| onFacesDetected | Face detection callback; response.faces is the face array (not yet supported on harmony) | function | `(response: {faces: Face[]}) => void` | no | iOS/Android | no | +| onDoubleTap | Double tap on preview callback; origin is the touch point {x, y} (component-relative) | function | `(origin: Point) => void` | no | iOS/Android | yes | +| notAuthorizedView | UI shown when not authorized | View | | no | Android | no | +| pendingAuthorizationView | UI shown while authorizing | View | | no | Android | no | +| ratio | Camera ratio | string | | no | Android | no | +| defaultVideoQuality | Default video quality | string | | no | iOS/Android | yes | +| autoFocusPointOfInterest | Set auto focus point (x/y) | Point | | no | iOS/Android | no | +| useNativeZoom | Enable native pinch-to-zoom gesture | boolean | | no | iOS/Android | yes | +| onStatusChange | Camera status change callback; event contains cameraStatus, recordAudioPermissionStatus | function | `(event: {cameraStatus, recordAudioPermissionStatus}) => void` | no | iOS/Android | yes | +| onMountError | Camera error callback; error.message is the error message | function | `(error: {message}) => void` | no | iOS/Android | yes | +| onPictureTaken | Picture taken callback, no arguments | function | `() => void` | no | iOS/Android | yes | +| onRecordingStart | Recording start callback; event.nativeEvent contains uri, videoOrientation, deviceOrientation | function | `(event: {nativeEvent: {uri, videoOrientation, deviceOrientation}}) => void` | no | iOS/Android | yes | +| onRecordingEnd | Recording end callback, no arguments | function | `() => void` | no | iOS/Android | yes | +| videoStabilizationMode | Video stabilization mode | string | | no | iOS | no | +| barCodeTypes | Barcode type array | BarCodeType[] | | no | iOS/Android | no | +| detectedImageInEvent | Include raw image in barcode callback | boolean | | no | iOS/Android | no | +| rectOfInterest | Limit barcode recognition area | RectOfInterest | | no | iOS/Android | no | +| googleVisionBarcodeType | Barcode type (Google Vision) | enum | | no | Android | no | +| googleVisionBarcodeMode | Barcode recognition mode (Google Vision) | enum | | no | Android | no | +| onGoogleVisionBarcodesDetected | Google Vision barcode callback | function | `(event: {type, barcodes, target}) => void` | no | Android | no | +| onFaceDetectionError | Face detection error callback | function | `(response: {isOperational}) => void` | no | iOS/Android | no | +| faceDetectionMode | Face detection mode | string | | no | iOS/Android | no | +| faceDetectionLandmarks | Face landmarks detection | string | | no | iOS/Android | no | +| faceDetectionClassifications | Face classification detection | string | | no | iOS/Android | no | +| trackingEnabled | Face tracking | boolean | | no | iOS/Android | no | +| onTextRecognized | Text recognition callback | function | `(response: {textBlocks}) => void` | no | iOS/Android | no | +| onTap | Single tap on preview callback; origin is the touch point {x, y} | function | `(origin: Point) => void` | no | iOS | no | +| onAudioInterrupted | Audio session interruption callback, no arguments | function | `() => void` | no | iOS | no | +| onAudioConnected | Audio session connected callback, no arguments | function | `() => void` | no | iOS | no | +| onSubjectAreaChanged | Subject area changed callback | function | `(event: {nativeEvent: {prevPoint: {x, y}}}) => void` | no | iOS | no | +| useCamera2Api | Use Android Camera2 API | boolean | | no | Android | no | +| playSoundOnCapture | Shutter sound on capture | boolean | | no | Android | no | +| permissionDialogTitle | Permission dialog title (deprecated) | string | | no | Android | no | +| permissionDialogMessage | Permission dialog message (deprecated) | string | | no | Android | no | +| androidCameraPermissionOptions | Camera permission dialog config | object | | no | Android | no | +| androidRecordAudioPermissionOptions | Record-audio permission dialog config | object | | no | Android | no | +| cameraViewDimensions | Scan area dimensions (Android) | object | | no | Android | no | +| keepAudioSession | Keep audio session on unmount | boolean | | no | iOS | no | + +### camera + +| Name | Description | Type | Function Signature | Required | Platform | HarmonyOS Support | +| ---- | ----------- | ---- | ------------------ | -------- | --------|-------------------| +| takePictureAsync | Take a photo; optional options: quality (0~1), imageType (jpeg/png), path, etc.; resolves with TakePictureResponse (width, height, path, pictureOrientation, deviceOrientation) | function | `(options?: TakePictureOptions) => Promise` | no | iOS/Android | yes | +| recordAsync | Start recording; optional options: quality, maxDuration, maxFileSize, mute, path, etc.; resolves with RecordResponse (uri, videoOrientation, deviceOrientation, isRecordingInterrupted) | function | `(options?: RecordOptions) => Promise` | no | iOS/Android | yes | +| stopRecording | Stop recording, no arguments, no return value; on harmony there is a transcoding delay before recordAsync resolves | function | `() => void` | no | iOS/Android | yes | +| pauseRecording | Pause recording, no arguments, no return value; valid only while recording | function | `() => void` | no | iOS/Android | yes | +| resumeRecording | Resume recording, no arguments, no return value; valid only after pause | function | `() => void` | no | iOS/Android | yes | +| pausePreview | Pause preview (frame freeze), no arguments, no return value | function | `() => void` | no | iOS/Android | yes | +| resumePreview | Resume preview, no arguments, no return value | function | `() => void` | no | iOS/Android | yes | +| getCameraIdsAsync | Get available cameras; resolves with CameraDeviceInfo[], members: { id: string (device id, settable to cameraId), type: number (camera type), deviceType?: string } | function | `() => Promise` | no | iOS/Android | yes | +| getAvailablePictureSizes | Get available picture sizes; resolves with string[] such as "1920x1080" | function | `() => Promise` | no | iOS/Android | yes | +| getSupportedPreviewFpsRange | Get supported fps ranges; resolves with string[], each item "min,max" | function | `() => Promise` | no | iOS/Android | yes | +| checkIfVideoIsValid | Validate a recorded file; path is the video file path; returns true = valid, false = not found or not a video | function | `(path: string) => Promise` | no | iOS/Android | yes | +| hasTorch | Query flash support; returns true/false | function | `() => Promise` | no | iOS/Android | yes | +| refreshAuthorizationStatus | Re-request camera/microphone permission; after resolve the latest authorization status can be read | function | `() => Promise` | no | iOS/Android | yes | +| getSupportedRatiosAsync | Get supported preview aspect ratios; resolves with string[] such as "4:3", "16:9" | function | `() => Promise` | no | Android | yes | + +## Known Issues + +- [ ] onFacesDetected (face detection) is not yet supported [issue#8](https://github.com/react-native-oh-library/react-native-camera/issues/8) + + +## Others + +- notAuthorizedView, pendingAuthorizationView and ratio are Android-only properties; HarmonyOS provides the system permission dialog when requesting authorization. +- playSoundOnRecord originates from Android semantics (MediaActionSound); the OHOS camera has no shutter-sound equivalent API, so it is currently a no-op. The property can be passed normally without affecting other features. +- checkIfVideoIsValid failure semantics differ by platform: HarmonyOS returns false for "file not found / metadata parsing failure"; the Android upstream conservatively returns true on load failure. + +## Directory Structure + +```` +/react-native-camera # Project root +├── harmony # HarmonyOS adaptation code +│ └── reactNativeCamera.har # har package +│ └── reactNativeCamera # Core HarmonyOS adaptation code +│ └─ index.ets # Entry of the HarmonyOS adaptation code +│ └─ src/main/ets # ArkTS implementation layer +│ └─ src/main/cpp # C++ layer (NativeCameraPackage/Props/RNCCameraModule, etc.) +├── src # RN code +│ └─ index.js # Entry file +│ └─ RNCamera.js / FaceDetector.js # Component and capability wrappers +│ └─ NativeCamera.ts / NativeCameraModule.ts / NativeFaceDetector.ts # codegen specs +├── types # TS type definitions +├── README.md # Chinese installation and usage guide +├── README_en.md # English installation and usage guide +```` + +## Contributing + +If you find any problem during usage, feel free to submit an [Issue](https://github.com/react-native-oh-library/react-native-camera/issues); PRs are also welcome ([Pull Requests](https://github.com/react-native-oh-library/react-native-camera/pulls)). + +## License + +This project is based on the [MIT License](https://github.com/react-native-camera/react-native-camera/blob/master/LICENSE), please feel free to enjoy and participate in open source. diff --git a/harmony/reactNativeCamera.har b/harmony/reactNativeCamera.har index 83fa5482632b42512c1dada4cfb1060c3a3c20af..864753264a3b0fb29cd92d5f8a56713d716ad828 100644 GIT binary patch literal 50413 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

}y#HA2D@Fv=467laj7J`+p*(qxYjA=X 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 literal 47753 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%0D6(>i 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 diff --git a/harmony/reactNativeCamera/index.ets b/harmony/reactNativeCamera/index.ets index 0ddc9136f..45f8e0d79 100644 --- a/harmony/reactNativeCamera/index.ets +++ b/harmony/reactNativeCamera/index.ets @@ -1,26 +1,18 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2023 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + export * from "./ts" export * from "./src/main/ets/ReactCameraView" export { ReactNativeCameraPackage as default } from "./src/main/ets/ReactNativeCameraPackage" diff --git a/harmony/reactNativeCamera/oh-package.json5 b/harmony/reactNativeCamera/oh-package.json5 index 59b8a7aa8..fadba5daf 100644 --- a/harmony/reactNativeCamera/oh-package.json5 +++ b/harmony/reactNativeCamera/oh-package.json5 @@ -6,9 +6,9 @@ }, "author": "", "name": "@react-native-ohos/react-native-camera", - "description": "Please describe the basic information.", + "description": "React Native camera component for OpenHarmony. Supports camera preview, photo capture, video recording, barcode scanning, with flash/focus/white-balance/zoom control.", "main": "index.ets", - "version": "3.42.1-rc.1", + "version": "4.0.0-beta.1", "dependencies": { "@rnoh/react-native-openharmony": "file:../../node_modules/@react-native-oh/react-native-harmony/react_native_openharmony.har", "@ohos/mp4parser": "^2.0.6" diff --git a/harmony/reactNativeCamera/src/main/cpp/ComponentDescriptors.h b/harmony/reactNativeCamera/src/main/cpp/ComponentDescriptors.h index 9496f8e0b..7c10d3f9f 100644 --- a/harmony/reactNativeCamera/src/main/cpp/ComponentDescriptors.h +++ b/harmony/reactNativeCamera/src/main/cpp/ComponentDescriptors.h @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * diff --git a/harmony/reactNativeCamera/src/main/cpp/EventEmitters.cpp b/harmony/reactNativeCamera/src/main/cpp/EventEmitters.cpp index d96f1d421..eea0294d2 100644 --- a/harmony/reactNativeCamera/src/main/cpp/EventEmitters.cpp +++ b/harmony/reactNativeCamera/src/main/cpp/EventEmitters.cpp @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * diff --git a/harmony/reactNativeCamera/src/main/cpp/EventEmitters.h b/harmony/reactNativeCamera/src/main/cpp/EventEmitters.h index 94713dc78..e27c80e24 100644 --- a/harmony/reactNativeCamera/src/main/cpp/EventEmitters.h +++ b/harmony/reactNativeCamera/src/main/cpp/EventEmitters.h @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * diff --git a/harmony/reactNativeCamera/src/main/cpp/FaceNativeTurboModule.cpp b/harmony/reactNativeCamera/src/main/cpp/FaceNativeTurboModule.cpp index 42f0fdb07..655b11a7f 100644 --- a/harmony/reactNativeCamera/src/main/cpp/FaceNativeTurboModule.cpp +++ b/harmony/reactNativeCamera/src/main/cpp/FaceNativeTurboModule.cpp @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by "react-native codegen-harmony" * diff --git a/harmony/reactNativeCamera/src/main/cpp/FaceNativeTurboModule.h b/harmony/reactNativeCamera/src/main/cpp/FaceNativeTurboModule.h index fbe8f5797..2dcafa063 100644 --- a/harmony/reactNativeCamera/src/main/cpp/FaceNativeTurboModule.h +++ b/harmony/reactNativeCamera/src/main/cpp/FaceNativeTurboModule.h @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by "react-native codegen-harmony" * diff --git a/harmony/reactNativeCamera/src/main/cpp/NativeCameraComponentDescriptor.h b/harmony/reactNativeCamera/src/main/cpp/NativeCameraComponentDescriptor.h index 6ada4a791..0af56bd11 100644 --- a/harmony/reactNativeCamera/src/main/cpp/NativeCameraComponentDescriptor.h +++ b/harmony/reactNativeCamera/src/main/cpp/NativeCameraComponentDescriptor.h @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by "react-native codegen-harmony" * diff --git a/harmony/reactNativeCamera/src/main/cpp/NativeCameraEventEmitRequestHandler.h b/harmony/reactNativeCamera/src/main/cpp/NativeCameraEventEmitRequestHandler.h index 90318daff..35977b9e4 100644 --- a/harmony/reactNativeCamera/src/main/cpp/NativeCameraEventEmitRequestHandler.h +++ b/harmony/reactNativeCamera/src/main/cpp/NativeCameraEventEmitRequestHandler.h @@ -1,26 +1,18 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2024 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + #ifndef HARMONY_NATIVECAMERAEVENTEMITREQUESTHANDLER_H #define HARMONY_NATIVECAMERAEVENTEMITREQUESTHANDLER_H diff --git a/harmony/reactNativeCamera/src/main/cpp/NativeCameraJSIBinder.h b/harmony/reactNativeCamera/src/main/cpp/NativeCameraJSIBinder.h index dcebb428c..914498ec3 100644 --- a/harmony/reactNativeCamera/src/main/cpp/NativeCameraJSIBinder.h +++ b/harmony/reactNativeCamera/src/main/cpp/NativeCameraJSIBinder.h @@ -1,26 +1,18 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + #pragma once /** @@ -51,6 +43,7 @@ class NativeCameraJSIBinder : public ViewComponentJSIBinder { object.setProperty(rt, "testID", true); object.setProperty(rt, "autoFocus", true); object.setProperty(rt, "autoFocusPointOfInterest", "Object"); + object.setProperty(rt, "cameraId", ""); object.setProperty(rt, "pictureSize", ""); object.setProperty(rt, "type", ""); object.setProperty(rt, "flashMode", true); @@ -71,6 +64,7 @@ class NativeCameraJSIBinder : public ViewComponentJSIBinder { object.setProperty(rt, "trackingEnabled", true); object.setProperty(rt, "ratio", true); object.setProperty(rt, "playSoundOnCapture", true); + object.setProperty(rt, "playSoundOnRecord", true); object.setProperty(rt, "videoStabilizationMode", ""); object.setProperty(rt, "defaultVideoQuality", true); object.setProperty(rt, "keepAudioSession", true); diff --git a/harmony/reactNativeCamera/src/main/cpp/NativeCameraPackage.h b/harmony/reactNativeCamera/src/main/cpp/NativeCameraPackage.h index b75c65aa9..6c1cd470d 100644 --- a/harmony/reactNativeCamera/src/main/cpp/NativeCameraPackage.h +++ b/harmony/reactNativeCamera/src/main/cpp/NativeCameraPackage.h @@ -1,26 +1,18 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2024 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + #pragma once #include "ComponentDescriptors.h" #include "FaceNativeTurboModule.h" diff --git a/harmony/reactNativeCamera/src/main/cpp/Props.cpp b/harmony/reactNativeCamera/src/main/cpp/Props.cpp index fec4681e1..f3287023e 100644 --- a/harmony/reactNativeCamera/src/main/cpp/Props.cpp +++ b/harmony/reactNativeCamera/src/main/cpp/Props.cpp @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * @@ -56,6 +48,7 @@ NativeCameraProps::NativeCameraProps( autoFocus(convertRawProp(context, rawProps, "autoFocus", sourceProps.autoFocus, {AutoFocusMode::ON})), autoFocusPointOfInterest(convertRawProp(context, rawProps, "autoFocusPointOfInterest", sourceProps.autoFocusPointOfInterest, {})), pictureSize(convertRawProp(context, rawProps, "pictureSize", sourceProps.pictureSize, {""})), + cameraId(convertRawProp(context, rawProps, "cameraId", sourceProps.cameraId, {""})), // 4.2.1 新增 type(convertRawProp(context, rawProps, "type", sourceProps.type, {CameraType::BACK})), flashMode(convertRawProp(context, rawProps, "flashMode", sourceProps.flashMode, {FlashMode::AUTO})), exposure(convertRawProp(context, rawProps, "exposure", sourceProps.exposure, {0.0})), @@ -75,6 +68,7 @@ NativeCameraProps::NativeCameraProps( trackingEnabled(convertRawProp(context, rawProps, "trackingEnabled", sourceProps.trackingEnabled, {true})), ratio(convertRawProp(context, rawProps, "ratio", sourceProps.ratio, {true})), playSoundOnCapture(convertRawProp(context, rawProps, "playSoundOnCapture", sourceProps.playSoundOnCapture, {true})), + playSoundOnRecord(convertRawProp(context, rawProps, "playSoundOnRecord", sourceProps.playSoundOnRecord, {false})), // 4.2.1 新增 videoStabilizationMode(convertRawProp(context, rawProps, "videoStabilizationMode", sourceProps.videoStabilizationMode, {VideoStabilizationMode::AUTO})), defaultVideoQuality(convertRawProp(context, rawProps, "defaultVideoQuality", sourceProps.defaultVideoQuality, {VideoQuality::ONE_ZERO_P})), whiteBalance(convertRawProp(context, rawProps, "whiteBalance", sourceProps.whiteBalance, {WhiteBalance::AUTO})), diff --git a/harmony/reactNativeCamera/src/main/cpp/Props.h b/harmony/reactNativeCamera/src/main/cpp/Props.h index c5b1704a2..47ce849f2 100644 --- a/harmony/reactNativeCamera/src/main/cpp/Props.h +++ b/harmony/reactNativeCamera/src/main/cpp/Props.h @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * @@ -329,6 +321,7 @@ class NativeCameraProps final : public ViewProps { AutoFocusMode autoFocus{AutoFocusMode::ON}; FocusPoint autoFocusPointOfInterest{}; std::string pictureSize{}; + std::string cameraId{}; // 4.2.1 新增:精确指定摄像头设备 CameraType type{CameraType::BACK}; FlashMode flashMode{FlashMode::AUTO}; Float exposure{0.0}; @@ -347,6 +340,7 @@ class NativeCameraProps final : public ViewProps { bool trackingEnabled{false}; std::string ratio{}; bool playSoundOnCapture{false}; + bool playSoundOnRecord{false}; // 4.2.1 新增:Android 语义,OHOS 无对等 no-op VideoStabilizationMode videoStabilizationMode{VideoStabilizationMode::AUTO}; VideoQuality defaultVideoQuality{VideoQuality::ONE_ZERO_P}; bool keepAudioSession{false}; diff --git a/harmony/reactNativeCamera/src/main/cpp/RNCCameraModule.cpp b/harmony/reactNativeCamera/src/main/cpp/RNCCameraModule.cpp index 70db454ed..9ce7979a5 100644 --- a/harmony/reactNativeCamera/src/main/cpp/RNCCameraModule.cpp +++ b/harmony/reactNativeCamera/src/main/cpp/RNCCameraModule.cpp @@ -13,6 +13,10 @@ RNCCameraModule::RNCCameraModule(const ArkTSTurboModule::Context ctx, const std: ARK_ASYNC_METHOD_METADATA(getAuidPermission, 0), ARK_ASYNC_METHOD_METADATA(getCameraIds, 0), ARK_ASYNC_METHOD_METADATA(getAvailablePictureSizes, 0), + ARK_ASYNC_METHOD_METADATA(hasTorch, 0), + ARK_ASYNC_METHOD_METADATA(checkIfVideoIsValid, 1), + ARK_ASYNC_METHOD_METADATA(getSupportedPreviewFpsRange, 1), + ARK_ASYNC_METHOD_METADATA(getSupportedRatios, 1), }; } diff --git a/harmony/reactNativeCamera/src/main/cpp/ShadowNodes.cpp b/harmony/reactNativeCamera/src/main/cpp/ShadowNodes.cpp index 42b0528c7..7bba76f5a 100644 --- a/harmony/reactNativeCamera/src/main/cpp/ShadowNodes.cpp +++ b/harmony/reactNativeCamera/src/main/cpp/ShadowNodes.cpp @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * diff --git a/harmony/reactNativeCamera/src/main/cpp/ShadowNodes.h b/harmony/reactNativeCamera/src/main/cpp/ShadowNodes.h index c9e973350..5d625adf9 100644 --- a/harmony/reactNativeCamera/src/main/cpp/ShadowNodes.h +++ b/harmony/reactNativeCamera/src/main/cpp/ShadowNodes.h @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * diff --git a/harmony/reactNativeCamera/src/main/cpp/States.cpp b/harmony/reactNativeCamera/src/main/cpp/States.cpp index 5aa651cb9..ae23844c1 100644 --- a/harmony/reactNativeCamera/src/main/cpp/States.cpp +++ b/harmony/reactNativeCamera/src/main/cpp/States.cpp @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * diff --git a/harmony/reactNativeCamera/src/main/cpp/States.h b/harmony/reactNativeCamera/src/main/cpp/States.h index 7f259f9ab..99b458d35 100644 --- a/harmony/reactNativeCamera/src/main/cpp/States.h +++ b/harmony/reactNativeCamera/src/main/cpp/States.h @@ -1,27 +1,19 @@ -/** - * MIT License - * - * Copyright (C) 2024 Huawei Device Co., Ltd. +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + /** * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * diff --git a/harmony/reactNativeCamera/src/main/ets/FaceDectorPackage.ets b/harmony/reactNativeCamera/src/main/ets/FaceDectorPackage.ets index a9a244934..2e749241a 100644 --- a/harmony/reactNativeCamera/src/main/ets/FaceDectorPackage.ets +++ b/harmony/reactNativeCamera/src/main/ets/FaceDectorPackage.ets @@ -1,27 +1,19 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2023 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import { TurboModulesFactory } from '@rnoh/react-native-openharmony/ts'; import { RNOHPackage } from '@rnoh/react-native-openharmony'; import type { TurboModule, TurboModuleContext } from '@rnoh/react-native-openharmony/ts'; diff --git a/harmony/reactNativeCamera/src/main/ets/FaceDectorTurboModule.ts b/harmony/reactNativeCamera/src/main/ets/FaceDectorTurboModule.ts index 88fc17e2f..b0e6edf4c 100644 --- a/harmony/reactNativeCamera/src/main/ets/FaceDectorTurboModule.ts +++ b/harmony/reactNativeCamera/src/main/ets/FaceDectorTurboModule.ts @@ -1,28 +1,20 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2023 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import type { TurboModuleContext } from '@rnoh/react-native-openharmony/ts'; import { TurboModule } from '@rnoh/react-native-openharmony/ts'; import Logger from './Logger'; diff --git a/harmony/reactNativeCamera/src/main/ets/Logger.ts b/harmony/reactNativeCamera/src/main/ets/Logger.ts index c1adbab8a..1c6520beb 100644 --- a/harmony/reactNativeCamera/src/main/ets/Logger.ts +++ b/harmony/reactNativeCamera/src/main/ets/Logger.ts @@ -1,27 +1,19 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2023 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import hilog from '@ohos.hilog'; class Logger { diff --git a/harmony/reactNativeCamera/src/main/ets/ReactCameraView.ets b/harmony/reactNativeCamera/src/main/ets/ReactCameraView.ets index adf4aab06..837761ab8 100644 --- a/harmony/reactNativeCamera/src/main/ets/ReactCameraView.ets +++ b/harmony/reactNativeCamera/src/main/ets/ReactCameraView.ets @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import camera from '@ohos.multimedia.camera'; import { Descriptor, RNComponentContext, RNViewBase } from '@rnoh/react-native-openharmony'; import Logger from './utils/Logger'; @@ -118,6 +128,7 @@ export struct ReactCameraView { @State isRecording: boolean = false @State videoStabilizationMode: string = "" @State maxZoom: number = 0 + private prevCameraId: string = '' aboutToAppear() { this.localDisplay = display.getDefaultDisplaySync(); @@ -205,11 +216,22 @@ export struct ReactCameraView { this.exposure = descriptor.props.exposure; } - if (descriptor.props.useNativeZoom) { + // 修复 truthy 检查: false 时也要更新状态, 否则 Switch 关闭后捏合变焦仍生效 + if (descriptor.props.useNativeZoom != null) { Logger.debug(`RNOH in NativeCamera descriptor useNativeZoom, ${JSON.stringify(descriptor.props.useNativeZoom)}`); this.useNativeZoom = descriptor.props.useNativeZoom; } + // 接线 autoFocusPointOfInterest: prop 为 0~1 归一化坐标, 换算组件像素后交 CameraSession.focus → setFocusPoint + // cameraSession 在 surface 就绪前是 {} as CameraSession 空对象, 需检查 focus 方法存在 + if (descriptor.props.autoFocusPointOfInterest != null && this.cameraSession?.focus) { + const p = descriptor.props.autoFocusPointOfInterest; + Logger.debug(`RNOH in NativeCamera autoFocusPointOfInterest, ${JSON.stringify(p)}`); + this.cameraSession.focus(this.focusMode, { + x: p.x * this.localDisplayWidth, + y: p.y * this.localDisplayHeight + }); + } if (descriptor.props.focusDepth) { Logger.debug(`RNOH in NativeCamera descriptor focusDepth, ${JSON.stringify(descriptor.props.focusDepth)}`); @@ -226,6 +248,20 @@ export struct ReactCameraView { private updateProp(descriptor: ReactNativeCameraDescriptor) { Logger.debug(`RNOH in NativeCamera descriptor props, ${JSON.stringify(descriptor.props)}`); this.tempProp = descriptor.props; + if (descriptor.props.cameraId && descriptor.props.cameraId !== '' && + this.prevCameraId !== descriptor.props.cameraId) { + // 4.2.1 新增 cameraId prop:设备切换走 changeCameraPosition 重新 initCamera + this.prevCameraId = descriptor.props.cameraId; + this.cameraSession.changeCameraPosition({ + surfaceId: this.surfaceId, props: this.tempProp, + mediaModel: this.cameraState === cameraState.PHOTO ? camera.SceneMode.NORMAL_PHOTO : + camera.SceneMode.NORMAL_VIDEO + }) + } + if (descriptor.props.playSoundOnRecord != null) { + // 4.2.1 新增 playSoundOnRecord prop:OHOS camera API 无快门音开关对等,行为 no-op(合理差异) + Logger.debug(`RNOH in NativeCamera descriptor playSoundOnRecord, ${JSON.stringify(descriptor.props.playSoundOnRecord)}`); + } this.cameraSession.changeCameraPosition({ surfaceId: this.surfaceId, props: this.tempProp, mediaModel: this.cameraState === cameraState.PHOTO ? camera.SceneMode.NORMAL_PHOTO : camera.SceneMode.NORMAL_VIDEO @@ -261,11 +297,23 @@ export struct ReactCameraView { this.cameraSession.setAudio(descriptor.props.captureAudio) } - if (descriptor.props.useNativeZoom) { + // 修复 truthy 检查: false 时也要更新状态, 否则 Switch 关闭后捏合变焦仍生效 + if (descriptor.props.useNativeZoom != null) { Logger.debug(`RNOH in NativeCamera descriptor useNativeZoom, ${JSON.stringify(descriptor.props.useNativeZoom)}`); this.useNativeZoom = descriptor.props.useNativeZoom; } + // 接线 autoFocusPointOfInterest: prop 为 0~1 归一化坐标, 换算组件像素后交 CameraSession.focus → setFocusPoint + // cameraSession 在 surface 就绪前是 {} as CameraSession 空对象, 需检查 focus 方法存在 + if (descriptor.props.autoFocusPointOfInterest != null && this.cameraSession?.focus) { + const p = descriptor.props.autoFocusPointOfInterest; + Logger.debug(`RNOH in NativeCamera autoFocusPointOfInterest, ${JSON.stringify(p)}`); + this.cameraSession.focus(this.focusMode, { + x: p.x * this.localDisplayWidth, + y: p.y * this.localDisplayHeight + }); + } + if (descriptor.props.maxZoom > 0) { this.maxZoom = descriptor.props.maxZoom } @@ -486,6 +534,7 @@ export struct ReactCameraView { } if (command === 'record') { const options = args[0] as RecordVideoOptions; + Logger.info(TAG, 'CMD-IN record command received, options=' + JSON.stringify(options)); Logger.debug(TAG, `registerCommandCallback: record.${JSON.stringify(options)}}`) if (this.cameraState == cameraState.PHOTO) { this.cameraState = cameraState.VIDEO @@ -512,6 +561,7 @@ export struct ReactCameraView { } if (command === 'stopRecording') { + Logger.info(TAG, 'CMD-IN stopRecording command received'); Logger.debug(TAG, `registerCommandCallback: stopRecording, ${command}`) this.stopRecording(); this.ctx.rnInstance.emitComponentEvent( @@ -685,13 +735,19 @@ export struct ReactCameraView { * 停止录制 */ async stopRecording() { - await this.cameraSession.stopRecording() - this.isRecording = false - //当帧率设置30fps时直接回调,不需要转换,系统默认录制出来是30fps - if (this.cameraSession.fps == 30) { - Logger.debug("stopRecording fps 30") - this.cameraSession.sendOnRecordingFinishedEvent(OrientationUtils.getDefaultDisplay()); - return; + try { + await this.cameraSession.stopRecording() + } catch (error) { + // session 停止失败(reset 状态冲突等)不能阻断完成事件回调 + Logger.error(TAG, 'stopRecording: session stop failed: ' + JSON.stringify(error)); + } finally { + this.isRecording = false + //当帧率设置30fps时直接回调,不需要转换,系统默认录制出来是30fps + if (this.cameraSession.fps == 30) { + Logger.debug("stopRecording fps 30") + this.cameraSession.sendOnRecordingFinishedEvent(OrientationUtils.getDefaultDisplay()); + return; + } } //由于系统Api media.AVRecorder->prepare录制后videoFrameRate设置无效,因此使用FFmpeg处理帧率转换 let inPath = this.cameraSession.videoUri; @@ -815,7 +871,8 @@ export struct ReactCameraView { .height('100%') .justifyContent(FlexAlign.Center) .gesture( - GestureGroup(GestureMode.Parallel, PinchGesture({ fingers: 2 })// 当捏合手势触发时,可以通过回调函数获取缩放比例,从而修改组件的缩放比例 + GestureGroup(GestureMode.Parallel, + PinchGesture({ fingers: 2 })// 当捏合手势触发时,可以通过回调函数获取缩放比例,从而修改组件的缩放比例 .onActionUpdate((event: GestureEvent) => { this.scaleValue = this.cameraSession.photoPreviewScale * event.scale; }) @@ -827,9 +884,30 @@ export struct ReactCameraView { this.cameraSession?.setNativeSmoothZoom(this.scaleValue); } } - })) - // 在组件上绑定三指触发的捏合手势 - + }) + , + // onDoubleTap 链路: 双击手势 → emit onTouch{tapCallback:{isDoubleTap, touchOrigin}} → cpp handler → JS _onTouch 分发 onDoubleTap + TapGesture({ count: 2 }) + .onAction((event: GestureEvent) => { + // GestureEvent 静态类型无坐标, 运行时为 TapGestureEvent, 经 tapLocation 取组件相对坐标(vp) + const tapLocation = (event as TapGestureEvent).tapLocation; + this.ctx.rnInstance.emitComponentEvent( + this.tag, + VisionCameraViewSpec.NAME, + { + type: 'onTouch', + // cpp 侧 NativeCameraEventEmitRequestHandler 从 payload.tapCallback 取值 + tapCallback: { + isDoubleTap: true, + touchOrigin: { + x: tapLocation ? Math.round(tapLocation.x) : 0, + y: tapLocation ? Math.round(tapLocation.y) : 0 + } + } + } + ) + }) + ) ) } .width(this.localDisplayWidth + 'px') diff --git a/harmony/reactNativeCamera/src/main/ets/ReactNativeCameraPackage.ets b/harmony/reactNativeCamera/src/main/ets/ReactNativeCameraPackage.ets index e8b089182..e3e3c310e 100644 --- a/harmony/reactNativeCamera/src/main/ets/ReactNativeCameraPackage.ets +++ b/harmony/reactNativeCamera/src/main/ets/ReactNativeCameraPackage.ets @@ -1,32 +1,25 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2023 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import { TurboModulesFactory } from '@rnoh/react-native-openharmony/ts'; -import { RNOHPackage } from '@rnoh/react-native-openharmony'; +import { RNOHPackage, ComponentBuilderContext } from '@rnoh/react-native-openharmony'; import type { TurboModule, TurboModuleContext } from '@rnoh/react-native-openharmony/ts'; import { ReactNativeCameraTurboModule } from './ReactNativeCameraTurboModule'; import { FaceDectorTurboModule } from './FaceDectorTurboModule'; +import { ReactCameraView } from './ReactCameraView'; class ReactNativeCameraTurboModulesFactory extends TurboModulesFactory { createTurboModule(name: string): TurboModule | null { @@ -44,8 +37,27 @@ class ReactNativeCameraTurboModulesFactory extends TurboModulesFactory { } } +@Builder +function buildRNWebRTCComponent(ctx: ComponentBuilderContext) { + Stack() { + if (ctx.componentName === ReactCameraView.NAME) { + ReactCameraView({ + ctx: ctx.rnComponentContext, + tag: ctx.tag + }) + } + } + .position({ x: 0, y: 0 }) +} + export class ReactNativeCameraPackage extends RNOHPackage { createTurboModulesFactory(ctx: TurboModuleContext): TurboModulesFactory { return new ReactNativeCameraTurboModulesFactory(ctx); } + + createWrappedCustomRNComponentBuilderByComponentNameMap(): Map> { + const map = new Map>() + map.set(ReactCameraView.NAME, wrapBuilder(buildRNWebRTCComponent)) + return map + } } diff --git a/harmony/reactNativeCamera/src/main/ets/ReactNativeCameraTurboModule.ts b/harmony/reactNativeCamera/src/main/ets/ReactNativeCameraTurboModule.ts index 3b1f3e02d..2ccbfb765 100644 --- a/harmony/reactNativeCamera/src/main/ets/ReactNativeCameraTurboModule.ts +++ b/harmony/reactNativeCamera/src/main/ets/ReactNativeCameraTurboModule.ts @@ -1,28 +1,20 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2023 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import type { TurboModuleContext } from '@rnoh/react-native-openharmony/ts'; import { TurboModule } from '@rnoh/react-native-openharmony/ts'; import { BusinessError, emitter } from '@kit.BasicServicesKit'; @@ -33,6 +25,8 @@ import Logger from './Logger'; import { SimpleCameraDeviceInfo } from './core/CameraDeviceInfo'; import { camera } from '@kit.CameraKit'; import { AuthType } from './types/AuthType'; +import { media } from '@kit.MediaKit'; +import fs from '@ohos.file.fs'; const TAG: string = '[RNOH]ReactNativeCameraTurboModule' @@ -158,12 +152,26 @@ export class ReactNativeCameraTurboModule extends TurboModule { let temp = new SimpleCameraDeviceInfo(); temp.id = camerasArrayTemp[i].cameraId; temp.type = camerasArrayTemp[i].cameraType; + temp.deviceType = this.getDeviceType(camerasArrayTemp[i].cameraType); data.push(temp) } resolve(data) }); } + private getDeviceType(cameraType: camera.CameraType): string { + switch (cameraType) { + case camera.CameraType.CAMERA_TYPE_ULTRA_WIDE: + return 'AVCaptureDeviceTypeBuiltInUltraWideCamera'; + case camera.CameraType.CAMERA_TYPE_WIDE_ANGLE: + return 'AVCaptureDeviceTypeBuiltInWideAngleCamera'; + case camera.CameraType.CAMERA_TYPE_TELEPHOTO: + return 'AVCaptureDeviceTypeBuiltInTelephotoCamera'; + default: + return 'AVCaptureDeviceTypeBuiltInWideAngleCamera'; + } + } + getAvailablePictureSizes(): Promise { Logger.debug("getAvailablePictureSizes") return new Promise(async (resolve, reject) => { @@ -182,4 +190,118 @@ export class ReactNativeCameraTurboModule extends TurboModule { resolve(data) }); } + + hasTorch(): Promise { + Logger.debug("hasTorch") + return new Promise((resolve, reject) => { + try { + let cameraManager = camera.getCameraManager(this.ctx.getUIContext().getHostContext()); + let isSupported: boolean = cameraManager.isTorchModeSupported(camera.TorchMode.ON); + resolve(isSupported) + } catch (err) { + let businessError = err as BusinessError; + Logger.error(TAG, `hasTorch failed, code: ${businessError.code}, message: ${businessError.message}`); + reject(businessError) + } + }); + } + + checkIfVideoIsValid(path: string): Promise { + Logger.debug("checkIfVideoIsValid") + return new Promise(async (resolve, reject) => { + let fileExists = fs.accessSync(path); + if (!fileExists) { + resolve(false) + return; + } + let avMetadataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor(); + let file = fs.openSync(path, fs.OpenMode.READ_ONLY); + avMetadataExtractor.fdSrc = { + fd: file.fd + } + avMetadataExtractor.fetchMetadata().then((metadata) => { + if (metadata.duration.length > 0 && parseInt(metadata.duration) > 0) { + resolve(true) + } else { + resolve(false) + } + }).catch((err: BusinessError) => { + Logger.error(TAG, `checkIfVideoIsValid fetchMetadata failed, code: ${err.code}, message: ${err.message}`); + resolve(false) + }).finally(() => { + avMetadataExtractor.release(); + fs.closeSync(file); + }); + }); + } + + getSupportedPreviewFpsRange(handle: number): Promise { + Logger.debug("getSupportedPreviewFpsRange") + return new Promise(async (resolve, reject) => { + try { + let cameraManager = camera.getCameraManager(this.ctx.getUIContext().getHostContext()); + let camerasArray = cameraManager?.getSupportedCameras(); + if (!camerasArray) { + Logger.error(TAG, 'getSupportedPreviewFpsRange cannot get cameras'); + reject('getSupportedPreviewFpsRange cannot get cameras') + return; + } + let deviceIndex = handle >= 0 && handle < camerasArray.length ? handle : 0; + let capability = + cameraManager.getSupportedOutputCapability(camerasArray[deviceIndex], camera.SceneMode.NORMAL_VIDEO); + let data: string[] = []; + for (let i = 0; i < capability.videoProfiles.length; i++) { + let fpsRange = capability.videoProfiles[i].frameRateRange; + data.push(`${fpsRange.min},${fpsRange.max}`) + } + resolve(data) + } catch (err) { + let businessError = err as BusinessError; + Logger.error(TAG, + `getSupportedPreviewFpsRange failed, code: ${businessError.code}, message: ${businessError.message}`); + reject(businessError) + } + }); + } + + getSupportedRatios(handle: number): Promise { + Logger.debug("getSupportedRatios") + return new Promise(async (resolve, reject) => { + try { + let cameraManager = camera.getCameraManager(this.ctx.getUIContext().getHostContext()); + let camerasArray = cameraManager?.getSupportedCameras(); + if (!camerasArray) { + Logger.error(TAG, 'getSupportedRatios cannot get cameras'); + reject('getSupportedRatios cannot get cameras') + return; + } + let deviceIndex = handle >= 0 && handle < camerasArray.length ? handle : 0; + let capability = + cameraManager.getSupportedOutputCapability(camerasArray[deviceIndex], camera.SceneMode.NORMAL_VIDEO); + let data: string[] = []; + for (let i = 0; i < capability.previewProfiles.length; i++) { + let w = capability.previewProfiles[i].size.width; + let h = capability.previewProfiles[i].size.height; + // 辗转相除化简为最简整数比,与 Android 上游 "4:3"/"16:9" 输出格式对齐 + let a = w; + let b = h; + while (b !== 0) { + let t = b; + b = a % b; + a = t; + } + let ratio = (w / a) + ':' + (h / a); + if (!data.includes(ratio)) { + data.push(ratio); + } + } + resolve(data) + } catch (err) { + let businessError = err as BusinessError; + Logger.error(TAG, + `getSupportedRatios failed, code: ${businessError.code}, message: ${businessError.message}`); + reject(businessError) + } + }); + } } diff --git a/harmony/reactNativeCamera/src/main/ets/core/CameraConfig.ts b/harmony/reactNativeCamera/src/main/ets/core/CameraConfig.ts index 92211668f..061b33d3b 100644 --- a/harmony/reactNativeCamera/src/main/ets/core/CameraConfig.ts +++ b/harmony/reactNativeCamera/src/main/ets/core/CameraConfig.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import { Permissions } from '@kit.AbilityKit'; import { ErrorWithCause } from '../types/CameraError'; import { Orientation } from './CameraEnumBox'; @@ -34,6 +44,8 @@ export interface TakePhotoOptions { fixOrientation?: boolean, //todo 不知道 forceUpOrientation?: boolean, //强制向上方向 pauseAfterCapture?: boolean, //拍摄后暂停 + imageType?: 'jpeg' | 'png', // 4.2.1 新增:拍照输出格式(png 走 ImagePacker 编码) + path?: string, // 4.2.1 新增:自定义保存路径 } export interface PhotoFile { diff --git a/harmony/reactNativeCamera/src/main/ets/core/CameraDeviceInfo.ts b/harmony/reactNativeCamera/src/main/ets/core/CameraDeviceInfo.ts index ec6f147b1..110c3bdcb 100644 --- a/harmony/reactNativeCamera/src/main/ets/core/CameraDeviceInfo.ts +++ b/harmony/reactNativeCamera/src/main/ets/core/CameraDeviceInfo.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import type { AutoFocusSystem, VideoStabilizationMode, @@ -86,7 +96,9 @@ export interface CameraDeviceInfo { export class SimpleCameraDeviceInfo { - id: string + deviceType: string = '' + + id: string = '' - type:number + type: number = 0 } \ No newline at end of file diff --git a/harmony/reactNativeCamera/src/main/ets/core/CameraEnumBox.ts b/harmony/reactNativeCamera/src/main/ets/core/CameraEnumBox.ts index 500b64522..13b764be7 100644 --- a/harmony/reactNativeCamera/src/main/ets/core/CameraEnumBox.ts +++ b/harmony/reactNativeCamera/src/main/ets/core/CameraEnumBox.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + export enum Orientation { PORTRAIT = 'portrait', LANDSCAPE_RIGHT = 'landscapeRight', @@ -49,3 +59,8 @@ export enum cameraState { VIDEO, SCAN } + +export enum ImageType { + JPEG = 'jpeg', + PNG = 'png' +} diff --git a/harmony/reactNativeCamera/src/main/ets/service/CameraManager.ts b/harmony/reactNativeCamera/src/main/ets/service/CameraManager.ts index d66cd6c55..59faa733a 100644 --- a/harmony/reactNativeCamera/src/main/ets/service/CameraManager.ts +++ b/harmony/reactNativeCamera/src/main/ets/service/CameraManager.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import Logger from '../utils/Logger'; import { CameraPermissionRequestResult, CameraPermissionStatus, PermissionArray } from '../core/CameraConfig'; diff --git a/harmony/reactNativeCamera/src/main/ets/service/CameraSession.ts b/harmony/reactNativeCamera/src/main/ets/service/CameraSession.ts index ceb38ddc2..95d14850c 100644 --- a/harmony/reactNativeCamera/src/main/ets/service/CameraSession.ts +++ b/harmony/reactNativeCamera/src/main/ets/service/CameraSession.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import camera from '@ohos.multimedia.camera'; import Logger from '../utils/Logger'; import { PhotoFile, Point, TakePhotoOptions } from '../core/CameraConfig'; @@ -68,6 +78,8 @@ export default class CameraSession { private hasAudio: boolean = false private ctx!: RNOHContext; private cameraDeviceIndex: number = 0; + // 4.2.1 cameraId prop:记录上一次生效的 cameraId,用于检测设备精确切换 + private pendingCameraId: string = ''; private pictureSize: string; public ZoomRange: ZoomRangeType | null = null; public photoPreviewScale: number = 1; @@ -89,6 +101,9 @@ export default class CameraSession { private offsetY: number = 0; public fps: number = 30; private tag: number; + // 4.2.1 新增拍照选项:imageType(jpeg/png)与自定义保存 path + private pendingImageType: string = 'jpeg'; + private pendingPhotoPath: string = ''; constructor(_ctx?: RNOHContext) { _ctx && (this.ctx = _ctx); @@ -139,12 +154,22 @@ export default class CameraSession { const { surfaceId, props, mediaModel } = config; let cameraIndex = 0; props.type === "front" ? cameraIndex = 1 : cameraIndex = 0; - if (this.cameraDeviceIndex === cameraIndex) { + // 4.2.1 cameraId prop:设备精确切换时忽略 type 相同的短路,强制重新 initCamera + let forceReinit = false; + if (props.cameraId && props.cameraId !== '') { + if (this.pendingCameraId !== props.cameraId) { + forceReinit = true; + } + this.pendingCameraId = props.cameraId; + } else { + this.pendingCameraId = ''; + } + if (this.cameraDeviceIndex === cameraIndex && !forceReinit) { return; } this.cameraDeviceIndex = cameraIndex await this.cameraRelease(); - Logger.info(TAG, `changeCameraPosition: ${props.type}`); + Logger.info(TAG, `changeCameraPosition: ${props.type}, cameraId: ${props.cameraId ?? ''}`); this.cameraManager = this.getCameraManagerFn(); this.initCamera(surfaceId, props, mediaModel) } @@ -287,6 +312,17 @@ export default class CameraSession { } else { currentDevice = this.camerasArray[1] } + // cameraId prop 优先:精确匹配 getSupportedCameras 返回的 cameraId + if (props.cameraId && props.cameraId !== '') { + let matchedDevice = this.camerasArray.find(device => { + return device.cameraId === props.cameraId + }) + if (matchedDevice) { + currentDevice = matchedDevice + } else { + Logger.error(TAG, `initCamera cameraId ${props.cameraId} not found, fallback to type selection`); + } + } if (this.mediaModel === camera.SceneMode.NORMAL_PHOTO) { Logger.debug("initPhotoSession") await this.initPhotoSession(currentDevice, surfaceId, props); @@ -772,8 +808,15 @@ export default class CameraSession { await targetSession.stop(); } } catch (error) { - Logger.error(TAG, `The activeChange targetSession start call failed. error code: ${error.code}`); - this.onError(`The activeChange targetSession start call failed. error code: ${error.code}`) + let err = error as BusinessError; + // 7400201(SESSION_ERROR_NOT_CONFIG): initCamera 早于 XComponent surface 创建的启动竞态, + // surface 就绪后的 changeCameraPosition/initCamera 会重新拉起, 不应向 JS 报错 + if (err.code === 7400201) { + Logger.warn(TAG, "activeChange start skipped: session not config yet (surface not ready)"); + return; + } + Logger.error(TAG, `The activeChange targetSession start call failed. error code: ${err.code}`); + this.onError(`The activeChange targetSession start call failed. error code: ${err.code}`) } } @@ -998,8 +1041,11 @@ export default class CameraSession { // 保存图片 async savePicture(photoAccess: photoAccessHelper.PhotoAsset): Promise { - let photoFile = `${this.basicPath}/${this.outPathArray[0]}/${Date.now().toString()}.jpeg`; - // photoFile = await this.getMediaLibraryUri(photoFile, `${Date.now()}`, 'jpeg', photoAccessHelper.PhotoType.IMAGE) + // imageType 拍照选项:支持 png(imageType==='png')与 jpeg 输出;path 拍照选项:自定义保存路径 + let imageType: string = this.pendingImageType === 'png' ? 'png' : 'jpeg'; + let photoFile = this.pendingPhotoPath && this.pendingPhotoPath !== '' ? + this.pendingPhotoPath : + `${this.basicPath}/${this.outPathArray[0]}/${Date.now().toString()}.${imageType}`; // 根据相机拍照图片路径,获取文件buffer let file = fs.openSync(photoAccess.uri, fs.OpenMode.READ_ONLY); let stat = fs.statSync(file.fd); @@ -1007,6 +1053,26 @@ export default class CameraSession { fs.readSync(file.fd, buffer); fs.fsyncSync(file.fd); fs.closeSync(file); + if (imageType === 'png') { + // PNG 输出:解码图片后用 ImagePacker 以 image/png 格式编码 + try { + let imageSource = image.createImageSource(photoAccess.uri); + let pixelMap = await imageSource.createPixelMap(); + let imagePacker = image.createImagePacker(); + let packOpts: image.PackingOption = { format: 'image/png', quality: 100 }; + let packBuffer = await imagePacker.packToData(pixelMap, packOpts); + let _pngFile = fs.openSync(photoFile, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + await fs.write(_pngFile.fd, packBuffer); + fs.closeSync(_pngFile); + imagePacker.release(); + pixelMap.release(); + imageSource.release(); + this.photoPath = photoFile; + return; + } catch (error) { + Logger.error(TAG, `savePicture png packing failed,code:${JSON.stringify(error)}. fallback to raw copy`); + } + } let _file; try { _file = fs.openSync(photoFile, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); @@ -1184,6 +1250,19 @@ export default class CameraSession { Logger.debug("takePhoto orientation:" + options.orientation) Logger.debug("takePhoto mirrorImage:" + options.mirrorImage) + // 4.2.1 新增:imageType(jpeg/png)与自定义保存 path + if (options) { + if (options.imageType === 'png') { + this.pendingImageType = 'png'; + } else { + this.pendingImageType = 'jpeg'; + } + this.pendingPhotoPath = options.path ?? ''; + } else { + this.pendingImageType = 'jpeg'; + this.pendingPhotoPath = ''; + } + let quality = camera.QualityLevel.QUALITY_LEVEL_MEDIUM; if (options) { if (options.quality) { @@ -1708,7 +1787,13 @@ export default class CameraSession { }); } // 重置 - await this.avRecorder.reset(); + try { + await this.avRecorder.reset(); + } catch (resetError) { + let resetErr = resetError as BusinessError; + // stop 后立即 reset 状态冲突会抛错,不能阻断 onRecordingFinished 回调链 + Logger.error(TAG, `stopRecording: Failed to reset the avRecorder. error: ${JSON.stringify(resetErr)}`); + } // 此处不要释放录制实例,否则需要重新创建 videoOutput 加入 session里,会导致画面闪断 if (this.videoSession.hasFlash() && diff --git a/harmony/reactNativeCamera/src/main/ets/service/ScanSession.ts b/harmony/reactNativeCamera/src/main/ets/service/ScanSession.ts index ad516c7fe..a1caf7fc9 100644 --- a/harmony/reactNativeCamera/src/main/ets/service/ScanSession.ts +++ b/harmony/reactNativeCamera/src/main/ets/service/ScanSession.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import { customScan, scanBarcode, scanCore } from '@kit.ScanKit'; import Logger from '../utils/Logger'; import { BusinessError } from '@ohos.base'; diff --git a/harmony/reactNativeCamera/src/main/ets/types/AuthType.ts b/harmony/reactNativeCamera/src/main/ets/types/AuthType.ts index c84a7cd81..c34b591b6 100644 --- a/harmony/reactNativeCamera/src/main/ets/types/AuthType.ts +++ b/harmony/reactNativeCamera/src/main/ets/types/AuthType.ts @@ -1,26 +1,18 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2025 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + export enum AuthType { READY, PENDING_AUTHORIZATION, diff --git a/harmony/reactNativeCamera/src/main/ets/types/CameraError.ts b/harmony/reactNativeCamera/src/main/ets/types/CameraError.ts index 463db7b5f..9a3ecdfd8 100644 --- a/harmony/reactNativeCamera/src/main/ets/types/CameraError.ts +++ b/harmony/reactNativeCamera/src/main/ets/types/CameraError.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + export type PermissionError = 'permission/microphone-permission-denied' | 'permission/camera-permission-denied' export type ParameterError = | 'parameter/invalid-parameter' diff --git a/harmony/reactNativeCamera/src/main/ets/types/ReactNativeCameraProps.ets b/harmony/reactNativeCamera/src/main/ets/types/ReactNativeCameraProps.ets index 9b7695890..0a859da23 100644 --- a/harmony/reactNativeCamera/src/main/ets/types/ReactNativeCameraProps.ets +++ b/harmony/reactNativeCamera/src/main/ets/types/ReactNativeCameraProps.ets @@ -1,26 +1,18 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2025 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import { ViewBaseProps } from "@rnoh/react-native-openharmony" export interface FocusPoint { @@ -45,6 +37,7 @@ export interface ReactNativeCameraProps extends ViewBaseProps { showsUserLocation: boolean, autoFocus: string | number, autoFocusPointOfInterest: FocusPoint, + cameraId: string, pictureSize: string, type: string, flashMode: string, @@ -66,6 +59,7 @@ export interface ReactNativeCameraProps extends ViewBaseProps { ratio: string, whiteBalance: string, playSoundOnCapture: boolean, + playSoundOnRecord: boolean, videoStabilizationMode: string, defaultVideoQuality: string, keepAudioSession: boolean diff --git a/harmony/reactNativeCamera/src/main/ets/types/TempCameraProp.ts b/harmony/reactNativeCamera/src/main/ets/types/TempCameraProp.ts index 496e85011..11fb06491 100644 --- a/harmony/reactNativeCamera/src/main/ets/types/TempCameraProp.ts +++ b/harmony/reactNativeCamera/src/main/ets/types/TempCameraProp.ts @@ -1,26 +1,18 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2025 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + interface FocusPoint { x: number, y: number @@ -43,6 +35,7 @@ export class TempCameraProps { showsUserLocation: boolean = false; autoFocus: string | number = ""; autoFocusPointOfInterest: FocusPoint = null; + cameraId: string = ""; pictureSize: string = ""; type: string = "back"; flashMode: string = ""; @@ -63,6 +56,7 @@ export class TempCameraProps { trackingEnabled: boolean = false; ratio: string = ""; playSoundOnCapture: boolean = false; + playSoundOnRecord: boolean = false; videoStabilizationMode: string = ""; defaultVideoQuality: string = ""; keepAudioSession: boolean = false diff --git a/harmony/reactNativeCamera/src/main/ets/types/TemporaryFile.ts b/harmony/reactNativeCamera/src/main/ets/types/TemporaryFile.ts index e0ae97f55..66660a94c 100644 --- a/harmony/reactNativeCamera/src/main/ets/types/TemporaryFile.ts +++ b/harmony/reactNativeCamera/src/main/ets/types/TemporaryFile.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + export interface TemporaryFile { path: string } diff --git a/harmony/reactNativeCamera/src/main/ets/types/VideoFile.ts b/harmony/reactNativeCamera/src/main/ets/types/VideoFile.ts index 2f5722c98..4746b0b88 100644 --- a/harmony/reactNativeCamera/src/main/ets/types/VideoFile.ts +++ b/harmony/reactNativeCamera/src/main/ets/types/VideoFile.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import { Orientation } from '../core/CameraEnumBox'; import type { CameraCaptureError } from './CameraError' import type { TemporaryFile } from './TemporaryFile' diff --git a/harmony/reactNativeCamera/src/main/ets/types/VisionCameraModuleSpec.ts b/harmony/reactNativeCamera/src/main/ets/types/VisionCameraModuleSpec.ts index 9ce9df7a6..389ff66ed 100644 --- a/harmony/reactNativeCamera/src/main/ets/types/VisionCameraModuleSpec.ts +++ b/harmony/reactNativeCamera/src/main/ets/types/VisionCameraModuleSpec.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import { Tag } from "@rnoh/react-native-openharmony/ts" export namespace VisionCameraModuleSpec { diff --git a/harmony/reactNativeCamera/src/main/ets/types/VisionCameraViewSpec.ts b/harmony/reactNativeCamera/src/main/ets/types/VisionCameraViewSpec.ts index 9ca023187..3424f6ab8 100644 --- a/harmony/reactNativeCamera/src/main/ets/types/VisionCameraViewSpec.ts +++ b/harmony/reactNativeCamera/src/main/ets/types/VisionCameraViewSpec.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import { Descriptor as ComponentDescriptor, ViewBaseProps, diff --git a/harmony/reactNativeCamera/src/main/ets/utils/Logger.ts b/harmony/reactNativeCamera/src/main/ets/utils/Logger.ts index 0b78beb29..68c2f8d30 100644 --- a/harmony/reactNativeCamera/src/main/ets/utils/Logger.ts +++ b/harmony/reactNativeCamera/src/main/ets/utils/Logger.ts @@ -1,10 +1,20 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import hilog from '@ohos.hilog'; class Logger { diff --git a/harmony/reactNativeCamera/src/main/ets/utils/OrientationUtils.ets b/harmony/reactNativeCamera/src/main/ets/utils/OrientationUtils.ets index 10eb69c09..762de2e3a 100644 --- a/harmony/reactNativeCamera/src/main/ets/utils/OrientationUtils.ets +++ b/harmony/reactNativeCamera/src/main/ets/utils/OrientationUtils.ets @@ -1,26 +1,18 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2025 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import { display } from '@kit.ArkUI'; import { Orientation } from '../core/CameraEnumBox'; import Logger from '../Logger'; diff --git a/harmony/reactNativeCamera/src/main/ets/utils/PermissionUtils.ts b/harmony/reactNativeCamera/src/main/ets/utils/PermissionUtils.ts index 544ac915a..5c59658b6 100644 --- a/harmony/reactNativeCamera/src/main/ets/utils/PermissionUtils.ts +++ b/harmony/reactNativeCamera/src/main/ets/utils/PermissionUtils.ts @@ -1,10 +1,20 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + import abilityAccessCtrl, { PermissionRequestResult } from '@ohos.abilityAccessCtrl'; import { Permissions } from '@ohos.abilityAccessCtrl'; import bundleManager from '@ohos.bundle.bundleManager'; diff --git a/harmony/reactNativeCamera/src/main/ets/utils/Utils.ts b/harmony/reactNativeCamera/src/main/ets/utils/Utils.ts index f6909f4f6..e3b05c308 100644 --- a/harmony/reactNativeCamera/src/main/ets/utils/Utils.ts +++ b/harmony/reactNativeCamera/src/main/ets/utils/Utils.ts @@ -1,9 +1,19 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + const TAG = 'CameraUtils' const Utils = { diff --git a/harmony/reactNativeCamera/ts.ets b/harmony/reactNativeCamera/ts.ets index 15438a215..3a227857c 100644 --- a/harmony/reactNativeCamera/ts.ets +++ b/harmony/reactNativeCamera/ts.ets @@ -1,27 +1,19 @@ -/** - * MIT License +/* + * Copyright (c) 2026 Huawei Device Co., Ltd. + * Licensed under the MIT License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Copyright (C) 2023 Huawei Device Co., Ltd. + * https://github.com/react-native-camera/react-native-camera/blob/v4.2.1/LICENSE * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + export * from "./src/main/ets/FaceDectorPackage" export * from "./src/main/ets/FaceDectorTurboModule" export * from "./src/main/ets/ReactNativeCameraPackage" diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 000000000..f5a5204d4 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * 库根薄壳 jest 配置:转发到 jest/jest.config.js(真正的配置与注释在那里)。 + * Jest CLI 从库根运行时只自动发现库根的 jest.config.js。 + */ + +module.exports = require("./jest/jest.config.js"); diff --git a/jest/__tests__/constants-defaultprops-test.js b/jest/__tests__/constants-defaultprops-test.js new file mode 100644 index 000000000..f0e5a296b --- /dev/null +++ b/jest/__tests__/constants-defaultprops-test.js @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * RNCamera.js 的 Constants 与 defaultProps 形状白盒测试。 + * 重点锁定 harmony 适配的枚举常量字符串化(JS stub 常量须与 cpp 枚举解析一致)。 + */ + +jest.mock("react", () => require("../helpers/reactMock")); +jest.mock("prop-types", () => require("../helpers/propTypesMock")); +jest.mock("react-native", () => require("../helpers/reactNativeMock")); +jest.mock("../../src/NativeCamera", () => require("../helpers/nativeCameraMock")); +jest.mock("../../src/NativeCameraModule", () => require("../helpers/nativeCameraModuleMock")); +jest.mock("../../src/NativeFaceDetector", () => require("../helpers/nativeFaceDetectorMock")); +jest.mock("../../src/FaceDetector", () => { + const Native = require("../helpers/nativeFaceDetectorMock"); + class FaceDetector {} + FaceDetector.Constants = { + Mode: {}, + Landmarks: {}, + Classifications: {}, + }; + FaceDetector.detectFacesAsync = jest.fn(); + return { __esModule: true, default: FaceDetector, Constants: FaceDetector.Constants }; +}); + +const { RNCamera } = require("../../src/index"); + +describe("Constants 形状(harmony 枚举字符串化锁定)", () => { + const { Constants } = RNCamera; + + it("Type 为字符串枚举", () => { + expect(Constants.Type.back).toBe("back"); + expect(Constants.Type.front).toBe("front"); + }); + + it("AutoFocus 为字符串枚举", () => { + expect(Constants.AutoFocus.on).toBe("on"); + expect(Constants.AutoFocus.off).toBe("off"); + }); + + it("FlashMode 为字符串枚举(4 值)", () => { + expect(Constants.FlashMode).toEqual({ off: "off", on: "on", torch: "torch", auto: "auto" }); + }); + + it("WhiteBalance 为字符串枚举(6 值)", () => { + expect(Constants.WhiteBalance).toEqual({ + sunny: "sunny", + cloudy: "cloudy", + shadow: "shadow", + incandescent: "incandescent", + fluorescent: "fluorescent", + auto: "auto", + }); + }); + + it("GoogleVisionBarcodeDetection 形状", () => { + expect(Constants.GoogleVisionBarcodeDetection).toEqual({ BarcodeType: 0, BarcodeMode: 0 }); + }); + + it("视频稳定枚举默认值 stub 存在", () => { + expect(Constants.VideoStabilization).toBeDefined(); + }); +}); + +describe("defaultProps 形状", () => { + const { RNCamera } = require("../../src/index"); + const defaultProps = RNCamera.defaultProps; + + it("cameraId 默认空字符串(4.2.1 语义:null -> '')", () => { + expect(defaultProps.cameraId).toBe(""); + }); + + it("type 默认 back", () => { + expect(defaultProps.type).toBe("back"); + }); + + it("playSoundOnRecord 默认 false", () => { + expect(defaultProps.playSoundOnRecord).toBe(false); + }); + + it("captureAudio 默认 true", () => { + expect(defaultProps.captureAudio).toBe(true); + }); + + it("枚举类默认值取自字符串常量", () => { + expect(defaultProps.autoFocus).toBe("on"); + expect(defaultProps.flashMode).toBe("off"); + expect(defaultProps.whiteBalance).toBe("auto"); + }); + + it("常规默认值", () => { + expect(defaultProps.ratio).toBe("4:3"); + expect(defaultProps.pictureSize).toBe("None"); + expect(defaultProps.playSoundOnCapture).toBe(false); + expect(defaultProps.videoStabilizationMode).toBe("off"); + }); +}); diff --git a/jest/__tests__/events-test.js b/jest/__tests__/events-test.js new file mode 100644 index 000000000..a07cd71db --- /dev/null +++ b/jest/__tests__/events-test.js @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * RNCamera 事件处理白盒测试: + * - _onObjectDetected 的事件去重节流(同 payload 在 EventThrottleMs 内不重复回调) + * - _onTouch 的 onTap / onDoubleTap 分发(isDoubleTap 标志区分) + */ + +jest.mock("react", () => require("../helpers/reactMock")); +jest.mock("prop-types", () => require("../helpers/propTypesMock")); +jest.mock("react-native", () => require("../helpers/reactNativeMock")); +jest.mock("../../src/NativeCamera", () => require("../helpers/nativeCameraMock")); +jest.mock("../../src/NativeCameraModule", () => require("../helpers/nativeCameraModuleMock")); +jest.mock("../../src/NativeFaceDetector", () => require("../helpers/nativeFaceDetectorMock")); +jest.mock("../../src/FaceDetector", () => { + const Native = require("../helpers/nativeFaceDetectorMock"); + class FaceDetector {} + FaceDetector.Constants = { Mode: {}, Landmarks: {}, Classifications: {} }; + FaceDetector.detectFacesAsync = jest.fn(); + return { __esModule: true, default: FaceDetector, Constants: FaceDetector.Constants }; +}); + +const { RNCamera } = require("../../src/index"); + +function makeEvent(type, payload) { + return { type, ...payload }; +} + +describe("_onObjectDetected 节流", () => { + it("相同 payload 在节流窗口内只回调一次", () => { + const cam = new RNCamera({}); + const cb = jest.fn(); + const handler = cam._onObjectDetected(cb); + const nativeEvent = makeEvent("onBarCodeRead", { data: "qr-1" }); + handler({ nativeEvent }); + handler({ nativeEvent }); + expect(cb).toHaveBeenCalledTimes(1); + }); + + it("payload 变化后再次回调", () => { + const cam = new RNCamera({}); + const cb = jest.fn(); + const handler = cam._onObjectDetected(cb); + handler({ nativeEvent: makeEvent("onBarCodeRead", { data: "qr-1" }) }); + handler({ nativeEvent: makeEvent("onBarCodeRead", { data: "qr-2" }) }); + expect(cb).toHaveBeenCalledTimes(2); + }); + + it("callback 为空时不写入节流缓存", () => { + const cam = new RNCamera({}); + const handler = cam._onObjectDetected(undefined); + expect(() => handler({ nativeEvent: makeEvent("onBarCodeRead", { data: "x" }) })).not.toThrow(); + }); +}); + +describe("_onTouch 分发", () => { + it("单击分发 onTap", () => { + const cam = new RNCamera({}); + const onTap = jest.fn(); + const onDoubleTap = jest.fn(); + cam.props = { onTap, onDoubleTap }; + cam._onTouch({ nativeEvent: { isDoubleTap: false, touchOrigin: { x: 1, y: 2 } } }); + expect(onTap).toHaveBeenCalledWith({ x: 1, y: 2 }); + expect(onDoubleTap).not.toHaveBeenCalled(); + }); + + it("双击分发 onDoubleTap", () => { + const cam = new RNCamera({}); + const onTap = jest.fn(); + const onDoubleTap = jest.fn(); + cam.props = { onTap, onDoubleTap }; + cam._onTouch({ nativeEvent: { isDoubleTap: true, touchOrigin: { x: 3, y: 4 } } }); + expect(onDoubleTap).toHaveBeenCalledWith({ x: 3, y: 4 }); + expect(onTap).not.toHaveBeenCalled(); + }); + + it("未传回调时不抛错", () => { + const cam = new RNCamera({}); + cam.props = {}; + expect(() => + cam._onTouch({ nativeEvent: { isDoubleTap: false, touchOrigin: { x: 0, y: 0 } } }) + ).not.toThrow(); + }); +}); diff --git a/jest/__tests__/facedetector-test.js b/jest/__tests__/facedetector-test.js new file mode 100644 index 000000000..78b1e837f --- /dev/null +++ b/jest/__tests__/facedetector-test.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * FaceDetector.js 白盒测试: + * - detectFacesAsync 的参数合并({...options, uri}) + * - NativeTurboModule 缺失时的 stub 兜底(detectFaces reject) + */ + +jest.mock("../../src/NativeFaceDetector", () => require("../helpers/nativeFaceDetectorMock")); + +const NativeFaceDetector = require("../../src/NativeFaceDetector"); +const FaceDetector = require("../../src/FaceDetector").default; + +describe("FaceDetector(注入 stub)", () => { + it("detectFacesAsync 合并 uri 与 options", async () => { + NativeFaceDetector.detectFaces.mockResolvedValueOnce([]); + const out = await FaceDetector.detectFacesAsync("file:///f.jpg", { mode: "fast" }); + expect(out).toEqual([]); + expect(NativeFaceDetector.detectFaces).toHaveBeenCalledWith({ mode: "fast", uri: "file:///f.jpg" }); + }); + + it("Constants 形状透传", () => { + expect(FaceDetector.Constants).toBeDefined(); + expect(typeof FaceDetector.detectFacesAsync).toBe("function"); + }); + +}); diff --git a/jest/__tests__/index-test.js b/jest/__tests__/index-test.js new file mode 100644 index 000000000..52b7d4fdd --- /dev/null +++ b/jest/__tests__/index-test.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * index.js 白盒测试(exports 形状与再导出透传)。 + */ + +jest.mock("react", () => require("../helpers/reactMock")); +jest.mock("prop-types", () => require("../helpers/propTypesMock")); +jest.mock("react-native", () => require("../helpers/reactNativeMock")); +jest.mock("../../src/NativeCamera", () => require("../helpers/nativeCameraMock")); +jest.mock("../../src/NativeCameraModule", () => require("../helpers/nativeCameraModuleMock")); +jest.mock("../../src/NativeFaceDetector", () => require("../helpers/nativeFaceDetectorMock")); +jest.mock("../../src/FaceDetector", () => { + const Native = require("../helpers/nativeFaceDetectorMock"); + class FaceDetector {} + FaceDetector.Constants = { + Mode: Native.Mode, + Landmarks: Native.Landmarks, + Classifications: Native.Classifications, + }; + FaceDetector.detectFacesAsync = jest.fn(); + return { __esModule: true, default: FaceDetector, Constants: FaceDetector.Constants }; +}); + +const index = require("../../src/index"); +const nativeModuleMock = require("../helpers/nativeCameraModuleMock"); + +describe("index exports 形状", () => { + it("导出 RNCamera(类)", () => { + expect(typeof index.RNCamera).toBe("function"); + expect(index.RNCamera.displayName).toBeUndefined(); + expect(index.RNCamera.prototype).toBeDefined(); + }); + + it("导出 FaceDetector(类)", () => { + expect(typeof index.FaceDetector).toBe("function"); + }); + + it("导出 hasTorch(函数)", () => { + expect(typeof index.hasTorch).toBe("function"); + }); + + it("RNCamera.Constants 形状(Type/AutoFocus/FlashMode 字符串化)", () => { + expect(index.RNCamera.Constants.Type.back).toBe("back"); + expect(index.RNCamera.Constants.FlashMode.off).toBe("off"); + }); + + it("hasTorch 调用透传到 harmony 分支的 NativeCameraModule", async () => { + nativeModuleMock.hasTorch.mockResolvedValueOnce(false); + await expect(index.hasTorch()).resolves.toBe(false); + expect(nativeModuleMock.hasTorch).toHaveBeenCalledTimes(1); + }); +}); diff --git a/jest/__tests__/platform-routing-test.js b/jest/__tests__/platform-routing-test.js new file mode 100644 index 000000000..b156bd7d4 --- /dev/null +++ b/jest/__tests__/platform-routing-test.js @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * 平台路由白盒测试:4.2.1 新增接口的 harmony 分支透传与 iOS 分支行为。 + * 通过 RNCameraJestMock.setPlatformOS 在文件内切换平台态。 + */ + +jest.mock("react", () => require("../helpers/reactMock")); +jest.mock("prop-types", () => require("../helpers/propTypesMock")); +jest.mock("react-native", () => require("../helpers/reactNativeMock")); +jest.mock("../../src/NativeCamera", () => require("../helpers/nativeCameraMock")); +jest.mock("../../src/NativeCameraModule", () => require("../helpers/nativeCameraModuleMock")); +jest.mock("../../src/NativeFaceDetector", () => require("../helpers/nativeFaceDetectorMock")); +jest.mock("../../src/FaceDetector", () => { + const Native = require("../helpers/nativeFaceDetectorMock"); + class FaceDetector {} + FaceDetector.Constants = { Mode: {}, Landmarks: {}, Classifications: {} }; + FaceDetector.detectFacesAsync = jest.fn(); + return { __esModule: true, default: FaceDetector, Constants: FaceDetector.Constants }; +}); + +const { RNCamera, hasTorch } = require("../../src/index"); +const NativeCameraModule = require("../../src/NativeCameraModule"); +const { RNCameraJestMock } = require("../helpers/reactNativeMock"); + +describe("harmony 平台路由(4.2.1 新增接口)", () => { + afterEach(() => { + jest.clearAllMocks(); + RNCameraJestMock.setPlatformOS("harmony"); + }); + + it("hasTorch 透传 NativeCameraModule.hasTorch", async () => { + NativeCameraModule.hasTorch.mockResolvedValueOnce(true); + await expect(hasTorch()).resolves.toBe(true); + expect(NativeCameraModule.hasTorch).toHaveBeenCalledTimes(1); + }); + + it("checkIfVideoIsValid 透传路径参数", async () => { + NativeCameraModule.checkIfVideoIsValid.mockResolvedValueOnce(false); + await expect(RNCamera.checkIfVideoIsValid("/tmp/v.mp4")).resolves.toBe(false); + expect(NativeCameraModule.checkIfVideoIsValid).toHaveBeenCalledWith("/tmp/v.mp4"); + }); + + it("getSupportedPreviewFpsRange 透传 handle", async () => { + NativeCameraModule.getSupportedPreviewFpsRange.mockResolvedValueOnce(["15,30"]); + const cam = new RNCamera({}); + cam._cameraHandle = 7; + await expect(cam.getSupportedPreviewFpsRange()).resolves.toEqual(["15,30"]); + expect(NativeCameraModule.getSupportedPreviewFpsRange).toHaveBeenCalledWith(7); + }); + + it("getSupportedRatiosAsync 透传 handle", async () => { + NativeCameraModule.getSupportedRatios.mockResolvedValueOnce(["4:3"]); + const cam = new RNCamera({}); + cam._cameraHandle = 3; + await expect(cam.getSupportedRatiosAsync()).resolves.toEqual(["4:3"]); + expect(NativeCameraModule.getSupportedRatios).toHaveBeenCalledWith(3); + }); + + it("getAvailablePictureSizes 透传", async () => { + NativeCameraModule.getAvailablePictureSizes.mockResolvedValueOnce(["640x480"]); + const cam = new RNCamera({}); + await expect(cam.getAvailablePictureSizes()).resolves.toEqual(["640x480"]); + expect(NativeCameraModule.getAvailablePictureSizes).toHaveBeenCalledTimes(1); + }); + + it("getCameraIdsAsync 走 harmony 分支", async () => { + NativeCameraModule.getCameraIds.mockResolvedValueOnce([{ id: "camX", type: 1 }]); + const cam = new RNCamera({}); + const data = await cam.getCameraIdsAsync(); + expect(data).toEqual([{ id: "camX", type: 1 }]); + expect(NativeCameraModule.getCameraIds).toHaveBeenCalledTimes(1); + }); +}); + +describe("iOS 平台分支", () => { + afterEach(() => { + jest.clearAllMocks(); + RNCameraJestMock.setPlatformOS("harmony"); + }); + + it("checkIfVideoIsValid 在 iOS 短路返回 true 且不调 native", async () => { + RNCameraJestMock.setPlatformOS("ios"); + await expect(RNCamera.checkIfVideoIsValid("/tmp/x.mp4")).resolves.toBe(true); + expect(NativeCameraModule.checkIfVideoIsValid).not.toHaveBeenCalled(); + }); + + it("getSupportedPreviewFpsRange 在 iOS 抛不支持错误", async () => { + RNCameraJestMock.setPlatformOS("ios"); + const cam = new RNCamera({}); + await expect(cam.getSupportedPreviewFpsRange()).rejects.toThrow("not supported on iOS"); + expect(NativeCameraModule.getSupportedPreviewFpsRange).not.toHaveBeenCalled(); + }); + + it("getSupportedRatiosAsync 在 iOS 抛不支持错误", async () => { + RNCameraJestMock.setPlatformOS("ios"); + const cam = new RNCamera({}); + await expect(cam.getSupportedRatiosAsync()).rejects.toThrow("Ratio is not supported on iOS"); + expect(NativeCameraModule.getSupportedRatios).not.toHaveBeenCalled(); + }); +}); diff --git a/jest/babel.config.js b/jest/babel.config.js new file mode 100644 index 000000000..aebffbd46 --- /dev/null +++ b/jest/babel.config.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * Babel 配置(rnoh-js-test skill 配置三件套的对齐产物)。 + * + * jest.config.js 的 transform 已给 babel-jest 内联了同样的 presets/plugins + * (并显式 configFile: false / babelrc: false),因此 jest 运行时不会加载本文件。 + * 本文件作用:对齐 skill 配置产物形态,供未来独立 babel 转译脚本复用。 + */ + +module.exports = { + plugins: [ + ["babel-plugin-syntax-hermes-parser", { parseLangTypes: "flow" }], + ], + presets: [ + ["@babel/preset-env", { targets: { node: "current" }, modules: "commonjs" }], + ["@babel/preset-flow", { all: true }], + ], +}; diff --git a/jest/coverage/clover.xml b/jest/coverage/clover.xml new file mode 100644 index 000000000..e102c53e8 --- /dev/null +++ b/jest/coverage/clover.xml @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jest/coverage/coverage-final.json b/jest/coverage/coverage-final.json new file mode 100644 index 000000000..2039d3b92 --- /dev/null +++ b/jest/coverage/coverage-final.json @@ -0,0 +1,4 @@ +{"D:\\work_file\\tpc\\react-native-camera\\react-native-camera-sig\\src\\FaceDetector.js": {"path":"D:\\work_file\\tpc\\react-native-camera\\react-native-camera-sig\\src\\FaceDetector.js","statementMap":{"0":{"start":{"line":4,"column":37},"end":{"line":4,"column":90}},"1":{"start":{"line":6,"column":35},"end":{"line":12,"column":1}},"2":{"start":{"line":11,"column":21},"end":{"line":11,"column":85}},"3":{"start":{"line":11,"column":48},"end":{"line":11,"column":84}},"4":{"start":{"line":49,"column":21},"end":{"line":53,"column":3}},"5":{"start":{"line":56,"column":4},"end":{"line":56,"column":63}},"6":{"start":{"line":60,"column":25},"end":{"line":60,"column":47}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":11,"column":15},"end":{"line":11,"column":16}},"loc":{"start":{"line":11,"column":21},"end":{"line":11,"column":85}},"line":11},"1":{"name":"(anonymous_1)","decl":{"start":{"line":11,"column":33},"end":{"line":11,"column":34}},"loc":{"start":{"line":11,"column":48},"end":{"line":11,"column":84}},"line":11},"2":{"name":"(anonymous_2)","decl":{"start":{"line":55,"column":2},"end":{"line":55,"column":3}},"loc":{"start":{"line":55,"column":96},"end":{"line":57,"column":3}},"line":55}},"branchMap":{"0":{"loc":{"start":{"line":6,"column":35},"end":{"line":12,"column":1}},"type":"binary-expr","locations":[{"start":{"line":6,"column":35},"end":{"line":6,"column":52}},{"start":{"line":6,"column":56},"end":{"line":12,"column":1}}],"line":6}},"s":{"0":1,"1":1,"2":0,"3":0,"4":1,"5":1,"6":1},"f":{"0":0,"1":0,"2":1},"b":{"0":[1,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"8b13d8069c8b977e3bd35f0d68d599043abe0aac"} +,"D:\\work_file\\tpc\\react-native-camera\\react-native-camera-sig\\src\\RNCamera.js": {"path":"D:\\work_file\\tpc\\react-native-camera\\react-native-camera-sig\\src\\RNCamera.js","statementMap":{"0":{"start":{"line":19,"column":18},"end":{"line":25,"column":2}},"1":{"start":{"line":27,"column":27},"end":{"line":86,"column":1}},"2":{"start":{"line":33,"column":29},"end":{"line":33,"column":34}},"3":{"start":{"line":34,"column":34},"end":{"line":34,"column":39}},"4":{"start":{"line":35,"column":2},"end":{"line":51,"column":3}},"5":{"start":{"line":36,"column":4},"end":{"line":36,"column":79}},"6":{"start":{"line":37,"column":9},"end":{"line":51,"column":3}},"7":{"start":{"line":38,"column":35},"end":{"line":41,"column":5}},"8":{"start":{"line":42,"column":4},"end":{"line":46,"column":5}},"9":{"start":{"line":43,"column":6},"end":{"line":43,"column":52}},"10":{"start":{"line":45,"column":6},"end":{"line":45,"column":91}},"11":{"start":{"line":47,"column":9},"end":{"line":51,"column":3}},"12":{"start":{"line":48,"column":4},"end":{"line":48,"column":77}},"13":{"start":{"line":49,"column":9},"end":{"line":51,"column":3}},"14":{"start":{"line":50,"column":4},"end":{"line":50,"column":74}},"15":{"start":{"line":53,"column":2},"end":{"line":80,"column":3}},"16":{"start":{"line":54,"column":4},"end":{"line":79,"column":5}},"17":{"start":{"line":55,"column":6},"end":{"line":55,"column":92}},"18":{"start":{"line":56,"column":11},"end":{"line":79,"column":5}},"19":{"start":{"line":57,"column":6},"end":{"line":74,"column":7}},"20":{"start":{"line":58,"column":38},"end":{"line":61,"column":9}},"21":{"start":{"line":62,"column":8},"end":{"line":66,"column":9}},"22":{"start":{"line":63,"column":10},"end":{"line":63,"column":60}},"23":{"start":{"line":65,"column":10},"end":{"line":65,"column":99}},"24":{"start":{"line":67,"column":13},"end":{"line":74,"column":7}},"25":{"start":{"line":69,"column":8},"end":{"line":73,"column":10}},"26":{"start":{"line":75,"column":11},"end":{"line":79,"column":5}},"27":{"start":{"line":76,"column":6},"end":{"line":76,"column":84}},"28":{"start":{"line":77,"column":11},"end":{"line":79,"column":5}},"29":{"start":{"line":78,"column":6},"end":{"line":78,"column":79}},"30":{"start":{"line":82,"column":2},"end":{"line":85,"column":4}},"31":{"start":{"line":88,"column":15},"end":{"line":98,"column":2}},"32":{"start":{"line":317,"column":48},"end":{"line":321,"column":1}},"33":{"start":{"line":327,"column":4},"end":{"line":331,"column":1}},"34":{"start":{"line":333,"column":30},"end":{"line":395,"column":1}},"35":{"start":{"line":397,"column":24},"end":{"line":397,"column":27}},"36":{"start":{"line":399,"column":18},"end":{"line":405,"column":1}},"37":{"start":{"line":400,"column":17},"end":{"line":400,"column":19}},"38":{"start":{"line":401,"column":2},"end":{"line":403,"column":5}},"39":{"start":{"line":402,"column":4},"end":{"line":402,"column":37}},"40":{"start":{"line":404,"column":2},"end":{"line":404,"column":16}},"41":{"start":{"line":408,"column":21},"end":{"line":430,"column":3}},"42":{"start":{"line":433,"column":28},"end":{"line":445,"column":3}},"43":{"start":{"line":447,"column":21},"end":{"line":506,"column":3}},"44":{"start":{"line":509,"column":32},"end":{"line":548,"column":3}},"45":{"start":{"line":550,"column":61},"end":{"line":550,"column":65}},"46":{"start":{"line":557,"column":4},"end":{"line":557,"column":17}},"47":{"start":{"line":558,"column":4},"end":{"line":558,"column":26}},"48":{"start":{"line":559,"column":4},"end":{"line":559,"column":31}},"49":{"start":{"line":560,"column":4},"end":{"line":560,"column":27}},"50":{"start":{"line":561,"column":4},"end":{"line":565,"column":6}},"51":{"start":{"line":569,"column":4},"end":{"line":571,"column":5}},"52":{"start":{"line":570,"column":6},"end":{"line":570,"column":19}},"53":{"start":{"line":572,"column":4},"end":{"line":574,"column":5}},"54":{"start":{"line":573,"column":6},"end":{"line":573,"column":26}},"55":{"start":{"line":576,"column":4},"end":{"line":587,"column":5}},"56":{"start":{"line":577,"column":6},"end":{"line":586,"column":7}},"57":{"start":{"line":578,"column":32},"end":{"line":578,"column":39}},"58":{"start":{"line":579,"column":8},"end":{"line":579,"column":69}},"59":{"start":{"line":580,"column":8},"end":{"line":585,"column":9}},"60":{"start":{"line":581,"column":10},"end":{"line":584,"column":11}},"61":{"start":{"line":583,"column":12},"end":{"line":583,"column":69}},"62":{"start":{"line":589,"column":4},"end":{"line":591,"column":5}},"63":{"start":{"line":590,"column":6},"end":{"line":590,"column":40}},"64":{"start":{"line":593,"column":4},"end":{"line":595,"column":5}},"65":{"start":{"line":594,"column":6},"end":{"line":594,"column":43}},"66":{"start":{"line":597,"column":4},"end":{"line":599,"column":5}},"67":{"start":{"line":598,"column":6},"end":{"line":598,"column":43}},"68":{"start":{"line":601,"column":4},"end":{"line":614,"column":7}},"69":{"start":{"line":602,"column":6},"end":{"line":613,"column":7}},"70":{"start":{"line":603,"column":38},"end":{"line":606,"column":10}},"71":{"start":{"line":604,"column":10},"end":{"line":604,"column":24}},"72":{"start":{"line":605,"column":10},"end":{"line":605,"column":41}},"73":{"start":{"line":608,"column":8},"end":{"line":608,"column":54}},"74":{"start":{"line":611,"column":8},"end":{"line":611,"column":18}},"75":{"start":{"line":612,"column":8},"end":{"line":612,"column":15}},"76":{"start":{"line":618,"column":4},"end":{"line":624,"column":5}},"77":{"start":{"line":619,"column":6},"end":{"line":619,"column":67}},"78":{"start":{"line":620,"column":11},"end":{"line":624,"column":5}},"79":{"start":{"line":621,"column":6},"end":{"line":621,"column":77}},"80":{"start":{"line":623,"column":6},"end":{"line":623,"column":55}},"81":{"start":{"line":628,"column":4},"end":{"line":643,"column":5}},"82":{"start":{"line":629,"column":6},"end":{"line":629,"column":66}},"83":{"start":{"line":630,"column":11},"end":{"line":643,"column":5}},"84":{"start":{"line":631,"column":6},"end":{"line":631,"column":48}},"85":{"start":{"line":632,"column":11},"end":{"line":643,"column":5}},"86":{"start":{"line":633,"column":6},"end":{"line":642,"column":9}},"87":{"start":{"line":634,"column":8},"end":{"line":641,"column":9}},"88":{"start":{"line":635,"column":21},"end":{"line":635,"column":60}},"89":{"start":{"line":636,"column":10},"end":{"line":636,"column":23}},"90":{"start":{"line":639,"column":10},"end":{"line":639,"column":20}},"91":{"start":{"line":640,"column":10},"end":{"line":640,"column":17}},"92":{"start":{"line":647,"column":4},"end":{"line":653,"column":5}},"93":{"start":{"line":648,"column":6},"end":{"line":648,"column":59}},"94":{"start":{"line":649,"column":11},"end":{"line":653,"column":5}},"95":{"start":{"line":650,"column":6},"end":{"line":650,"column":64}},"96":{"start":{"line":652,"column":6},"end":{"line":652,"column":18}},"97":{"start":{"line":656,"column":32},"end":{"line":664,"column":3}},"98":{"start":{"line":657,"column":4},"end":{"line":663,"column":5}},"99":{"start":{"line":658,"column":6},"end":{"line":658,"column":76}},"100":{"start":{"line":659,"column":11},"end":{"line":663,"column":5}},"101":{"start":{"line":660,"column":6},"end":{"line":660,"column":86}},"102":{"start":{"line":662,"column":6},"end":{"line":662,"column":77}},"103":{"start":{"line":666,"column":29},"end":{"line":669,"column":3}},"104":{"start":{"line":668,"column":4},"end":{"line":668,"column":63}},"105":{"start":{"line":672,"column":4},"end":{"line":676,"column":5}},"106":{"start":{"line":673,"column":6},"end":{"line":673,"column":19}},"107":{"start":{"line":674,"column":11},"end":{"line":676,"column":5}},"108":{"start":{"line":675,"column":6},"end":{"line":675,"column":71}},"109":{"start":{"line":677,"column":4},"end":{"line":688,"column":5}},"110":{"start":{"line":678,"column":6},"end":{"line":687,"column":7}},"111":{"start":{"line":679,"column":32},"end":{"line":679,"column":39}},"112":{"start":{"line":680,"column":8},"end":{"line":680,"column":69}},"113":{"start":{"line":681,"column":8},"end":{"line":686,"column":9}},"114":{"start":{"line":682,"column":10},"end":{"line":685,"column":11}},"115":{"start":{"line":684,"column":12},"end":{"line":684,"column":69}},"116":{"start":{"line":690,"column":4},"end":{"line":695,"column":5}},"117":{"start":{"line":691,"column":6},"end":{"line":694,"column":7}},"118":{"start":{"line":693,"column":8},"end":{"line":693,"column":67}},"119":{"start":{"line":697,"column":44},"end":{"line":697,"column":54}},"120":{"start":{"line":698,"column":29},"end":{"line":698,"column":39}},"121":{"start":{"line":700,"column":4},"end":{"line":705,"column":5}},"122":{"start":{"line":704,"column":6},"end":{"line":704,"column":26}},"123":{"start":{"line":707,"column":4},"end":{"line":715,"column":5}},"124":{"start":{"line":708,"column":6},"end":{"line":714,"column":7}},"125":{"start":{"line":713,"column":8},"end":{"line":713,"column":84}},"126":{"start":{"line":716,"column":4},"end":{"line":718,"column":5}},"127":{"start":{"line":717,"column":6},"end":{"line":717,"column":43}},"128":{"start":{"line":720,"column":4},"end":{"line":732,"column":7}},"129":{"start":{"line":721,"column":6},"end":{"line":731,"column":7}},"130":{"start":{"line":722,"column":44},"end":{"line":725,"column":10}},"131":{"start":{"line":723,"column":10},"end":{"line":723,"column":24}},"132":{"start":{"line":724,"column":10},"end":{"line":724,"column":47}},"133":{"start":{"line":726,"column":8},"end":{"line":726,"column":49}},"134":{"start":{"line":729,"column":8},"end":{"line":729,"column":18}},"135":{"start":{"line":730,"column":8},"end":{"line":730,"column":15}},"136":{"start":{"line":736,"column":4},"end":{"line":738,"column":5}},"137":{"start":{"line":737,"column":6},"end":{"line":737,"column":43}},"138":{"start":{"line":739,"column":4},"end":{"line":739,"column":44}},"139":{"start":{"line":743,"column":4},"end":{"line":745,"column":5}},"140":{"start":{"line":744,"column":6},"end":{"line":744,"column":43}},"141":{"start":{"line":746,"column":4},"end":{"line":746,"column":45}},"142":{"start":{"line":750,"column":4},"end":{"line":752,"column":5}},"143":{"start":{"line":751,"column":6},"end":{"line":751,"column":43}},"144":{"start":{"line":753,"column":4},"end":{"line":753,"column":46}},"145":{"start":{"line":757,"column":4},"end":{"line":759,"column":5}},"146":{"start":{"line":758,"column":6},"end":{"line":758,"column":43}},"147":{"start":{"line":761,"column":4},"end":{"line":761,"column":43}},"148":{"start":{"line":765,"column":4},"end":{"line":767,"column":5}},"149":{"start":{"line":766,"column":6},"end":{"line":766,"column":43}},"150":{"start":{"line":768,"column":4},"end":{"line":780,"column":7}},"151":{"start":{"line":769,"column":6},"end":{"line":779,"column":7}},"152":{"start":{"line":770,"column":44},"end":{"line":773,"column":10}},"153":{"start":{"line":771,"column":10},"end":{"line":771,"column":30}},"154":{"start":{"line":772,"column":10},"end":{"line":772,"column":47}},"155":{"start":{"line":774,"column":8},"end":{"line":774,"column":45}},"156":{"start":{"line":777,"column":8},"end":{"line":777,"column":18}},"157":{"start":{"line":778,"column":8},"end":{"line":778,"column":15}},"158":{"start":{"line":784,"column":4},"end":{"line":786,"column":5}},"159":{"start":{"line":785,"column":6},"end":{"line":785,"column":43}},"160":{"start":{"line":787,"column":4},"end":{"line":787,"column":44}},"161":{"start":{"line":790,"column":18},"end":{"line":794,"column":3}},"162":{"start":{"line":791,"column":4},"end":{"line":793,"column":5}},"163":{"start":{"line":792,"column":6},"end":{"line":792,"column":43}},"164":{"start":{"line":796,"column":19},"end":{"line":800,"column":3}},"165":{"start":{"line":797,"column":4},"end":{"line":799,"column":5}},"166":{"start":{"line":798,"column":6},"end":{"line":798,"column":33}},"167":{"start":{"line":802,"column":24},"end":{"line":806,"column":3}},"168":{"start":{"line":803,"column":4},"end":{"line":805,"column":5}},"169":{"start":{"line":804,"column":6},"end":{"line":804,"column":38}},"170":{"start":{"line":807,"column":13},"end":{"line":814,"column":3}},"171":{"start":{"line":808,"column":4},"end":{"line":810,"column":5}},"172":{"start":{"line":809,"column":6},"end":{"line":809,"column":48}},"173":{"start":{"line":811,"column":4},"end":{"line":813,"column":5}},"174":{"start":{"line":812,"column":6},"end":{"line":812,"column":54}},"175":{"start":{"line":815,"column":22},"end":{"line":819,"column":3}},"176":{"start":{"line":816,"column":4},"end":{"line":818,"column":5}},"177":{"start":{"line":817,"column":6},"end":{"line":817,"column":36}},"178":{"start":{"line":821,"column":20},"end":{"line":828,"column":3}},"179":{"start":{"line":822,"column":4},"end":{"line":827,"column":5}},"180":{"start":{"line":823,"column":6},"end":{"line":826,"column":9}},"181":{"start":{"line":830,"column":20},"end":{"line":834,"column":3}},"182":{"start":{"line":831,"column":4},"end":{"line":833,"column":5}},"183":{"start":{"line":832,"column":6},"end":{"line":832,"column":45}},"184":{"start":{"line":836,"column":22},"end":{"line":852,"column":3}},"185":{"start":{"line":836,"column":47},"end":{"line":852,"column":3}},"186":{"start":{"line":837,"column":21},"end":{"line":837,"column":32}},"187":{"start":{"line":838,"column":4},"end":{"line":845,"column":5}},"188":{"start":{"line":844,"column":6},"end":{"line":844,"column":13}},"189":{"start":{"line":847,"column":4},"end":{"line":851,"column":5}},"190":{"start":{"line":848,"column":6},"end":{"line":848,"column":28}},"191":{"start":{"line":849,"column":6},"end":{"line":849,"column":47}},"192":{"start":{"line":850,"column":6},"end":{"line":850,"column":59}},"193":{"start":{"line":854,"column":26},"end":{"line":858,"column":3}},"194":{"start":{"line":855,"column":4},"end":{"line":857,"column":5}},"195":{"start":{"line":856,"column":6},"end":{"line":856,"column":41}},"196":{"start":{"line":860,"column":18},"end":{"line":868,"column":3}},"197":{"start":{"line":861,"column":4},"end":{"line":867,"column":5}},"198":{"start":{"line":862,"column":6},"end":{"line":862,"column":28}},"199":{"start":{"line":863,"column":6},"end":{"line":863,"column":47}},"200":{"start":{"line":865,"column":6},"end":{"line":865,"column":29}},"201":{"start":{"line":866,"column":6},"end":{"line":866,"column":32}},"202":{"start":{"line":871,"column":4},"end":{"line":871,"column":28}},"203":{"start":{"line":880,"column":8},"end":{"line":880,"column":18}},"204":{"start":{"line":882,"column":28},"end":{"line":882,"column":58}},"205":{"start":{"line":883,"column":27},"end":{"line":883,"column":62}},"206":{"start":{"line":884,"column":4},"end":{"line":899,"column":5}},"207":{"start":{"line":886,"column":6},"end":{"line":888,"column":8}},"208":{"start":{"line":889,"column":6},"end":{"line":893,"column":8}},"209":{"start":{"line":894,"column":6},"end":{"line":898,"column":8}},"210":{"start":{"line":901,"column":64},"end":{"line":906,"column":5}},"211":{"start":{"line":908,"column":40},"end":{"line":910,"column":54}},"212":{"start":{"line":911,"column":4},"end":{"line":911,"column":65}},"213":{"start":{"line":918,"column":8},"end":{"line":918,"column":42}},"214":{"start":{"line":919,"column":4},"end":{"line":921,"column":5}},"215":{"start":{"line":920,"column":6},"end":{"line":920,"column":13}},"216":{"start":{"line":923,"column":4},"end":{"line":927,"column":7}},"217":{"start":{"line":934,"column":8},"end":{"line":934,"column":42}},"218":{"start":{"line":935,"column":4},"end":{"line":937,"column":5}},"219":{"start":{"line":936,"column":6},"end":{"line":936,"column":13}},"220":{"start":{"line":939,"column":4},"end":{"line":946,"column":6}},"221":{"start":{"line":949,"column":14},"end":{"line":955,"column":3}},"222":{"start":{"line":950,"column":53},"end":{"line":950,"column":63}},"223":{"start":{"line":951,"column":4},"end":{"line":953,"column":5}},"224":{"start":{"line":952,"column":6},"end":{"line":952,"column":48}},"225":{"start":{"line":954,"column":4},"end":{"line":954,"column":75}},"226":{"start":{"line":958,"column":12},"end":{"line":958,"column":62}},"227":{"start":{"line":958,"column":21},"end":{"line":958,"column":62}},"228":{"start":{"line":960,"column":19},"end":{"line":969,"column":3}},"229":{"start":{"line":961,"column":4},"end":{"line":967,"column":5}},"230":{"start":{"line":962,"column":6},"end":{"line":966,"column":9}},"231":{"start":{"line":968,"column":4},"end":{"line":968,"column":31}},"232":{"start":{"line":972,"column":38},"end":{"line":972,"column":74}},"233":{"start":{"line":974,"column":4},"end":{"line":1010,"column":5}},"234":{"start":{"line":975,"column":6},"end":{"line":1005,"column":8}},"235":{"start":{"line":986,"column":14},"end":{"line":986,"column":36}},"236":{"start":{"line":987,"column":14},"end":{"line":987,"column":55}},"237":{"start":{"line":1006,"column":11},"end":{"line":1010,"column":5}},"238":{"start":{"line":1007,"column":6},"end":{"line":1007,"column":49}},"239":{"start":{"line":1009,"column":6},"end":{"line":1009,"column":42}},"240":{"start":{"line":1014,"column":21},"end":{"line":1014,"column":56}},"241":{"start":{"line":1016,"column":4},"end":{"line":1018,"column":5}},"242":{"start":{"line":1017,"column":6},"end":{"line":1017,"column":44}},"243":{"start":{"line":1020,"column":4},"end":{"line":1022,"column":5}},"244":{"start":{"line":1021,"column":6},"end":{"line":1021,"column":57}},"245":{"start":{"line":1024,"column":4},"end":{"line":1026,"column":5}},"246":{"start":{"line":1025,"column":6},"end":{"line":1025,"column":42}},"247":{"start":{"line":1028,"column":4},"end":{"line":1030,"column":5}},"248":{"start":{"line":1029,"column":6},"end":{"line":1029,"column":43}},"249":{"start":{"line":1032,"column":4},"end":{"line":1034,"column":5}},"250":{"start":{"line":1033,"column":6},"end":{"line":1033,"column":44}},"251":{"start":{"line":1036,"column":4},"end":{"line":1038,"column":5}},"252":{"start":{"line":1037,"column":6},"end":{"line":1037,"column":28}},"253":{"start":{"line":1040,"column":4},"end":{"line":1040,"column":20}},"254":{"start":{"line":1044,"column":4},"end":{"line":1046,"column":5}},"255":{"start":{"line":1045,"column":6},"end":{"line":1045,"column":49}},"256":{"start":{"line":1048,"column":4},"end":{"line":1048,"column":17}},"257":{"start":{"line":1052,"column":25},"end":{"line":1052,"column":41}},"258":{"start":{"line":1055,"column":2},"end":{"line":1057,"column":3}},"259":{"start":{"line":1056,"column":4},"end":{"line":1056,"column":41}},"260":{"start":{"line":1058,"column":2},"end":{"line":1058,"column":34}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":27,"column":27},"end":{"line":27,"column":28}},"loc":{"start":{"line":32,"column":85},"end":{"line":86,"column":1}},"line":32},"1":{"name":"(anonymous_1)","decl":{"start":{"line":399,"column":18},"end":{"line":399,"column":19}},"loc":{"start":{"line":399,"column":37},"end":{"line":405,"column":1}},"line":399},"2":{"name":"(anonymous_2)","decl":{"start":{"line":401,"column":28},"end":{"line":401,"column":29}},"loc":{"start":{"line":401,"column":46},"end":{"line":403,"column":3}},"line":401},"3":{"name":"(anonymous_3)","decl":{"start":{"line":556,"column":2},"end":{"line":556,"column":3}},"loc":{"start":{"line":556,"column":32},"end":{"line":566,"column":3}},"line":556},"4":{"name":"(anonymous_4)","decl":{"start":{"line":568,"column":2},"end":{"line":568,"column":3}},"loc":{"start":{"line":568,"column":51},"end":{"line":615,"column":3}},"line":568},"5":{"name":"(anonymous_5)","decl":{"start":{"line":601,"column":23},"end":{"line":601,"column":24}},"loc":{"start":{"line":601,"column":44},"end":{"line":614,"column":5}},"line":601},"6":{"name":"(anonymous_6)","decl":{"start":{"line":603,"column":84},"end":{"line":603,"column":85}},"loc":{"start":{"line":603,"column":94},"end":{"line":606,"column":9}},"line":603},"7":{"name":"(anonymous_7)","decl":{"start":{"line":617,"column":2},"end":{"line":617,"column":3}},"loc":{"start":{"line":617,"column":34},"end":{"line":625,"column":3}},"line":617},"8":{"name":"(anonymous_8)","decl":{"start":{"line":627,"column":2},"end":{"line":627,"column":3}},"loc":{"start":{"line":627,"column":28},"end":{"line":644,"column":3}},"line":627},"9":{"name":"(anonymous_9)","decl":{"start":{"line":633,"column":25},"end":{"line":633,"column":26}},"loc":{"start":{"line":633,"column":52},"end":{"line":642,"column":7}},"line":633},"10":{"name":"(anonymous_10)","decl":{"start":{"line":646,"column":2},"end":{"line":646,"column":3}},"loc":{"start":{"line":646,"column":41},"end":{"line":654,"column":3}},"line":646},"11":{"name":"(anonymous_11)","decl":{"start":{"line":656,"column":32},"end":{"line":656,"column":33}},"loc":{"start":{"line":656,"column":57},"end":{"line":664,"column":3}},"line":656},"12":{"name":"(anonymous_12)","decl":{"start":{"line":666,"column":29},"end":{"line":666,"column":30}},"loc":{"start":{"line":666,"column":51},"end":{"line":669,"column":3}},"line":666},"13":{"name":"(anonymous_13)","decl":{"start":{"line":671,"column":2},"end":{"line":671,"column":3}},"loc":{"start":{"line":671,"column":48},"end":{"line":733,"column":3}},"line":671},"14":{"name":"(anonymous_14)","decl":{"start":{"line":720,"column":23},"end":{"line":720,"column":24}},"loc":{"start":{"line":720,"column":44},"end":{"line":732,"column":5}},"line":720},"15":{"name":"(anonymous_15)","decl":{"start":{"line":722,"column":98},"end":{"line":722,"column":99}},"loc":{"start":{"line":722,"column":125},"end":{"line":725,"column":9}},"line":722},"16":{"name":"(anonymous_16)","decl":{"start":{"line":735,"column":2},"end":{"line":735,"column":3}},"loc":{"start":{"line":735,"column":18},"end":{"line":740,"column":3}},"line":735},"17":{"name":"(anonymous_17)","decl":{"start":{"line":742,"column":2},"end":{"line":742,"column":3}},"loc":{"start":{"line":742,"column":19},"end":{"line":747,"column":3}},"line":742},"18":{"name":"(anonymous_18)","decl":{"start":{"line":749,"column":2},"end":{"line":749,"column":3}},"loc":{"start":{"line":749,"column":20},"end":{"line":754,"column":3}},"line":749},"19":{"name":"(anonymous_19)","decl":{"start":{"line":756,"column":2},"end":{"line":756,"column":3}},"loc":{"start":{"line":756,"column":17},"end":{"line":762,"column":3}},"line":756},"20":{"name":"(anonymous_20)","decl":{"start":{"line":764,"column":2},"end":{"line":764,"column":3}},"loc":{"start":{"line":764,"column":16},"end":{"line":781,"column":3}},"line":764},"21":{"name":"(anonymous_21)","decl":{"start":{"line":768,"column":23},"end":{"line":768,"column":24}},"loc":{"start":{"line":768,"column":44},"end":{"line":780,"column":5}},"line":768},"22":{"name":"(anonymous_22)","decl":{"start":{"line":770,"column":90},"end":{"line":770,"column":91}},"loc":{"start":{"line":770,"column":107},"end":{"line":773,"column":9}},"line":770},"23":{"name":"(anonymous_23)","decl":{"start":{"line":783,"column":2},"end":{"line":783,"column":3}},"loc":{"start":{"line":783,"column":18},"end":{"line":788,"column":3}},"line":783},"24":{"name":"(anonymous_24)","decl":{"start":{"line":790,"column":18},"end":{"line":790,"column":19}},"loc":{"start":{"line":790,"column":67},"end":{"line":794,"column":3}},"line":790},"25":{"name":"(anonymous_25)","decl":{"start":{"line":796,"column":19},"end":{"line":796,"column":20}},"loc":{"start":{"line":796,"column":25},"end":{"line":800,"column":3}},"line":796},"26":{"name":"(anonymous_26)","decl":{"start":{"line":802,"column":24},"end":{"line":802,"column":25}},"loc":{"start":{"line":802,"column":30},"end":{"line":806,"column":3}},"line":802},"27":{"name":"(anonymous_27)","decl":{"start":{"line":807,"column":13},"end":{"line":807,"column":14}},"loc":{"start":{"line":807,"column":62},"end":{"line":814,"column":3}},"line":807},"28":{"name":"(anonymous_28)","decl":{"start":{"line":815,"column":22},"end":{"line":815,"column":23}},"loc":{"start":{"line":815,"column":28},"end":{"line":819,"column":3}},"line":815},"29":{"name":"(anonymous_29)","decl":{"start":{"line":821,"column":20},"end":{"line":821,"column":21}},"loc":{"start":{"line":821,"column":26},"end":{"line":828,"column":3}},"line":821},"30":{"name":"(anonymous_30)","decl":{"start":{"line":830,"column":20},"end":{"line":830,"column":21}},"loc":{"start":{"line":830,"column":69},"end":{"line":834,"column":3}},"line":830},"31":{"name":"(anonymous_31)","decl":{"start":{"line":836,"column":22},"end":{"line":836,"column":23}},"loc":{"start":{"line":836,"column":47},"end":{"line":852,"column":3}},"line":836},"32":{"name":"(anonymous_32)","decl":{"start":{"line":836,"column":47},"end":{"line":836,"column":48}},"loc":{"start":{"line":836,"column":96},"end":{"line":852,"column":3}},"line":836},"33":{"name":"(anonymous_33)","decl":{"start":{"line":854,"column":26},"end":{"line":854,"column":27}},"loc":{"start":{"line":854,"column":31},"end":{"line":858,"column":3}},"line":854},"34":{"name":"(anonymous_34)","decl":{"start":{"line":860,"column":18},"end":{"line":860,"column":19}},"loc":{"start":{"line":860,"column":36},"end":{"line":868,"column":3}},"line":860},"35":{"name":"(anonymous_35)","decl":{"start":{"line":870,"column":2},"end":{"line":870,"column":3}},"loc":{"start":{"line":870,"column":25},"end":{"line":872,"column":3}},"line":870},"36":{"name":"(anonymous_36)","decl":{"start":{"line":874,"column":2},"end":{"line":874,"column":3}},"loc":{"start":{"line":874,"column":32},"end":{"line":912,"column":3}},"line":874},"37":{"name":"(anonymous_37)","decl":{"start":{"line":914,"column":2},"end":{"line":914,"column":3}},"loc":{"start":{"line":914,"column":37},"end":{"line":928,"column":3}},"line":914},"38":{"name":"(anonymous_38)","decl":{"start":{"line":930,"column":2},"end":{"line":930,"column":3}},"loc":{"start":{"line":930,"column":28},"end":{"line":947,"column":3}},"line":930},"39":{"name":"(anonymous_39)","decl":{"start":{"line":949,"column":14},"end":{"line":949,"column":15}},"loc":{"start":{"line":949,"column":28},"end":{"line":955,"column":3}},"line":949},"40":{"name":"(anonymous_40)","decl":{"start":{"line":958,"column":12},"end":{"line":958,"column":13}},"loc":{"start":{"line":958,"column":21},"end":{"line":958,"column":62}},"line":958},"41":{"name":"(anonymous_41)","decl":{"start":{"line":960,"column":19},"end":{"line":960,"column":20}},"loc":{"start":{"line":960,"column":28},"end":{"line":969,"column":3}},"line":960},"42":{"name":"(anonymous_42)","decl":{"start":{"line":971,"column":2},"end":{"line":971,"column":3}},"loc":{"start":{"line":971,"column":11},"end":{"line":1011,"column":3}},"line":971},"43":{"name":"(anonymous_43)","decl":{"start":{"line":985,"column":17},"end":{"line":985,"column":18}},"loc":{"start":{"line":985,"column":26},"end":{"line":988,"column":13}},"line":985},"44":{"name":"(anonymous_44)","decl":{"start":{"line":1013,"column":2},"end":{"line":1013,"column":3}},"loc":{"start":{"line":1013,"column":57},"end":{"line":1041,"column":3}},"line":1013},"45":{"name":"(anonymous_45)","decl":{"start":{"line":1043,"column":2},"end":{"line":1043,"column":3}},"loc":{"start":{"line":1043,"column":41},"end":{"line":1049,"column":3}},"line":1043},"46":{"name":"hasTorch","decl":{"start":{"line":1054,"column":16},"end":{"line":1054,"column":24}},"loc":{"start":{"line":1054,"column":27},"end":{"line":1059,"column":1}},"line":1054}},"branchMap":{"0":{"loc":{"start":{"line":35,"column":2},"end":{"line":51,"column":3}},"type":"if","locations":[{"start":{"line":35,"column":2},"end":{"line":51,"column":3}},{"start":{"line":37,"column":9},"end":{"line":51,"column":3}}],"line":35},"1":{"loc":{"start":{"line":37,"column":9},"end":{"line":51,"column":3}},"type":"if","locations":[{"start":{"line":37,"column":9},"end":{"line":51,"column":3}},{"start":{"line":47,"column":9},"end":{"line":51,"column":3}}],"line":37},"2":{"loc":{"start":{"line":42,"column":4},"end":{"line":46,"column":5}},"type":"if","locations":[{"start":{"line":42,"column":4},"end":{"line":46,"column":5}},{"start":{"line":44,"column":11},"end":{"line":46,"column":5}}],"line":42},"3":{"loc":{"start":{"line":47,"column":9},"end":{"line":51,"column":3}},"type":"if","locations":[{"start":{"line":47,"column":9},"end":{"line":51,"column":3}},{"start":{"line":49,"column":9},"end":{"line":51,"column":3}}],"line":47},"4":{"loc":{"start":{"line":49,"column":9},"end":{"line":51,"column":3}},"type":"if","locations":[{"start":{"line":49,"column":9},"end":{"line":51,"column":3}},{"start":{},"end":{}}],"line":49},"5":{"loc":{"start":{"line":53,"column":2},"end":{"line":80,"column":3}},"type":"if","locations":[{"start":{"line":53,"column":2},"end":{"line":80,"column":3}},{"start":{},"end":{}}],"line":53},"6":{"loc":{"start":{"line":54,"column":4},"end":{"line":79,"column":5}},"type":"if","locations":[{"start":{"line":54,"column":4},"end":{"line":79,"column":5}},{"start":{"line":56,"column":11},"end":{"line":79,"column":5}}],"line":54},"7":{"loc":{"start":{"line":56,"column":11},"end":{"line":79,"column":5}},"type":"if","locations":[{"start":{"line":56,"column":11},"end":{"line":79,"column":5}},{"start":{"line":75,"column":11},"end":{"line":79,"column":5}}],"line":56},"8":{"loc":{"start":{"line":57,"column":6},"end":{"line":74,"column":7}},"type":"if","locations":[{"start":{"line":57,"column":6},"end":{"line":74,"column":7}},{"start":{"line":67,"column":13},"end":{"line":74,"column":7}}],"line":57},"9":{"loc":{"start":{"line":62,"column":8},"end":{"line":66,"column":9}},"type":"if","locations":[{"start":{"line":62,"column":8},"end":{"line":66,"column":9}},{"start":{"line":64,"column":15},"end":{"line":66,"column":9}}],"line":62},"10":{"loc":{"start":{"line":67,"column":13},"end":{"line":74,"column":7}},"type":"if","locations":[{"start":{"line":67,"column":13},"end":{"line":74,"column":7}},{"start":{},"end":{}}],"line":67},"11":{"loc":{"start":{"line":75,"column":11},"end":{"line":79,"column":5}},"type":"if","locations":[{"start":{"line":75,"column":11},"end":{"line":79,"column":5}},{"start":{"line":77,"column":11},"end":{"line":79,"column":5}}],"line":75},"12":{"loc":{"start":{"line":77,"column":11},"end":{"line":79,"column":5}},"type":"if","locations":[{"start":{"line":77,"column":11},"end":{"line":79,"column":5}},{"start":{},"end":{}}],"line":77},"13":{"loc":{"start":{"line":439,"column":24},"end":{"line":439,"column":57}},"type":"binary-expr","locations":[{"start":{"line":439,"column":24},"end":{"line":439,"column":51}},{"start":{"line":439,"column":55},"end":{"line":439,"column":57}}],"line":439},"14":{"loc":{"start":{"line":440,"column":29},"end":{"line":440,"column":62}},"type":"binary-expr","locations":[{"start":{"line":440,"column":29},"end":{"line":440,"column":56}},{"start":{"line":440,"column":60},"end":{"line":440,"column":62}}],"line":440},"15":{"loc":{"start":{"line":441,"column":35},"end":{"line":441,"column":68}},"type":"binary-expr","locations":[{"start":{"line":441,"column":35},"end":{"line":441,"column":62}},{"start":{"line":441,"column":66},"end":{"line":441,"column":68}}],"line":441},"16":{"loc":{"start":{"line":442,"column":30},"end":{"line":442,"column":78}},"type":"binary-expr","locations":[{"start":{"line":442,"column":30},"end":{"line":442,"column":72}},{"start":{"line":442,"column":76},"end":{"line":442,"column":78}}],"line":442},"17":{"loc":{"start":{"line":443,"column":30},"end":{"line":443,"column":78}},"type":"binary-expr","locations":[{"start":{"line":443,"column":30},"end":{"line":443,"column":72}},{"start":{"line":443,"column":76},"end":{"line":443,"column":78}}],"line":443},"18":{"loc":{"start":{"line":444,"column":28},"end":{"line":444,"column":66}},"type":"binary-expr","locations":[{"start":{"line":444,"column":28},"end":{"line":444,"column":60}},{"start":{"line":444,"column":64},"end":{"line":444,"column":66}}],"line":444},"19":{"loc":{"start":{"line":518,"column":24},"end":{"line":518,"column":57}},"type":"binary-expr","locations":[{"start":{"line":518,"column":24},"end":{"line":518,"column":51}},{"start":{"line":518,"column":55},"end":{"line":518,"column":57}}],"line":518},"20":{"loc":{"start":{"line":520,"column":30},"end":{"line":520,"column":98}},"type":"binary-expr","locations":[{"start":{"line":520,"column":30},"end":{"line":520,"column":92}},{"start":{"line":520,"column":96},"end":{"line":520,"column":98}}],"line":520},"21":{"loc":{"start":{"line":520,"column":31},"end":{"line":520,"column":79}},"type":"binary-expr","locations":[{"start":{"line":520,"column":31},"end":{"line":520,"column":73}},{"start":{"line":520,"column":77},"end":{"line":520,"column":79}}],"line":520},"22":{"loc":{"start":{"line":522,"column":30},"end":{"line":522,"column":98}},"type":"binary-expr","locations":[{"start":{"line":522,"column":30},"end":{"line":522,"column":92}},{"start":{"line":522,"column":96},"end":{"line":522,"column":98}}],"line":522},"23":{"loc":{"start":{"line":522,"column":31},"end":{"line":522,"column":79}},"type":"binary-expr","locations":[{"start":{"line":522,"column":31},"end":{"line":522,"column":73}},{"start":{"line":522,"column":77},"end":{"line":522,"column":79}}],"line":522},"24":{"loc":{"start":{"line":524,"column":29},"end":{"line":524,"column":80}},"type":"binary-expr","locations":[{"start":{"line":524,"column":29},"end":{"line":524,"column":74}},{"start":{"line":524,"column":78},"end":{"line":524,"column":80}}],"line":524},"25":{"loc":{"start":{"line":524,"column":30},"end":{"line":524,"column":63}},"type":"binary-expr","locations":[{"start":{"line":524,"column":30},"end":{"line":524,"column":57}},{"start":{"line":524,"column":61},"end":{"line":524,"column":63}}],"line":524},"26":{"loc":{"start":{"line":525,"column":35},"end":{"line":525,"column":92}},"type":"binary-expr","locations":[{"start":{"line":525,"column":35},"end":{"line":525,"column":86}},{"start":{"line":525,"column":90},"end":{"line":525,"column":92}}],"line":525},"27":{"loc":{"start":{"line":525,"column":36},"end":{"line":525,"column":69}},"type":"binary-expr","locations":[{"start":{"line":525,"column":36},"end":{"line":525,"column":63}},{"start":{"line":525,"column":67},"end":{"line":525,"column":69}}],"line":525},"28":{"loc":{"start":{"line":569,"column":4},"end":{"line":571,"column":5}},"type":"if","locations":[{"start":{"line":569,"column":4},"end":{"line":571,"column":5}},{"start":{},"end":{}}],"line":569},"29":{"loc":{"start":{"line":572,"column":4},"end":{"line":574,"column":5}},"type":"if","locations":[{"start":{"line":572,"column":4},"end":{"line":574,"column":5}},{"start":{},"end":{}}],"line":572},"30":{"loc":{"start":{"line":576,"column":4},"end":{"line":587,"column":5}},"type":"if","locations":[{"start":{"line":576,"column":4},"end":{"line":587,"column":5}},{"start":{},"end":{}}],"line":576},"31":{"loc":{"start":{"line":577,"column":6},"end":{"line":586,"column":7}},"type":"if","locations":[{"start":{"line":577,"column":6},"end":{"line":586,"column":7}},{"start":{},"end":{}}],"line":577},"32":{"loc":{"start":{"line":580,"column":8},"end":{"line":585,"column":9}},"type":"if","locations":[{"start":{"line":580,"column":8},"end":{"line":585,"column":9}},{"start":{},"end":{}}],"line":580},"33":{"loc":{"start":{"line":581,"column":10},"end":{"line":584,"column":11}},"type":"if","locations":[{"start":{"line":581,"column":10},"end":{"line":584,"column":11}},{"start":{},"end":{}}],"line":581},"34":{"loc":{"start":{"line":589,"column":4},"end":{"line":591,"column":5}},"type":"if","locations":[{"start":{"line":589,"column":4},"end":{"line":591,"column":5}},{"start":{},"end":{}}],"line":589},"35":{"loc":{"start":{"line":593,"column":4},"end":{"line":595,"column":5}},"type":"if","locations":[{"start":{"line":593,"column":4},"end":{"line":595,"column":5}},{"start":{},"end":{}}],"line":593},"36":{"loc":{"start":{"line":597,"column":4},"end":{"line":599,"column":5}},"type":"if","locations":[{"start":{"line":597,"column":4},"end":{"line":599,"column":5}},{"start":{},"end":{}}],"line":597},"37":{"loc":{"start":{"line":618,"column":4},"end":{"line":624,"column":5}},"type":"if","locations":[{"start":{"line":618,"column":4},"end":{"line":624,"column":5}},{"start":{"line":620,"column":11},"end":{"line":624,"column":5}}],"line":618},"38":{"loc":{"start":{"line":620,"column":11},"end":{"line":624,"column":5}},"type":"if","locations":[{"start":{"line":620,"column":11},"end":{"line":624,"column":5}},{"start":{"line":622,"column":11},"end":{"line":624,"column":5}}],"line":620},"39":{"loc":{"start":{"line":628,"column":4},"end":{"line":643,"column":5}},"type":"if","locations":[{"start":{"line":628,"column":4},"end":{"line":643,"column":5}},{"start":{"line":630,"column":11},"end":{"line":643,"column":5}}],"line":628},"40":{"loc":{"start":{"line":630,"column":11},"end":{"line":643,"column":5}},"type":"if","locations":[{"start":{"line":630,"column":11},"end":{"line":643,"column":5}},{"start":{"line":632,"column":11},"end":{"line":643,"column":5}}],"line":630},"41":{"loc":{"start":{"line":632,"column":11},"end":{"line":643,"column":5}},"type":"if","locations":[{"start":{"line":632,"column":11},"end":{"line":643,"column":5}},{"start":{},"end":{}}],"line":632},"42":{"loc":{"start":{"line":647,"column":4},"end":{"line":653,"column":5}},"type":"if","locations":[{"start":{"line":647,"column":4},"end":{"line":653,"column":5}},{"start":{"line":649,"column":11},"end":{"line":653,"column":5}}],"line":647},"43":{"loc":{"start":{"line":649,"column":11},"end":{"line":653,"column":5}},"type":"if","locations":[{"start":{"line":649,"column":11},"end":{"line":653,"column":5}},{"start":{"line":651,"column":11},"end":{"line":653,"column":5}}],"line":649},"44":{"loc":{"start":{"line":657,"column":4},"end":{"line":663,"column":5}},"type":"if","locations":[{"start":{"line":657,"column":4},"end":{"line":663,"column":5}},{"start":{"line":659,"column":11},"end":{"line":663,"column":5}}],"line":657},"45":{"loc":{"start":{"line":659,"column":11},"end":{"line":663,"column":5}},"type":"if","locations":[{"start":{"line":659,"column":11},"end":{"line":663,"column":5}},{"start":{"line":661,"column":11},"end":{"line":663,"column":5}}],"line":659},"46":{"loc":{"start":{"line":672,"column":4},"end":{"line":676,"column":5}},"type":"if","locations":[{"start":{"line":672,"column":4},"end":{"line":676,"column":5}},{"start":{"line":674,"column":11},"end":{"line":676,"column":5}}],"line":672},"47":{"loc":{"start":{"line":672,"column":8},"end":{"line":672,"column":47}},"type":"binary-expr","locations":[{"start":{"line":672,"column":8},"end":{"line":672,"column":16}},{"start":{"line":672,"column":20},"end":{"line":672,"column":47}}],"line":672},"48":{"loc":{"start":{"line":674,"column":11},"end":{"line":676,"column":5}},"type":"if","locations":[{"start":{"line":674,"column":11},"end":{"line":676,"column":5}},{"start":{},"end":{}}],"line":674},"49":{"loc":{"start":{"line":677,"column":4},"end":{"line":688,"column":5}},"type":"if","locations":[{"start":{"line":677,"column":4},"end":{"line":688,"column":5}},{"start":{},"end":{}}],"line":677},"50":{"loc":{"start":{"line":678,"column":6},"end":{"line":687,"column":7}},"type":"if","locations":[{"start":{"line":678,"column":6},"end":{"line":687,"column":7}},{"start":{},"end":{}}],"line":678},"51":{"loc":{"start":{"line":681,"column":8},"end":{"line":686,"column":9}},"type":"if","locations":[{"start":{"line":681,"column":8},"end":{"line":686,"column":9}},{"start":{},"end":{}}],"line":681},"52":{"loc":{"start":{"line":682,"column":10},"end":{"line":685,"column":11}},"type":"if","locations":[{"start":{"line":682,"column":10},"end":{"line":685,"column":11}},{"start":{},"end":{}}],"line":682},"53":{"loc":{"start":{"line":690,"column":4},"end":{"line":695,"column":5}},"type":"if","locations":[{"start":{"line":690,"column":4},"end":{"line":695,"column":5}},{"start":{},"end":{}}],"line":690},"54":{"loc":{"start":{"line":691,"column":6},"end":{"line":694,"column":7}},"type":"if","locations":[{"start":{"line":691,"column":6},"end":{"line":694,"column":7}},{"start":{},"end":{}}],"line":691},"55":{"loc":{"start":{"line":691,"column":10},"end":{"line":691,"column":74}},"type":"binary-expr","locations":[{"start":{"line":691,"column":10},"end":{"line":691,"column":30}},{"start":{"line":691,"column":34},"end":{"line":691,"column":74}}],"line":691},"56":{"loc":{"start":{"line":700,"column":4},"end":{"line":705,"column":5}},"type":"if","locations":[{"start":{"line":700,"column":4},"end":{"line":705,"column":5}},{"start":{},"end":{}}],"line":700},"57":{"loc":{"start":{"line":701,"column":6},"end":{"line":702,"column":80}},"type":"binary-expr","locations":[{"start":{"line":701,"column":6},"end":{"line":701,"column":19}},{"start":{"line":702,"column":6},"end":{"line":702,"column":80}}],"line":701},"58":{"loc":{"start":{"line":707,"column":4},"end":{"line":715,"column":5}},"type":"if","locations":[{"start":{"line":707,"column":4},"end":{"line":715,"column":5}},{"start":{},"end":{}}],"line":707},"59":{"loc":{"start":{"line":708,"column":6},"end":{"line":714,"column":7}},"type":"if","locations":[{"start":{"line":708,"column":6},"end":{"line":714,"column":7}},{"start":{},"end":{}}],"line":708},"60":{"loc":{"start":{"line":709,"column":8},"end":{"line":710,"column":82}},"type":"binary-expr","locations":[{"start":{"line":709,"column":9},"end":{"line":709,"column":22}},{"start":{"line":709,"column":26},"end":{"line":709,"column":38}},{"start":{"line":710,"column":8},"end":{"line":710,"column":82}}],"line":709},"61":{"loc":{"start":{"line":716,"column":4},"end":{"line":718,"column":5}},"type":"if","locations":[{"start":{"line":716,"column":4},"end":{"line":718,"column":5}},{"start":{},"end":{}}],"line":716},"62":{"loc":{"start":{"line":736,"column":4},"end":{"line":738,"column":5}},"type":"if","locations":[{"start":{"line":736,"column":4},"end":{"line":738,"column":5}},{"start":{},"end":{}}],"line":736},"63":{"loc":{"start":{"line":743,"column":4},"end":{"line":745,"column":5}},"type":"if","locations":[{"start":{"line":743,"column":4},"end":{"line":745,"column":5}},{"start":{},"end":{}}],"line":743},"64":{"loc":{"start":{"line":750,"column":4},"end":{"line":752,"column":5}},"type":"if","locations":[{"start":{"line":750,"column":4},"end":{"line":752,"column":5}},{"start":{},"end":{}}],"line":750},"65":{"loc":{"start":{"line":757,"column":4},"end":{"line":759,"column":5}},"type":"if","locations":[{"start":{"line":757,"column":4},"end":{"line":759,"column":5}},{"start":{},"end":{}}],"line":757},"66":{"loc":{"start":{"line":765,"column":4},"end":{"line":767,"column":5}},"type":"if","locations":[{"start":{"line":765,"column":4},"end":{"line":767,"column":5}},{"start":{},"end":{}}],"line":765},"67":{"loc":{"start":{"line":784,"column":4},"end":{"line":786,"column":5}},"type":"if","locations":[{"start":{"line":784,"column":4},"end":{"line":786,"column":5}},{"start":{},"end":{}}],"line":784},"68":{"loc":{"start":{"line":791,"column":4},"end":{"line":793,"column":5}},"type":"if","locations":[{"start":{"line":791,"column":4},"end":{"line":793,"column":5}},{"start":{},"end":{}}],"line":791},"69":{"loc":{"start":{"line":797,"column":4},"end":{"line":799,"column":5}},"type":"if","locations":[{"start":{"line":797,"column":4},"end":{"line":799,"column":5}},{"start":{},"end":{}}],"line":797},"70":{"loc":{"start":{"line":803,"column":4},"end":{"line":805,"column":5}},"type":"if","locations":[{"start":{"line":803,"column":4},"end":{"line":805,"column":5}},{"start":{},"end":{}}],"line":803},"71":{"loc":{"start":{"line":808,"column":4},"end":{"line":810,"column":5}},"type":"if","locations":[{"start":{"line":808,"column":4},"end":{"line":810,"column":5}},{"start":{},"end":{}}],"line":808},"72":{"loc":{"start":{"line":808,"column":8},"end":{"line":808,"column":52}},"type":"binary-expr","locations":[{"start":{"line":808,"column":8},"end":{"line":808,"column":24}},{"start":{"line":808,"column":28},"end":{"line":808,"column":52}}],"line":808},"73":{"loc":{"start":{"line":811,"column":4},"end":{"line":813,"column":5}},"type":"if","locations":[{"start":{"line":811,"column":4},"end":{"line":813,"column":5}},{"start":{},"end":{}}],"line":811},"74":{"loc":{"start":{"line":811,"column":8},"end":{"line":811,"column":57}},"type":"binary-expr","locations":[{"start":{"line":811,"column":8},"end":{"line":811,"column":30}},{"start":{"line":811,"column":34},"end":{"line":811,"column":57}}],"line":811},"75":{"loc":{"start":{"line":816,"column":4},"end":{"line":818,"column":5}},"type":"if","locations":[{"start":{"line":816,"column":4},"end":{"line":818,"column":5}},{"start":{},"end":{}}],"line":816},"76":{"loc":{"start":{"line":822,"column":4},"end":{"line":827,"column":5}},"type":"if","locations":[{"start":{"line":822,"column":4},"end":{"line":827,"column":5}},{"start":{},"end":{}}],"line":822},"77":{"loc":{"start":{"line":831,"column":4},"end":{"line":833,"column":5}},"type":"if","locations":[{"start":{"line":831,"column":4},"end":{"line":833,"column":5}},{"start":{},"end":{}}],"line":831},"78":{"loc":{"start":{"line":838,"column":4},"end":{"line":845,"column":5}},"type":"if","locations":[{"start":{"line":838,"column":4},"end":{"line":845,"column":5}},{"start":{},"end":{}}],"line":838},"79":{"loc":{"start":{"line":839,"column":6},"end":{"line":842,"column":64}},"type":"binary-expr","locations":[{"start":{"line":839,"column":6},"end":{"line":839,"column":28}},{"start":{"line":840,"column":6},"end":{"line":840,"column":33}},{"start":{"line":841,"column":6},"end":{"line":841,"column":60}},{"start":{"line":842,"column":6},"end":{"line":842,"column":64}}],"line":839},"80":{"loc":{"start":{"line":847,"column":4},"end":{"line":851,"column":5}},"type":"if","locations":[{"start":{"line":847,"column":4},"end":{"line":851,"column":5}},{"start":{},"end":{}}],"line":847},"81":{"loc":{"start":{"line":855,"column":4},"end":{"line":857,"column":5}},"type":"if","locations":[{"start":{"line":855,"column":4},"end":{"line":857,"column":5}},{"start":{},"end":{}}],"line":855},"82":{"loc":{"start":{"line":861,"column":4},"end":{"line":867,"column":5}},"type":"if","locations":[{"start":{"line":861,"column":4},"end":{"line":867,"column":5}},{"start":{"line":864,"column":11},"end":{"line":867,"column":5}}],"line":861},"83":{"loc":{"start":{"line":884,"column":4},"end":{"line":899,"column":5}},"type":"if","locations":[{"start":{"line":884,"column":4},"end":{"line":899,"column":5}},{"start":{},"end":{}}],"line":884},"84":{"loc":{"start":{"line":884,"column":8},"end":{"line":884,"column":56}},"type":"binary-expr","locations":[{"start":{"line":884,"column":8},"end":{"line":884,"column":29}},{"start":{"line":884,"column":33},"end":{"line":884,"column":56}}],"line":884},"85":{"loc":{"start":{"line":908,"column":40},"end":{"line":910,"column":54}},"type":"cond-expr","locations":[{"start":{"line":909,"column":8},"end":{"line":909,"column":50}},{"start":{"line":910,"column":8},"end":{"line":910,"column":54}}],"line":908},"86":{"loc":{"start":{"line":919,"column":4},"end":{"line":921,"column":5}},"type":"if","locations":[{"start":{"line":919,"column":4},"end":{"line":921,"column":5}},{"start":{},"end":{}}],"line":919},"87":{"loc":{"start":{"line":935,"column":4},"end":{"line":937,"column":5}},"type":"if","locations":[{"start":{"line":935,"column":4},"end":{"line":937,"column":5}},{"start":{},"end":{}}],"line":935},"88":{"loc":{"start":{"line":951,"column":4},"end":{"line":953,"column":5}},"type":"if","locations":[{"start":{"line":951,"column":4},"end":{"line":953,"column":5}},{"start":{},"end":{}}],"line":951},"89":{"loc":{"start":{"line":954,"column":11},"end":{"line":954,"column":74}},"type":"cond-expr","locations":[{"start":{"line":954,"column":26},"end":{"line":954,"column":44}},{"start":{"line":954,"column":47},"end":{"line":954,"column":74}}],"line":954},"90":{"loc":{"start":{"line":961,"column":4},"end":{"line":967,"column":5}},"type":"if","locations":[{"start":{"line":961,"column":4},"end":{"line":967,"column":5}},{"start":{},"end":{}}],"line":961},"91":{"loc":{"start":{"line":974,"column":4},"end":{"line":1010,"column":5}},"type":"if","locations":[{"start":{"line":974,"column":4},"end":{"line":1010,"column":5}},{"start":{"line":1006,"column":11},"end":{"line":1010,"column":5}}],"line":974},"92":{"loc":{"start":{"line":974,"column":8},"end":{"line":974,"column":49}},"type":"binary-expr","locations":[{"start":{"line":974,"column":8},"end":{"line":974,"column":31}},{"start":{"line":974,"column":35},"end":{"line":974,"column":49}}],"line":974},"93":{"loc":{"start":{"line":1006,"column":11},"end":{"line":1010,"column":5}},"type":"if","locations":[{"start":{"line":1006,"column":11},"end":{"line":1010,"column":5}},{"start":{"line":1008,"column":11},"end":{"line":1010,"column":5}}],"line":1006},"94":{"loc":{"start":{"line":1016,"column":4},"end":{"line":1018,"column":5}},"type":"if","locations":[{"start":{"line":1016,"column":4},"end":{"line":1018,"column":5}},{"start":{},"end":{}}],"line":1016},"95":{"loc":{"start":{"line":1020,"column":4},"end":{"line":1022,"column":5}},"type":"if","locations":[{"start":{"line":1020,"column":4},"end":{"line":1022,"column":5}},{"start":{},"end":{}}],"line":1020},"96":{"loc":{"start":{"line":1024,"column":4},"end":{"line":1026,"column":5}},"type":"if","locations":[{"start":{"line":1024,"column":4},"end":{"line":1026,"column":5}},{"start":{},"end":{}}],"line":1024},"97":{"loc":{"start":{"line":1028,"column":4},"end":{"line":1030,"column":5}},"type":"if","locations":[{"start":{"line":1028,"column":4},"end":{"line":1030,"column":5}},{"start":{},"end":{}}],"line":1028},"98":{"loc":{"start":{"line":1028,"column":8},"end":{"line":1028,"column":40}},"type":"binary-expr","locations":[{"start":{"line":1028,"column":8},"end":{"line":1028,"column":19}},{"start":{"line":1028,"column":23},"end":{"line":1028,"column":40}}],"line":1028},"99":{"loc":{"start":{"line":1032,"column":4},"end":{"line":1034,"column":5}},"type":"if","locations":[{"start":{"line":1032,"column":4},"end":{"line":1034,"column":5}},{"start":{},"end":{}}],"line":1032},"100":{"loc":{"start":{"line":1036,"column":4},"end":{"line":1038,"column":5}},"type":"if","locations":[{"start":{"line":1036,"column":4},"end":{"line":1038,"column":5}},{"start":{},"end":{}}],"line":1036},"101":{"loc":{"start":{"line":1044,"column":4},"end":{"line":1046,"column":5}},"type":"if","locations":[{"start":{"line":1044,"column":4},"end":{"line":1046,"column":5}},{"start":{},"end":{}}],"line":1044},"102":{"loc":{"start":{"line":1044,"column":8},"end":{"line":1044,"column":65}},"type":"binary-expr","locations":[{"start":{"line":1044,"column":8},"end":{"line":1044,"column":33}},{"start":{"line":1044,"column":37},"end":{"line":1044,"column":65}}],"line":1044},"103":{"loc":{"start":{"line":1055,"column":2},"end":{"line":1057,"column":3}},"type":"if","locations":[{"start":{"line":1055,"column":2},"end":{"line":1057,"column":3}},{"start":{},"end":{}}],"line":1055}},"s":{"0":4,"1":4,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":4,"32":4,"33":4,"34":4,"35":4,"36":4,"37":0,"38":0,"39":0,"40":0,"41":4,"42":4,"43":4,"44":4,"45":12,"46":12,"47":12,"48":12,"49":12,"50":12,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":2,"77":0,"78":2,"79":1,"80":1,"81":1,"82":0,"83":1,"84":0,"85":1,"86":1,"87":1,"88":1,"89":1,"90":0,"91":0,"92":2,"93":0,"94":2,"95":1,"96":1,"97":12,"98":2,"99":0,"100":2,"101":1,"102":1,"103":12,"104":1,"105":0,"106":0,"107":0,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":0,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0,"121":0,"122":0,"123":0,"124":0,"125":0,"126":0,"127":0,"128":0,"129":0,"130":0,"131":0,"132":0,"133":0,"134":0,"135":0,"136":0,"137":0,"138":0,"139":0,"140":0,"141":0,"142":0,"143":0,"144":0,"145":0,"146":0,"147":0,"148":0,"149":0,"150":0,"151":0,"152":0,"153":0,"154":0,"155":0,"156":0,"157":0,"158":0,"159":0,"160":0,"161":12,"162":0,"163":0,"164":12,"165":0,"166":0,"167":12,"168":0,"169":0,"170":12,"171":3,"172":1,"173":3,"174":1,"175":12,"176":0,"177":0,"178":12,"179":0,"180":0,"181":12,"182":0,"183":0,"184":12,"185":3,"186":5,"187":5,"188":1,"189":4,"190":3,"191":3,"192":3,"193":12,"194":0,"195":0,"196":12,"197":0,"198":0,"199":0,"200":0,"201":0,"202":0,"203":0,"204":0,"205":0,"206":0,"207":0,"208":0,"209":0,"210":0,"211":0,"212":0,"213":0,"214":0,"215":0,"216":0,"217":0,"218":0,"219":0,"220":0,"221":12,"222":0,"223":0,"224":0,"225":0,"226":12,"227":0,"228":12,"229":0,"230":0,"231":0,"232":0,"233":0,"234":0,"235":0,"236":0,"237":0,"238":0,"239":0,"240":0,"241":0,"242":0,"243":0,"244":0,"245":0,"246":0,"247":0,"248":0,"249":0,"250":0,"251":0,"252":0,"253":0,"254":0,"255":0,"256":0,"257":4,"258":2,"259":2,"260":0},"f":{"0":0,"1":0,"2":0,"3":12,"4":0,"5":0,"6":0,"7":2,"8":1,"9":1,"10":2,"11":2,"12":1,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":3,"28":0,"29":0,"30":0,"31":3,"32":5,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":2},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[4,0],"14":[4,0],"15":[4,0],"16":[4,0],"17":[4,0],"18":[4,0],"19":[4,0],"20":[4,4],"21":[4,0],"22":[4,4],"23":[4,0],"24":[4,0],"25":[4,0],"26":[4,0],"27":[4,0],"28":[0,0],"29":[0,0],"30":[0,0],"31":[0,0],"32":[0,0],"33":[0,0],"34":[0,0],"35":[0,0],"36":[0,0],"37":[0,2],"38":[1,1],"39":[0,1],"40":[0,1],"41":[1,0],"42":[0,2],"43":[1,1],"44":[0,2],"45":[1,1],"46":[0,0],"47":[0,0],"48":[0,0],"49":[0,0],"50":[0,0],"51":[0,0],"52":[0,0],"53":[0,0],"54":[0,0],"55":[0,0],"56":[0,0],"57":[0,0],"58":[0,0],"59":[0,0],"60":[0,0,0],"61":[0,0],"62":[0,0],"63":[0,0],"64":[0,0],"65":[0,0],"66":[0,0],"67":[0,0],"68":[0,0],"69":[0,0],"70":[0,0],"71":[1,2],"72":[3,2],"73":[1,2],"74":[3,2],"75":[0,0],"76":[0,0],"77":[0,0],"78":[1,4],"79":[5,2,2,1],"80":[3,1],"81":[0,0],"82":[0,0],"83":[0,0],"84":[0,0],"85":[0,0],"86":[0,0],"87":[0,0],"88":[0,0],"89":[0,0],"90":[0,0],"91":[0,0],"92":[0,0],"93":[0,0],"94":[0,0],"95":[0,0],"96":[0,0],"97":[0,0],"98":[0,0],"99":[0,0],"100":[0,0],"101":[0,0],"102":[0,0],"103":[2,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"df1e21405b439fdcf2c3dda1d902a02883b2bb38"} +,"D:\\work_file\\tpc\\react-native-camera\\react-native-camera-sig\\src\\index.js": {"path":"D:\\work_file\\tpc\\react-native-camera\\react-native-camera-sig\\src\\index.js","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"9606ceff87ebb292d629f8d97d4d3927bf883749"} +} diff --git a/jest/coverage/lcov-report/FaceDetector.js.html b/jest/coverage/lcov-report/FaceDetector.js.html new file mode 100644 index 000000000..a51fe64f8 --- /dev/null +++ b/jest/coverage/lcov-report/FaceDetector.js.html @@ -0,0 +1,265 @@ + + + + + + Code coverage report for FaceDetector.js + + + + + + + + + +

+
+

All files FaceDetector.js

+
+ +
+ 71.42% + Statements + 5/7 +
+ + +
+ 50% + Branches + 1/2 +
+ + +
+ 33.33% + Functions + 1/3 +
+ + +
+ 83.33% + Lines + 5/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61  +  +  +1x +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +1x +  +  +  +1x + 
// @flow
+import  NativeTurboModule from './NativeFaceDetector';
+ 
+const faceDetectionDisabledMessage = 'Face detection has not been included in this build.';
+ 
+const FaceDetectorModule: Object = NativeTurboModule || {
+  stubbed: true,
+  Mode: {},
+  Landmarks: {},
+  Classifications: {},
+  detectFaces: () => new Promise((_, reject) => reject(faceDetectionDisabledMessage)),
+};
+ 
+type Point = { x: number, y: number };
+ 
+export type FaceFeature = {
+  bounds: {
+    size: {
+      width: number,
+      height: number,
+    },
+    origin: Point,
+  },
+  smilingProbability?: number,
+  leftEarPosition?: Point,
+  rightEarPosition?: Point,
+  leftEyePosition?: Point,
+  leftEyeOpenProbability?: number,
+  rightEyePosition?: Point,
+  rightEyeOpenProbability?: number,
+  leftCheekPosition?: Point,
+  rightCheekPosition?: Point,
+  leftMouthPosition?: Point,
+  mouthPosition?: Point,
+  rightMouthPosition?: Point,
+  bottomMouthPosition?: Point,
+  noseBasePosition?: Point,
+  yawAngle?: number,
+  rollAngle?: number,
+};
+ 
+type DetectionOptions = {
+  mode?: $Keys<typeof FaceDetectorModule.Mode>,
+  detectLandmarks?: $Keys<typeof FaceDetectorModule.Landmarks>,
+  runClassifications?: $Keys<typeof FaceDetectorModule.Classifications>,
+};
+ 
+export default class FaceDetector {
+  static Constants = {
+    Mode: FaceDetectorModule.Mode,
+    Landmarks: FaceDetectorModule.Landmarks,
+    Classifications: FaceDetectorModule.Classifications,
+  };
+ 
+  static detectFacesAsync(uri: string, options: ?DetectionOptions): Promise<Array<FaceFeature>> {
+    return FaceDetectorModule.detectFaces({ ...options, uri });
+  }
+}
+ 
+export const Constants = FaceDetector.Constants;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/jest/coverage/lcov-report/RNCamera.js.html b/jest/coverage/lcov-report/RNCamera.js.html new file mode 100644 index 000000000..d5536ab71 --- /dev/null +++ b/jest/coverage/lcov-report/RNCamera.js.html @@ -0,0 +1,3262 @@ + + + + + + Code coverage report for RNCamera.js + + + + + + + + + +
+
+

All files RNCamera.js

+
+ +
+ 26.05% + Statements + 68/261 +
+ + +
+ 21.8% + Branches + 46/211 +
+ + +
+ 23.4% + Functions + 11/47 +
+ + +
+ 25.86% + Lines + 67/259 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +901 +902 +903 +904 +905 +906 +907 +908 +909 +910 +911 +912 +913 +914 +915 +916 +917 +918 +919 +920 +921 +922 +923 +924 +925 +926 +927 +928 +929 +930 +931 +932 +933 +934 +935 +936 +937 +938 +939 +940 +941 +942 +943 +944 +945 +946 +947 +948 +949 +950 +951 +952 +953 +954 +955 +956 +957 +958 +959 +960 +961 +962 +963 +964 +965 +966 +967 +968 +969 +970 +971 +972 +973 +974 +975 +976 +977 +978 +979 +980 +981 +982 +983 +984 +985 +986 +987 +988 +989 +990 +991 +992 +993 +994 +995 +996 +997 +998 +999 +1000 +1001 +1002 +1003 +1004 +1005 +1006 +1007 +1008 +1009 +1010 +1011 +1012 +1013 +1014 +1015 +1016 +1017 +1018 +1019 +1020 +1021 +1022 +1023 +1024 +1025 +1026 +1027 +1028 +1029 +1030 +1031 +1032 +1033 +1034 +1035 +1036 +1037 +1038 +1039 +1040 +1041 +1042 +1043 +1044 +1045 +1046 +1047 +1048 +1049 +1050 +1051 +1052 +1053 +1054 +1055 +1056 +1057 +1058 +1059 +1060  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +4x +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +12x +  +  +  +  +  +  +12x +12x +12x +12x +12x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +2x +1x +  +1x +  +  +  +  +1x +  +1x +  +1x +1x +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +2x +  +2x +1x +  +1x +  +  +  +12x +2x +  +2x +1x +  +1x +  +  +  +12x +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +12x +  +  +  +  +  +12x +  +  +  +  +  +12x +  +  +  +  +12x +3x +1x +  +3x +1x +  +  +12x +  +  +  +  +  +12x +  +  +  +  +  +  +  +  +12x +  +  +  +  +  +12x +5x +5x +  +  +  +  +  +1x +  +  +4x +3x +3x +3x +  +  +  +12x +  +  +  +  +  +12x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +12x +  +  +  +  +  +  +  +  +12x +  +12x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +  +2x +2x +  +  +  + 
// @flow
+import React from 'react';
+import PropTypes from 'prop-types';
+import NativeCamera, { Commands, RecordResponse } from "./NativeCamera";
+import NativeCameraModule from "./NativeCameraModule";
+import { FaceFeature } from './FaceDetector';
+import {
+  findNodeHandle,
+  Platform,
+  ViewPropTypes,
+  View,
+  ActivityIndicator,
+  Text,
+  StyleSheet,
+  PermissionsAndroid,
+  DeviceEventEmitter
+} from 'react-native';
+ 
+const Rationale = PropTypes.shape({
+  title: PropTypes.string.isRequired,
+  message: PropTypes.string.isRequired,
+  buttonPositive: PropTypes.string,
+  buttonNegative: PropTypes.string,
+  buttonNeutral: PropTypes.string,
+});
+ 
+const requestPermissions = async (
+  captureAudio: boolean,
+  CameraManager: any,
+  androidCameraPermissionOptions: Rationale | null,
+  androidRecordAudioPermissionOptions: Rationale | null,
+): Promise<{ hasCameraPermissions: boolean, hasRecordAudioPermissions: boolean }> => {
+  let hasCameraPermissions = false;
+  let hasRecordAudioPermissions = false;
+  if (Platform.OS === 'ios') {
+    hasCameraPermissions = await CameraManager.checkVideoAuthorizationStatus();
+  } else if (Platform.OS === 'android') {
+    const cameraPermissionResult = await PermissionsAndroid.request(
+      PermissionsAndroid.PERMISSIONS.CAMERA,
+      androidCameraPermissionOptions,
+    );
+    if (typeof cameraPermissionResult === 'boolean') {
+      hasCameraPermissions = cameraPermissionResult;
+    } else {
+      hasCameraPermissions = cameraPermissionResult === PermissionsAndroid.RESULTS.GRANTED;
+    }
+  } else if (Platform.OS === 'windows') {
+    hasCameraPermissions = await CameraManager.checkMediaCapturePermission();
+  } else if (Platform.OS === 'harmony') {
+    hasCameraPermissions = await NativeCameraModule.getCameraPermission();
+  }
+ 
+  if (captureAudio) {
+    if (Platform.OS === 'ios') {
+      hasRecordAudioPermissions = await CameraManager.checkRecordAudioAuthorizationStatus();
+    } else if (Platform.OS === 'android') {
+      if (await CameraManager.checkIfRecordAudioPermissionsAreDefined()) {
+        const audioPermissionResult = await PermissionsAndroid.request(
+          PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
+          androidRecordAudioPermissionOptions,
+        );
+        if (typeof audioPermissionResult === 'boolean') {
+          hasRecordAudioPermissions = audioPermissionResult;
+        } else {
+          hasRecordAudioPermissions = audioPermissionResult === PermissionsAndroid.RESULTS.GRANTED;
+        }
+      } else if (__DEV__) {
+        // eslint-disable-next-line no-console
+        console.warn(
+          `The 'captureAudio' property set on RNCamera instance but 'RECORD_AUDIO' permissions not defined in the applications 'AndroidManifest.xml'. ` +
+          `If you want to record audio you will have to add '<uses-permission android:name="android.permission.RECORD_AUDIO"/>' to your 'AndroidManifest.xml'. ` +
+          `Otherwise you should set the 'captureAudio' property on the component instance to 'false'.`,
+        );
+      }
+    } else if (Platform.OS === 'windows') {
+      hasRecordAudioPermissions = await CameraManager.checkMediaCapturePermission();
+    } else if (Platform.OS === 'harmony') {
+      hasRecordAudioPermissions = await NativeCameraModule.getAuidPermission();
+    }
+  }
+ 
+  return {
+    hasCameraPermissions,
+    hasRecordAudioPermissions,
+  };
+};
+ 
+const styles = StyleSheet.create({
+  authorizationContainer: {
+    flex: 1,
+    alignItems: 'center',
+    justifyContent: 'center',
+  },
+  notAuthorizedText: {
+    textAlign: 'center',
+    fontSize: 16,
+  },
+});
+ 
+type Orientation = 'auto' | 'landscapeLeft' | 'landscapeRight' | 'portrait' | 'portraitUpsideDown';
+type OrientationNumber = 1 | 2 | 3 | 4;
+ 
+type PictureOptions = {
+  quality?: number,
+  orientation?: Orientation | OrientationNumber,
+  base64?: boolean,
+  mirrorImage?: boolean,
+  exif?: boolean,
+  writeExif?: boolean | { [name: string]: any },
+  width?: number,
+  fixOrientation?: boolean,
+  forceUpOrientation?: boolean,
+  pauseAfterCapture?: boolean,
+  imageType?: 'jpeg' | 'png',
+  path?: string,
+};
+ 
+type TrackedFaceFeature = FaceFeature & {
+  faceID?: number,
+};
+ 
+type TrackedTextFeature = {
+  type: string,
+  bounds: {
+    size: {
+      width: number,
+      height: number,
+    },
+    origin: {
+      x: number,
+      y: number,
+    },
+  },
+  value: string,
+  components: Array<TrackedTextFeature>,
+};
+ 
+type TrackedBarcodeFeature = {
+  bounds: {
+    size: {
+      width: number,
+      height: number,
+    },
+    origin: {
+      x: number,
+      y: number,
+    },
+  },
+  data: string,
+  dataRaw: string,
+  type: BarcodeType,
+  format?: string,
+  addresses?: {
+    addressesType?: 'UNKNOWN' | 'Work' | 'Home',
+    addressLines?: string[],
+  }[],
+  emails?: Email[],
+  phones?: Phone[],
+  urls: ?(string[]),
+  name?: {
+    firstName?: string,
+    lastName?: string,
+    middleName?: string,
+    prefix?: string,
+    pronounciation?: string,
+    suffix?: string,
+    formattedName?: string,
+  },
+  phone?: Phone,
+  organization?: string,
+  latitude?: number,
+  longitude?: number,
+  ssid?: string,
+  password?: string,
+  encryptionType?: string,
+  title?: string,
+  url?: string,
+  firstName?: string,
+  middleName?: string,
+  lastName?: string,
+  gender?: string,
+  addressCity?: string,
+  addressState?: string,
+  addressStreet?: string,
+  addressZip?: string,
+  birthDate?: string,
+  documentType?: string,
+  licenseNumber?: string,
+  expiryDate?: string,
+  issuingDate?: string,
+  issuingCountry?: string,
+  eventDescription?: string,
+  location?: string,
+  organizer?: string,
+  status?: string,
+  summary?: string,
+  start?: string,
+  end?: string,
+  email?: Email,
+  phoneNumber?: string,
+  message?: string,
+};
+ 
+type BarcodeType =
+  | 'EMAIL'
+  | 'PHONE'
+  | 'CALENDAR_EVENT'
+  | 'DRIVER_LICENSE'
+  | 'GEO'
+  | 'SMS'
+  | 'CONTACT_INFO'
+  | 'WIFI'
+  | 'TEXT'
+  | 'ISBN'
+  | 'PRODUCT'
+  | 'URL';
+ 
+type Email = {
+  address?: string,
+  body?: string,
+  subject?: string,
+  emailType?: 'UNKNOWN' | 'Work' | 'Home',
+};
+ 
+type Phone = {
+  number?: string,
+  phoneType?: 'UNKNOWN' | 'Work' | 'Home' | 'Fax' | 'Mobile',
+};
+ 
+type ImageType = {
+  jpeg: any,
+  png: any,
+};
+ 
+type HardwareCamera = {
+  deviceType?: string,
+  id: string,
+  type: number,
+};
+ 
+type RecordingOptions = {
+  maxDuration?: number,
+  maxFileSize?: number,
+  orientation?: Orientation,
+  quality?: number | string,
+  fps?: number,
+  codec?: string,
+  mute?: boolean,
+  path?: string,
+  videoBitrate?: number,
+};
+ 
+type EventCallbackArgumentsType = {
+  nativeEvent: Object,
+};
+ 
+type Rect = {
+  x: number,
+  y: number,
+  width: number,
+  height: number,
+};
+ 
+type PropsType = typeof View.props & {
+  zoom?: number,
+  useNativeZoom?: boolean,
+  maxZoom?: number,
+  ratio?: string,
+  focusDepth?: number,
+  type?: number | string,
+  onCameraReady?: Function,
+  onAudioInterrupted?: Function,
+  onAudioConnected?: Function,
+  onStatusChange?: Function,
+  onBarCodeRead?: Function,
+  onPictureTaken?: Function,
+  onPictureSaved?: Function,
+  onRecordingStart?: Function,
+  onRecordingEnd?: Function,
+  onTap?: Function,
+  onDoubleTap?: Function,
+  onGoogleVisionBarcodesDetected?: ({ barcodes: Array<TrackedBarcodeFeature> }) => void,
+  onSubjectAreaChanged?: ({ nativeEvent: { prevPoint: {| x: number, y: number |} } }) => void,
+    faceDetectionMode ?: number,
+    trackingEnabled ?: boolean,
+    flashMode ?: number | string,
+    exposure ?: number,
+    barCodeTypes ?: Array < string >,
+    googleVisionBarcodeType ?: number,
+    googleVisionBarcodeMode ?: number,
+    whiteBalance ?: number | string | { temperature: number, tint: number, redGainOffset?: number, greenGainOffset?: number, blueGainOffset?: number },
+    faceDetectionLandmarks ?: number,
+    autoFocus ?: string | boolean | number,
+    autoFocusPointOfInterest ?: { x: number, y: number },
+    faceDetectionClassifications ?: number,
+    onFacesDetected ?: ({ faces: Array < TrackedFaceFeature > }) => void,
+      onTextRecognized ?: ({ textBlocks: Array < TrackedTextFeature > }) => void,
+        captureAudio ?: boolean,
+        keepAudioSession ?: boolean,
+        useCamera2Api ?: boolean,
+        playSoundOnCapture ?: boolean,
+        playSoundOnRecord ?: boolean,
+        videoStabilizationMode ?: number | string,
+        pictureSize ?: string,
+        cameraId ?: string,
+        rectOfInterest: Rect,
+};
+ 
+type StateType = {
+  isAuthorized: boolean,
+  isAuthorizationChecked: boolean,
+  recordAudioPermissionStatus: RecordAudioPermissionStatus,
+};
+ 
+export type Status = 'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED';
+ 
+const CameraStatus: { [key: Status]: Status } = {
+  READY: 'READY',
+  PENDING_AUTHORIZATION: 'PENDING_AUTHORIZATION',
+  NOT_AUTHORIZED: 'NOT_AUTHORIZED',
+};
+ 
+export type RecordAudioPermissionStatus = 'AUTHORIZED' | 'NOT_AUTHORIZED' | 'PENDING_AUTHORIZATION';
+ 
+const RecordAudioPermissionStatusEnum: {
+  [key: RecordAudioPermissionStatus]: RecordAudioPermissionStatus,
+} = {
+  AUTHORIZED: 'AUTHORIZED',
+  PENDING_AUTHORIZATION: 'PENDING_AUTHORIZATION',
+  NOT_AUTHORIZED: 'NOT_AUTHORIZED',
+};
+ 
+const CameraManager: Object = {
+  stubbed: true,
+  Type: {
+    back: 'back',
+    front: 'front',
+  },
+  AutoFocus: {
+    on: 'on',
+    off: 'off',
+  },
+  FlashMode: {
+    off: 'off',
+    on: 'on',
+    torch: 'torch',
+    auto: 'auto',
+  },
+  WhiteBalance: {
+    sunny: 'sunny',
+    cloudy: 'cloudy',
+    shadow: 'shadow',
+    incandescent: 'incandescent',
+    fluorescent: 'fluorescent',
+    auto: 'auto',
+  },
+  BarCodeType: {},
+  FaceDetection: {
+    fast: 1,
+    Mode: {},
+    Landmarks: {
+      none: 0,
+    },
+    Classifications: {
+      none: 0,
+    },
+  },
+  GoogleVisionBarcodeDetection: {
+    BarcodeType: 0,
+    BarcodeMode: 0,
+  },
+  VideoQuality: {
+    '2160p': 1,
+    '1080p': 2,
+    '720p': 3,
+    '480p': 4,
+    '4:3': 5,
+  },
+  ImageType: {
+    jpeg: 1,
+    png: 2,
+  },
+  VideoCodec: {},
+  CaptureTarget: {
+    disk: 0,
+  },
+  VideoStabilization: {},
+  Orientation: {
+    auto: 'auto',
+    landscapeLeft: 'landscapeLeft',
+    landscapeRight: 'landscapeRight',
+    portrait: 'portrait',
+    portraitUpsideDown: 'portraitUpsideDown',
+  },
+};
+ 
+const EventThrottleMs = 500;
+ 
+const mapValues = (input, mapper) => {
+  const result = {};
+  Object.entries(input).map(([key, value]) => {
+    result[key] = mapper(value, key);
+  });
+  return result;
+};
+ 
+export default class Camera extends React.Component<PropsType, StateType> {
+  static Constants = {
+    Type: CameraManager.Type,
+    FlashMode: CameraManager.FlashMode,
+    AutoFocus: CameraManager.AutoFocus,
+    WhiteBalance: CameraManager.WhiteBalance,
+    VideoQuality: CameraManager.VideoQuality,
+    ImageType: CameraManager.ImageType,
+    VideoCodec: CameraManager.VideoCodec,
+    BarCodeType: CameraManager.BarCodeType,
+    GoogleVisionBarcodeDetection: CameraManager.GoogleVisionBarcodeDetection,
+    FaceDetection: CameraManager.FaceDetection,
+    CameraStatus,
+    CaptureTarget: CameraManager.CaptureTarget,
+    RecordAudioPermissionStatus: RecordAudioPermissionStatusEnum,
+    VideoStabilization: CameraManager.VideoStabilization,
+    Orientation: {
+      auto: 'auto',
+      landscapeLeft: 'landscapeLeft',
+      landscapeRight: 'landscapeRight',
+      portrait: 'portrait',
+      portraitUpsideDown: 'portraitUpsideDown',
+    },
+  };
+ 
+  // Values under keys from this object will be transformed to native options
+  static ConversionTables = {
+    type: CameraManager.Type,
+    flashMode: CameraManager.FlashMode,
+    exposure: CameraManager.Exposure,
+    autoFocus: CameraManager.AutoFocus,
+    whiteBalance: CameraManager.WhiteBalance,
+    faceDetectionMode: (CameraManager.FaceDetection || {}).Mode,
+    faceDetectionLandmarks: (CameraManager.FaceDetection || {}).Landmarks,
+    faceDetectionClassifications: (CameraManager.FaceDetection || {}).Classifications,
+    googleVisionBarcodeType: (CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeType,
+    googleVisionBarcodeMode: (CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeMode,
+    videoStabilizationMode: CameraManager.VideoStabilization || {},
+  };
+ 
+  static propTypes = {
+    ...ViewPropTypes,
+    zoom: PropTypes.number,
+    useNativeZoom: PropTypes.bool,
+    maxZoom: PropTypes.number,
+    ratio: PropTypes.string,
+    focusDepth: PropTypes.number,
+    onMountError: PropTypes.func,
+    onCameraReady: PropTypes.func,
+    onAudioInterrupted: PropTypes.func,
+    onAudioConnected: PropTypes.func,
+    onStatusChange: PropTypes.func,
+    onBarCodeRead: PropTypes.func,
+    onPictureTaken: PropTypes.func,
+    onPictureSaved: PropTypes.func,
+    onRecordingStart: PropTypes.func,
+    onRecordingEnd: PropTypes.func,
+    onTap: PropTypes.func,
+    onDoubleTap: PropTypes.func,
+    onGoogleVisionBarcodesDetected: PropTypes.func,
+    onFacesDetected: PropTypes.func,
+    onTextRecognized: PropTypes.func,
+    onSubjectAreaChanged: PropTypes.func,
+    trackingEnabled: PropTypes.bool,
+    faceDetectionMode: PropTypes.number,
+    faceDetectionLandmarks: PropTypes.number,
+    faceDetectionClassifications: PropTypes.number,
+    barCodeTypes: PropTypes.arrayOf(PropTypes.string),
+    googleVisionBarcodeType: PropTypes.number,
+    googleVisionBarcodeMode: PropTypes.number,
+    type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+    cameraId: PropTypes.string,
+    flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+    exposure: PropTypes.number,
+    whiteBalance: PropTypes.oneOfType([PropTypes.string, PropTypes.number,
+    PropTypes.shape({
+      temperature: PropTypes.number, tint: PropTypes.number,
+      redGainOffset: PropTypes.number,
+      greenGainOffset: PropTypes.number,
+      blueGainOffset: PropTypes.number
+    })]),
+    autoFocus: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
+    autoFocusPointOfInterest: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }),
+    permissionDialogTitle: PropTypes.string,
+    permissionDialogMessage: PropTypes.string,
+    androidCameraPermissionOptions: Rationale,
+    androidRecordAudioPermissionOptions: Rationale,
+    notAuthorizedView: PropTypes.element,
+    pendingAuthorizationView: PropTypes.element,
+    captureAudio: PropTypes.bool,
+    keepAudioSession: PropTypes.bool,
+    useCamera2Api: PropTypes.bool,
+    playSoundOnCapture: PropTypes.bool,
+    playSoundOnRecord: PropTypes.bool,
+    videoStabilizationMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+    pictureSize: PropTypes.string,
+    mirrorVideo: PropTypes.bool,
+    rectOfInterest: PropTypes.any,
+    defaultVideoQuality: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+  };
+  
+ 
+  static defaultProps: Object = {
+    useNativeZoom: false,
+    ratio: '4:3',
+    focusDepth: 0,
+    type: "back",
+    cameraId: '',
+    autoFocus: CameraManager.AutoFocus.on,
+    flashMode: CameraManager.FlashMode.off,
+    whiteBalance: CameraManager.WhiteBalance.auto,
+    faceDetectionMode: (CameraManager.FaceDetection || {}).fast,
+    barCodeTypes: Object.values(CameraManager.BarCodeType),
+    googleVisionBarcodeType: ((CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeType || {})
+      .None,
+    googleVisionBarcodeMode: ((CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeMode || {})
+      .NORMAL,
+    faceDetectionLandmarks: ((CameraManager.FaceDetection || {}).Landmarks || {}).none,
+    faceDetectionClassifications: ((CameraManager.FaceDetection || {}).Classifications || {}).none,
+    permissionDialogTitle: '',
+    permissionDialogMessage: '',
+    androidCameraPermissionOptions: null,
+    androidRecordAudioPermissionOptions: null,
+    notAuthorizedView: (
+      <View style={styles.authorizationContainer}>
+        <Text style={styles.notAuthorizedText}>Camera not authorized</Text>
+      </View>
+    ),
+    pendingAuthorizationView: (
+      <View style={styles.authorizationContainer}>
+        <ActivityIndicator size="small" />
+      </View>
+    ),
+    captureAudio: true,
+    keepAudioSession: false,
+    useCamera2Api: false,
+    playSoundOnCapture: false,
+    playSoundOnRecord: false,
+    pictureSize: 'None',
+    videoStabilizationMode: 'off', // harmony: cpp 枚举按字符串解析("off"/"standard"/"cinematic"/"auto")
+    mirrorVideo: false,
+  };
+ 
+  _cameraRef: React.ElementRef<typeof NativeCamera> | null = null
+  _cameraHandle: ?number;
+  _lastEvents: { [string]: string };
+  _lastEventsTimes: { [string]: Date };
+  _isMounted: boolean;
+ 
+  constructor(props: PropsType) {
+    super(props);
+    this._lastEvents = {};
+    this._lastEventsTimes = {};
+    this._isMounted = true;
+    this.state = {
+      isAuthorized: false,
+      isAuthorizationChecked: false,
+      recordAudioPermissionStatus: RecordAudioPermissionStatusEnum.PENDING_AUTHORIZATION,
+    };
+  }
+ 
+  async takePictureAsync(options?: PictureOptions) {
+    if (!options) {
+      options = {};
+    }
+    if (!options.quality) {
+      options.quality = 1;
+    }
+ 
+    if (options.orientation) {
+      if (typeof options.orientation !== 'number') {
+        const { orientation } = options;
+        options.orientation = CameraManager.Orientation[orientation];
+        if (__DEV__) {
+          if (typeof options.orientation !== 'number') {
+            // eslint-disable-next-line no-console
+            console.warn(`Orientation '${orientation}' is invalid.`);
+          }
+        }
+      }
+    }
+ 
+    if (options.pauseAfterCapture === undefined) {
+      options.pauseAfterCapture = false;
+    }
+ 
+    if (!this._cameraHandle) {
+      throw 'Camera handle cannot be null';
+    }
+ 
+    if (!this._cameraRef) {
+      throw 'Camera handle cannot be null';
+    }
+ 
+    return new Promise((resolve, reject) => {
+      try {
+        const onCodeScannedListener = DeviceEventEmitter.addListener('onTaskPhoto', (data) => {
+          resolve(data);
+          onCodeScannedListener.remove();
+        });
+ 
+        Commands.takePicture(this._cameraRef, options)
+      }
+      catch (e) {
+        reject(e);
+        return;
+      }
+    });
+  }
+ 
+  async getSupportedRatiosAsync() {
+    Iif (Platform.OS === 'android') {
+      return await Commands.getSupportedRatios(this._cameraHandle);
+    } else if (Platform.OS === 'harmony') {
+      return await NativeCameraModule.getSupportedRatios(this._cameraHandle);
+    } else {
+      throw new Error('Ratio is not supported on iOS');
+    }
+  }
+ 
+  async getCameraIdsAsync() {
+    Iif (Platform.OS === 'android') {
+      return await CameraManager.getCameraIds(this._cameraHandle);
+    } else Iif (Platform.OS === 'ios') {
+      return await CameraManager.getCameraIds(); // iOS does not need a camera instance
+    } else Eif (Platform.OS === 'harmony') {
+      return new Promise(async (resolve, reject) => {
+        try {
+          let data = await NativeCameraModule.getCameraIds();
+          resolve(data)
+        }
+        catch (e) {
+          reject(e);
+          return;
+        }
+      });;
+    }
+  }
+ 
+  static async checkIfVideoIsValid(path) {
+    Iif (Platform.OS === 'android') {
+      return await CameraManager.checkIfVideoIsValid(path);
+    } else if (Platform.OS === 'harmony') {
+      return await NativeCameraModule.checkIfVideoIsValid(path);
+    } else {
+      return true; // iOS: not implemented
+    }
+  }
+ 
+  getSupportedPreviewFpsRange = async (): Promise<[]> => {
+    Iif (Platform.OS === 'android') {
+      return await Commands.getSupportedPreviewFpsRange(this._cameraHandle);
+    } else if (Platform.OS === 'harmony') {
+      return await NativeCameraModule.getSupportedPreviewFpsRange(this._cameraHandle);
+    } else {
+      throw new Error('getSupportedPreviewFpsRange is not supported on iOS');
+    }
+  };
+ 
+  getAvailablePictureSizes = async (): string[] => {
+    //$FlowFixMe
+    return await NativeCameraModule.getAvailablePictureSizes();
+  };
+ 
+  async recordAsync(options?: RecordingOptions) {
+    if (!options || typeof options !== 'object') {
+      options = {};
+    } else if (typeof options.quality === 'string') {
+      options.quality = Camera.Constants.VideoQuality[options.quality];
+    }
+    if (options.orientation) {
+      if (typeof options.orientation !== 'number') {
+        const { orientation } = options;
+        options.orientation = CameraManager.Orientation[orientation];
+        if (__DEV__) {
+          if (typeof options.orientation !== 'number') {
+            // eslint-disable-next-line no-console
+            console.warn(`Orientation '${orientation}' is invalid.`);
+          }
+        }
+      }
+    }
+ 
+    if (__DEV__) {
+      if (options.videoBitrate && typeof options.videoBitrate !== 'number') {
+        // eslint-disable-next-line no-console
+        console.warn('Video Bitrate should be a positive integer');
+      }
+    }
+ 
+    const { recordAudioPermissionStatus } = this.state;
+    const { captureAudio } = this.props;
+ 
+    if (
+      !captureAudio ||
+      recordAudioPermissionStatus !== RecordAudioPermissionStatusEnum.AUTHORIZED
+    ) {
+      options.mute = true;
+    }
+ 
+    if (__DEV__) {
+      if (
+        (!options.mute || captureAudio) &&
+        recordAudioPermissionStatus !== RecordAudioPermissionStatusEnum.AUTHORIZED
+      ) {
+        // eslint-disable-next-line no-console
+        console.warn('Recording with audio not possible. Permissions are missing.');
+      }
+    }
+    if (!this._cameraRef) {
+      throw 'Camera handle cannot be null';
+    }
+ 
+    return new Promise((resolve, reject) => {
+      try {
+        const onRecordingFinishedListener = DeviceEventEmitter.addListener('onRecordingFinished', (video: RecordResponse) => {
+          resolve(video)
+          onRecordingFinishedListener.remove();
+        });
+        Commands.record(this._cameraRef, options)
+      }
+      catch (e) {
+        reject(e);
+        return;
+      }
+    });
+  }
+ 
+  stopRecording() {
+    if (!this._cameraRef) {
+      throw 'Camera handle cannot be null';
+    }
+    Commands.stopRecording(this._cameraRef);
+  }
+ 
+  pauseRecording() {
+    if (!this._cameraRef) {
+      throw 'Camera handle cannot be null';
+    }
+    Commands.pauseRecording(this._cameraRef);
+  }
+ 
+  resumeRecording() {
+    if (!this._cameraRef) {
+      throw 'Camera handle cannot be null';
+    }
+    Commands.resumeRecording(this._cameraRef);
+  }
+ 
+  pausePreview() {
+    if (!this._cameraRef) {
+      throw 'Camera handle cannot be null';
+    }
+ 
+    Commands.pausePreview(this._cameraRef);
+  }
+ 
+  isRecording() {
+    if (!this._cameraRef) {
+      throw 'Camera handle cannot be null';
+    }
+    return new Promise((resolve, reject) => {
+      try {
+        const isRecordingFinishedListener = DeviceEventEmitter.addListener('isRecording', (isRecording) => {
+          resolve(isRecording)
+          isRecordingFinishedListener.remove();
+        });
+        Commands.isRecording(this._cameraRef)
+      }
+      catch (e) {
+        reject(e);
+        return;
+      }
+    });
+  }
+ 
+  resumePreview() {
+    if (!this._cameraRef) {
+      throw 'Camera handle cannot be null';
+    }
+    Commands.resumePreview(this._cameraRef);
+  }
+ 
+  _onMountError = ({ nativeEvent }: EventCallbackArgumentsType) => {
+    if (this.props.onMountError) {
+      this.props.onMountError(nativeEvent);
+    }
+  };
+ 
+  _onCameraReady = () => {
+    if (this.props.onCameraReady) {
+      this.props.onCameraReady();
+    }
+  };
+ 
+  _onAudioInterrupted = () => {
+    if (this.props.onAudioInterrupted) {
+      this.props.onAudioInterrupted();
+    }
+  };
+  _onTouch = ({ nativeEvent }: EventCallbackArgumentsType) => {
+    if (this.props.onTap && !nativeEvent.isDoubleTap) {
+      this.props.onTap(nativeEvent.touchOrigin);
+    }
+    if (this.props.onDoubleTap && nativeEvent.isDoubleTap) {
+      this.props.onDoubleTap(nativeEvent.touchOrigin);
+    }
+  };
+  _onAudioConnected = () => {
+    if (this.props.onAudioConnected) {
+      this.props.onAudioConnected();
+    }
+  };
+ 
+  _onStatusChange = () => {
+    if (this.props.onStatusChange) {
+      this.props.onStatusChange({
+        cameraStatus: this.getStatus(),
+        recordAudioPermissionStatus: this.state.recordAudioPermissionStatus,
+      });
+    }
+  };
+ 
+  _onPictureSaved = ({ nativeEvent }: EventCallbackArgumentsType) => {
+    if (this.props.onPictureSaved) {
+      this.props.onPictureSaved(nativeEvent);
+    }
+  };
+ 
+  _onObjectDetected = (callback: ?Function) => ({ nativeEvent }: EventCallbackArgumentsType) => {
+    const { type } = nativeEvent;
+    if (
+      this._lastEvents[type] &&
+      this._lastEventsTimes[type] &&
+      JSON.stringify(nativeEvent) === this._lastEvents[type] &&
+      new Date() - this._lastEventsTimes[type] < EventThrottleMs
+    ) {
+      return;
+    }
+ 
+    if (callback) {
+      callback(nativeEvent);
+      this._lastEventsTimes[type] = new Date();
+      this._lastEvents[type] = JSON.stringify(nativeEvent);
+    }
+  };
+ 
+  _onSubjectAreaChanged = e => {
+    if (this.props.onSubjectAreaChanged) {
+      this.props.onSubjectAreaChanged(e);
+    }
+  };
+ 
+  _setReference = (ref: ?Object) => {
+    if (ref) {
+      this._cameraRef = ref;
+      this._cameraHandle = findNodeHandle(ref);
+    } else {
+      this._cameraRef = null;
+      this._cameraHandle = null;
+    }
+  };
+ 
+  componentWillUnmount() {
+    this._isMounted = false;
+  }
+ 
+  async arePermissionsGranted() {
+    const {
+      permissionDialogTitle,
+      permissionDialogMessage,
+      androidCameraPermissionOptions,
+      androidRecordAudioPermissionOptions,
+    } = this.props;
+ 
+    let cameraPermissions = androidCameraPermissionOptions;
+    let audioPermissions = androidRecordAudioPermissionOptions;
+    if (permissionDialogTitle || permissionDialogMessage) {
+      // eslint-disable-next-line no-console
+      console.warn(
+        'permissionDialogTitle and permissionDialogMessage are deprecated. Please use androidCameraPermissionOptions instead.',
+      );
+      cameraPermissions = {
+        ...cameraPermissions,
+        title: permissionDialogTitle,
+        message: permissionDialogMessage,
+      };
+      audioPermissions = {
+        ...audioPermissions,
+        title: permissionDialogTitle,
+        message: permissionDialogMessage,
+      };
+    }
+ 
+    const { hasCameraPermissions, hasRecordAudioPermissions } = await requestPermissions(
+      this.props.captureAudio,
+      CameraManager,
+      cameraPermissions,
+      audioPermissions,
+    );
+ 
+    const recordAudioPermissionStatus = hasRecordAudioPermissions
+      ? RecordAudioPermissionStatusEnum.AUTHORIZED
+      : RecordAudioPermissionStatusEnum.NOT_AUTHORIZED;
+    return { hasCameraPermissions, recordAudioPermissionStatus };
+  }
+ 
+  async refreshAuthorizationStatus() {
+    const {
+      hasCameraPermissions,
+      recordAudioPermissionStatus,
+    } = await this.arePermissionsGranted();
+    if (this._isMounted === false) {
+      return;
+    }
+ 
+    this.setState({
+      isAuthorized: hasCameraPermissions,
+      isAuthorizationChecked: true,
+      recordAudioPermissionStatus,
+    });
+  }
+ 
+  async componentDidMount() {
+    const {
+      hasCameraPermissions,
+      recordAudioPermissionStatus,
+    } = await this.arePermissionsGranted();
+    if (this._isMounted === false) {
+      return;
+    }
+ 
+    this.setState(
+      {
+        isAuthorized: hasCameraPermissions,
+        isAuthorizationChecked: true,
+        recordAudioPermissionStatus,
+      },
+      this._onStatusChange,
+    );
+  }
+ 
+  getStatus = (): Status => {
+    const { isAuthorized, isAuthorizationChecked } = this.state;
+    if (isAuthorizationChecked === false) {
+      return CameraStatus.PENDING_AUTHORIZATION;
+    }
+    return isAuthorized ? CameraStatus.READY : CameraStatus.NOT_AUTHORIZED;
+  };
+ 
+  // FaCC = Function as Child Component;
+  hasFaCC = (): * => typeof this.props.children === 'function';
+ 
+  renderChildren = (): * => {
+    if (this.hasFaCC()) {
+      return this.props.children({
+        camera: this,
+        status: this.getStatus(),
+        recordAudioPermissionStatus: this.state.recordAudioPermissionStatus,
+      });
+    }
+    return this.props.children;
+  };
+ 
+  render() {
+    const { style, ...nativeProps } = this._convertNativeProps(this.props);
+ 
+    if (this.state.isAuthorized || this.hasFaCC()) {
+      return (
+        <View style={style}>
+          <NativeCamera
+            {...nativeProps}
+            videoStabilizationMode = {this.props.videoStabilizationMode}
+            autoFocus={this.props.autoFocus}
+            whiteBalance={this.props.whiteBalance}
+            flashMode={this.props.flashMode}
+            type={this.props.type}
+            style={StyleSheet.absoluteFill}
+            ref={(ref) => {
+              this._cameraRef = ref;
+              this._cameraHandle = findNodeHandle(ref);
+            }}
+            onMountError={this._onMountError}
+            onCameraReady={this._onObjectDetected(this._onCameraReady)}
+            onAudioInterrupted={this._onAudioInterrupted}
+            onAudioConnected={this._onAudioConnected}
+            onGoogleVisionBarcodesDetected={this._onObjectDetected(
+              this.props.onGoogleVisionBarcodesDetected,
+            )}
+            onBarCodeRead={this._onObjectDetected(this.props.onBarCodeRead)}
+            onTouch={this._onTouch}
+            onFacesDetected={this._onObjectDetected(this.props.onFacesDetected)}
+            onTextRecognized={this._onObjectDetected(this.props.onTextRecognized)}
+            onPictureSaved={this._onPictureSaved}
+            onSubjectAreaChanged={this._onSubjectAreaChanged}
+          />
+          {this.renderChildren()}
+        </View>
+      );
+    } else if (!this.state.isAuthorizationChecked) {
+      return this.props.pendingAuthorizationView;
+    } else {
+      return this.props.notAuthorizedView;
+    }
+  }
+ 
+  _convertNativeProps({ children, ...props }: PropsType) {
+    const newProps = mapValues(props, this._convertProp);
+ 
+    if (props.onBarCodeRead) {
+      newProps.barCodeScannerEnabled = true;
+    }
+ 
+    if (props.onGoogleVisionBarcodesDetected) {
+      newProps.googleVisionBarcodeDetectorEnabled = true;
+    }
+ 
+    if (props.onFacesDetected) {
+      newProps.faceDetectorEnabled = true;
+    }
+ 
+    if (props.onTap || props.onDoubleTap) {
+      newProps.touchDetectorEnabled = true;
+    }
+ 
+    if (props.onTextRecognized) {
+      newProps.textRecognizerEnabled = true;
+    }
+ 
+    if (Platform.OS === 'ios') {
+      delete newProps.ratio;
+    }
+ 
+    return newProps;
+  }
+ 
+  _convertProp(value: *, key: string): * {
+    if (typeof value === 'string' && Camera.ConversionTables[key]) {
+      return Camera.ConversionTables[key][value];
+    }
+ 
+    return value;
+  }
+}
+ 
+export const Constants = Camera.Constants;
+ 
+export function hasTorch() {
+  Eif (Platform.OS === 'harmony') {
+    return NativeCameraModule.hasTorch();
+  }
+  return CameraManager.hasTorch();
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/jest/coverage/lcov-report/base.css b/jest/coverage/lcov-report/base.css new file mode 100644 index 000000000..f418035b4 --- /dev/null +++ b/jest/coverage/lcov-report/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/jest/coverage/lcov-report/block-navigation.js b/jest/coverage/lcov-report/block-navigation.js new file mode 100644 index 000000000..530d1ed2b --- /dev/null +++ b/jest/coverage/lcov-report/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/jest/coverage/lcov-report/favicon.png b/jest/coverage/lcov-report/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1525b811a167671e9de1fa78aab9f5c0b61cef7 GIT binary patch literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 27.23% + Statements + 73/268 +
+ + +
+ 22.06% + Branches + 47/213 +
+ + +
+ 24% + Functions + 12/50 +
+ + +
+ 27.16% + Lines + 72/265 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
FaceDetector.js +
+
71.42%5/750%1/233.33%1/383.33%5/6
RNCamera.js +
+
26.05%68/26121.8%46/21123.4%11/4725.86%67/259
index.js +
+
0%0/00%0/00%0/00%0/0
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/jest/coverage/lcov-report/index.js.html b/jest/coverage/lcov-report/index.js.html new file mode 100644 index 000000000..cd713dcbe --- /dev/null +++ b/jest/coverage/lcov-report/index.js.html @@ -0,0 +1,103 @@ + + + + + + Code coverage report for index.js + + + + + + + + + +
+
+

All files index.js

+
+ +
+ 0% + Statements + 0/0 +
+ + +
+ 0% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/0 +
+ + +
+ 0% + Lines + 0/0 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7  +  +  +  +  +  + 
// @flow
+import RNCamera, { type Status as _CameraStatus, hasTorch } from './RNCamera';
+import FaceDetector from './FaceDetector';
+ 
+export type CameraStatus = _CameraStatus;
+export { RNCamera, FaceDetector, hasTorch };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/jest/coverage/lcov-report/prettify.css b/jest/coverage/lcov-report/prettify.css new file mode 100644 index 000000000..b317a7cda --- /dev/null +++ b/jest/coverage/lcov-report/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/jest/coverage/lcov-report/prettify.js b/jest/coverage/lcov-report/prettify.js new file mode 100644 index 000000000..b3225238f --- /dev/null +++ b/jest/coverage/lcov-report/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/jest/coverage/lcov-report/sort-arrow-sprite.png b/jest/coverage/lcov-report/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/jest/coverage/lcov-report/sorter.js b/jest/coverage/lcov-report/sorter.js new file mode 100644 index 000000000..4ed70ae5a --- /dev/null +++ b/jest/coverage/lcov-report/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/jest/coverage/lcov.info b/jest/coverage/lcov.info new file mode 100644 index 000000000..2aaef8bfc --- /dev/null +++ b/jest/coverage/lcov.info @@ -0,0 +1,605 @@ +TN: +SF:src\FaceDetector.js +FN:11,(anonymous_0) +FN:11,(anonymous_1) +FN:55,(anonymous_2) +FNF:3 +FNH:1 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:1,(anonymous_2) +DA:4,1 +DA:6,1 +DA:11,0 +DA:49,1 +DA:56,1 +DA:60,1 +LF:6 +LH:5 +BRDA:6,0,0,1 +BRDA:6,0,1,0 +BRF:2 +BRH:1 +end_of_record +TN: +SF:src\RNCamera.js +FN:27,(anonymous_0) +FN:399,(anonymous_1) +FN:401,(anonymous_2) +FN:556,(anonymous_3) +FN:568,(anonymous_4) +FN:601,(anonymous_5) +FN:603,(anonymous_6) +FN:617,(anonymous_7) +FN:627,(anonymous_8) +FN:633,(anonymous_9) +FN:646,(anonymous_10) +FN:656,(anonymous_11) +FN:666,(anonymous_12) +FN:671,(anonymous_13) +FN:720,(anonymous_14) +FN:722,(anonymous_15) +FN:735,(anonymous_16) +FN:742,(anonymous_17) +FN:749,(anonymous_18) +FN:756,(anonymous_19) +FN:764,(anonymous_20) +FN:768,(anonymous_21) +FN:770,(anonymous_22) +FN:783,(anonymous_23) +FN:790,(anonymous_24) +FN:796,(anonymous_25) +FN:802,(anonymous_26) +FN:807,(anonymous_27) +FN:815,(anonymous_28) +FN:821,(anonymous_29) +FN:830,(anonymous_30) +FN:836,(anonymous_31) +FN:836,(anonymous_32) +FN:854,(anonymous_33) +FN:860,(anonymous_34) +FN:870,(anonymous_35) +FN:874,(anonymous_36) +FN:914,(anonymous_37) +FN:930,(anonymous_38) +FN:949,(anonymous_39) +FN:958,(anonymous_40) +FN:960,(anonymous_41) +FN:971,(anonymous_42) +FN:985,(anonymous_43) +FN:1013,(anonymous_44) +FN:1043,(anonymous_45) +FN:1054,hasTorch +FNF:47 +FNH:11 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +FNDA:12,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +FNDA:0,(anonymous_6) +FNDA:2,(anonymous_7) +FNDA:1,(anonymous_8) +FNDA:1,(anonymous_9) +FNDA:2,(anonymous_10) +FNDA:2,(anonymous_11) +FNDA:1,(anonymous_12) +FNDA:0,(anonymous_13) +FNDA:0,(anonymous_14) +FNDA:0,(anonymous_15) +FNDA:0,(anonymous_16) +FNDA:0,(anonymous_17) +FNDA:0,(anonymous_18) +FNDA:0,(anonymous_19) +FNDA:0,(anonymous_20) +FNDA:0,(anonymous_21) +FNDA:0,(anonymous_22) +FNDA:0,(anonymous_23) +FNDA:0,(anonymous_24) +FNDA:0,(anonymous_25) +FNDA:0,(anonymous_26) +FNDA:3,(anonymous_27) +FNDA:0,(anonymous_28) +FNDA:0,(anonymous_29) +FNDA:0,(anonymous_30) +FNDA:3,(anonymous_31) +FNDA:5,(anonymous_32) +FNDA:0,(anonymous_33) +FNDA:0,(anonymous_34) +FNDA:0,(anonymous_35) +FNDA:0,(anonymous_36) +FNDA:0,(anonymous_37) +FNDA:0,(anonymous_38) +FNDA:0,(anonymous_39) +FNDA:0,(anonymous_40) +FNDA:0,(anonymous_41) +FNDA:0,(anonymous_42) +FNDA:0,(anonymous_43) +FNDA:0,(anonymous_44) +FNDA:0,(anonymous_45) +FNDA:2,hasTorch +DA:19,4 +DA:27,4 +DA:33,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:42,0 +DA:43,0 +DA:45,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:62,0 +DA:63,0 +DA:65,0 +DA:67,0 +DA:69,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:82,0 +DA:88,4 +DA:317,4 +DA:327,4 +DA:333,4 +DA:397,4 +DA:399,4 +DA:400,0 +DA:401,0 +DA:402,0 +DA:404,0 +DA:408,4 +DA:433,4 +DA:447,4 +DA:509,4 +DA:550,12 +DA:557,12 +DA:558,12 +DA:559,12 +DA:560,12 +DA:561,12 +DA:569,0 +DA:570,0 +DA:572,0 +DA:573,0 +DA:576,0 +DA:577,0 +DA:578,0 +DA:579,0 +DA:580,0 +DA:581,0 +DA:583,0 +DA:589,0 +DA:590,0 +DA:593,0 +DA:594,0 +DA:597,0 +DA:598,0 +DA:601,0 +DA:602,0 +DA:603,0 +DA:604,0 +DA:605,0 +DA:608,0 +DA:611,0 +DA:612,0 +DA:618,2 +DA:619,0 +DA:620,2 +DA:621,1 +DA:623,1 +DA:628,1 +DA:629,0 +DA:630,1 +DA:631,0 +DA:632,1 +DA:633,1 +DA:634,1 +DA:635,1 +DA:636,1 +DA:639,0 +DA:640,0 +DA:647,2 +DA:648,0 +DA:649,2 +DA:650,1 +DA:652,1 +DA:656,12 +DA:657,2 +DA:658,0 +DA:659,2 +DA:660,1 +DA:662,1 +DA:666,12 +DA:668,1 +DA:672,0 +DA:673,0 +DA:674,0 +DA:675,0 +DA:677,0 +DA:678,0 +DA:679,0 +DA:680,0 +DA:681,0 +DA:682,0 +DA:684,0 +DA:690,0 +DA:691,0 +DA:693,0 +DA:697,0 +DA:698,0 +DA:700,0 +DA:704,0 +DA:707,0 +DA:708,0 +DA:713,0 +DA:716,0 +DA:717,0 +DA:720,0 +DA:721,0 +DA:722,0 +DA:723,0 +DA:724,0 +DA:726,0 +DA:729,0 +DA:730,0 +DA:736,0 +DA:737,0 +DA:739,0 +DA:743,0 +DA:744,0 +DA:746,0 +DA:750,0 +DA:751,0 +DA:753,0 +DA:757,0 +DA:758,0 +DA:761,0 +DA:765,0 +DA:766,0 +DA:768,0 +DA:769,0 +DA:770,0 +DA:771,0 +DA:772,0 +DA:774,0 +DA:777,0 +DA:778,0 +DA:784,0 +DA:785,0 +DA:787,0 +DA:790,12 +DA:791,0 +DA:792,0 +DA:796,12 +DA:797,0 +DA:798,0 +DA:802,12 +DA:803,0 +DA:804,0 +DA:807,12 +DA:808,3 +DA:809,1 +DA:811,3 +DA:812,1 +DA:815,12 +DA:816,0 +DA:817,0 +DA:821,12 +DA:822,0 +DA:823,0 +DA:830,12 +DA:831,0 +DA:832,0 +DA:836,12 +DA:837,5 +DA:838,5 +DA:844,1 +DA:847,4 +DA:848,3 +DA:849,3 +DA:850,3 +DA:854,12 +DA:855,0 +DA:856,0 +DA:860,12 +DA:861,0 +DA:862,0 +DA:863,0 +DA:865,0 +DA:866,0 +DA:871,0 +DA:880,0 +DA:882,0 +DA:883,0 +DA:884,0 +DA:886,0 +DA:889,0 +DA:894,0 +DA:901,0 +DA:908,0 +DA:911,0 +DA:918,0 +DA:919,0 +DA:920,0 +DA:923,0 +DA:934,0 +DA:935,0 +DA:936,0 +DA:939,0 +DA:949,12 +DA:950,0 +DA:951,0 +DA:952,0 +DA:954,0 +DA:958,12 +DA:960,12 +DA:961,0 +DA:962,0 +DA:968,0 +DA:972,0 +DA:974,0 +DA:975,0 +DA:986,0 +DA:987,0 +DA:1006,0 +DA:1007,0 +DA:1009,0 +DA:1014,0 +DA:1016,0 +DA:1017,0 +DA:1020,0 +DA:1021,0 +DA:1024,0 +DA:1025,0 +DA:1028,0 +DA:1029,0 +DA:1032,0 +DA:1033,0 +DA:1036,0 +DA:1037,0 +DA:1040,0 +DA:1044,0 +DA:1045,0 +DA:1048,0 +DA:1052,4 +DA:1055,2 +DA:1056,2 +DA:1058,0 +LF:259 +LH:67 +BRDA:35,0,0,0 +BRDA:35,0,1,0 +BRDA:37,1,0,0 +BRDA:37,1,1,0 +BRDA:42,2,0,0 +BRDA:42,2,1,0 +BRDA:47,3,0,0 +BRDA:47,3,1,0 +BRDA:49,4,0,0 +BRDA:49,4,1,0 +BRDA:53,5,0,0 +BRDA:53,5,1,0 +BRDA:54,6,0,0 +BRDA:54,6,1,0 +BRDA:56,7,0,0 +BRDA:56,7,1,0 +BRDA:57,8,0,0 +BRDA:57,8,1,0 +BRDA:62,9,0,0 +BRDA:62,9,1,0 +BRDA:67,10,0,0 +BRDA:67,10,1,0 +BRDA:75,11,0,0 +BRDA:75,11,1,0 +BRDA:77,12,0,0 +BRDA:77,12,1,0 +BRDA:439,13,0,4 +BRDA:439,13,1,0 +BRDA:440,14,0,4 +BRDA:440,14,1,0 +BRDA:441,15,0,4 +BRDA:441,15,1,0 +BRDA:442,16,0,4 +BRDA:442,16,1,0 +BRDA:443,17,0,4 +BRDA:443,17,1,0 +BRDA:444,18,0,4 +BRDA:444,18,1,0 +BRDA:518,19,0,4 +BRDA:518,19,1,0 +BRDA:520,20,0,4 +BRDA:520,20,1,4 +BRDA:520,21,0,4 +BRDA:520,21,1,0 +BRDA:522,22,0,4 +BRDA:522,22,1,4 +BRDA:522,23,0,4 +BRDA:522,23,1,0 +BRDA:524,24,0,4 +BRDA:524,24,1,0 +BRDA:524,25,0,4 +BRDA:524,25,1,0 +BRDA:525,26,0,4 +BRDA:525,26,1,0 +BRDA:525,27,0,4 +BRDA:525,27,1,0 +BRDA:569,28,0,0 +BRDA:569,28,1,0 +BRDA:572,29,0,0 +BRDA:572,29,1,0 +BRDA:576,30,0,0 +BRDA:576,30,1,0 +BRDA:577,31,0,0 +BRDA:577,31,1,0 +BRDA:580,32,0,0 +BRDA:580,32,1,0 +BRDA:581,33,0,0 +BRDA:581,33,1,0 +BRDA:589,34,0,0 +BRDA:589,34,1,0 +BRDA:593,35,0,0 +BRDA:593,35,1,0 +BRDA:597,36,0,0 +BRDA:597,36,1,0 +BRDA:618,37,0,0 +BRDA:618,37,1,2 +BRDA:620,38,0,1 +BRDA:620,38,1,1 +BRDA:628,39,0,0 +BRDA:628,39,1,1 +BRDA:630,40,0,0 +BRDA:630,40,1,1 +BRDA:632,41,0,1 +BRDA:632,41,1,0 +BRDA:647,42,0,0 +BRDA:647,42,1,2 +BRDA:649,43,0,1 +BRDA:649,43,1,1 +BRDA:657,44,0,0 +BRDA:657,44,1,2 +BRDA:659,45,0,1 +BRDA:659,45,1,1 +BRDA:672,46,0,0 +BRDA:672,46,1,0 +BRDA:672,47,0,0 +BRDA:672,47,1,0 +BRDA:674,48,0,0 +BRDA:674,48,1,0 +BRDA:677,49,0,0 +BRDA:677,49,1,0 +BRDA:678,50,0,0 +BRDA:678,50,1,0 +BRDA:681,51,0,0 +BRDA:681,51,1,0 +BRDA:682,52,0,0 +BRDA:682,52,1,0 +BRDA:690,53,0,0 +BRDA:690,53,1,0 +BRDA:691,54,0,0 +BRDA:691,54,1,0 +BRDA:691,55,0,0 +BRDA:691,55,1,0 +BRDA:700,56,0,0 +BRDA:700,56,1,0 +BRDA:701,57,0,0 +BRDA:701,57,1,0 +BRDA:707,58,0,0 +BRDA:707,58,1,0 +BRDA:708,59,0,0 +BRDA:708,59,1,0 +BRDA:709,60,0,0 +BRDA:709,60,1,0 +BRDA:709,60,2,0 +BRDA:716,61,0,0 +BRDA:716,61,1,0 +BRDA:736,62,0,0 +BRDA:736,62,1,0 +BRDA:743,63,0,0 +BRDA:743,63,1,0 +BRDA:750,64,0,0 +BRDA:750,64,1,0 +BRDA:757,65,0,0 +BRDA:757,65,1,0 +BRDA:765,66,0,0 +BRDA:765,66,1,0 +BRDA:784,67,0,0 +BRDA:784,67,1,0 +BRDA:791,68,0,0 +BRDA:791,68,1,0 +BRDA:797,69,0,0 +BRDA:797,69,1,0 +BRDA:803,70,0,0 +BRDA:803,70,1,0 +BRDA:808,71,0,1 +BRDA:808,71,1,2 +BRDA:808,72,0,3 +BRDA:808,72,1,2 +BRDA:811,73,0,1 +BRDA:811,73,1,2 +BRDA:811,74,0,3 +BRDA:811,74,1,2 +BRDA:816,75,0,0 +BRDA:816,75,1,0 +BRDA:822,76,0,0 +BRDA:822,76,1,0 +BRDA:831,77,0,0 +BRDA:831,77,1,0 +BRDA:838,78,0,1 +BRDA:838,78,1,4 +BRDA:839,79,0,5 +BRDA:839,79,1,2 +BRDA:839,79,2,2 +BRDA:839,79,3,1 +BRDA:847,80,0,3 +BRDA:847,80,1,1 +BRDA:855,81,0,0 +BRDA:855,81,1,0 +BRDA:861,82,0,0 +BRDA:861,82,1,0 +BRDA:884,83,0,0 +BRDA:884,83,1,0 +BRDA:884,84,0,0 +BRDA:884,84,1,0 +BRDA:908,85,0,0 +BRDA:908,85,1,0 +BRDA:919,86,0,0 +BRDA:919,86,1,0 +BRDA:935,87,0,0 +BRDA:935,87,1,0 +BRDA:951,88,0,0 +BRDA:951,88,1,0 +BRDA:954,89,0,0 +BRDA:954,89,1,0 +BRDA:961,90,0,0 +BRDA:961,90,1,0 +BRDA:974,91,0,0 +BRDA:974,91,1,0 +BRDA:974,92,0,0 +BRDA:974,92,1,0 +BRDA:1006,93,0,0 +BRDA:1006,93,1,0 +BRDA:1016,94,0,0 +BRDA:1016,94,1,0 +BRDA:1020,95,0,0 +BRDA:1020,95,1,0 +BRDA:1024,96,0,0 +BRDA:1024,96,1,0 +BRDA:1028,97,0,0 +BRDA:1028,97,1,0 +BRDA:1028,98,0,0 +BRDA:1028,98,1,0 +BRDA:1032,99,0,0 +BRDA:1032,99,1,0 +BRDA:1036,100,0,0 +BRDA:1036,100,1,0 +BRDA:1044,101,0,0 +BRDA:1044,101,1,0 +BRDA:1044,102,0,0 +BRDA:1044,102,1,0 +BRDA:1055,103,0,2 +BRDA:1055,103,1,0 +BRF:211 +BRH:46 +end_of_record +TN: +SF:src\index.js +FNF:0 +FNH:0 +LF:0 +LH:0 +BRF:0 +BRH:0 +end_of_record diff --git a/jest/helpers/nativeCameraMock.js b/jest/helpers/nativeCameraMock.js new file mode 100644 index 000000000..d715b004e --- /dev/null +++ b/jest/helpers/nativeCameraMock.js @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * src/NativeCamera(codegen 组件 spec)的 jest mock。 + * + * 源文件依赖 codegenNativeComponent / codegenNativeCommands(RN 内部模块), + * 测试侧整体替换为 stub 组件 + Commands jest.fn。 + */ + +const Commands = { + takePicture: jest.fn(), + record: jest.fn(), + stopRecording: jest.fn(), + pauseRecording: jest.fn(), + resumeRecording: jest.fn(), + pausePreview: jest.fn(), + resumePreview: jest.fn(), + getSupportedRatios: jest.fn(), + getSupportedPreviewFpsRange: jest.fn(), +}; + +const NativeCamera = function NativeCamera() { + return null; +}; + +module.exports = NativeCamera; +module.exports.default = NativeCamera; +module.exports.Commands = Commands; +module.exports.RecordResponse = {}; diff --git a/jest/helpers/nativeCameraModuleMock.js b/jest/helpers/nativeCameraModuleMock.js new file mode 100644 index 000000000..693187923 --- /dev/null +++ b/jest/helpers/nativeCameraModuleMock.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * src/NativeCameraModule(TurboModule spec)的 jest mock。 + * + * 全方法 jest.fn,默认返回已 resolve 的 Promise,便于透传断言; + * 测试侧可用 RNCameraModuleMock 控制返回值。 + */ + +const createOk = (value) => jest.fn(async () => value); + +const NativeCameraModule = { + getCameraPermission: createOk(true), + getAuidPermission: createOk(true), + getCameraIds: jest.fn(async () => [{ id: "cam0", type: 0 }]), + getAvailablePictureSizes: jest.fn(async () => ["640x480", "1280x720"]), + hasTorch: createOk(true), + checkIfVideoIsValid: createOk(true), + getSupportedPreviewFpsRange: jest.fn(async () => ["15,30", "30,30"]), + getSupportedRatios: jest.fn(async () => ["4:3", "16:9"]), +}; + +module.exports = NativeCameraModule; +module.exports.RNCameraModuleMock = NativeCameraModule; diff --git a/jest/helpers/nativeFaceDetectorMock.js b/jest/helpers/nativeFaceDetectorMock.js new file mode 100644 index 000000000..24f52f26e --- /dev/null +++ b/jest/helpers/nativeFaceDetectorMock.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * src/NativeFaceDetector(TurboModule spec)的 jest mock。 + */ + +const NativeFaceDetector = { + detectFaces: jest.fn(async () => []), +}; + +module.exports = NativeFaceDetector; diff --git a/jest/helpers/propTypesMock.js b/jest/helpers/propTypesMock.js new file mode 100644 index 000000000..a7ed57d48 --- /dev/null +++ b/jest/helpers/propTypesMock.js @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * prop-types 的 jest mock(各测试文件顶部 + * `jest.mock("prop-types", () => require("../helpers/propTypesMock"))` 显式激活)。 + * + * RNCamera.js 用 PropTypes 声明 static propTypes(仅 dev 校验用),运行时只需要 + * 校验器对象存在。stub 成员取值返回可链式占位对象(.isRequired 继续可链)。 + */ + +function checker() { + const fn = function () {}; + fn.isRequired = fn; + return fn; +} + +const PropTypes = { + array: checker(), + bool: checker(), + func: checker(), + number: checker(), + object: checker(), + string: checker(), + symbol: checker(), + any: checker(), + element: checker(), + elementType: checker(), + instanceOf: checker, + enum: checker, + node: checker(), + objectOf: checker, + oneOf: checker, + oneOfType: checker, + arrayOf: checker, + exact: checker, + shape: checker, +}; + +module.exports = PropTypes; diff --git a/jest/helpers/reactMock.js b/jest/helpers/reactMock.js new file mode 100644 index 000000000..7ce525bd6 --- /dev/null +++ b/jest/helpers/reactMock.js @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * react 的 jest mock(各测试文件顶部 + * `jest.mock("react", () => require("../helpers/reactMock"))` 显式激活)。 + * + * RNCamera.js 是 class 组件 + JSX。Component stub 提供可用的 + * constructor/setState/forceUpdate,让测试能直接实例化 Camera 类。 + * JSX 由 babel 的 @babel/plugin-transform-react-jsx 转为 createElement, + * mock 的 createElement 返回描述对象即可(测试不渲染)。 + */ + +class Component { + constructor(props) { + this.props = props || {}; + this.state = {}; + } + setState(partial, callback) { + const next = typeof partial === "function" ? partial(this.state, this.props) : partial; + this.state = { ...this.state, ...next }; + if (callback) callback(); + } + forceUpdate() {} +} + +const React = { + Component, + PureComponent: Component, + memo: (x) => x, + createElement: jest.fn((type, props, ...children) => ({ type, props, children })), + cloneElement: jest.fn((el, props) => ({ ...el, props: { ...el.props, ...props } })), + useRef: jest.fn((init) => ({ current: init })), + useState: jest.fn((init) => [init, jest.fn()]), + useEffect: jest.fn(), + useCallback: jest.fn((fn) => fn), + Fragment: "Fragment", +}; + +module.exports = React; +module.exports.default = React; diff --git a/jest/helpers/reactNativeMock.js b/jest/helpers/reactNativeMock.js new file mode 100644 index 000000000..30fc51e6f --- /dev/null +++ b/jest/helpers/reactNativeMock.js @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * react-native 的 jest mock(各测试文件顶部 + * `jest.mock("react-native", () => require("../helpers/reactNativeMock"))` 显式激活)。 + * + * 覆盖 RNCamera.js / FaceDetector.js / index.js 的 react-native 依赖面: + * Platform / DeviceEventEmitter / findNodeHandle / PermissionsAndroid / + * ViewPropTypes / View / ActivityIndicator / Text / StyleSheet。 + * 每个 Jest 测试文件是独立 module registry,本文件在每个测试文件中各执行一次。 + */ + +// 平台态可切换:测试 iOS 分支时用 RNCameraJestMock.setPlatformOS("ios") +let currentOS = "harmony"; + +const Platform = { + get OS() { + return currentOS; + }, + select: (specifics) => specifics[currentOS] || specifics.ios, +}; + +// 事件名 → 最近注册的 handler(供测试侧 emit 触发) +const handlers = new Map(); +// 事件名 → 最近返回的订阅句柄(供测试侧断言 remove) +const subsByEvent = new Map(); + +const DeviceEventEmitter = { + addListener: jest.fn((eventName, handler) => { + handlers.set(eventName, handler); + const sub = { remove: jest.fn() }; + subsByEvent.set(eventName, sub); + return sub; + }), +}; + +const PermissionsAndroid = { + REQUEST_INSTALL_PACKAGES: undefined, + RESULTS: { GRANTED: "granted", DENIED: "denied", NEVER_ASK_AGAIN: "never_ask_again" }, + request: jest.fn(async () => PermissionsAndroid.RESULTS.GRANTED), + requestMultiple: jest.fn(async () => ({ "android.permission.CAMERA": "granted" })), + check: jest.fn(async () => true), +}; + +function StyleStub() {} + +module.exports = { + Platform, + DeviceEventEmitter, + findNodeHandle: jest.fn(() => 1), + PermissionsAndroid, + ViewPropTypes: new Proxy({}, { get: () => ({}), has: () => true }), + View: StyleStub, + ActivityIndicator: StyleStub, + Text: StyleStub, + StyleSheet: { create: (styles) => styles }, + // 测试钩子命名空间 + RNCameraJestMock: { + setPlatformOS: (os) => { + currentOS = os; + }, + emit: (eventName, payload) => { + const handler = handlers.get(eventName); + if (!handler) return false; + handler(payload); + return true; + }, + getSubscription: (eventName) => subsByEvent.get(eventName), + }, +}; diff --git a/jest/jest.config.js b/jest/jest.config.js new file mode 100644 index 000000000..26c427574 --- /dev/null +++ b/jest/jest.config.js @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * RNOH JS 层白盒测试基础设施(@react-native-ohos/react-native-camera) + * + * 按 rnoh-js-test skill 方法论生成(仿 @react-native-ohos/react-native-sensors 落地形态): + * - babel-jest + @babel/preset-flow 转译方案(hermes parser 解析 Flow/ESM/JSX) + * - testMatch 精确匹配连字符命名 `*-test.js` + * - `__DEV__` globals、transformIgnorePatterns、coverage 白名单 + * + * 工具链解析:库根 devDependencies 声明 jest@29.7.0 与 @babel/* 工具链, + * 装入库根 node_modules,createRequire 锚定库根解析为绝对路径。 + * + * 运行(在库根目录执行): + * npm test # jest --no-coverage + * npm run test:coverage # jest --coverage + */ + +const path = require("path"); +const { createRequire } = require("module"); + +const toolchainRequire = createRequire(path.join(__dirname, "..", "node_modules", "_anchor_")); + +const babelJest = toolchainRequire.resolve("babel-jest"); +const presetEnv = toolchainRequire.resolve("@babel/preset-env"); +const presetFlow = toolchainRequire.resolve("@babel/preset-flow"); +const hermesParserPlugin = toolchainRequire.resolve("babel-plugin-syntax-hermes-parser"); +const jsxPlugin = toolchainRequire.resolve("@babel/plugin-transform-react-jsx"); + +module.exports = { + // rootDir 指向库根:coverage 以库根为基准收集 src/*.js + rootDir: path.resolve(__dirname, ".."), + + // roots 限制在本测试目录:库根全树有 harmony 等(上万文件), + // 不让 jest 参与 haste 扫描,避免拖慢启动 + roots: [path.resolve(__dirname)], + + // skill C2:testMatch 精确匹配连字符命名 *-test.js + testMatch: ["/jest/__tests__/*-test.js"], + + // skill C3:排除 node_modules 与 oh_modules + testPathIgnorePatterns: ["/node_modules/", "/oh_modules/", "/harmony/"], + + transform: { + "^.+\.js$": [ + babelJest, + { + // 显式禁用 babel 配置文件查找:绝不加载库根上游遗留的 babel 配置 + configFile: false, + babelrc: false, + presets: [ + // ESM → CommonJS,node 环境直跑 + [presetEnv, { targets: { node: "current" }, modules: "commonjs" }], + // Flow 类型剥离 + [presetFlow, { all: true }], + ], + plugins: [ + // hermes parser:解析 Flow/ESM/JSX 语法(仅语法,不做转换) + [hermesParserPlugin, { parseLangTypes: "flow" }], + // JSX 转换(RNCamera.js 的 render 含 JSX) + [jsxPlugin], + ], + }, + ], + }, + + // skill 关键配置:RN 内部代码检查 __DEV__ 全局变量 + globals: { + __DEV__: true, + }, + + transformIgnorePatterns: ["node_modules/(?!react-native|@react-native|react)"], + + // 全局 mock/兜底 + setupFiles: ["/jest/jest.setup.js"], + + // coverage 白名单:本库 JS 层生产文件(src/*.ts 为 codegen spec 声明,无独立运行时逻辑) + collectCoverageFrom: ["src/*.js"], + coverageDirectory: "/jest/coverage", + coverageThreshold: { + global: { + // RNCamera.js 为千行 UI 组件(render/生命周期胶水占比高),白盒测试聚焦 + // 逻辑分支(平台路由/常量/事件节流/导出面),门槛按该定位设定 + branches: 20, + }, + }, +}; diff --git a/jest/jest.setup.js b/jest/jest.setup.js new file mode 100644 index 000000000..fb319544b --- /dev/null +++ b/jest/jest.setup.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2026 Huawei Technologies Co., Ltd. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +/** + * Jest 全局 setup(rnoh-js-test skill 的 setupFiles 配置位)。 + * + * camera 的 JS 层依赖面在测试侧由各测试文件显式 jest.mock 隔离: + * 1. react-native(Platform/DeviceEventEmitter/findNodeHandle/组件等)—— helpers/reactNativeMock.js + * 2. prop-types —— helpers/propTypesMock.js + * 3. react —— helpers/reactMock.js + * 4. src/NativeCameraModule(TurboModule spec)—— helpers/nativeCameraModuleMock.js + * 5. src/NativeCamera(codegen 组件 spec + Commands)—— helpers/nativeCameraMock.js + * 6. src/NativeFaceDetector —— helpers/nativeFaceDetectorMock.js + * + * 库源码检查 __DEV__(由 jest.config.js 的 globals 提供),其余 RN 全局未引用。 + */ diff --git a/package.json b/package.json index f756b4b6c..3c1e766b5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-ohos/react-native-camera", "description": "A Camera component for React Native. Also reads barcodes.", - "version": "3.42.1-rc.1", + "version": "4.0.0-beta.1", "author": "Lochlan Wansbrough (http://lwansbrough.com)", "collective": { "type": "opencollective", @@ -13,6 +13,13 @@ }, "devDependencies": { "@babel/runtime": "^7.3.1", + "@babel/core": "*", + "@babel/preset-env": "^7.24.0", + "@babel/preset-flow": "^7.24.0", + "@babel/plugin-transform-react-jsx": "^7.24.0", + "babel-jest": "^29.7.0", + "babel-plugin-syntax-hermes-parser": "^0.25.0", + "jest": "^29.7.0", "@commitlint/cli": "^9.1.2", "@commitlint/config-conventional": "^9.1.2", "@commitlint/prompt": "^9.1.2", @@ -42,12 +49,15 @@ "metro": "0.82.5", "memfs": "4.17.2" }, - "homepage": "https://github.com/react-native-community/react-native-camera", + "homepage": "https://github.com/react-native-oh-library/react-native-camera", "keywords": [ "barcode", "camera", "code", + "harmony", "native", + "ohos", + "openharmony", "qr", "react", "react-native" @@ -73,7 +83,7 @@ "pre-commit": "lint:staged", "repository": { "type": "git", - "url": "https://github.com/react-native-community/react-native-camera.git" + "url": "https://github.com/react-native-oh-library/react-native-camera.git" }, "scripts": { "commit": "git-cz", @@ -81,6 +91,8 @@ "lint": "eslint src --max-warnings=0", "lint:staged": "lint-staged", "prettier": "prettier --write", + "test": "jest --no-coverage", + "test:coverage": "jest --coverage", "ci:publish": "semantic-release", "codegen-lib": "react-native codegen-lib-harmony --no-safety-check --npm-package-name react-native-camera --cpp-components-spec-paths ./src --turbo-modules-spec-paths ./src --cpp-output-path ./harmony/reactNativeCamera/src/main/cpp/generated --ets-output-path ./harmony/reactNativeCamera/src/main/ets/generated" }, diff --git a/src/NativeCamera.ts b/src/NativeCamera.ts index 3adb39e6b..fafd072b7 100644 --- a/src/NativeCamera.ts +++ b/src/NativeCamera.ts @@ -290,6 +290,7 @@ type FlashMode = 'on' | 'off' | 'torch' | 'auto'; export interface NativeProps extends ViewProps { autoFocus?: WithDefault, autoFocusPointOfInterest?: { x: Int32, y: Int32 }, + cameraId?: string, pictureSize?: string, type?: WithDefault, flashMode?: WithDefault, @@ -315,6 +316,7 @@ export interface NativeProps extends ViewProps { videoStabilizationMode?: WithDefault, whiteBalance?: WithDefault, playSoundOnCapture?: boolean, + playSoundOnRecord?: boolean, barCodeScannerEnabled: boolean, touchDetectorEnabled: boolean, googleVisionBarcodeDetectorEnabled: boolean, diff --git a/src/NativeCameraModule.ts b/src/NativeCameraModule.ts index 7bac6e3aa..b06f8b47c 100644 --- a/src/NativeCameraModule.ts +++ b/src/NativeCameraModule.ts @@ -11,6 +11,10 @@ export interface Spec extends TurboModule { getAuidPermission(): Promise; getCameraIds():Promise; getAvailablePictureSizes():Promise; + hasTorch():Promise; + checkIfVideoIsValid(path: string): Promise; + getSupportedPreviewFpsRange(handle: number): Promise; + getSupportedRatios(handle: number): Promise; } export default TurboModuleRegistry.get('RNCCameraModule'); diff --git a/src/RNCamera.js b/src/RNCamera.js index 8f801e02a..d7f80df37 100644 --- a/src/RNCamera.js +++ b/src/RNCamera.js @@ -111,6 +111,8 @@ type PictureOptions = { fixOrientation?: boolean, forceUpOrientation?: boolean, pauseAfterCapture?: boolean, + imageType?: 'jpeg' | 'png', + path?: string, }; type TrackedFaceFeature = FaceFeature & { @@ -225,6 +227,17 @@ type Phone = { phoneType?: 'UNKNOWN' | 'Work' | 'Home' | 'Fax' | 'Mobile', }; +type ImageType = { + jpeg: any, + png: any, +}; + +type HardwareCamera = { + deviceType?: string, + id: string, + type: number, +}; + type RecordingOptions = { maxDuration?: number, maxFileSize?: number, @@ -286,8 +299,10 @@ type PropsType = typeof View.props & { keepAudioSession ?: boolean, useCamera2Api ?: boolean, playSoundOnCapture ?: boolean, + playSoundOnRecord ?: boolean, videoStabilizationMode ?: number | string, pictureSize ?: string, + cameraId ?: string, rectOfInterest: Rect, }; @@ -318,15 +333,27 @@ const RecordAudioPermissionStatusEnum: { const CameraManager: Object = { stubbed: true, Type: { - back: 1, + back: 'back', + front: 'front', }, AutoFocus: { - on: 1, + on: 'on', + off: 'off', }, FlashMode: { - off: 1, + off: 'off', + on: 'on', + torch: 'torch', + auto: 'auto', + }, + WhiteBalance: { + sunny: 'sunny', + cloudy: 'cloudy', + shadow: 'shadow', + incandescent: 'incandescent', + fluorescent: 'fluorescent', + auto: 'auto', }, - WhiteBalance: {}, BarCodeType: {}, FaceDetection: { fast: 1, @@ -342,6 +369,29 @@ const CameraManager: Object = { BarcodeType: 0, BarcodeMode: 0, }, + VideoQuality: { + '2160p': 1, + '1080p': 2, + '720p': 3, + '480p': 4, + '4:3': 5, + }, + ImageType: { + jpeg: 1, + png: 2, + }, + VideoCodec: {}, + CaptureTarget: { + disk: 0, + }, + VideoStabilization: {}, + Orientation: { + auto: 'auto', + landscapeLeft: 'landscapeLeft', + landscapeRight: 'landscapeRight', + portrait: 'portrait', + portraitUpsideDown: 'portraitUpsideDown', + }, }; const EventThrottleMs = 500; @@ -361,6 +411,7 @@ export default class Camera extends React.Component { AutoFocus: CameraManager.AutoFocus, WhiteBalance: CameraManager.WhiteBalance, VideoQuality: CameraManager.VideoQuality, + ImageType: CameraManager.ImageType, VideoCodec: CameraManager.VideoCodec, BarCodeType: CameraManager.BarCodeType, GoogleVisionBarcodeDetection: CameraManager.GoogleVisionBarcodeDetection, @@ -446,6 +497,7 @@ export default class Camera extends React.Component { keepAudioSession: PropTypes.bool, useCamera2Api: PropTypes.bool, playSoundOnCapture: PropTypes.bool, + playSoundOnRecord: PropTypes.bool, videoStabilizationMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), pictureSize: PropTypes.string, mirrorVideo: PropTypes.bool, @@ -459,7 +511,7 @@ export default class Camera extends React.Component { ratio: '4:3', focusDepth: 0, type: "back", - cameraId: null, + cameraId: '', autoFocus: CameraManager.AutoFocus.on, flashMode: CameraManager.FlashMode.off, whiteBalance: CameraManager.WhiteBalance.auto, @@ -489,8 +541,9 @@ export default class Camera extends React.Component { keepAudioSession: false, useCamera2Api: false, playSoundOnCapture: false, + playSoundOnRecord: false, pictureSize: 'None', - videoStabilizationMode: 0, + videoStabilizationMode: 'off', // harmony: cpp 枚举按字符串解析("off"/"standard"/"cinematic"/"auto") mirrorVideo: false, }; @@ -564,6 +617,8 @@ export default class Camera extends React.Component { async getSupportedRatiosAsync() { if (Platform.OS === 'android') { return await Commands.getSupportedRatios(this._cameraHandle); + } else if (Platform.OS === 'harmony') { + return await NativeCameraModule.getSupportedRatios(this._cameraHandle); } else { throw new Error('Ratio is not supported on iOS'); } @@ -573,7 +628,7 @@ export default class Camera extends React.Component { if (Platform.OS === 'android') { return await CameraManager.getCameraIds(this._cameraHandle); } else if (Platform.OS === 'ios') { - return await CameraNativeCameraModuleManager.getCameraIds(); // iOS does not need a camera instance + return await CameraManager.getCameraIds(); // iOS does not need a camera instance } else if (Platform.OS === 'harmony') { return new Promise(async (resolve, reject) => { try { @@ -588,9 +643,21 @@ export default class Camera extends React.Component { } } + static async checkIfVideoIsValid(path) { + if (Platform.OS === 'android') { + return await CameraManager.checkIfVideoIsValid(path); + } else if (Platform.OS === 'harmony') { + return await NativeCameraModule.checkIfVideoIsValid(path); + } else { + return true; // iOS: not implemented + } + } + getSupportedPreviewFpsRange = async (): Promise<[]> => { if (Platform.OS === 'android') { return await Commands.getSupportedPreviewFpsRange(this._cameraHandle); + } else if (Platform.OS === 'harmony') { + return await NativeCameraModule.getSupportedPreviewFpsRange(this._cameraHandle); } else { throw new Error('getSupportedPreviewFpsRange is not supported on iOS'); } @@ -920,7 +987,7 @@ export default class Camera extends React.Component { this._cameraHandle = findNodeHandle(ref); }} onMountError={this._onMountError} - onCameraReady={this._onCameraReady} + onCameraReady={this._onObjectDetected(this._onCameraReady)} onAudioInterrupted={this._onAudioInterrupted} onAudioConnected={this._onAudioConnected} onGoogleVisionBarcodesDetected={this._onObjectDetected( @@ -982,4 +1049,11 @@ export default class Camera extends React.Component { } } -export const Constants = Camera.Constants; \ No newline at end of file +export const Constants = Camera.Constants; + +export function hasTorch() { + if (Platform.OS === 'harmony') { + return NativeCameraModule.hasTorch(); + } + return CameraManager.hasTorch(); +} diff --git a/src/index.js b/src/index.js index 6a81d8b75..01f84872b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ // @flow -import RNCamera, { type Status as _CameraStatus } from './RNCamera'; +import RNCamera, { type Status as _CameraStatus, hasTorch } from './RNCamera'; import FaceDetector from './FaceDetector'; export type CameraStatus = _CameraStatus; -export { RNCamera, FaceDetector }; +export { RNCamera, FaceDetector, hasTorch }; diff --git a/types/index.d.ts b/types/index.d.ts index 41e0c5855..8aa925e8e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -72,6 +72,10 @@ type VideoCodec = Readonly<{ AppleProRes422: symbol; AppleProRes4444: symbol; }>; +type ImageType = Readonly<{ + 'jpeg': any; + 'png': any; +}>; type FaceDetectionClassifications = Readonly<{ all: any; none: any }>; type FaceDetectionLandmarks = Readonly<{ all: any; none: any }>; @@ -119,6 +123,7 @@ export interface Constants { Type: CameraType; WhiteBalance: WhiteBalance; VideoQuality: VideoQuality; + ImageType: ImageType; BarCodeType: BarCodeType; FaceDetection: { Classifications: FaceDetectionClassifications; @@ -168,6 +173,7 @@ export interface GoogleVisionBarcodesDetectedEvent { export interface RNCameraProps { children?: ReactNode | FaCC; + cameraId?: string; autoFocus?: keyof AutoFocus; autoFocusPointOfInterest?: Point; @@ -227,7 +233,7 @@ export interface RNCameraProps { onGoogleVisionBarcodesDetected?(event: GoogleVisionBarcodesDetectedEvent): void; // limiting scan area - rectOfInterest?: Point; + rectOfInterest?: RectOfInterest; // -- FACE DETECTION PROPS @@ -248,6 +254,8 @@ export interface RNCameraProps { permissionDialogMessage?: string; /** Android only */ playSoundOnCapture?: boolean; + /** Android only */ + playSoundOnRecord?: boolean; androidCameraPermissionOptions?: { title: string; @@ -285,6 +293,8 @@ interface Size { height: T; } +interface RectOfInterest extends Point,Size{} + export interface Barcode { bounds: { size: Size; @@ -425,6 +435,8 @@ interface TakePictureOptions { /** iOS only */ forceUpOrientation?: boolean; + imageType?: keyof ImageType; + path?: string; } export interface TakePictureResponse { @@ -462,6 +474,15 @@ export interface RecordResponse { codec: VideoCodec[keyof VideoCodec]; } +export interface HardwareCamera { + /** (iOS only) e.g: 'AVCaptureDeviceTypeBuiltInWideAngleCamera', 'AVCaptureDeviceTypeBuiltInUltraWideCamera' */ + deviceType?: string; + id: string; + type: number; +} + +export function hasTorch(): Promise; + export class RNCamera extends Component { static Constants: Constants; @@ -475,9 +496,12 @@ export class RNCamera extends Component { pausePreview(): void; resumePreview(): void; getAvailablePictureSizes(): Promise; + getCameraIdsAsync(): Promise; /** Android only */ getSupportedRatiosAsync(): Promise; + getSupportedPreviewFpsRange: Promise; + static checkIfVideoIsValid: Promise; /** iOS only */ isRecording(): Promise;