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 @@
+
+
\ 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