diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..48439ed68073b99be118d374d1ebd43d04d4c4eb --- /dev/null +++ b/.clang-format @@ -0,0 +1,64 @@ +Language: Cpp +# BasedOnStyle: LLVM +ColumnLimit: 120 +SortIncludes: CaseSensitive +TabWidth: 4 +IndentWidth: 4 +UseTab: Never +AccessModifierOffset: -4 +ContinuationIndentWidth: 4 +IndentCaseBlocks: false +IndentCaseLabels: false +IndentGotoLabels: true +IndentWrappedFunctionNames: false +SortUsingDeclarations: false +NamespaceIndentation: None +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +AlignTrailingComments: true +AlignAfterOpenBracket: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +InsertBraces: false +IndentExternBlock: NoIndent +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false +ReflowComments: true +MaxEmptyLinesToKeep: 2 \ No newline at end of file diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..1c93f413449d588b8a4e95d3853384f7dc0aacd4 --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.multishortvideo", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..d0af5758dbb3ff545161358591dea214cea4cd97 --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "MultiShortVideo" + } + ] +} diff --git a/AppScope/resources/base/media/background.png b/AppScope/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/AppScope/resources/base/media/background.png differ diff --git a/AppScope/resources/base/media/foreground.png b/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/AppScope/resources/base/media/foreground.png differ diff --git a/AppScope/resources/base/media/layered_image.json b/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/AppScope/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 3e8a76400563db6b7a564e36b3287385bcccdc71..b9644f1e96dbc845f0474e2ac91d91f9adc351e4 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,37 @@ -# MutilDeviceCamera +# 基于相机开放能力和一多能力实现多设备相机 -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +本示例展示了如何使用HarmonyOS提供的相机开放能力和一多能力,主要包括使用Camera Kit预览拍照、photoAccessHelper进行保存图片和一多断点实现多设备页面及功能差异。本示例主要用于如何在多设备(手机、大折叠、阔折叠、三折叠、平板)上实现正常状态和折叠状态切换时的预览旋转、拍照旋转、切换镜头等功能。 +## 效果展示 -#### 软件架构 -软件架构说明 +## 使用说明 +应用可以点击底部按钮拍摄照片,同时可以实现旋转相机角度、切换前后摄像头、切换折叠状态完成拍摄的操作,拍摄完成后可以预览照片。 -#### 安装教程 +## 工程目录 -1. xxxx -2. xxxx -3. xxxx +``` +├──entry/src/main/ets/ +│ ├──entryability +│ │ └──EntryAbility.ets +│ ├──entrybackupability +│ │ └──EntryBackupAbility.ets +│ ├──pages +│ │ └──Index.ets // 主页 +│ ├──utils +│ │ ├──BreakpointType.ets // 一多断点工具类 +│ │ └──CameraUtil.ets // 相机工具类 +│ └──views +│ └──CommonView.ets // 公共视图类 +└──entry/src/main/resource // 应用静态资源目录 +``` -#### 使用说明 +## 实现思路 -1. xxxx -2. xxxx -3. xxxx +1. 使用Camera Kit预览拍照。 +2. 使用photoAccessHelper保存图片。 +3. 使用一多断点能力实现多设备页面和功能差异。 -#### 参与贡献 +## 相关权限 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +1. 相机权限:ohos.permission.CAMERA,用于相机开发场景。 +2. 媒体库权限: ohos.permission.READ_IMAGEVIDEO,用于读取图库文件;ohos.permission.WRITE_IMAGEVIDEO:用于保存文件至图库。 diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2844c740f45df0bfbe0cc3e3472327be382fc1db --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,41 @@ +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.2(14)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/code-linter.json5 b/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..073990fa45394e1f8e85d85418ee60a8953f9b99 --- /dev/null +++ b/code-linter.json5 @@ -0,0 +1,32 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4d611879c7913fb0610c686e2399258ab3a6dad1 --- /dev/null +++ b/entry/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/entry/hvigorfile.ts b/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3 --- /dev/null +++ b/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/entry/obfuscation-rules.txt b/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/entry/oh-package.json5 b/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..248c3b7541a589682a250f86a6d3ecf7414d2d6a --- /dev/null +++ b/entry/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {} +} + diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..4f91d3c225c10636ce1cab64c8755fc61663d624 --- /dev/null +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { display, window } from '@kit.ArkUI'; +import { BusinessError, deviceInfo } from '@kit.BasicServicesKit'; +import { CameraUtil } from '../utils/CameraUtil'; +import { camera } from '@kit.CameraKit'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + windowData?: window.Window; + uiContext?: UIContext; + cameraUtil?: CameraUtil = CameraUtil.getInstance(); + onWindowSizeChange: (windowSize: window.Size) => void = (windowSize: window.Size) => { + let widthBp: WidthBreakpoint = this.uiContext!.getWindowWidthBreakpoint(); + AppStorage.setOrCreate('widthBp', widthBp); + let heightBp: HeightBreakpoint = this.uiContext!.getWindowHeightBreakpoint(); + AppStorage.setOrCreate('heightBp', heightBp); + let displayOrientation: display.Orientation = display.getDefaultDisplaySync().orientation; + AppStorage.setOrCreate('displayOrientation', displayOrientation); + this.setOrientation(this.uiContext!.px2vp(windowSize.width), this.uiContext!.px2vp(windowSize.height)); + let isFront: boolean | undefined = AppStorage.get('isFront'); + let surfaceId: string | undefined = AppStorage.get('surfaceId'); + if (widthBp === WidthBreakpoint.WIDTH_MD && heightBp === HeightBreakpoint.HEIGHT_MD && + deviceInfo.productSeries === 'GRL') { + this.cameraUtil?.cameraShooting(surfaceId!, this.context!, camera.CameraPosition.CAMERA_POSITION_BACK); + return; + } + if (isFront) { + this.cameraUtil?.cameraShooting(surfaceId!, this.context!, camera.CameraPosition.CAMERA_POSITION_FRONT); + } else { + this.cameraUtil?.cameraShooting(surfaceId!, this.context!, camera.CameraPosition.CAMERA_POSITION_BACK); + } + } + + setOrientation(width: number, height: number): void { + if (Math.min(width, height) >= 600) { + this.windowData?.setPreferredOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED); + } else { + this.windowData?.setPreferredOrientation(window.Orientation.PORTRAIT); + } + } + + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + windowStage.getMainWindow().then((data: window.Window) => { + this.windowData = data; + data.setWindowLayoutFullScreen(true); + data.setWindowSystemBarEnable([]); + this.uiContext = data.getUIContext(); + let widthBp: WidthBreakpoint = this.uiContext.getWindowWidthBreakpoint(); + let heightBp: HeightBreakpoint = this.uiContext.getWindowHeightBreakpoint(); + AppStorage.setOrCreate('widthBp', widthBp); + AppStorage.setOrCreate('heightBp', heightBp); + let displayOrientation: display.Orientation = display.getDefaultDisplaySync().orientation; + AppStorage.setOrCreate('displayOrientation', displayOrientation); + data.on('windowSizeChange', this.onWindowSizeChange); + let rect: window.Rect = data.getWindowProperties().windowRect; + this.setOrientation(this.uiContext.px2vp(rect.width), this.uiContext.px2vp(rect.height)); + AppStorage.setOrCreate('isBackground', false); + }).catch((err: BusinessError) => { + hilog.error(0x0000, 'testTag', `Error occured, error code: ${err.code}, error message: ${err.message}`); + }) + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + if (AppStorage.get('isBackground')) { + this.cameraUtil?.fromBack(); + AppStorage.setOrCreate('isBackground', false); + } + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + this.cameraUtil?.releaseCamera();`` + AppStorage.setOrCreate('isBackground', true); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..0a97e21bd7a15599af76a806695860ff1eb0ebfe --- /dev/null +++ b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..0c483c63e8866809fa61e7d99f8135a574aefcf5 --- /dev/null +++ b/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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, window } from '@kit.ArkUI'; +import { camera } from '@kit.CameraKit'; +import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'; +import { BusinessError, deviceInfo } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { ChooseMusic, SettingButton, ShotArea, ShotAreaSm } from '../views/CommonView'; +import { CameraUtil } from '../utils/CameraUtil'; + +@Entry +@Component +struct Index { + @StorageLink('widthBp') widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_SM; + @StorageLink('heightBp') heightBp: HeightBreakpoint = HeightBreakpoint.HEIGHT_SM; + @StorageLink('displayOrientation') displayOrientation: display.Orientation = display.Orientation.PORTRAIT; + @StorageLink('photoUri') photoUri: string | Resource | PixelMap = ''; + @StorageLink('surfaceId') surfaceId: string = ''; + @StorageLink('rotation') rotation: number = 0; + @StorageLink('isFront') isFront: boolean = false; + context?: Context = this.getUIContext().getHostContext(); + xComponentController: XComponentController = new XComponentController(); + cameraUtil?: CameraUtil = CameraUtil.getInstance(); + permissions: Array = [ + 'ohos.permission.CAMERA', + 'ohos.permission.READ_IMAGEVIDEO', + 'ohos.permission.WRITE_IMAGEVIDEO' + ]; + + aboutToAppear(): void { + abilityAccessCtrl.createAtManager().requestPermissionsFromUser(this.context, this.permissions).then(() => { + setTimeout(async () => { + this.cameraUtil?.cameraShooting(this.surfaceId, this.context!, camera.CameraPosition.CAMERA_POSITION_BACK); + }, 200); + }) + } + + aboutToDisappear(): void { + try { + window.getLastWindow(this.context, (err: BusinessError, data) => { + if (err) { + hilog.error(0x0000, 'testTag', `Failed to obtain the top window. Cause code: ${err.code}, message: ${err.message}`); + return; + } + data.off('windowSizeChange'); + }); + } catch (err) { + hilog.error(0x0000, 'testTag', `Failed to obtain the top window. Cause code: ${err.code}, message: ${err.message}`); + } + } + + build() { + Navigation() { + FolderStack({ upperItems: ['upper'] }) { + // 相机组件 + Column() { + XComponent({ + type: XComponentType.SURFACE, + controller: this.xComponentController + }) {} + .onLoad(async () => { + this.surfaceId = this.xComponentController.getXComponentSurfaceId(); + this.xComponentController.setXComponentSurfaceRotation({ lock: true }); + }) + .onDestroy(() => { + this.cameraUtil?.releaseCamera(); + }) + .width(this.widthBp === WidthBreakpoint.WIDTH_SM || (this.widthBp === WidthBreakpoint.WIDTH_MD && + this.heightBp === HeightBreakpoint.HEIGHT_LG)? '100%' : '') + .height(this.widthBp !== WidthBreakpoint.WIDTH_SM && (this.widthBp !== WidthBreakpoint.WIDTH_MD && + this.heightBp !== HeightBreakpoint.HEIGHT_LG)? '100%' : '') + .aspectRatio(this.getXComponentAspectRatio()) + } + .width('100%') + .layoutWeight(1) + .id('upper') + .alignItems(this.widthBp === WidthBreakpoint.WIDTH_MD && this.heightBp === HeightBreakpoint.HEIGHT_MD ? + HorizontalAlign.Start : HorizontalAlign.Center) + .margin({ bottom: deviceInfo.productSeries !== 'VDE' && this.widthBp === WidthBreakpoint.WIDTH_SM ? 156 : 0 }) + + // 拍照组件 + Stack() { + // sm断点对应选择音乐区 + Row() { + Image($r('app.media.icon_close')) + .width(this.heightBp === HeightBreakpoint.HEIGHT_MD ? 28 : 40) + .height(this.heightBp === HeightBreakpoint.HEIGHT_MD ? 28 : 40) + .position({ x: 16, y: 0 }) + + ChooseMusic() + } + .width('100%') + .height(this.heightBp === HeightBreakpoint.HEIGHT_MD ? 60 : 72) + .padding({ + top: 16, + bottom: 16 + }) + .position({ x: 0, y: 0 }) + .justifyContent(FlexAlign.Center) + .visibility(this.widthBp === WidthBreakpoint.WIDTH_SM ? Visibility.Visible : Visibility.None) + + // sm断点对应设置区 + Column() { + SettingButton({ + imageButton: $r('app.media.icon_lighting'), + text: '闪光灯' + }) + SettingButton({ + imageButton: $r('app.media.icon_filters'), + text: '滤镜' + }) + SettingButton({ + imageButton: $r('app.media.icon_setting'), + text: '设置' + }) + } + .width(this.heightBp === HeightBreakpoint.HEIGHT_MD ? 30 : 48) + .height('100%') + .margin({ right: 16 }) + .padding({ top: this.heightBp === HeightBreakpoint.HEIGHT_MD ? 16 : 80}) + .visibility(this.widthBp === WidthBreakpoint.WIDTH_SM ? Visibility.Visible : Visibility.None) + + // sm断点对应拍照区 + Column() { + ShotAreaSm() + } + .visibility(this.widthBp === WidthBreakpoint.WIDTH_SM ? Visibility.Visible : Visibility.None) + .height(this.heightBp === HeightBreakpoint.HEIGHT_MD ? 96 : 132) + .width('100%') + .margin({ bottom: this.heightBp === HeightBreakpoint.HEIGHT_MD ? 16 : 84 }) + + // md、lg断点的设置区 + Column() { + Column() { + Image($r('app.media.icon_close')) + .width(40) + .height(40) + } + .width(40) + .layoutWeight(1) + .padding({ top: this.widthBp === WidthBreakpoint.WIDTH_MD ? 24 : 32 }) + + Column() { + Blank() + SettingButton({ + imageButton: $r('app.media.icon_lighting'), + text: '闪光灯' + }) + SettingButton({ + imageButton: $r('app.media.icon_filters'), + text: '滤镜' + }) + SettingButton({ + imageButton: $r('app.media.icon_setting'), + text: '设置' + }) + Blank() + } + .layoutWeight(1) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Start) + .width(48) + + Column() { + ChooseMusic() + } + .layoutWeight(1) + .width('100%') + .padding({ top: this.heightBp === HeightBreakpoint.HEIGHT_MD ? 72 : 60 }) + } + .width(this.widthBp === WidthBreakpoint.WIDTH_MD ? 144 : 152 ) + .height('100%') + .justifyContent(FlexAlign.Start) + .padding({ left: this.heightBp === HeightBreakpoint.HEIGHT_MD ? 24 : 32 }) + .position({ x: 0, y: 0 }) + .alignItems(HorizontalAlign.Start) + .visibility(this.widthBp === WidthBreakpoint.WIDTH_MD || this.widthBp === WidthBreakpoint.WIDTH_LG ? + Visibility.Visible : Visibility.None) + + // md、lg断点的拍照区 + Column() { + ShotArea() + } + .width(this.widthBp === WidthBreakpoint.WIDTH_MD ? 92 : 132) + .height('100%') + .justifyContent(FlexAlign.Center) + .padding({ right: this.widthBp === WidthBreakpoint.WIDTH_MD ? 16 : 56 }) + .visibility(this.widthBp === WidthBreakpoint.WIDTH_MD || this.widthBp === WidthBreakpoint.WIDTH_LG ? + Visibility.Visible : Visibility.None) + } + .height('100%') + .width('100%') + .alignContent(Alignment.BottomEnd) + } + .height('100%') + .width('100%') + .alignContent(this.widthBp === WidthBreakpoint.WIDTH_MD ? Alignment.Start : Alignment.Center) + } + .height('100%') + .width('100%') + .mode(NavigationMode.Stack) + .hideTitleBar(true) + .hideToolBar(true) + .backgroundColor(Color.Black) + } + + getXComponentAspectRatio(): number { + if (this.widthBp === WidthBreakpoint.WIDTH_SM) { + if (this.heightBp === HeightBreakpoint.HEIGHT_LG) { + return 0.75; + } + return 1; + } + if (this.widthBp === WidthBreakpoint.WIDTH_MD) { + if (this.heightBp === HeightBreakpoint.HEIGHT_MD && (this.displayOrientation === display.Orientation.PORTRAIT || + this.displayOrientation === display.Orientation.PORTRAIT_INVERTED)) { + return 0.75; + } + return 1.33; + } + return 0.75; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/BreakpointType.ets b/entry/src/main/ets/utils/BreakpointType.ets new file mode 100644 index 0000000000000000000000000000000000000000..bd2aab03a76db57a6318911db698bdbc6ad653ea --- /dev/null +++ b/entry/src/main/ets/utils/BreakpointType.ets @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 class BreakpointType { + sm: T; + md: T; + lg: T; + + constructor(sm: T, md: T, lg: T) { + this.sm = sm; + this.md = md; + this.lg = lg; + } + + getValue(widthBp: WidthBreakpoint): T { + if (widthBp === WidthBreakpoint.WIDTH_SM) { + return this.sm; + } + if (widthBp === WidthBreakpoint.WIDTH_MD) { + return this.md; + } + if (widthBp === WidthBreakpoint.WIDTH_LG || widthBp === WidthBreakpoint.WIDTH_XL) { + return this.lg; + } + return this.sm; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/CameraUtil.ets b/entry/src/main/ets/utils/CameraUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..6903ac3929364c225b8b126ee86cad3635da8089 --- /dev/null +++ b/entry/src/main/ets/utils/CameraUtil.ets @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 "@kit.CameraKit" +import { common, Context } from "@kit.AbilityKit"; +import { hilog } from "@kit.PerformanceAnalysisKit"; +import { BusinessError, deviceInfo } from "@kit.BasicServicesKit"; +import { photoAccessHelper } from "@kit.MediaLibraryKit"; +import { colorSpaceManager } from "@kit.ArkGraphics2D"; +import { sensor } from "@kit.SensorServiceKit"; +import { display } from "@kit.ArkUI"; + +export class CameraUtil { + previewOutput?: camera.PreviewOutput; + cameraInput?: camera.CameraInput; + photoSession?: camera.PhotoSession; + photoOutput?: camera.PhotoOutput; + uri: string = ''; + currentContext?: Context; + surfaceId: string = ''; + + static getInstance(): CameraUtil | undefined { + if (!AppStorage.get('cameraUtil')) { + AppStorage.setOrCreate('cameraUtil', new CameraUtil()); + } else { + hilog.error(0x0000, 'testLog', `AppStorage does not have cameraUtil.`); + } + return AppStorage.get('cameraUtil'); + } + + async cameraShooting(surfaceId: string, context: Context, cameraPosition: camera.CameraPosition): Promise { + this.surfaceId = surfaceId; + this.currentContext = context; + this.releaseCamera(); + let cameraManager: camera.CameraManager = camera.getCameraManager(context); + if (!cameraManager) { + return; + } + // Obtaining the camera list. + let cameraArray: camera.CameraDevice[] = cameraManager.getSupportedCameras(); + if (cameraArray.length <= 0) { + return; + } + let cameraIndex: number = this.getCamera(cameraArray, cameraPosition); + this.cameraInput = cameraManager.createCameraInput(cameraArray[cameraIndex]); + // console.info(`testLog ---> 选择相机:${JSON.stringify(cameraArray[cameraIndex])}`); + // Open the camera. + await this.cameraInput.open(); + let cameraRotation: number = cameraArray[cameraIndex].cameraOrientation; + // console.info(`testLog ---> 镜头安装角度: ${cameraRotation}`); + let sceneModes: camera.SceneMode[] = cameraManager.getSupportedSceneModes(cameraArray[cameraIndex]); + let cameraOutputCap: camera.CameraOutputCapability = + cameraManager.getSupportedOutputCapability(cameraArray[cameraIndex], camera.SceneMode.NORMAL_PHOTO); + let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; + if (!isSupportPhotoMode) { + return; + } + if (!cameraOutputCap) { + return; + } + + let previewProfileArray: camera.Profile[] = cameraOutputCap.previewProfiles; + let previewProfile: camera.Profile = this.getProfile(previewProfileArray); + let photoProfileArray: camera.Profile[] = cameraOutputCap.photoProfiles; + let photoProfile: camera.Profile = this.getProfile(photoProfileArray); + this.previewOutput = cameraManager.createPreviewOutput(previewProfile, surfaceId); + if (this.previewOutput === undefined) { + return; + } + let deviceRotation: number = display.getDefaultDisplaySync().rotation; + console.info(`testLog ---> 当前设备旋转方向 ${deviceRotation}`); + this.photoOutput = cameraManager.createPhotoOutput(photoProfile); + if (this.photoOutput === undefined) { + return; + } + // Save photo. + this.setPhotoOutputCb(); + + this.photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO); + if (this.photoSession === undefined) { + return; + } + this.photoSession.beginConfig(); + this.photoSession.addInput(this.cameraInput); + this.photoSession.addOutput(this.previewOutput); + this.photoSession.addOutput(this.photoOutput); + this.photoSession.setColorSpace(colorSpaceManager.ColorSpace.DISPLAY_P3); + await this.photoSession.commitConfig(); + await this.photoSession.start(); + return; + } + + getCamera(cameras: Array, cameraPosition: camera.CameraPosition): number { + let widthBp: WidthBreakpoint | undefined = AppStorage.get('widthBp'); + let heightBp: HeightBreakpoint | undefined = AppStorage.get('heightBp'); + if (widthBp === WidthBreakpoint.WIDTH_MD && heightBp === HeightBreakpoint.HEIGHT_MD && + deviceInfo.productSeries === 'GRL' ? Visibility.Hidden : Visibility.Visible) { + cameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK; + } + for (let i: number = 0; i < cameras.length; ++i) { + if (cameras[i].cameraPosition === cameraPosition) { + if (cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK) { + AppStorage.setOrCreate('isFront', false); + } + if (cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT) { + AppStorage.setOrCreate('isFront', true); + } + return i; + } + } + hilog.error(0x0000, 'testLog', `testLog ---> Failed to find the camera with the corresponding position.`); + if (cameras[0].cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK) { + AppStorage.setOrCreate('isFront', false); + } + if (cameras[0].cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT) { + AppStorage.setOrCreate('isFront', true); + } + return 0; + } + + getProfile(profileArray: camera.Profile[]): camera.Profile { + // 根据断点返回profile + let widthBp: WidthBreakpoint | undefined = AppStorage.get('widthBp'); + let heightBp: HeightBreakpoint | undefined = AppStorage.get('heightBp'); + let orientation: display.Orientation | undefined = AppStorage.get('displayOrientation'); + let aspectRatio: number = 4 / 3; + if (widthBp === WidthBreakpoint.WIDTH_SM && heightBp !== HeightBreakpoint.HEIGHT_LG) { + aspectRatio = 1; + } + let maxWidth: number = 0; + let maxHeight: number = 0; + for (let i: number = 0; i < profileArray.length; ++i) { + if (profileArray[i].size.width / profileArray[i].size.height === aspectRatio && + profileArray[i].size.width > maxWidth) { + maxWidth = profileArray[i].size.width; + maxHeight = profileArray[i].size.height; + } + } + // console.info(`testLog ---> 当前最大值 ${maxWidth}, ${maxHeight}`); + let resProfile: undefined | camera.Profile = profileArray.find((profile: camera.Profile) => { + return profile.size.width === maxWidth && profile.size.height === maxHeight; + }) + if (resProfile === undefined) { + hilog.error(0x0000, 'testLog', `Failed to get the profile.`); + return profileArray[0]; + } + return resProfile; + } + + setPhotoOutputCb(): void { + this.photoOutput!.on('photoAssetAvailable', async (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): + Promise => { + let accessHelper: photoAccessHelper.PhotoAccessHelper = photoAccessHelper.getPhotoAccessHelper(this.currentContext); + let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = + new photoAccessHelper.MediaAssetChangeRequest(photoAsset); + assetChangeRequest.saveCameraPhoto(); + await accessHelper.applyChanges(assetChangeRequest); + this.uri = photoAsset.uri; + AppStorage.setOrCreate('photoUri', await photoAsset.getThumbnail()); + }) + } + + capture(): void { + let rotation: number = -1; + // Obtain the angle of the gravity sensor during shooting and set the shooting rotation angle. + sensor.once(sensor.SensorId.GRAVITY, async (data: sensor.GravityResponse) => { + let degree: number = this.getCalDegree(data.x, data.y, data.z); + console.info(`testLog ---> 重力传感器角度 ${degree}`); + let isFront: boolean | undefined = AppStorage.get('isFront'); + if (degree >= 0 && (degree <= 30 || degree >= 330)) { + rotation = camera.ImageRotation.ROTATION_0; + } else if (degree >= 60 && degree <= 120) { + if (isFront) { + // Use ROTATION_270 when degree range is [60, 120] for front camera. + rotation = camera.ImageRotation.ROTATION_270; + } else { + // Use ROTATION_90 when degree range is [60, 120] for back camera. + rotation = camera.ImageRotation.ROTATION_90; + } + } else if (degree >= 150 && degree <= 210) { + // Use ROTATION_180 when degree range is [150, 210]. + rotation = camera.ImageRotation.ROTATION_180; + } else if (degree >= 240 && degree <= 300) { + if (isFront) { + // Use ROTATION_90 when degree range is [240, 300] for front camera. + rotation = camera.ImageRotation.ROTATION_90; + } else { + // Use ROTATION_270 when degree range is [240, 300] for back camera. + rotation = camera.ImageRotation.ROTATION_270; + } + }; + console.info(`testLog ---> rotation ${rotation}`); + + let setting: camera.PhotoCaptureSetting = { + quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, + rotation: rotation, + mirror: isFront + } + this.photoOutput?.capture(setting); + }) + } + + async releaseCamera(): Promise { + if (this.photoSession) { + this.photoSession.stop(); + } + if (this.cameraInput) { + this.cameraInput.close(); + } + if (this.previewOutput) { + this.previewOutput.release(); + } + if (this.photoSession) { + this.photoSession.stop(); + } + if (this.photoOutput) { + this.photoOutput.release(); + } + } + + async fromBack(): Promise { + let isFront: boolean | undefined = AppStorage.get('isFront'); + if (isFront) { + this.cameraShooting(this.surfaceId, this.currentContext!, camera.CameraPosition.CAMERA_POSITION_FRONT); + return; + } + this.cameraShooting(this.surfaceId, this.currentContext!, camera.CameraPosition.CAMERA_POSITION_BACK); + } + + previewPhoto(): void { + let photoContext: common.UIAbilityContext = this.currentContext as common.UIAbilityContext; + // Start the gallery application. + photoContext.startAbility({ + parameters: { uri: this.uri }, + action: 'ohos.want.action.viewData', + bundleName: 'com.huawei.hmos.photos', + abilityName: 'com.huawei.hmos.photos.MainAbility' + }).then(() => { + hilog.info(0x0000, 'testLog', `Start ability successed.`); + }).catch((err: BusinessError) => { + hilog.error(0x0000, 'testLog', `Strat ability failed with err: ${err.code}, ${err.message}`); + }) + } + + getCalDegree(x: number, y: number, z: number): number { + let degree: number = -1; + // three is Effective Delta Angle Threshold Coefficient. + if ((x * x + y * y) * 3 < z * z) { + return degree; + } + degree = 90 - (Number)(Math.round(Math.atan2(y, -x) / Math.PI * 180)); + return degree >= 0 ? degree % 360 : degree % 360 + 360; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/views/CommonView.ets b/entry/src/main/ets/views/CommonView.ets new file mode 100644 index 0000000000000000000000000000000000000000..4efb4a9ef4a792544cad47b0883248d17ae080e5 --- /dev/null +++ b/entry/src/main/ets/views/CommonView.ets @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 { BreakpointType } from "../utils/BreakpointType"; +import { camera } from "@kit.CameraKit"; +import { CameraUtil } from "../utils/CameraUtil"; +import { curves } from "@kit.ArkUI"; + +@Component +export struct ChooseMusic { + @StorageLink('heightBp') heightBp: HeightBreakpoint = HeightBreakpoint.HEIGHT_SM; + + build() { + Row() { + Image($r('app.media.icon_music')) + .width(this.heightBp === HeightBreakpoint.HEIGHT_MD ? 12 : 16) + .height(this.heightBp === HeightBreakpoint.HEIGHT_MD ? 12 : 16) + .margin({ right: 10 }) + Text('选择音乐') + .fontSize(16) + .lineHeight(21) + .fontColor(Color.White) + } + .width(120) + .height(40) + .backgroundColor('#1AFFFFFF') + .justifyContent(FlexAlign.Center) + .borderRadius(8) + } +} + +@Component +export struct SettingButton { + @StorageLink('heightBp') heightBp: HeightBreakpoint = HeightBreakpoint.HEIGHT_SM; + @StorageLink('widthBp') widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_SM; + imageButton?: Resource; + text: string = ''; + + build() { + Column() { + Image(this.imageButton) + .height(new BreakpointType((this.heightBp === HeightBreakpoint.HEIGHT_LG ? 48 : 30), 48, 48).getValue(this.widthBp)) + .width(new BreakpointType((this.heightBp === HeightBreakpoint.HEIGHT_LG ? 48 : 30), 48, 48).getValue(this.widthBp)) + .margin({ bottom: 2 }) + Text(this.text) + .lineHeight(new BreakpointType((this.heightBp === HeightBreakpoint.HEIGHT_LG ? 16 : 14), 16, 16).getValue(this.widthBp)) + .fontSize(new BreakpointType((this.heightBp === HeightBreakpoint.HEIGHT_LG ? 12 : 10), 12, 12).getValue(this.widthBp)) + .fontColor(Color.White) + } + .width('100%') + .margin({ + bottom: new BreakpointType((this.heightBp === HeightBreakpoint.HEIGHT_LG ? 16 : 8), 16, 16).getValue(this.widthBp)}) + } +} + +@Component +export struct ShotAreaSm { + @StorageLink('heightBp') heightBp: HeightBreakpoint = HeightBreakpoint.HEIGHT_SM; + @StorageLink('photoUri') photoUri: string | Resource | PixelMap = ''; + @StorageLink('surfaceId') surfaceId: string = ''; + cameraUtil?: CameraUtil = CameraUtil.getInstance(); + context?: Context = this.getUIContext().getHostContext(); + + build() { + Column() { + // 照片、视频 + Column() { + Row() { + Column() { + Text('照片') + .fontSize(14) + .lineHeight(20) + .fontColor(Color.White) + .fontWeight(700) + .margin({ bottom: 6 }) + Image($r('app.media.icon_red_circle')) + .height(6) + .width(6) + } + .height('100%') + .width(28) + + Blank() + + Text('视频') + .fontSize(14) + .lineHeight(20) + .fontColor(Color.White) + } + .height(32) + .width(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 80 : 72) + .alignItems(VerticalAlign.Top) + } + .width(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 80 : 72) + .height(32) + .margin({ bottom: this.heightBp === HeightBreakpoint.HEIGHT_LG ? 24 : 16 }) + + // 拍摄按钮 + Row() { + Image(this.photoUri) + .width(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 44 : 28) + .height(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 44 : 28) + .borderWidth(this.photoUri === '' ? 0 : 1) + .borderColor(Color.White) + .borderRadius(44) + .animation({ curve: curves.springMotion() }) + .onClick(() => { + if (this.photoUri !== '') { + this.cameraUtil?.previewPhoto(); + } + }) + Image($r('app.media.icon_shoot')) + .width(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 76 : 46) + .height(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 76 : 46) + .onClick(() => { + this.cameraUtil?.capture(); + }) + Image($r('app.media.icon_flip')) + .width(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 44 : 28) + .height(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 44 : 28) + .onClick(() => { + let isFront: boolean | undefined = AppStorage.get('isFront'); + if (isFront) { + this.cameraUtil?.cameraShooting(this.surfaceId, this.context!, camera.CameraPosition.CAMERA_POSITION_BACK); + return; + } + this.cameraUtil?.cameraShooting(this.surfaceId, this.context!, camera.CameraPosition.CAMERA_POSITION_FRONT); + }) + } + .width(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 256 : 158) + .height(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 76 : 46) + .justifyContent(FlexAlign.SpaceBetween) + } + .width('100%') + .height(this.heightBp === HeightBreakpoint.HEIGHT_LG ? 132 : 94) + .margin({ bottom: this.heightBp === HeightBreakpoint.HEIGHT_LG ? 84 : 16 }) + } +} + +@Component +export struct ShotArea { + @StorageLink('heightBp') heightBp: HeightBreakpoint = HeightBreakpoint.HEIGHT_SM; + @StorageLink('widthBp') widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_SM; + @StorageLink('photoUri') photoUri: string | Resource | PixelMap = ''; + @StorageLink('surfaceId') surfaceId: string = ''; + cameraUtil?: CameraUtil = CameraUtil.getInstance(); + context?: Context = this.getUIContext().getHostContext(); + + build() { + Stack() { + Column() { + Image($r('app.media.icon_flip')) + .width(44) + .height(44) + .onClick(() => { + let isFront: boolean | undefined = AppStorage.get('isFront'); + if (isFront) { + this.cameraUtil?.cameraShooting(this.surfaceId, this.context!, camera.CameraPosition.CAMERA_POSITION_BACK); + return; + } + this.cameraUtil?.cameraShooting(this.surfaceId, this.context!, camera.CameraPosition.CAMERA_POSITION_FRONT); + }) + Image($r('app.media.icon_shoot')) + .width(76) + .height(76) + .onClick(() => { + this.cameraUtil?.capture(); + }) + Image(this.photoUri) + .width(44) + .height(44) + .borderWidth(this.photoUri === '' ? 0 : 1) + .borderColor(Color.White) + .borderRadius(22) + .animation({ curve: curves.springMotion() }) + .onClick(() => { + if (this.photoUri !== '') { + this.cameraUtil?.previewPhoto(); + } + }) + } + .height(288) + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + + Column() { + Row() { + Text('照片') + .fontSize(14) + .fontColor(Color.White) + .fontWeight(700) + Blank() + Image($r('app.media.icon_red_circle')) + .height(6) + .width(6) + } + .height(20) + .width(40) + .margin({ bottom: 16 }) + + Text('视频') + .fontSize(14) + .fontColor('#80FFFFFF') + .width(40) + } + .width('100%') + .height(56) + .alignItems(HorizontalAlign.Center) + .position({ x: 0, y: 416 }) + } + .height(472) + .width('100%') + .alignContent(Alignment.Center) + } +} \ No newline at end of file diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..fb2d4f071a0081b1d8ae1277bcf299e166b5c17d --- /dev/null +++ b/entry/src/main/module.json5 @@ -0,0 +1,91 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "supportWindowMode": ["fullscreen", "floating"], + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.CAMERA", + "reason": "$string:reason_camera", + "usedScene": { + "abilities": [ + "EntryAbility" + ] + } + }, + { + "name": "ohos.permission.MICROPHONE", + "reason": "$string:reason_microphone", + "usedScene": { + "abilities": [ + "EntryAbility" + ] + } + }, + { + "name": "ohos.permission.WRITE_IMAGEVIDEO", + "reason": "$string:reason_write_image_video", + "usedScene": { + "abilities": [ + "EntryAbility" + ] + } + }, + { + "name": "ohos.permission.READ_IMAGEVIDEO", + "reason": "$string:reason_read_image_video", + "usedScene": { + "abilities": [ + "EntryAbility" + ] + } + } + ] + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/float.json b/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..33ea22304f9b1485b5f22d811023701b5d4e35b6 --- /dev/null +++ b/entry/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..e28f700d4aadf063c9d8d4fe58def3b8e13f5d2d --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,32 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "Multi-device camera application." + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "多设备相机" + }, + { + "name": "reason_camera", + "value": "To access the system camera" + }, + { + "name": "reason_microphone", + "value": "To access the microphone" + }, + { + "name": "reason_write_image_video", + "value": "To write the album" + }, + { + "name": "reason_read_image_video", + "value": "To read the album" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/1x.png b/entry/src/main/resources/base/media/1x.png new file mode 100644 index 0000000000000000000000000000000000000000..3fc8a051a6fd8f54d3a8c20f8d45ebe3b2fc94ba Binary files /dev/null and b/entry/src/main/resources/base/media/1x.png differ diff --git a/entry/src/main/resources/base/media/background.png b/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/entry/src/main/resources/base/media/background.png differ diff --git a/entry/src/main/resources/base/media/foreground.png b/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/entry/src/main/resources/base/media/foreground.png differ diff --git a/entry/src/main/resources/base/media/icon_close.png b/entry/src/main/resources/base/media/icon_close.png new file mode 100644 index 0000000000000000000000000000000000000000..adb796b355341e8282fd8b107ca0978cef86a120 Binary files /dev/null and b/entry/src/main/resources/base/media/icon_close.png differ diff --git a/entry/src/main/resources/base/media/icon_filters.png b/entry/src/main/resources/base/media/icon_filters.png new file mode 100644 index 0000000000000000000000000000000000000000..79e0d2372b2ed4f7b365fbb32316f9a60839a025 Binary files /dev/null and b/entry/src/main/resources/base/media/icon_filters.png differ diff --git a/entry/src/main/resources/base/media/icon_flip.png b/entry/src/main/resources/base/media/icon_flip.png new file mode 100644 index 0000000000000000000000000000000000000000..addf340daf016e9c3816ed1fc15a70cacf9e20c0 Binary files /dev/null and b/entry/src/main/resources/base/media/icon_flip.png differ diff --git a/entry/src/main/resources/base/media/icon_lighting.png b/entry/src/main/resources/base/media/icon_lighting.png new file mode 100644 index 0000000000000000000000000000000000000000..b523ebdf7d2e4a728f4f866ee0e88a30f5785dad Binary files /dev/null and b/entry/src/main/resources/base/media/icon_lighting.png differ diff --git a/entry/src/main/resources/base/media/icon_music.png b/entry/src/main/resources/base/media/icon_music.png new file mode 100644 index 0000000000000000000000000000000000000000..98202621bf34745a28efb53319e9d5f9fbe8f984 Binary files /dev/null and b/entry/src/main/resources/base/media/icon_music.png differ diff --git a/entry/src/main/resources/base/media/icon_red_circle.png b/entry/src/main/resources/base/media/icon_red_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..09e304c892373103a7f582231baaa741263bc8d4 Binary files /dev/null and b/entry/src/main/resources/base/media/icon_red_circle.png differ diff --git a/entry/src/main/resources/base/media/icon_setting.png b/entry/src/main/resources/base/media/icon_setting.png new file mode 100644 index 0000000000000000000000000000000000000000..0cb1c041c926f5ff7543ff281c5fa3d02a09b2a5 Binary files /dev/null and b/entry/src/main/resources/base/media/icon_setting.png differ diff --git a/entry/src/main/resources/base/media/icon_shoot.png b/entry/src/main/resources/base/media/icon_shoot.png new file mode 100644 index 0000000000000000000000000000000000000000..56581426afa95fc51429effd88bd66a888f06caa Binary files /dev/null and b/entry/src/main/resources/base/media/icon_shoot.png differ diff --git a/entry/src/main/resources/base/media/layered_image.json b/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/startIcon.png b/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/entry/src/main/resources/base/media/startIcon.png differ diff --git a/entry/src/main/resources/base/profile/backup_config.json b/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/entry/src/main/resources/dark/element/color.json b/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/entry/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..fe83c3e838abfb3629eed84f6c7092f306d818f9 --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.0.2", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/local.properties b/local.properties new file mode 100644 index 0000000000000000000000000000000000000000..20f608699522ce5b5f170457336fbfbf96165c62 --- /dev/null +++ b/local.properties @@ -0,0 +1,9 @@ +# This file is automatically generated by DevEco Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file should *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# For customization when using a Version Control System, please read the header note. + + diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..b185185d31c2454034ed6fbd80bcc73beea65fc6 --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,8 @@ +{ + "modelVersion": "5.0.2", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + } +}