diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0b4fe98693fed9ec6c218036c970046b139269b2 --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.crossDeviceFileOperation", + "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..6030b4d442410b1240b8ba2f0b5740845fabf266 --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "crossDeviceFileOperation" + } + ] +} 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..eb9427585b36d14b12477435b6419d1f07b3e0bb 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.en.md b/README.en.md deleted file mode 100644 index 7eae4bc00b1fe99a215d67f3d1236867487536ca..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# CrossDeviceFileOperation - -#### Description -跨设备文件操作 - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 759390ff31bd40acd276bf79a722ed1e4f92c61c..9528422115aa2e9e38b318dff51c650ef3b73b7e 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,78 @@ -# CrossDeviceFileOperation +# 基于Core File Kit(文件基础服务) 实现跨设备文件操作 -#### 介绍 -跨设备文件操作 +## 项目简介 -#### 软件架构 -软件架构说明 +本示例基于Core File Kit(文件基础服务),介绍多设备间跨设备文件访问与拷贝功能,涵盖分布式文件的创建、保存、写入、读取、预览等操作,实现跨设备查看、修改、拷贝等场景。 +## 效果预览 -#### 安装教程 + -1. xxxx -2. xxxx -3. xxxx +## 使用说明 -#### 使用说明 +1. 首页填写文件名和文件内容,点击“确定”按钮创建文件,若需要修改填写内容,点击“清除”按钮。 +2. 创建成功后,点击“远端文件”按钮,查看已创建好的文件。 +3. 远端文件列表: + - 右上角打开编辑状态,选择某一项文件,可在底部进行删除,或拷贝复制操作。左上角可取消编辑状态。 + - 点击任意一项文件,进入预览界面。 +4. 本地文件列表:页面功能和远端文件列表大致相同,区别在于: + - 本地文件需要在远端文件列表中进行拷贝复制到本地。 + - 编辑状态下无复制按钮。 +5. 预览界面: + - 点击编辑区域,自动进入编辑状态。 + - 右上角对钩为编辑后保存按钮。 + - 左上角叉号,如果进入编辑状态但未编辑,则直接返回上一页;如果进行了编辑,则弹窗询问是否保存,取消则撤回刚刚编辑内容,确定则保存内容。 -1. xxxx -2. xxxx -3. xxxx -#### 参与贡献 +## 工程目录 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +``` +├──entry/src/main/ets // 代码区 +│ ├──entryability +│ │ └──EntryAbility.ets // 程序入口类 +│ ├──pages +│ │ └──Index.ets // 应用主界面 +│ ├──utils // 工具类 +│ │ └──CommonUtils.ets // 公共工具类 +│ └──view +│ ├──DeleteDialog.ets // 删除弹窗组件 +│ ├──EditTitleBar.ets // 编辑页顶部组件 +│ ├──ListComp.ets // 列表组件 +│ ├──Loading.ets // 加载弹窗组件 +│ ├──LocalFileList.ets // 访问本地文件列表视图 +│ ├──Preview.ets // 预览界面 +│ ├──ProgressDialog.ets // 拷贝进度弹窗组件 +│ ├──RemoteFileList.ets // 访问远端文件列表视图 +│ ├──TitleBar.ets // 通用顶部栏组件 +│ └──TitleBarDeleteDialog.ets // 通用顶部栏删除弹窗组件 +└──entry/src/main/resources // 应用静态资源目录 +``` +## 具体实现 -#### 特技 +1. 获取当前应用分布式共享目录沙箱路径,通过[getUIContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-custom-component-api#getuicontext)然后获取[getHostContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-uicontext#gethostcontext12) + ,随后访问distributedFilesDir分布式共享目录。 +2. 创建文件通过[fs.openSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsopensync)以同步方法打开文件或目录,然后通过[fs.writeSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fswritesync)以同步方法将数据写入文件,最后通过[fs.closeSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsclosesync)以同步方法关闭文件或目录,即可创建成功。 +3. 访问本地文件,通过[fs.listFile()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fslistfile)列出本地filesDir沙箱路径目录下所有文件。 +4. 访问远端文件: + - 首先通过[设备管理接口](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-distributeddevicemanager)获取设备networkId。 + - 通过[fs.connectDfs()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsconnectdfs12)开启跨设备文件访问。 + - 随后通过[fs.listFile()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fslistfile)列出远端沙箱路径distributedFilesDir目录下所有文件。 +5. 预览界面: + - 通过[fs.readTextSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsreadtextsync)读取沙箱路径下的某一项文件,格式为:fs.readTextSync(沙箱路径 + 文件名)。 +6. 保存功能: + - 通过[fs.openSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsopensync)以同步方法打开相应文件路径,随后通过[fs.writeSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fswritesync)以同步方法将数据写入文件,最后通过[fs.closeSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsclosesync)以同步方法关闭文件,即可保存成功。 -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/) + +## 相关权限 + +* ohos.permission.DISTRIBUTED_DATASYNC:允许不同设备间的数据交换。 + +## 约束与限制 + +1. 本示例仅支持标准系统上运行,支持设备:华为手机。 +2. HarmonyOS系统:HarmonyOS 5.1.1 Release及以上。 +3. DevEco Studio版本:DevEco Studio 5.1.1 Release及以上。 +4. HarmonyOS SDK版本:HarmonyOS 5.1.1 Release SDK及以上。 +5. 双端设备需要登录同一华为账号。 +6. 双端设备需要打开Wi-Fi和蓝牙开关。条件允许时,建议双端设备接入同一个局域网,可提升数据传输的速度。 diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..f46d2f2222c48e9a4c938da0612b0a9b06d47161 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,43 @@ +{ + "app": { + "signingConfigs": [ + ], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.1.1(19)", + "targetSdkVersion": "5.1.1(19)", + "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..eaa4bb5eb40a6bccd365319fa01a5da9907b3efa --- /dev/null +++ b/code-linter.json5 @@ -0,0 +1,33 @@ +{ + "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", + "@typescript-eslint/semi": "error" + } +} \ No newline at end of file diff --git a/entry/.gitignore b/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ 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..b0e3a1ab98a91bc918d6404b2413111a5011f14a --- /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. */ +} \ No newline at end of file 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..e4513908c9fc63adcf2791a7c41f4964fd00d053 --- /dev/null +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,70 @@ +/* + * 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 { window } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; + +const DOMAIN = 0x0000; +let uiContext: UIContext | undefined = undefined; + +export default class EntryAbility extends UIAbility { + onCreate(_want: Want, _launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } catch (error) { + hilog.error(0xFF00, 'EntryAbility', `have errors Code: ${error.code}, message: ${error.message}`); + } + } + + 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'); + try { + windowStage.loadContent('pages/Index', (err: BusinessError) => { + 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.'); + uiContext = windowStage.getMainWindowSync().getUIContext(); + AppStorage.setOrCreate('uiContext', windowStage.getMainWindowSync().getUIContext()); + }); + } catch (error) { + hilog.error(0xFF00, 'EntryAbility', `have errors Code: ${error.code}, message: ${error.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'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} diff --git a/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..8e4de99282050bad799ac892eb85ac5449364a51 --- /dev/null +++ b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,16 @@ +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..22c2417bb284a9a9de518589b5de98c7b33419fc --- /dev/null +++ b/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,263 @@ +/* + * 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. + */ + +// [Start import_common] +import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit'; + +// [StartExclude import_common] +// [Start import_file] +import { fileIo as fs } from '@kit.CoreFileKit'; +// [StartExclude import_file] +import CommonUtils from '../utils/CommonUtils'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +// [EndExclude import_common] +// [EndExclude import_file] + +// [Start create_file] +@Entry +@Component +struct Index { + // [StartExclude import_common] + // [StartExclude create_file] + // [StartExclude import_file] + @Provide('pageInfo') pageStack: NavPathStack = new NavPathStack(); + @Provide('isDeleteChange') isDeleteChange: boolean = false; + @State atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); + @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); + // [EndExclude import_common] + @State context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext; + // [StartExclude import_common] + @State fileTile: string = ''; + @State fileContent: string = ''; + + // [EndExclude import_file] + // [EndExclude create_file] + + createFile() { + // [StartExclude import_file] + // [StartExclude create_file] + if (this.fileTile === '' && this.fileContent === '') { + CommonUtils.showToast($r('app.string.file_name_and_file_content_cannot_be_empty'), this.uiContext!); + return; + } + // [EndExclude import_file] + // Get the file path of the distributed directory + let pathDir: string = this.context.distributedFilesDir; + // [EndExclude create_file] + // [StartExclude import_file] + let filePath: string = pathDir + `/${this.fileTile}.txt`; + try { + // Create files in a distributed directory + let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + hilog.info(0x0000, 'Index', '%{public}s', 'Succeeded in creating.'); + // Write content to a file + fs.writeSync(file.fd, this.fileContent); + // close file + fs.closeSync(file.fd); + // [StartExclude create_file] + CommonUtils.showToast($r('app.string.created_successfully'), this.uiContext!); + this.fileTile = ''; + this.fileContent = ''; + // [EndExclude create_file] + } catch (error) { + hilog.error(0xFF00, 'Index', `have errors Code: ${error.code}, message: ${error.message}`); + } + // [EndExclude import_file] + } + + // [StartExclude create_file] + // [StartExclude import_file] + clear() { + this.fileTile = ''; + this.fileContent = ''; + } + + onPageShow(): void { + const permissionResult = this.checkPermissions(['ohos.permission.DISTRIBUTED_DATASYNC']); + if (permissionResult) { + return; + } else { + this.permissions(); + } + } + + /** + * Apply for the permission to exchange data between different devices. + */ + async permissions() { + try { + const result = + await this.atManager.requestPermissionsFromUser(this.context, ['ohos.permission.DISTRIBUTED_DATASYNC']); + + if (result.authResults[0] !== -1) { + this.uiContext!.getPromptAction().showToast({ message: $r('app.string.authorization_successful') }); + } + + const resultAgain = + await this.atManager.requestPermissionOnSetting(this.context, ['ohos.permission.DISTRIBUTED_DATASYNC']); + if (resultAgain[0] === 0) { + this.uiContext!.getPromptAction().showToast({ message: $r('app.string.authorization_successful') }); + } else { + this.uiContext!.getPromptAction().showToast({ message: $r('app.string.reauthorization') }); + } + } catch (error) { + hilog.error(0xFF00, 'Index', `have errors Code: ${error.code}, message: ${error.message}`); + } + } + + /** + * Check if authorized + * @param permissions Permission array + * @returns result + */ + checkPermissions(permissions: Permissions[]) { + // Get bundle information + let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION; + + const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags); + // Extract tokenID identifier + const tokenID = bundleInfo.appInfo.accessTokenId; + // Verify if the application has been granted permission + const authResults = permissions.map((item) => this.atManager.checkAccessTokenSync(tokenID, item)); + // Return whether the result has been authorized or not + return authResults.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED); + } + + build() { + Navigation(this.pageStack) { + Column({ space: 12 }) { + Column({ space: 12 }) { + Text($r('app.string.create_a_file')) + .padding({ + top: 9, + bottom: 0 + }) + .fontSize(18) + .fontWeight(700) + .fontColor('rgba(0,0,0,0.9)') + TextInput({ text: $$this.fileTile, placeholder: $r('app.string.please_enter_a_file_name') }) + .width('100%') + .height(56) + .borderRadius(16) + .backgroundColor(Color.White) + TextArea({ text: $$this.fileContent, placeholder: $r('app.string.please_enter_the_file_content') }) + .backgroundColor(Color.White) + .lineBreakStrategy(LineBreakStrategy.GREEDY) + .layoutWeight(1) + .borderRadius(16) + Row({ space: 12 }) { + Button($r('app.string.clean'), { buttonStyle: ButtonStyleMode.NORMAL }) + .layoutWeight(1) + .margin({ right: 6 }) + .onClick(() => { + this.clear(); + }); + + Button($r('app.string.sure')) + .layoutWeight(1) + .enabled(!!(this.fileContent && this.fileTile)) + .onClick(() => { + this.createFile(); + }); + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + } + .alignItems(HorizontalAlign.Start) + .width('100%') + .margin({ + bottom: 236 + }) + + Column({ space: 12 }) { + Text($r('app.string.view_file')) + .padding({ + top: 24, + bottom: 0 + }) + .width('100%') + .textAlign(TextAlign.Start) + .fontSize(18) + .fontWeight(700) + .fontColor('rgba(0,0,0,0.9)') + Row() { + Text($r('app.string.local_file')) + .fontWeight(500) + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor(['rgba(0,0,0,0.2)']) + .fontSize(24) + .width(12) + .height(24) + } + .padding({ + left: 12, + right: 12 + }) + .width('100%') + .height(56) + .borderRadius(16) + .backgroundColor(Color.White) + .justifyContent(FlexAlign.SpaceBetween) + .onClick(() => { + this.pageStack.replacePathByName('LocalFileList', ''); + }); + + Row() { + Text($r('app.string.remote_file')) + .fontWeight(500) + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor(['rgba(0,0,0,0.2)']) + .fontSize(24) + .width(12) + .height(24) + } + .padding({ + left: 12, + right: 12 + }) + .width('100%') + .height(56) + .borderRadius(16) + .backgroundColor(Color.White) + .justifyContent(FlexAlign.SpaceBetween) + .onClick(() => { + this.pageStack.replacePathByName('RemoteFileList', ''); + }); + } + .margin({ + top: -200 + }) + .width('100%') + } + .padding(16) + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Start) + } + .backgroundColor('rgb(241,243,245)') + .title($r('app.string.EntryAbility_label')) + .height('100%') + .width('100%') + } + + // [EndExclude import_common] + // [EndExclude import_file] + // [EndExclude create_file] +} + +// [End import_common] +// [End import_file] +// [End create_file] \ No newline at end of file diff --git a/entry/src/main/ets/utils/CommonUtils.ets b/entry/src/main/ets/utils/CommonUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..83b9424cd7e66a4911ec681896a26cdab1f2e55e --- /dev/null +++ b/entry/src/main/ets/utils/CommonUtils.ets @@ -0,0 +1,50 @@ +/* + * 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 { fileIo as fs } from '@kit.CoreFileKit'; +import { BusinessError } from '@kit.BasicServicesKit'; + +// [Start delete_file] +class CommonUtils { + // [StartExclude delete_file] + public showToast(message: ResourceStr, uiContext: UIContext) { + try { + uiContext.getPromptAction().showToast({ message }); + } catch (error) { + hilog.error(0xFF00, 'CommonUtils', `have errors Code: ${error.code}, message: ${error.message}`); + } + } + + // [EndExclude delete_file] + + public delete(selectedValue: string, pathDir: string, uiContext: UIContext, callback: (result: boolean) => void) { + const srcUri = pathDir + `/${selectedValue}`; // Splicing the sandbox path of the required file + fs.unlink(srcUri, (err: BusinessError) => { + if (err) { + hilog.error(0x0000, 'RemoteFile', '%{public}s', + 'remove file failed with error message: ' + err.message + ', error code: ' + err.code); + callback(false); + } else { + this.showToast($r('app.string.delete_successfully'), uiContext!); + callback(true); + } + }); + } +} + +export default new CommonUtils(); + +// [End delete_file] diff --git a/entry/src/main/ets/view/DeleteDialog.ets b/entry/src/main/ets/view/DeleteDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..f3d527b2fb3564b9ba3c24ba4bbaed05837a7f68 --- /dev/null +++ b/entry/src/main/ets/view/DeleteDialog.ets @@ -0,0 +1,73 @@ +/* + * 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. + */ + +@CustomDialog +export struct DeleteDialog { + controller?: CustomDialogController; + confirmBtn: () => void = () => { + }; + confirmCancel: () => void = () => { + }; + + build() { + Column() { + Text($r('app.string.delete_file')) + .fontSize(20) + .fontWeight(700) + .margin({ + bottom: 23 + }) + Text($r('app.string.confirm_to_delete_the_file')) + .fontWeight(500) + .margin({ + bottom: 8 + }) + Row() { + Button($r('app.string.cancel'), + { buttonStyle: ButtonStyleMode.TEXTUAL, role: ButtonRole.NORMAL, stateEffect: false }) + .fontSize(16) + .fontColor('#0A59F7') + .onClick(() => { + this.confirmCancel(); + }) + Divider() + .vertical(true) + .height(22) + .color('#33000000') + .opacity(0.6) + .margin({ left: 50, right: 50 }) + Button($r('app.string.confirm'), + { buttonStyle: ButtonStyleMode.TEXTUAL, role: ButtonRole.NORMAL, stateEffect: false }) + .fontSize(16) + .fontColor('#0A59F7') + .onClick(() => { + this.confirmBtn(); + }) + } + .width('100%') + .justifyContent(FlexAlign.Center) + .alignItems(VerticalAlign.Center) + + } + .margin({ + left: 16, + right: 16 + }) + .justifyContent(FlexAlign.Center) + .backgroundColor(Color.White) + .borderRadius(32) + .padding(16) + } +} diff --git a/entry/src/main/ets/view/EditTitleBar.ets b/entry/src/main/ets/view/EditTitleBar.ets new file mode 100644 index 0000000000000000000000000000000000000000..8276ec0716770f9dd689cff7d5de1a8b2a095cc7 --- /dev/null +++ b/entry/src/main/ets/view/EditTitleBar.ets @@ -0,0 +1,116 @@ +/* + * 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. + */ + +@Component +export struct EditTitleBar { + @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); + @Link isEdit: boolean; + @Link editTile: Resource; + @Link selectedValue: string | null; + @Link fileListArr: string[]; + @Consume('pageInfo') pageStack: NavPathStack; + @Consume('isDeleteChange') isDeleteChange: boolean; + + build() { + Row() { + Row() { + if (!this.isEdit) { + Row() { + SymbolGlyph($r('sys.symbol.chevron_backward')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + } + .width(40) + .height(40) + .borderRadius('50%') + .backgroundColor('rgba(0,0,0,0.05)') + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .onClick(() => { + this.pageStack.pop(); + }) + } else { + Row() { + SymbolGlyph($r('sys.symbol.xmark')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + } + .width(40) + .height(40) + .borderRadius('50%') + .backgroundColor('rgba(0,0,0,0.05)') + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .onClick(() => { + this.isEdit = false; + this.selectedValue = null; + }) + } + + Text(this.isEdit ? $r('app.string.edit') : this.editTile) + .fontSize(20) + .fontWeight(700) + .margin({ + left: 8 + }) + } + .width('50%') + + if (this.isEdit) { + Row() { + Row() { + SymbolGlyph($r('sys.symbol.checkmark')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + } + .width(40) + .height(40) + .borderRadius('50%') + .backgroundColor('rgba(0,0,0,0.05)') + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + } + .width('50%') + .justifyContent(FlexAlign.End) + .onClick(() => { + this.isEdit = false; + this.selectedValue = null; + }) + } else { + if (this.fileListArr.length > 0) { + Row() { + SymbolGlyph($r('sys.symbol.square_and_pencil')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + } + .width(40) + .height(40) + .borderRadius('50%') + .backgroundColor('rgba(0,0,0,0.05)') + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .onClick(() => { + this.isEdit = true; + }) + } + } + } + .margin({ + bottom: 28 + }) + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/ListComp.ets b/entry/src/main/ets/view/ListComp.ets new file mode 100644 index 0000000000000000000000000000000000000000..5aadf243c4b0642a9d90f004a129ad9e45801a58 --- /dev/null +++ b/entry/src/main/ets/view/ListComp.ets @@ -0,0 +1,109 @@ +/* + * 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. + */ + +interface Param { + fileName: string + path: string +} + +@Component +export struct ListComp { + confirmBtn: () => void = () => { + }; + confirmCancel: () => void = () => { + }; + @Consume('pageInfo') pageStack: NavPathStack; + @Link fileListArr: string[]; + @Link isEdit: boolean; + @Link selectedValue: string | null; + @Link pathDir: string; + + build() { + Column() { + Row() { + Text($r('app.string.file_list')) + .fontSize(14) + .fontWeight(500) + .fontColor('rgba(0,0,0,0.6)') + .width('100%') + .margin({ left: 12, bottom: 8 }) + } + .width('100%') + .justifyContent(FlexAlign.SpaceAround) + + List({ space: 12, initialIndex: 0 }) { + ForEach(this.fileListArr, (item: string) => { + ListItem() { + Row() { + Text(item) + .fontWeight(500) + if (this.isEdit) { + Radio({ + value: item, group: 'localRadioGroup', + indicatorType: RadioIndicatorType.TICK + }) + .width(20) + .height(20) + .checked(this.selectedValue === item) + .onChange((isChecked: boolean) => { + if (isChecked) { + this.selectedValue = item; + } + }) + .onClick(() => { + this.selectedValue = item; + }) + } else { + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor(['rgba(0,0,0,0.2)']) + .fontSize(24) + .width(12) + .height(24) + } + } + .width('100%') + .height(56) + .borderRadius(16) + .backgroundColor(Color.White) + .justifyContent(FlexAlign.SpaceBetween) + .padding({ + top: 17, + right: 12, + bottom: 17, + left: 12 + }) + .onClick(() => { + this.selectedValue = item; + const param: Param = { + fileName: this.selectedValue, + path: this.pathDir + }; + if (!this.isEdit) { + this.pageStack.pushPathByName('Preview', param); + } + }) + } + }, (item: string) => item) + } + .width('100%') + .height('100%') + .scrollBar(BarState.Off) + } + .width('100%') + .margin({ + bottom: this.isEdit ? 48 : 0 + }) + } +} diff --git a/entry/src/main/ets/view/Loading.ets b/entry/src/main/ets/view/Loading.ets new file mode 100644 index 0000000000000000000000000000000000000000..b9326b57d96340c33af158b244bf92bea9c66b83 --- /dev/null +++ b/entry/src/main/ets/view/Loading.ets @@ -0,0 +1,60 @@ +/* + * 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. + */ + +@CustomDialog +struct Dialog { + controller?: CustomDialogController; + + build() { + Column() { + LoadingProgress() + .color('#666666') + .width(72) + .height(72) + .scale({ x: 1.3, y: 1.3 }) + Text($r('app.string.loading')) + .fontSize(14) + .fontWeight(400) + .fontColor('rgba(0,0,0,0.6)') + .margin({ + top: 24 + }) + } + .padding({ + bottom: 112 + }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .width('100%') + .height('100%') + .backgroundColor('rgb(241,243,245)') + } +} + +@Component +export struct Loading { + deviceListDialogController: CustomDialogController = new CustomDialogController({ + builder: Dialog(), + alignment: DialogAlignment.Center, + autoCancel: false, + customStyle: true + }); + + build() { + Column() { + Dialog(); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/LocalFileList.ets b/entry/src/main/ets/view/LocalFileList.ets new file mode 100644 index 0000000000000000000000000000000000000000..838982e63d29a90bd7a001cfc0012010048e9e5a --- /dev/null +++ b/entry/src/main/ets/view/LocalFileList.ets @@ -0,0 +1,219 @@ +/* + * 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 { fileIo as fs } from '@kit.CoreFileKit'; +import { common } from '@kit.AbilityKit'; +import CommonUtils from '../utils/CommonUtils'; +import { ListComp } from './ListComp'; +import { Loading } from './Loading'; +import { EditTitleBar } from './EditTitleBar'; +import { DeleteDialog } from './DeleteDialog'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +@Builder +export function LocalFileListBuild() { + LocalFileList(); +} + +@Component +export struct LocalFileList { + @Consume('pageInfo') pageStack: NavPathStack; + @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); + @State context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext; + @State isEdit: boolean = false; + @State isShowDialog: boolean = false; + @State isLocalFileChecked: boolean = false; + @State selectedValue: string | null = null; + @State localFileListArr: string[] = []; + @State pathDir: string = ''; + @State editTile: Resource = $r('app.string.local_file'); + @Consume('isDeleteChange') @Watch('handleGetList') isDeleteChange: boolean; + DeleteDialogController: CustomDialogController = new CustomDialogController({ + builder: DeleteDialog({ + confirmBtn: () => { + this.delete(); + }, + confirmCancel: () => { + this.DeleteDialogController.close(); + }, + }), + alignment: DialogAlignment.Center, + autoCancel: false, + customStyle: true + }); + + aboutToAppear() { + this.getFileList(); + } + + handleGetList() { + this.getFileList(); + } + + // [Start get_file] + async getFileList() { + this.isShowDialog = true; + // Get the file path of the local directory + let pathDir: string = this.context.filesDir; + this.pathDir = pathDir; + try { + this.localFileListArr = await fs.listFile(pathDir); + } catch (error) { + hilog.error(0xFF00, 'LocalFileList', `have errors Code: ${error.code}, message: ${error.message}`); + } + // [StartExclude get_file] + if (this.localFileListArr.length > 0) { + this.isShowDialog = false; + } else { + this.isEdit = false; + this.isShowDialog = false; + } + // [EndExclude get_file] + } + + // [End get_file] + + delete() { + if (!this.selectedValue) { + CommonUtils.showToast($r('app.string.Please_select_a_file'), this.uiContext!); + } else { + CommonUtils.delete(this.selectedValue, this.pathDir, this.uiContext!, (result) => { + if (result) { + this.DeleteDialogController.close(); + this.isShowDialog = true; + this.selectedValue = null; + this.getFileList(); + } + }); + } + } + + build() { + NavDestination() { + Column({ space: 12 }) { + EditTitleBar({ + isEdit: this.isEdit, + editTile: this.editTile, + selectedValue: this.selectedValue, + fileListArr: this.localFileListArr + }) + if (!this.isShowDialog) { + Column({ space: 12 }) { + Column({ space: 12 }) { + if (this.localFileListArr.length > 0) { + ListComp( + { + fileListArr: this.localFileListArr, + isEdit: this.isEdit, + selectedValue: this.selectedValue, + pathDir: this.pathDir + } + ); + } else { + Column() { + Image($r('app.media.NoInstallationPackage')) + .width(120) + .height(120) + Text($r('app.string.please_copy_the_file_first')) + .fontSize(14) + .fontWeight(400) + .fontColor('#434343') + } + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + .width('100%') + .height('100%') + } + } + .width('100%') + .height('100%') + } + .width('100%') + .margin({ + bottom: 78 + }) + .justifyContent(FlexAlign.SpaceBetween) + .alignItems(HorizontalAlign.Start) + + if (this.isEdit) { + Row() { + Column() { + SymbolGlyph($r('sys.symbol.trash')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + .margin({ + bottom: 6 + }) + Text($r('app.string.delete')) + .fontSize(12) + .fontWeight(400) + .fontColor('rgba(0,0,0,0.9)') + } + .width('40%') + .onClick(() => { + if (this.selectedValue) { + this.DeleteDialogController.open(); + } else { + CommonUtils.showToast($r('app.string.Please_select_a_file'), this.uiContext!); + } + }) + } + .width('100%') + .height(48) + .justifyContent(FlexAlign.Center) + .margin({ + top: -94 + }) + .backgroundColor('rgb(241,243,245)') + } else { + if (this.localFileListArr.length === 0) { + Button($r('app.string.return_to_the_previous_page'), { buttonStyle: ButtonStyleMode.NORMAL }) + .width('100%') + .height(40) + .fontColor('#0A59F7') + .margin({ + top: -94 + }) + .onClick(async () => { + this.pageStack.pop(); + }); + } else { + Row() { + }.width('100%') + .height(40) + } + } + } else { + Loading(); + } + } + .backgroundColor('rgb(241,243,245)') + .padding({ + top: 8, + right: 16, + bottom: 16, + left: 16 + }) + .width('100%') + .height('100%') + .justifyContent(FlexAlign.SpaceBetween) + + } + .mode(NavDestinationMode.DIALOG) + .systemTransition(NavigationSystemTransitionType.SLIDE_RIGHT) + .title(this.isEdit ? $r('app.string.edit') : $r('app.string.local_file')) + .hideTitleBar(true) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/Preview.ets b/entry/src/main/ets/view/Preview.ets new file mode 100644 index 0000000000000000000000000000000000000000..df1bbef45dd5191fb616900a8ba66d16565f6bd9 --- /dev/null +++ b/entry/src/main/ets/view/Preview.ets @@ -0,0 +1,125 @@ +/* + * 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 { fileIo as fs } from '@kit.CoreFileKit'; +import { TitleBar } from './TitleBar'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +interface Param { + fileName: string + path: string +} + +@Builder +export function PreviewBuild() { + Preview(); +} + + +// [Start preview] +@Component +export struct Preview { + // [StartExclude preview] + @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); + @Consume('pageInfo') pageStack: NavPathStack; + controller: TextInputController = new TextInputController(); + @State fileContent: string = ''; + @State targetPath: string = ''; + @State filePath: string = ''; + @State fileTile: string = ''; + @State isEdit: boolean = false; + @Consume('isDeleteChange') isDeleteChange: boolean; + + // [EndExclude preview] + + aboutToAppear(): void { + const param = this.pageStack.getParamByName('Preview')[0] as Param; + this.fileTile = param.fileName; // Incoming file name + this.filePath = param.path; // The corresponding file sandbox path passed in + this.targetPath = this.filePath + `/${this.fileTile}`; //Splicing into the file path to be read + try { + this.fileContent = fs.readTextSync(this.targetPath); //Read the file, fileContent is the content of the read file + } catch (error) { + hilog.error(0xFF00, 'Preview', `have errors Code: ${error.code}, message: ${error.message}`); + } + } + + // [StartExclude preview] + stopEdit() { + this.controller.stopEditing(); + } + + // [EndExclude preview] + + build() { + // [StartExclude preview] + NavDestination() { + Column({ space: 12 }) { + TitleBar({ + isEdit: this.isEdit, + filePath: this.filePath, + fileTile: this.fileTile, + fileContent: this.fileContent, + stopEdit: () => { + this.stopEdit(); + } + }) + + Column({ space: 12 }) { + Text(this.fileTile) + .fontSize(24) + .fontWeight(500) + .margin({ + top: 8 + }) + Column({ space: 12 }) { + TextArea({ text: $$this.fileContent, controller: this.controller }) + .lineBreakStrategy(LineBreakStrategy.GREEDY) + .borderRadius(0) + .layoutWeight(1) + .width('100%') + .height('100%') + .backgroundColor(Color.White) + .padding(0) + .onClick(() => { + this.isEdit = true; + }) + } + .width('100%') + .height('100%') + } + .width('100%') + .margin({ + bottom: 84 + }) + .alignItems(HorizontalAlign.Start) + } + .padding({ + top: 8, + right: 16, + bottom: 16, + left: 16 + }) + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Start) + } + .hideTitleBar(true) + + // [EndExclude preview] + } +} + +// [End preview] diff --git a/entry/src/main/ets/view/ProgressDialog.ets b/entry/src/main/ets/view/ProgressDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..cadbbd8ed2832a161be8f5d76a281e68f046c416 --- /dev/null +++ b/entry/src/main/ets/view/ProgressDialog.ets @@ -0,0 +1,78 @@ +/* + * 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. + */ + +@CustomDialog +export struct ProgressDialog { + controller?: CustomDialogController; + @Link value: number; + @Link total: number; + + build() { + Column() { + Row() { + Text($r('app.string.file_copying')) + .fontSize(14) + .fontWeight(400) + .fontColor('rgba(0,0,0,0.9)') + .width('50%') + Text(this.value / this.total * 100 + '%') + .fontSize(14) + .fontWeight(400) + .fontColor('rgba(0,0,0,0.6)') + .textAlign(TextAlign.End) + .margin({ + right: 36 + }) + } + .margin({ + bottom: 4 + }) + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + .alignItems(VerticalAlign.Center) + + Row() { + Progress({ value: this.value, total: this.total, type: ProgressType.Linear }) + .color('#0A59F7') + .height(4) + .margin({ + right: 36 + }) + SymbolGlyph($r('sys.symbol.xmark_circle_fill')) + .fontColor(['rgba(0,0,0,0.4)']) + .width(24) + .height(24) + .margin({ + left: -36 + }) + .onClick(() => { + this.controller?.close(); + }) + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + .alignItems(VerticalAlign.Center) + } + .margin({ + left: 16, + right: 16 + }) + .height(96) + .justifyContent(FlexAlign.End) + .backgroundColor(Color.White) + .borderRadius(32) + .padding(24) + } +} diff --git a/entry/src/main/ets/view/RemoteFileList.ets b/entry/src/main/ets/view/RemoteFileList.ets new file mode 100644 index 0000000000000000000000000000000000000000..f2e0cf21299d0866e218150292ac64fddf0675d1 --- /dev/null +++ b/entry/src/main/ets/view/RemoteFileList.ets @@ -0,0 +1,391 @@ +/* + * 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 { distributedDeviceManager } from '@kit.DistributedServiceKit'; +import { fileUri, fileIo as fs } from '@kit.CoreFileKit'; +import { common } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import CommonUtils from '../utils/CommonUtils'; +import { ProgressDialog } from './ProgressDialog'; +import { ListComp } from './ListComp'; +import { Loading } from './Loading'; +import { EditTitleBar } from './EditTitleBar'; +import { DeleteDialog } from './DeleteDialog'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +@Builder +export function RemoteFileListBuild() { + RemoteFileList(); +} + +// [Start getRemoteFileList] +// [Start copy_file] +@Component +export struct RemoteFileList { + // [StartExclude getRemoteFileList] + // [StartExclude copy_file] + @Consume('pageInfo') pageStack: NavPathStack; + @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); + @State context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext; + @State isEdit: boolean = false; + @State isShowDialog: boolean = false; + @State editTile: Resource = $r('app.string.remote_file'); + @State remoteFileListArr: string[] = []; + @State isRemoteFileChecked: boolean = false; + @State selectedValue: string | null = null; + @State value: number = 0; + @State total: number = 0; + @State networkId: string = ''; + @State connectedNetworkIds: string[] = []; + listeners: fs.DfsListeners = { + onStatus: (_networkId: string, _status: number): void => { + hilog.info(0x0000, 'RemoteFile', '%{public}s', 'Failed to access public directory'); + } + }; + ProgressDialog: CustomDialogController = new CustomDialogController({ + builder: ProgressDialog({ + value: this.value, + total: this.total, + }), + alignment: DialogAlignment.Center, + autoCancel: false, + customStyle: true + }); + DeleteDialogController: CustomDialogController = new CustomDialogController({ + builder: DeleteDialog({ + confirmBtn: () => { + this.delete(); + }, + confirmCancel: () => { + this.DeleteDialogController.close(); + }, + }), + alignment: DialogAlignment.Center, + autoCancel: false, + customStyle: true + }); + @State pathDir: string = ''; + @Consume('isDeleteChange') @Watch('handleGetList') isDeleteChange: boolean; + + aboutToAppear() { + this.getRemoteFileList(); + } + + handleGetList() { + this.getRemoteFileList(); + } + + // [EndExclude getRemoteFileList] + + syncDevice(deviceInfoList: Array) { + // Create connection promises for each device information + const connectPromises = deviceInfoList.map(deviceInfo => { + return fs.connectDfs(deviceInfo.networkId, this.listeners) + .then(() => { + if (!this.connectedNetworkIds.includes(deviceInfo.networkId!)) { + this.connectedNetworkIds.push(deviceInfo.networkId!); + } + hilog.info(0x0000, 'RemoteFile', '%{public}s', + `Success to connect dfs for network ${deviceInfo.networkId}`); + }) + .catch((error: BusinessError) => { + hilog.error(0x0000, 'RemoteFile', '%{public}s', + `Failed to connect dfs for network ${deviceInfo.networkId}. Code: ${error.code}, message: ${error.message}`); + }); + }); + + // Wait for all connection operations to complete + Promise.all(connectPromises).then(async () => { + // Process data after all connection operations are completed + this.pathDir = this.context.distributedFilesDir; + try { + this.remoteFileListArr = await fs.listFile(this.pathDir); + if (this.remoteFileListArr.length > 0) { + this.isShowDialog = false; + } else { + this.isShowDialog = false; + this.isEdit = false; + } + } catch (error) { + hilog.error(0x0000, 'RemoteFile', '%{public}s', + `Error in remoteFileListArr. Code: ${error.code}, message: ${error.message}`); + } + }).catch((error: BusinessError) => { + hilog.error(0x0000, 'RemoteFile', '%{public}s', + `Error in connection process. Code: ${error.code}, message: ${error.message}`); + }); + } + + // [StartExclude getRemoteFileList] + async disconnectAllDfs() { + // If there are no connected devices, return directly + if (this.connectedNetworkIds.length === 0) { + hilog.info(0x0000, 'RemoteFile', '%{public}s', 'No connected devices to disconnect'); + return; + } + + // Perform a disconnection operation on each connected device and generate a disconnection Promise array + const disconnectPromises = this.connectedNetworkIds.map(networkId => { + return fs.disconnectDfs(networkId) + .then(() => { + hilog.info(0x0000, 'RemoteFile', '%{public}s', + `Success to disconnect dfs for network ${networkId}`); + }) + .catch((error: BusinessError) => { + const err: BusinessError = error as BusinessError; + hilog.error(0x0000, 'RemoteFile', '%{public}s', + `Failed to disconnect dfs for network ${networkId}. Code: ${err.code}, message: ${err.message}`); + }); + }); + + await Promise.all(disconnectPromises); + + // Clear the list of connected devices + this.connectedNetworkIds = []; + hilog.info(0x0000, 'RemoteFile', '%{public}s', 'All connected devices disconnect process completed'); + } + + aboutToDisappear(): void { + this.disconnectAllDfs(); + } + + // [EndExclude getRemoteFileList] + + // Retrieve the file list in the remote distributed directory + async getRemoteFileList() { + this.isShowDialog = true; + let dmInstance = distributedDeviceManager.createDeviceManager('com.example.crossDeviceFileOperation'); + try { + let deviceInfoList: Array = dmInstance.getAvailableDeviceListSync(); + if (deviceInfoList && deviceInfoList.length > 0) { + + // Start cross device file access + this.syncDevice(deviceInfoList); + } else { + CommonUtils.showToast($r('app.string.no_devices_currently_available'), this.uiContext!); + this.isShowDialog = false; + } + } catch (error) { + hilog.error(0xFF00, 'RemoteFileList', `have errors Code: ${error.code}, message: ${error.message}`); + } + } + + // [EndExclude copy_file] + + // [StartExclude getRemoteFileList] + // Copy files + copyFile() { + if (!this.selectedValue) { + CommonUtils.showToast($r('app.string.Please_select_a_file'), this.uiContext!); + } else { + this.ProgressDialog.open(); + const srcUri = fileUri.getUriFromPath(this.pathDir + `/${this.selectedValue}`); // To copy file path + const destUri = fileUri.getUriFromPath(this.context.filesDir + `/${this.selectedValue}`); // Target file path + const existingPath = this.context.filesDir + `/${this.selectedValue}`; + try { + let res = fs.accessSync(existingPath); + + if (res) { + fs.unlinkSync(existingPath); + } + let progressListener: fs.ProgressListener = (progress: fs.Progress) => { + this.value = progress.processedSize; // Copy file size + this.total = progress.totalSize; // Total size of copied files + if ((this.value / this.total * 100) > 99) { + CommonUtils.showToast($r('app.string.Success_copy'), this.uiContext!); + this.ProgressDialog.close(); + this.pageStack.pop(); + } + }; + let copyOption: fs.CopyOptions = { + 'progressListener': progressListener + }; + fs.copy(srcUri, destUri, copyOption).then(() => { + hilog.info(0x0000, 'RemoteFile', '%{public}s', 'Success to copy.'); + }).catch((err: BusinessError) => { + hilog.error(0x0000, 'RemoteFile', `Failed to copy.Code:${err.code},message: ${err.message}`); + }); + } catch (err) { + hilog.error(0x0000, 'RemoteFile', '%{public}s', + `Failed to copy.Code try: ${err.code}, message: ${err.message}`); + } + } + } + + // [StartExclude copy_file] + + delete() { + if (!this.selectedValue) { + CommonUtils.showToast($r('app.string.Please_select_a_file'), this.uiContext!); + } else { + CommonUtils.delete(this.selectedValue, this.pathDir, this.uiContext!, (result) => { + if (result) { + this.DeleteDialogController.close(); + this.isShowDialog = true; + this.selectedValue = null; + this.getRemoteFileList(); + } + }); + } + } + + // [EndExclude getRemoteFileList] + // [EndExclude copy_file] + build() { + // [StartExclude getRemoteFileList] + // [StartExclude copy_file] + NavDestination() { + Column({ space: 12 }) { + EditTitleBar({ + isEdit: this.isEdit, + editTile: this.editTile, + selectedValue: this.selectedValue, + fileListArr: this.remoteFileListArr + }) + if (!this.isShowDialog) { + Column({ space: 12 }) { + Column({ space: 12 }) { + if (this.remoteFileListArr.length > 0) { + ListComp( + { + fileListArr: this.remoteFileListArr, + isEdit: this.isEdit, + selectedValue: this.selectedValue, + pathDir: this.pathDir + } + ); + } else { + Column() { + Image($r('app.media.NoInstallationPackage')) + .width(120) + .height(120) + Text($r('app.string.please_create_the_file_first')) + .fontSize(14) + .fontWeight(400) + .fontColor('#434343') + } + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + .width('100%') + .height('100%') + } + } + .width('100%') + .height('100%') + } + .width('100%') + .margin({ + bottom: 78 + }) + .justifyContent(FlexAlign.SpaceBetween) + .alignItems(HorizontalAlign.Start) + + if (this.isEdit) { + Row() { + Column() { + SymbolGlyph($r('sys.symbol.trash')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + .margin({ + bottom: 6 + }) + Text($r('app.string.delete')) + .fontSize(12) + .fontWeight(400) + .fontColor('rgba(0,0,0,0.9)') + } + .width('40%') + .onClick(() => { + if (this.selectedValue) { + this.DeleteDialogController.open(); + } else { + CommonUtils.showToast($r('app.string.Please_select_a_file'), this.uiContext!); + } + }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + + Column() { + SymbolGlyph($r('sys.symbol.plus_square_on_square')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + .margin({ + bottom: 6 + }) + Text($r('app.string.copy')) + .fontSize(12) + .fontWeight(400) + .fontColor('rgba(0,0,0,0.9)') + } + .width('40%') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(() => { + this.copyFile(); + }) + } + .width('100%') + .height(48) + .margin({ + top: -94 + }) + .justifyContent(FlexAlign.SpaceAround) + .backgroundColor('rgb(241,243,245)') + } else { + if (this.remoteFileListArr.length === 0) { + Button($r('app.string.return_to_the_previous_page'), { buttonStyle: ButtonStyleMode.NORMAL }) + .width('100%') + .height(40) + .fontColor('#0A59F7') + .margin({ + bottom: 16, + top: -94 + }) + .onClick(async () => { + this.pageStack.pop(); + }) + } else { + Row() { + }.width('100%') + .height(40) + } + } + } else { + Loading(); + } + } + .backgroundColor('rgb(241,243,245)') + .padding({ + top: 8, + right: 16, + bottom: 16, + left: 16 + }) + .width('100%') + .height('100%') + .justifyContent(FlexAlign.SpaceBetween) + } + .mode(NavDestinationMode.DIALOG) + .systemTransition(NavigationSystemTransitionType.SLIDE_RIGHT) + .title(this.isEdit ? $r('app.string.edit') : $r('app.string.remote_file')) + .hideTitleBar(true) + + // [EndExclude getRemoteFileList] + // [EndExclude copy_file] + } +} + +// [End getRemoteFileList] +// [End copy_file] \ No newline at end of file diff --git a/entry/src/main/ets/view/TitleBar.ets b/entry/src/main/ets/view/TitleBar.ets new file mode 100644 index 0000000000000000000000000000000000000000..c7650f599199ceca8fa56aef44ee90e0a3bb4cdc --- /dev/null +++ b/entry/src/main/ets/view/TitleBar.ets @@ -0,0 +1,164 @@ +/* + * 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 { fileIo as fs, WriteOptions } from '@kit.CoreFileKit'; +import CommonUtils from '../utils/CommonUtils'; +import { TitleBarDeleteDialog } from './TitleBarDeleteDialog'; +import { util } from '@kit.ArkTS'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +// [Start save_file] +@Component +export struct TitleBar { + // [StartExclude save_file] + @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); + @Consume('pageInfo') pageStack: NavPathStack; + @Link isEdit: boolean; + @Link filePath: string; + @Link fileTile: string; + @Link fileContent: string; + @State oldFileContent: string = ''; + stopEdit: () => void = () => { + }; + deleteDialogController: CustomDialogController = new CustomDialogController({ + builder: TitleBarDeleteDialog({ + confirmCancel: () => { + this.deleteDialogController.close(); + this.fileContent = this.oldFileContent; + this.isEdit = false; + }, + confirmBtn: () => { + this.saveFile(); + this.deleteDialogController.close(); + }, + }), + alignment: DialogAlignment.Center, + autoCancel: false, + customStyle: true + }); + @Consume('isDeleteChange') isDeleteChange: boolean; + + aboutToAppear(): void { + this.oldFileContent = this.fileContent; + } + + // [EndExclude save_file] + + saveFile() { + try { + const file = fs.openSync(this.filePath + `/${this.fileTile}`, + fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC); // Open files in read-write mode + const writeOptions: WriteOptions = { + offset: 0, // Key point: Reset write position + }; + const textEncoder = new util.TextEncoder(); // Create an instance of TextEncoder and specify UTF-8 encoding + const encodedData = textEncoder.encodeInto(this.fileContent); // Convert a string to Uint8Array in UTF-8 format + let writeLen = fs.writeSync(file.fd, encodedData.buffer, writeOptions); // write file + fs.fsyncSync(file.fd); + if (writeLen) { + fs.closeSync(file); // close file + CommonUtils.showToast($r('app.string.successfully_saved'), this.uiContext!); + this.pageStack.pop(); // Back to previous level + // [StartExclude save_file] + this.isDeleteChange = !this.isDeleteChange; + // [EndExclude save_file] + } + } catch (error) { + hilog.error(0xFF00, 'TitleBar', `have errors Code: ${error.code}, message: ${error.message}`); + } + } + + build() { + // [StartExclude save_file] + Row() { + Row() { + Row() { + if (!this.isEdit) { + Row() { + SymbolGlyph($r('sys.symbol.chevron_backward')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + } + .width(40) + .height(40) + .borderRadius('50%') + .backgroundColor('rgba(0,0,0,0.05)') + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .onClick(() => { + this.pageStack.pop(); + }) + } else { + Row() { + SymbolGlyph($r('sys.symbol.xmark')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + } + .width(40) + .height(40) + .borderRadius('50%') + .backgroundColor('rgba(0,0,0,0.05)') + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .onClick(() => { + if (this.oldFileContent !== this.fileContent) { + this.stopEdit(); + this.deleteDialogController.open(); + } else { + this.isEdit = false; + this.pageStack.pop(); + } + }) + } + + Text(this.isEdit ? $r('app.string.edit') : $r('app.string.file_preview')) + .fontSize(20) + .fontWeight(700) + .margin({ + left: 8 + }) + } + .width('60%') + + if (this.isEdit) { + Row() { + Row() { + SymbolGlyph($r('sys.symbol.checkmark')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + } + .width(40) + .height(40) + .borderRadius('50%') + .backgroundColor('rgba(0,0,0,0.05)') + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + } + .onClick(() => { + this.saveFile(); + }) + .width('40%') + .justifyContent(FlexAlign.End) + } + } + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + + // [EndExclude save_file] + } +} + +// [End save_file] \ No newline at end of file diff --git a/entry/src/main/ets/view/TitleBarDeleteDialog.ets b/entry/src/main/ets/view/TitleBarDeleteDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..784bb62c1abd8c842fb88820ad328a7240803c2b --- /dev/null +++ b/entry/src/main/ets/view/TitleBarDeleteDialog.ets @@ -0,0 +1,72 @@ +/* + * 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. + */ + +@CustomDialog +export struct TitleBarDeleteDialog { + controller?: CustomDialogController; + confirmBtn: () => void = () => { + }; + confirmCancel: () => void = () => { + }; + + build() { + Column() { + Text($r('app.string.do_you_want_to_save_it')) + .fontSize(20) + .fontWeight(700) + .margin({ + bottom: 23 + }) + Text($r('app.string.confirm_to_save_the_file')) + .fontWeight(500) + .margin({ + bottom: 8 + }) + Row() { + Button($r('app.string.cancel'), + { buttonStyle: ButtonStyleMode.TEXTUAL, role: ButtonRole.NORMAL, stateEffect: false }) + .fontSize(16) + .fontColor('#0A59F7') + .onClick(() => { + this.confirmCancel(); + }) + Divider() + .vertical(true) + .height(22) + .color('#33000000') + .opacity(0.6) + .margin({ left: 50, right: 50 }) + Button($r('app.string.confirm'), + { buttonStyle: ButtonStyleMode.TEXTUAL, role: ButtonRole.NORMAL, stateEffect: false }) + .fontSize(16) + .fontColor('#0A59F7') + .onClick(() => { + this.confirmBtn(); + }) + } + .width('100%') + .justifyContent(FlexAlign.Center) + .alignItems(VerticalAlign.Center) + } + .margin({ + left: 16, + right: 16 + }) + .justifyContent(FlexAlign.Center) + .backgroundColor(Color.White) + .borderRadius(32) + .padding(16) + } +} diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..80ba5343da84e59f0189680c06b320d91504d0c0 --- /dev/null +++ b/entry/src/main/module.json5 @@ -0,0 +1,63 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "routerMap": "$profile:route_map", + "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, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "ohos.want.action.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.DISTRIBUTED_DATASYNC", + "reason": "$string:distributed_data_sync_reason", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + } + ] + } +} \ 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..2a605b6136418135a4907c55569cccf41d43ecf8 --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,180 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "Cross device file operation" + }, + { + "name": "create_a_file", + "value": "create a file" + }, + { + "name": "display_local_file_list", + "value": "Display local file list" + }, + { + "name": "display_remote_file_list", + "value": "Display remote file list" + }, + { + "name": "please_enter_a_file_name", + "value": "Please enter a file name" + }, + { + "name": "please_enter_the_file_content", + "value": "Please enter the file content" + }, + { + "name": "local_file", + "value": "Local file" + }, + { + "name": "preview_area", + "value": "Preview Area" + }, + { + "name": "modify_file", + "value": "Modify file" + }, + { + "name": "remote_file", + "value": "Remote file" + }, + { + "name": "copy_files", + "value": "Copy files" + }, + { + "name": "file_preview", + "value": "file preview" + }, + { + "name": "Save_modifications", + "value": "Save modifications" + }, + { + "name": "Success_copy", + "value": "Success copy" + }, + { + "name": "Please_select_a_file", + "value": "Please select a file" + }, + { + "name": "successfully_saved", + "value": "Successfully saved" + }, + { + "name": "distributed_data_sync_reason", + "value": "Allow data exchange between different devices" + }, + { + "name": "authorization_successful", + "value": "Authorization successful" + }, + { + "name": "reauthorization", + "value": "Authorization failed, please reauthorize" + }, + { + "name": "created_successfully", + "value": "Created successfully" + }, + { + "name": "file_name_and_file_content_cannot_be_empty", + "value": "The file name and file content cannot be empty" + }, + { + "name": "no_data_available_at_the_moment", + "value": "No data available at the moment" + }, + { + "name": "no_devices_currently_available", + "value": "No devices currently available" + }, + { + "name": "clean", + "value": "clean" + }, + { + "name": "sure", + "value": "sure" + }, + { + "name": "view_file", + "value": "view file" + }, + { + "name": "return_to_the_previous_page", + "value": "Return to the previous page" + }, + { + "name": "please_copy_the_file_first", + "value": "Please copy the file first" + }, + { + "name": "file_list", + "value": "file list" + }, + { + "name": "edit", + "value": "edit" + }, + { + "name": "delete", + "value": "delete" + }, + { + "name": "copy", + "value": "copy" + }, + { + "name": "delete_successfully", + "value": "Delete successfully" + }, + { + "name": "delete_file", + "value": "delete file" + }, + { + "name": "confirm_to_delete_the_file", + "value": "Confirm to delete the file" + }, + { + "name": "cancel", + "value": "cancel" + }, + { + "name": "confirm", + "value": "confirm" + }, + { + "name": "file_copying", + "value": "File copying" + }, + { + "name": "loading", + "value": "loading..." + }, + { + "name": "do_you_want_to_save_it", + "value": "Do you want to save it" + }, + { + "name": "confirm_to_save_the_file", + "value": "Confirm to save the file" + }, + { + "name": "please_create_the_file_first", + "value": "Please create the file first" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/NoInstallationPackage.svg b/entry/src/main/resources/base/media/NoInstallationPackage.svg new file mode 100644 index 0000000000000000000000000000000000000000..acf743d60d8097593c987f878208dc60886e0e7a --- /dev/null +++ b/entry/src/main/resources/base/media/NoInstallationPackage.svg @@ -0,0 +1,13 @@ + + + EmptyPage/08 NoInstallationPackage + + + + + + + + + + \ No newline at end of file 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/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/plus_square_on_square_fill.svg b/entry/src/main/resources/base/media/plus_square_on_square_fill.svg new file mode 100644 index 0000000000000000000000000000000000000000..aa70585336a0aab86901e97fecda6b1fd97ee375 --- /dev/null +++ b/entry/src/main/resources/base/media/plus_square_on_square_fill.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + 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/media/trash.svg b/entry/src/main/resources/base/media/trash.svg new file mode 100644 index 0000000000000000000000000000000000000000..056b47d17834a48cb79cb94d7ceec07dca642c2a --- /dev/null +++ b/entry/src/main/resources/base/media/trash.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file 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/base/profile/route_map.json b/entry/src/main/resources/base/profile/route_map.json new file mode 100644 index 0000000000000000000000000000000000000000..5f9fbe0ad463b58ef566bb0562fecbbf1d0ff41f --- /dev/null +++ b/entry/src/main/resources/base/profile/route_map.json @@ -0,0 +1,28 @@ +{ + "routerMap": [ + { + "name": "LocalFileList", + "pageSourceFile": "src/main/ets/view/LocalFileList.ets", + "buildFunction": "LocalFileListBuild", + "data": { + "description": "this is LocalFileList" + } + }, + { + "name": "RemoteFileList", + "pageSourceFile": "src/main/ets/view/RemoteFileList.ets", + "buildFunction": "RemoteFileListBuild", + "data": { + "description": "this is RemoteFileList" + } + }, + { + "name": "Preview", + "pageSourceFile": "src/main/ets/view/Preview.ets", + "buildFunction": "PreviewBuild", + "data": { + "description": "this is Preview" + } + } + ] +} \ No newline at end of file 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/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..efbc58f76034085ff3f1309998dc42355d632f6c --- /dev/null +++ b/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,180 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "Module description" + }, + { + "name": "EntryAbility_desc", + "value": "EntryAbility description" + }, + { + "name": "EntryAbility_label", + "value": "Cross device file operation" + }, + { + "name": "create_a_file", + "value": "create a file" + }, + { + "name": "display_local_file_list", + "value": "Display local file list" + }, + { + "name": "display_remote_file_list", + "value": "Display remote file list" + }, + { + "name": "please_enter_a_file_name", + "value": "Please enter a file name" + }, + { + "name": "please_enter_the_file_content", + "value": "Please enter the file content" + }, + { + "name": "local_file", + "value": "Local file list" + }, + { + "name": "preview_area", + "value": "Preview Area" + }, + { + "name": "modify_file", + "value": "Modify file" + }, + { + "name": "remote_file", + "value": "Remote file list" + }, + { + "name": "copy_files", + "value": "Copy files" + }, + { + "name": "file_preview", + "value": "file preview" + }, + { + "name": "Save_modifications", + "value": "Save modifications" + }, + { + "name": "Success_copy", + "value": "Success copy" + }, + { + "name": "Please_select_a_file", + "value": "Please select a file" + }, + { + "name": "successfully_saved", + "value": "Successfully saved" + }, + { + "name": "distributed_data_sync_reason", + "value": "Allow data exchange between different devices" + }, + { + "name": "authorization_successful", + "value": "Authorization successful" + }, + { + "name": "reauthorization", + "value": "Authorization failed, please reauthorize" + }, + { + "name": "created_successfully", + "value": "Created successfully" + }, + { + "name": "file_name_and_file_content_cannot_be_empty", + "value": "The file name and file content cannot be empty" + }, + { + "name": "no_data_available_at_the_moment", + "value": "No data available at the moment" + }, + { + "name": "no_devices_currently_available", + "value": "No devices currently available" + }, + { + "name": "clean", + "value": "clean" + }, + { + "name": "sure", + "value": "sure" + }, + { + "name": "view_file", + "value": "view file" + }, + { + "name": "return_to_the_previous_page", + "value": "Return to the previous page" + }, + { + "name": "please_copy_the_file_first", + "value": "Please copy the file first" + }, + { + "name": "file_list", + "value": "file list" + }, + { + "name": "edit", + "value": "edit" + }, + { + "name": "delete", + "value": "delete" + }, + { + "name": "copy", + "value": "copy" + }, + { + "name": "delete_successfully", + "value": "Delete successfully" + }, + { + "name": "delete_file", + "value": "delete file" + }, + { + "name": "confirm_to_delete_the_file", + "value": "Confirm to delete the file" + }, + { + "name": "cancel", + "value": "cancel" + }, + { + "name": "confirm", + "value": "confirm" + }, + { + "name": "file_copying", + "value": "File copying" + }, + { + "name": "loading", + "value": "loading..." + }, + { + "name": "do_you_want_to_save_it", + "value": "Do you want to save it" + }, + { + "name": "confirm_to_save_the_file", + "value": "Confirm to save the file" + }, + { + "name": "please_create_the_file_first", + "value": "Please create the file first" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..43e7b4b96e1d5a366bf76a40c6a2428b5b7ec256 --- /dev/null +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,180 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模板描述" + }, + { + "name": "EntryAbility_desc", + "value": "EntryAbility描述" + }, + { + "name": "EntryAbility_label", + "value": "跨设备文件操作" + }, + { + "name": "create_a_file", + "value": "创建文件" + }, + { + "name": "display_local_file_list", + "value": "显示本地文件列表" + }, + { + "name": "display_remote_file_list", + "value": "显示远端文件列表" + }, + { + "name": "please_enter_a_file_name", + "value": "请输入文件名" + }, + { + "name": "please_enter_the_file_content", + "value": "请输入文件内容" + }, + { + "name": "local_file", + "value": "本地文件" + }, + { + "name": "preview_area", + "value": "预览文件" + }, + { + "name": "modify_file", + "value": "修改文件" + }, + { + "name": "remote_file", + "value": "远端文件" + }, + { + "name": "copy_files", + "value": "拷贝文件" + }, + { + "name": "file_preview", + "value": "文件预览" + }, + { + "name": "Save_modifications", + "value": "保存修改" + }, + { + "name": "Success_copy", + "value": "拷贝成功" + }, + { + "name": "Please_select_a_file", + "value": "请选择文件" + }, + { + "name": "successfully_saved", + "value": "保存成功" + }, + { + "name": "distributed_data_sync_reason", + "value": "允许不同设备间的数据交换" + }, + { + "name": "authorization_successful", + "value": "授权成功" + }, + { + "name": "reauthorization", + "value": "授权失败,请重新授权" + }, + { + "name": "created_successfully", + "value": "创建成功" + }, + { + "name": "file_name_and_file_content_cannot_be_empty", + "value": "文件名和文件内容不能为空" + }, + { + "name": "no_data_available_at_the_moment", + "value": "暂无数据" + }, + { + "name": "no_devices_currently_available", + "value": "暂无设备" + }, + { + "name": "clean", + "value": "清除" + }, + { + "name": "sure", + "value": "确定" + }, + { + "name": "view_file", + "value": "查看文件" + }, + { + "name": "return_to_the_previous_page", + "value": "返回上一页" + }, + { + "name": "please_copy_the_file_first", + "value": "请先复制文件" + }, + { + "name": "file_list", + "value": "文件列表" + }, + { + "name": "edit", + "value": "编辑" + }, + { + "name": "delete", + "value": "删除" + }, + { + "name": "copy", + "value": "复制" + }, + { + "name": "delete_successfully", + "value": "删除成功" + }, + { + "name": "delete_file", + "value": "删除文件" + }, + { + "name": "confirm_to_delete_the_file", + "value": "确定删除该文件" + }, + { + "name": "cancel", + "value": "取消" + }, + { + "name": "confirm", + "value": "确定" + }, + { + "name": "file_copying", + "value": "文件复制" + }, + { + "name": "loading", + "value": "正在加载..." + }, + { + "name": "do_you_want_to_save_it", + "value": "是否保存" + }, + { + "name": "confirm_to_save_the_file", + "value": "确定保存该文件" + }, + { + "name": "please_create_the_file_first", + "value": "请先创建文件" + } + ] +} \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c63e910373e5ead86612270e605a391acdc32ec9 --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,23 @@ +{ + "modelVersion": "5.1.1", + "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 */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "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..47113e2e36ecefde41c136272a0bd6ff745cffe4 --- /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. */ +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e7f813016776b0c479424fbd4685faf2238f2734 --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,8 @@ +{ + "modelVersion": "5.1.1", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + } +} diff --git a/screenshots/device/preview.gif b/screenshots/device/preview.gif new file mode 100644 index 0000000000000000000000000000000000000000..f03ae9e42250d05e225aae26d299f1af54ce1a79 Binary files /dev/null and b/screenshots/device/preview.gif differ