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..1cc1b52bf3d3558c7bf88ac8de732b2cd108a063
--- /dev/null
+++ b/AppScope/app.json5
@@ -0,0 +1,10 @@
+{
+ "app": {
+ "bundleName": "com.huawei.healthylife",
+ "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..769d112c40fc332ae35f00dc93d1f361919f8632
--- /dev/null
+++ b/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "健康生活"
+ }
+ ]
+}
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..87a9c3c4939d6188b231b6e108e80f885a879541
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/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..0210352ae2ade0dd7b4c841cb6e8ba08b4780038
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,78 @@
+ Copyright (c) 2023 Huawei Device Co., Ltd. All rights reserved.
+
+ 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.
+
+Apache License, Version 2.0
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
+1.You must give any other recipients of the Work or Derivative Works a copy of this License; and
+2.You must cause any modified files to carry prominent notices stating that You changed the files; and
+3.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
+4.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
+
+You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/README.md b/README.md
index ff17c66dc35814e1863728dffd751298b336e203..b2b9656cc4e8a6a9b65d3d8d081691b7441b1d8a 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,140 @@
-# HealthyLife
+# 健康生活应用
-#### 介绍
-{**以下是 Gitee 平台说明,您可以替换此简介**
-Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
-无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
+## 项目简介
-#### 软件架构
-软件架构说明
+利用ArkTS声明式开发范式和HarmonyOS的关系型数据库等能力,实现了一个健康生活应用。
+## 效果预览
-#### 安装教程
+| | | |
+|---------------------------------------------------------|---------------------------------------------------------|---------------------------------------------------------|
+| | | |
-1. xxxx
-2. xxxx
-3. xxxx
+## 使用说明
-#### 使用说明
+1. 用户可以创建最多6个健康生活任务(早起,喝水,吃苹果,每日微笑,刷牙,早睡),并设置任务目标。
+2. 用户可以在主页面对设置的健康生活任务进行打卡,其中早起、每日微笑、刷牙和早睡只需打卡一次即可完成任务,喝水、吃苹果需要根据任务目标量多次打卡完成。
+3. 主页可显示当天的健康生活任务完成进度,当天所有任务都打卡完成后,进度为100%,并且用户的连续打卡天数加一。
+4. 当用户连续打卡天数达到3、7、30、50、73、99天时,可以获得相应的成就。成就在获得时会以动画形式弹出,并可以在“成就”页面查看。
+5. 用户可以查看以前的健康生活任务完成情况。
+6. 打开应用,显示主页面,点击加号添加任务,添加完任务后,任务列表显示所有添加的任务。
+7. 应用退出到后台,长按应用,点击服务卡片,选择2x4卡片,添加到桌面,显示已添加任务。
+8. 应用退出到后台,长按应用,点击服务卡片,选择2x2卡片,添加到桌面,显示任务完成进度。
+9. 点击2x2或2x4元服务卡片,拉起主页面,看到任务列表。
+10. 在卡片配置文件中,设置卡片更新时间,更新时间到后,桌面上2x2或2x4卡片会重置第二天任务,需要重新添加。
+11. 用户可以设置通知提醒,仅限早起、早睡。
-1. xxxx
-2. xxxx
-3. xxxx
+## 工程目录
-#### 参与贡献
+```
+├───common/src/main/ets
+│ ├──constants
+│ │ ├──CommonConstants.ets // 通用常量
+│ │ └──RdbConstant.ets // RDB常量-数据库相关
+│ ├──database
+│ │ ├──tables
+│ │ │ ├──DayInfoApi.ets // 日期信息-数据库操作API
+│ │ │ ├──DayTaskInfoApi.ets // 当日任务信息-数据库操作API
+│ │ │ ├──FormInfoApi.ets // 服务卡片信息-数据库操作API
+│ │ │ ├──TableApi.ets // 数据库操作API接口
+│ │ │ └──TaskInfoApi.ets // 任务信息-数据库操作API
+│ │ └──RdbUtils.ets // 数据库操作通用工具类
+│ ├──model
+│ │ ├──database
+│ │ │ ├──DayInfo.ets // 日期信息
+│ │ │ ├──DayTaskInfo.ets // 当日任务信息
+│ │ │ ├──FormInfo.ets // 服务卡片信息
+│ │ │ └──TaskInfo.ets // 任务信息
+│ │ ├──ColumnModel.ets // 数据库表字段信息
+│ │ ├──FormStorageModel.ets // 服务卡片数据共享实体
+│ │ └──TaskBaseModel.ets // 单个任务基础信息
+│ └──utils
+│ ├──agent
+│ │ ├──AgentUtils.ets // 代理提醒工具栏
+│ │ └──RequestAuthorization.ets // 配置权限工具类
+│ ├──FormUtils.ets // 服务卡片工具类
+│ ├──PreferencesUtils.ets // 用户首选项工具类
+│ ├──PromptActionClass.ets // 自定义弹窗工具类
+│ └──Utils.ets
+└──common/src/main/resource
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
+├───healthylife/src/main/ets
+│ ├──healthyfileability
+│ │ └──HealthylifeAbility.ets // 模块入口类
+│ ├──model
+│ │ ├──AchievementModel.ets // 成就信息实体
+│ │ └──NavItemModel.ets // 应用tab实体
+│ ├──pages
+│ │ └──HealthyLifePage.ets // 应用入口界面
+│ ├──viewmodel
+│ │ ├──dialog // 自定义弹窗
+│ │ │ ├──AchievementDialogParams.ets // 成就
+│ │ │ ├──TargetSettingDialogParams.ets // 目标任务
+│ │ │ └──TaskInfoDialogParams.ets // 任务信息
+│ │ ├──AchievementStore.ets // 用户首选项存储用于成就同步
+│ │ └──HomeStore.ets // 数据库存储用于界面显示
+│ └──view
+│ ├──dialog // 自定义弹窗
+│ │ ├──AchievementDialog.ets // 成就
+│ │ ├──TargetSettingDialog.ets // 目标设置
+│ │ └──TaskClockCustomDialog.ets // 打卡界面
+│ ├──home
+│ │ ├──HomeTopComponent.ets // 目标进度组件
+│ │ ├──TaskListComponent.ets // 任务列表组件
+│ │ └──WeekCalendarComponent.ets // 周视图组件
+│ ├──mine
+│ │ ├──MineMenuComponent.ets // 菜单组件
+│ │ └──UserInfoComponent.ets // 用户信息组件
+│ ├──task
+│ │ ├──AddTaskComponent.ets // 添加任务组件
+│ │ └──EditTaskComponent.ets // 编辑任务组件
+│ ├──AchievementComponent.ets // 成就页面
+│ ├──HomeComponent.ets // 首页页面
+│ └──MineComponent.ets // 我的页面
+└──common/src/main/resource
+├───default/src/main/ets
+│ ├──agency
+│ │ └──pages
+│ │ └──AgencyCard.ets // 任务列表-服务卡片
+│ ├──defaultformability
+│ │ └──DefaultFormAbility.ets // 服务卡片入口
+│ ├──entryability
+│ │ └──EntryAbility.ets // 主程序入口
+│ ├──pages
+│ │ ├──AdvertisingPage.ets // 广告界面
+│ │ ├──Index.ets // 主界面
+│ │ └──SplashPage.ets // 开始屏幕界面
+│ ├──progress
+│ │ └──pages
+│ │ └──ProgressCard.ets // 任务进度-服务卡片
+│ └──view
+│ └──UserPrivacyDialog.ets // 用户隐私协议弹窗
+└──common/src/main/resource
+```
-#### 特技
+## 具体实现
-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/)
+- AppStorage:应用程序中的单例对象,为应用程序范围内的可变状态属性提供中央存储。
+- @Observed和@ObjectLink:@Observed适用于类,表示类中的数据变化由UI页面管理;@ObjectLink应用于被@Observed装饰类的对象。
+- @Provide和@Consume:@Provide作为数据提供者,可以更新子节点的数据,触发页面渲染。@Consume检测到@Provide数据更新后,会发起当前视图的重新渲染。
+- Flex:一个功能强大的容器组件,支持横向布局,竖向布局,子组件均分和流式换行布局。
+- List:List是很常用的滚动类容器组件之一,它按照水平或者竖直方向线性排列子组件, List的子组件必须是ListItem,它的宽度默认充满List的宽度。
+- TimePicker:TimePicker是选择时间的滑动选择器组件,默认以00:00至23:59的时间区创建滑动选择器。
+- Toggle: 组件提供勾选框样式、状态按钮样式及开关样式。
+- 关系型数据库:一种基于关系模型来管理数据的数据库。
+- 首选项:首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。
+- ArkTS卡片:卡片框架的运作机制分三大模块:卡片使用方、卡片管理服务和卡片提供方。
+ - 卡片使用方:负责卡片的创建、删除、请求更新以及卡片服务通信。
+ - 卡片管理服务:负责卡片的周期性刷新、卡片缓存管理、卡片生命周期管理以及卡片使用对象管理。
+ - 卡片提供方:提供卡片显示内容的应用,控制卡片的显示内容、控件布局以及控件点击事件。
+
+## 相关权限
+
+允许应用使用后台代理权限:ohos.permission.PUBLISH_AGENT_REMINDER
+
+## 约束与限制
+1. 本示例仅支持标准系统上运行,支持设备:华为手机。
+2. HarmonyOS系统:HarmonyOS 6.0.0 Release及以上。
+3. DevEco Studio版本:DevEco Studio 6.0.0 Release及以上。
+4. HarmonyOS SDK版本:HarmonyOS 6.0.0 Release SDK及以上。
diff --git a/build-profile.json5 b/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..94274780dc618df3e4ddb4f031ecbc94d6741e28
--- /dev/null
+++ b/build-profile.json5
@@ -0,0 +1,51 @@
+{
+ "app": {
+ "signingConfigs": [
+ ],
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ "targetSdkVersion": "6.0.0(20)",
+ "compatibleSdkVersion": "6.0.0(20)",
+ "runtimeOS": "HarmonyOS",
+ "buildOption": {
+ "strictMode": {
+ "caseSensitiveCheck": true,
+ "useNormalizedOHMUrl": true
+ }
+ }
+ }
+ ],
+ "buildModeSet": [
+ {
+ "name": "debug",
+ },
+ {
+ "name": "release"
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "default",
+ "srcPath": "./products/default",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ },
+ {
+ "name": "common",
+ "srcPath": "./commons/common",
+ },
+ {
+ "name": "healthylife",
+ "srcPath": "./features/healthylife",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/code-linter.json5 b/code-linter.json5
new file mode 100644
index 0000000000000000000000000000000000000000..073990fa45394e1f8e85d85418ee60a8953f9b99
--- /dev/null
+++ b/code-linter.json5
@@ -0,0 +1,32 @@
+{
+ "files": [
+ "**/*.ets"
+ ],
+ "ignore": [
+ "**/src/ohosTest/**/*",
+ "**/src/test/**/*",
+ "**/src/mock/**/*",
+ "**/node_modules/**/*",
+ "**/oh_modules/**/*",
+ "**/build/**/*",
+ "**/.preview/**/*"
+ ],
+ "ruleSet": [
+ "plugin:@performance/recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "rules": {
+ "@security/no-unsafe-aes": "error",
+ "@security/no-unsafe-hash": "error",
+ "@security/no-unsafe-mac": "warn",
+ "@security/no-unsafe-dh": "error",
+ "@security/no-unsafe-dsa": "error",
+ "@security/no-unsafe-ecdsa": "error",
+ "@security/no-unsafe-rsa-encrypt": "error",
+ "@security/no-unsafe-rsa-sign": "error",
+ "@security/no-unsafe-rsa-key": "error",
+ "@security/no-unsafe-dsa-key": "error",
+ "@security/no-unsafe-dh-key": "error",
+ "@security/no-unsafe-3des": "error"
+ }
+}
\ No newline at end of file
diff --git a/commons/common/.gitignore b/commons/common/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5
--- /dev/null
+++ b/commons/common/.gitignore
@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test
\ No newline at end of file
diff --git a/commons/common/BuildProfile.ets b/commons/common/BuildProfile.ets
new file mode 100644
index 0000000000000000000000000000000000000000..3a501e5ddee8ea6d28961648fc7dd314a5304bd4
--- /dev/null
+++ b/commons/common/BuildProfile.ets
@@ -0,0 +1,17 @@
+/**
+ * Use these variables when you tailor your ArkTS code. They must be of the const type.
+ */
+export const HAR_VERSION = '1.0.0';
+export const BUILD_MODE_NAME = 'debug';
+export const DEBUG = true;
+export const TARGET_NAME = 'default';
+
+/**
+ * BuildProfile Class is used only for compatibility purposes.
+ */
+export default class BuildProfile {
+ static readonly HAR_VERSION = HAR_VERSION;
+ static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
+ static readonly DEBUG = DEBUG;
+ static readonly TARGET_NAME = TARGET_NAME;
+}
\ No newline at end of file
diff --git a/commons/common/Index.ets b/commons/common/Index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..52ab7ea9d3f74b53ddf559cb7b7effb204b62227
--- /dev/null
+++ b/commons/common/Index.ets
@@ -0,0 +1,21 @@
+export { PickerType, CommonConstants } from './src/main/ets/constants/CommonConstants';
+
+export { RdbConstants } from './src/main/ets/constants/RdbConstants';
+
+export { TaskBaseInfo, taskBaseInfoList } from './src/main/ets/model/TaskBaseModel';
+
+export { DayInfo } from './src/main/ets/model/database/DayInfo';
+
+export { TaskInfo } from './src/main/ets/model/database/TaskInfo';
+
+export { FormInfo } from './src/main/ets/model/database/FormInfo';
+
+export { DayTaskInfo } from './src/main/ets/model/database/DayTaskInfo';
+
+export { PromptActionClass } from './src/main/ets/utils/PromptActionClass';
+
+export { formatSystemTime, convertDate2Str, convertStr2Date, convertTime2Date } from './src/main/ets/utils/Utils';
+
+export { AgentUtils } from './src/main/ets/utils/agent/AgentUtils';
+
+export { RequestAuthorization } from './src/main/ets/utils/agent/RequestAuthorization';
\ No newline at end of file
diff --git a/commons/common/build-profile.json5 b/commons/common/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..68c40f9141e428c02e8ecf13504dd02d2a76776d
--- /dev/null
+++ b/commons/common/build-profile.json5
@@ -0,0 +1,36 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ "resOptions": {
+ "copyCodeResource": {
+ "enable": false
+ }
+ }
+ },
+ "buildOptionSet": [
+ {
+ "name": "release",
+ "arkOptions": {
+ "obfuscation": {
+ "ruleOptions": {
+ "enable": false,
+ "files": [
+ "./obfuscation-rules.txt"
+ ]
+ },
+ "consumerFiles": [
+ "./consumer-rules.txt"
+ ]
+ }
+ },
+ },
+ ],
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest"
+ }
+ ]
+}
diff --git a/commons/common/consumer-rules.txt b/commons/common/consumer-rules.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/commons/common/hvigorfile.ts b/commons/common/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..805c5d7f6809c51cff0b4adcc1142979f8f864b6
--- /dev/null
+++ b/commons/common/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { harTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: harTasks, /* 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/commons/common/obfuscation-rules.txt b/commons/common/obfuscation-rules.txt
new file mode 100644
index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b
--- /dev/null
+++ b/commons/common/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/commons/common/oh-package.json5 b/commons/common/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..538a06bede356ebbbd0036c7645f9bd920d11bff
--- /dev/null
+++ b/commons/common/oh-package.json5
@@ -0,0 +1,9 @@
+{
+ "name": "common",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "Index.ets",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {}
+}
diff --git a/commons/common/src/main/ets/constants/CommonConstants.ets b/commons/common/src/main/ets/constants/CommonConstants.ets
new file mode 100644
index 0000000000000000000000000000000000000000..6d6684904ac3ccf29b7eecbf6bd2f6899c80d4e0
--- /dev/null
+++ b/commons/common/src/main/ets/constants/CommonConstants.ets
@@ -0,0 +1,130 @@
+/*
+ *
+ * * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ */
+
+export enum PickerType {
+ NONE = 'none',
+ TIME = 'time',
+ TEXT = 'text'
+}
+
+export class CommonConstants {
+ // thousandth
+ static readonly THOUSANDTH_15: string = '1.5%';
+ static readonly THOUSANDTH_33: string = '3.3%';
+ static readonly THOUSANDTH_50: string = '5%';
+ static readonly THOUSANDTH_66: string = '6.6%';
+ static readonly THOUSANDTH_80: string = '8%';
+ static readonly THOUSANDTH_100: string = '10%';
+ static readonly THOUSANDTH_150: string = '15%';
+ static readonly THOUSANDTH_333: string = '33.3%';
+ static readonly THOUSANDTH_400: string = '40%';
+ static readonly THOUSANDTH_420: string = '42%';
+ static readonly THOUSANDTH_500: string = '50%';
+ static readonly THOUSANDTH_560: string = '56%';
+ static readonly THOUSANDTH_800: string = '80%';
+ static readonly THOUSANDTH_830: string = '83%';
+ static readonly THOUSANDTH_880: string = '88%';
+ static readonly THOUSANDTH_900: string = '90%';
+ static readonly THOUSANDTH_940: string = '94%';
+ static readonly THOUSANDTH_1000: string = '100%';
+ static readonly PERCENTAGE_100_EXCEPT_7: string = `${100 / 7}%`;
+ // default
+ static readonly DEFAULT_0: number = 0;
+ static readonly DEFAULT_56: number = 56;
+ static readonly DEFAULT_100: number = 100;
+ static readonly DEFAULT_NEGATIVE_20: number = -20;
+ static readonly PERCENT: string = '%';
+ // circle radius
+ static readonly BORDER_RADIUS_PERCENT_50: string = '50%';
+ // symbol glyph click scale
+ static readonly SYMBOL_GLYPH_CLICK_SCALE: number = 0.5;
+ // zIndex
+ static readonly HOME_COMPONENT_Z_INDEX: number = 1;
+ // aspect ratio
+ static readonly DEFAULT_ASPECT_RATIO: number = 1;
+ // flex grow
+ static readonly FLEX_GROW_1: number = 1;
+ // duration
+ static readonly AD_DURATION: number = 3; // 3s
+ static readonly LAUNCHER_DELAY_TIME: number = 1000; // 2000ms
+ static readonly DURATION_100: number = 100; // 100ms
+ static readonly DURATION_200: number = 200; // 200ms
+ static readonly DURATION_800: number = 800; // 800ms
+ static readonly DURATION_1000: number = 1000; // 1000ms
+ // animate
+ static readonly ANIMATE_ANGLE_0: number = 0;
+ static readonly ANIMATE_ANGLE_360: number = 360;
+ static readonly ANIMATE_OPACITY_0: number = 0;
+ static readonly ANIMATE_OPACITY_1: number = 1;
+ static readonly ANIMATE_SCALE_0: number = 0;
+ static readonly ANIMATE_SCALE_1: number = 1;
+ static readonly ANIMATE_ROTATE_0: number = 0;
+ static readonly ANIMATE_ROTATE_1: number = 1;
+ static readonly ANIMATE_ITERATIONS_1: number = 1;
+ // font family
+ static readonly HARMONY_HEI_TI_MEDIUM: string = 'HarmonyHeiTi-Medium';
+ static readonly HARMONY_HEI_TI_BOLD: string = 'HarmonyHeiTi-Bold';
+ static readonly HARMONY_HEI_TI: string = 'HarmonyHeiTi';
+ static readonly HELVETICA: string = 'Helvetica';
+ static readonly PING_FANG_SC_REGULAR: string = 'PingFangSC-Regular';
+ // week info
+ static readonly WEEK_LEFT_LIMIT: number = 150;
+ static readonly WEEK_RIGHT_LIMIT: number = 500;
+ static readonly WEEK_DAY_NUM: number = 7;
+ static readonly WEEK_SHORTHAND_LIST: Resource[] = [
+ $r('app.string.week_shorthand_mon'),
+ $r('app.string.week_shorthand_tue'),
+ $r('app.string.week_shorthand_wed'),
+ $r('app.string.week_shorthand_thu'),
+ $r('app.string.week_shorthand_fri'),
+ $r('app.string.week_shorthand_sat'),
+ $r('app.string.week_shorthand_sun')
+ ];
+ // task info
+ static readonly TASK_NUM = 6;
+ static readonly PICKER_RANGE_DRINK: string[] = [
+ '0.5',
+ '1',
+ '1.5',
+ '2',
+ '2.5',
+ '3',
+ ];
+ static readonly PICKER_RANGE_APPLE: string[] = [
+ '1',
+ '2',
+ '3',
+ '4',
+ '5'
+ ];
+ // achievement info
+ static readonly ACHIEVEMENT_STORE: string = 'achievementStore';
+ static readonly MAX_CONSECUTIVE_DAYS: string = 'maxConsecutiveDays';
+ static readonly CURRENT_CONSECUTIVE_DAYS: string = 'currentConsecutiveDays';
+ static readonly CURRENT_DAY_STATUS: string = 'currentDayStatus';
+ // menu info
+ static readonly DEFAULT_MENU_INFO: Resource[] = [
+ $r('app.string.mine_personal_data'),
+ $r('app.string.mine_check_updates'),
+ $r('app.string.mine_about')
+ ];
+ // Card Constants
+ static readonly DEFAULT_DIMENSION_2X2 = 2;
+ static readonly DEFAULT_DIMENSION_2X4 = 3;
+ static readonly FORM_NAME_AGENCY = "agency";
+ static readonly FORM_NAME_PROGRESS = "progress";
+}
\ No newline at end of file
diff --git a/commons/common/src/main/ets/constants/RdbConstants.ets b/commons/common/src/main/ets/constants/RdbConstants.ets
new file mode 100644
index 0000000000000000000000000000000000000000..9bde70a1c8755e1533ebef0ff332d8aafde85aaf
--- /dev/null
+++ b/commons/common/src/main/ets/constants/RdbConstants.ets
@@ -0,0 +1,79 @@
+/*
+ *
+ * * 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 ColumnInfo from '../model/ColumnModel';
+
+export class RdbConstants {
+ // database name
+ static readonly DATABASE_NAME: string = 'HealthyLife.db';
+ // database table name of day info
+ static readonly TABLE_OF_DAY: string = 'dayInfo';
+ // database table name of task info
+ static readonly TABLE_OF_TASK: string = 'taskInfo';
+ // database table name of day and task info
+ static readonly TABLE_OF_DAY_TASK: string = 'dayTaskInfo';
+ // database table name of form info
+ static readonly TABLE_OF_FORM: string = 'formInfo';
+ // database table name list
+ static readonly TABLE_NAME_LIST: string[] = [
+ this.TABLE_OF_DAY,
+ this.TABLE_OF_TASK,
+ this.TABLE_OF_DAY_TASK,
+ this.TABLE_OF_FORM
+ ];
+ // column information for database tables of day info
+ static readonly COLUMNS_OF_DAY: ColumnInfo[] = [
+ new ColumnInfo('date', 'text', 8, false, true, false),
+ new ColumnInfo('targetTaskNum', 'integer', 2, false, false, false),
+ new ColumnInfo('finTaskNum', 'integer', 2, false, false, false)
+ ];
+ // column information for database tables of task info
+ static readonly COLUMNS_OF_TASK: ColumnInfo[] = [
+ new ColumnInfo('taskId', 'integer', 2, false, true, false),
+ new ColumnInfo('isOpen', 'boolean', -1, false, false, false),
+ new ColumnInfo('targetValue', 'text', -1, false, false, false),
+ new ColumnInfo('finValue', 'text', -1, false, false, false),
+ new ColumnInfo('isDone', 'boolean', -1, false, false, false),
+ new ColumnInfo('isAlert', 'boolean', -1, false, false, false),
+ new ColumnInfo('alertStartTime', 'text', -1, true, false, false),
+ new ColumnInfo('alertEndTime', 'text', -1, true, false, false),
+ new ColumnInfo('alertFrequency', 'text', -1, true, false, false),
+ new ColumnInfo('reminderId', 'integer', -1, true, false, false)
+ ];
+ // column information for database tables of day and task info
+ static readonly COLUMNS_OF_DAY_TASK: ColumnInfo[] = [
+ new ColumnInfo('id', 'integer', -1, false, true, true),
+ new ColumnInfo('date', 'text', 8, false, false, false),
+ new ColumnInfo('taskId', 'integer', 2, false, false, false),
+ new ColumnInfo('targetValue', 'text', -1, false, false, false),
+ new ColumnInfo('finValue', 'text', -1, false, false, false),
+ new ColumnInfo('isDone', 'boolean', -1, false, false, false)
+ ];
+ // column information for database tables of day info
+ static readonly COLUMNS_OF_FORM: ColumnInfo[] = [
+ new ColumnInfo('formId', 'text', -1, false, true, false),
+ new ColumnInfo('formName', 'text', -1, false, false, false),
+ new ColumnInfo('formDimension', 'text', -1, false, false, false)
+ ];
+ // database tables and columns information mapping
+ static readonly TABLE_COLUMNS_MAPPING: Record = {
+ 'dayInfo': this.COLUMNS_OF_DAY,
+ 'taskInfo': this.COLUMNS_OF_TASK,
+ 'dayTaskInfo': this.COLUMNS_OF_DAY_TASK,
+ 'formInfo': this.COLUMNS_OF_FORM
+ }
+}
\ No newline at end of file
diff --git a/commons/common/src/main/ets/database/RdbUtils.ets b/commons/common/src/main/ets/database/RdbUtils.ets
new file mode 100644
index 0000000000000000000000000000000000000000..a1773bb730b5ce667790d68314601927d196209a
--- /dev/null
+++ b/commons/common/src/main/ets/database/RdbUtils.ets
@@ -0,0 +1,118 @@
+/*
+ *
+ * * 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 { relationalStore } from '@kit.ArkData';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { RdbConstants } from '../constants/RdbConstants';
+
+// hilog tag.
+const TAG: string = 'RdbUtils';
+
+class RdbUtils {
+ // database connection instance.
+ rdbStore?: relationalStore.RdbStore;
+
+ /**
+ * The method of creating an RDBStore Instance.
+ */
+ async createRdb(context: Context): Promise {
+ const STORE_CONFIG: relationalStore.StoreConfig = {
+ name: RdbConstants.DATABASE_NAME,
+ securityLevel: relationalStore.SecurityLevel.S1
+ };
+ await relationalStore.getRdbStore(context, STORE_CONFIG).then((rdbStore: relationalStore.RdbStore) => {
+ this.rdbStore = rdbStore;
+ hilog.info(0x0000, TAG, 'Get RdbStore successfully.');
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Get RdbStore failed, code is ${err.code},message is ${err.message}`);
+ });
+ }
+
+ /**
+ * The method of creating a table.
+ * @param { string } tableName the table name that needs to be created
+ */
+ createTable(tableName: string): void {
+ if (!this.rdbStore) {
+ hilog.error(0x0000, TAG, 'RdbStore is undefined');
+ return;
+ }
+ let createSql = this.generateTableSql(tableName);
+ this.rdbStore.execute(createSql)
+ .then(() => {
+ hilog.info(0x0000, TAG, `Execute create ${tableName} table sql success`);
+ })
+ .catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Execute create ${tableName} table sql failed, code is ${err.code},message is ${err.message}`);
+ });
+ }
+
+ /**
+ * generate a SQL statement for creating a new table based on the table name
+ * @param { string } tableName the table name that needs to be created
+ * @returns { string } the generated SQL statement
+ */
+ private generateTableSql(tableName: string): string {
+ let columns = RdbConstants.TABLE_COLUMNS_MAPPING[tableName];
+ let sql = `create table if not exists ${tableName}(`;
+ for (let column of columns) {
+ sql = sql.concat(`${column.name} ${column.type}`);
+ sql = sql.concat(`${column.length && column.length > 0 ? `(${column.length})` : ''}`);
+ sql = sql.concat(`${column.primary ? ' primary key' : ''}`);
+ sql = sql.concat(`${column.autoincrement ? ' autoincrement' : ''}`);
+ sql = sql.concat(`${column.nullable ? '' : ' not null'}`);
+ sql = sql.concat(', ');
+ }
+ sql = `${sql.substring(0, sql.length - 2)})`;
+ hilog.info(0x0000, TAG, `The SQL statement generated based on the ${tableName} is '${sql}'`);
+ return sql;
+ }
+
+ /**
+ * The method of execute sql.
+ * @param { string } executeSql the sql statement that needs to be executed.
+ * @returns { Promise } returns whether the execution was successful.
+ */
+ async executeSql(executeSql: string): Promise {
+ let result = true;
+ await this.rdbStore?.executeSql(executeSql).then(() => {
+ hilog.info(0x0000, TAG, `Execute Sql is successful, sql is ${executeSql}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Execute Sql is failed, sql is ${executeSql}, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ /**
+ * The method of creating an RDBStore Instance.
+ */
+ async closeRdb(): Promise {
+ if (this.rdbStore) {
+ this.rdbStore.close().then(() => {
+ hilog.info(0x0000, TAG, 'Close RdbStore successfully.');
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Close RdbStore failed, code is ${err.code},message is ${err.message}`);
+ })
+ }
+ }
+}
+
+export default new RdbUtils();
\ No newline at end of file
diff --git a/commons/common/src/main/ets/database/tables/DayInfoApi.ets b/commons/common/src/main/ets/database/tables/DayInfoApi.ets
new file mode 100644
index 0000000000000000000000000000000000000000..f22d73a4c71627707dc5867d65fe6a188ac43ddf
--- /dev/null
+++ b/commons/common/src/main/ets/database/tables/DayInfoApi.ets
@@ -0,0 +1,311 @@
+/*
+ *
+ * * 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 { relationalStore } from '@kit.ArkData';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import RdbUtils from '../RdbUtils';
+import { TableApi } from './TableApi';
+import TaskInfoApi from './TaskInfoApi';
+import DayTaskInfoApi from './DayTaskInfoApi';
+import ColumnInfo from '../../model/ColumnModel';
+import { DayInfo } from '../../model/database/DayInfo';
+import { TaskInfo } from '../../model/database/TaskInfo';
+import { RdbConstants } from '../../constants/RdbConstants';
+import PreferencesUtils from '../../utils/PreferencesUtils';
+import { DayTaskInfo } from '../../model/database/DayTaskInfo';
+import { convertDate2Str, convertStr2Date } from '../../utils/Utils';
+import { CommonConstants as Const } from '../../constants/CommonConstants';
+import FormUtils from '../../utils/FormUtils';
+
+// hilog tag.
+const TAG: string = 'DayInfoApi';
+// this api table name.
+const TABLE_NAME: string = RdbConstants.TABLE_OF_DAY;
+// this api columns info.
+const COLUMNS_INFO: ColumnInfo[] = RdbConstants.TABLE_COLUMNS_MAPPING[TABLE_NAME];
+// this api columns name of primary key.
+const PRIMARY_KEY: string = COLUMNS_INFO[0].name;
+
+class DayInfoApi implements TableApi {
+ async insert(data: DayInfo): Promise {
+ let result: number = -1;
+ const valueBucket = this.convertEntity2ValuesBucket(data, false);
+ await RdbUtils.rdbStore?.insert(TABLE_NAME, valueBucket,
+ relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then((rowId: number) => {
+ if (rowId === -1) {
+ hilog.error(0x0000, TAG, `Insert is failed, data is ${data.toString()}`);
+ }
+ hilog.info(0x0000, TAG, `Insert is successful, data is ${data.toString()}`);
+ result = rowId;
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Insert is failed, data is ${data.toString()}, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async batchInsert(dataList: DayInfo[]): Promise {
+ let result: number = -1;
+ const valueBucketList: relationalStore.ValuesBucket[] = [];
+ for (let data of dataList) {
+ valueBucketList.push(this.convertEntity2ValuesBucket(data, false))
+ }
+ await RdbUtils.rdbStore?.batchInsert(TABLE_NAME, valueBucketList).then((count: number) => {
+ if (count === -1) {
+ hilog.error(0x0000, TAG, 'Batch insert is failed');
+ }
+ hilog.info(0x0000, TAG, 'Batch insert is successful');
+ result = count;
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Batch insert is failed, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async delete(primaryKey: string): Promise {
+ let result = true;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.delete(predicates).then((rows: number) => {
+ hilog.info(0x0000, TAG, `Delete is successful, primaryKey is ${primaryKey}, affected rows is ${rows}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Delete is failed, primaryKey is ${primaryKey}, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ async update(primaryKey: string, data: DayInfo): Promise {
+ let result = true;
+ const valueBucket = this.convertEntity2ValuesBucket(data, true);
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.update(valueBucket, predicates,
+ relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then(async (rows: number) => {
+ hilog.info(0x0000, TAG,
+ `Updated is successful, primaryKey is ${primaryKey}, data is ${data.toString()}, affected rows is ${rows}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Updated is failed, primaryKey is ${primaryKey}, data is ${data.toString()}, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ async query(querySql: string): Promise {
+ let resultList: DayInfo[] = [];
+ await RdbUtils.rdbStore?.querySql(querySql).then((resultSet: relationalStore.ResultSet) => {
+ while (resultSet.goToNextRow()) {
+ resultList.push(this.convertResultSet2Entity(resultSet));
+ }
+ resultSet.close();
+ hilog.info(0x0000, TAG, `Query is successful, querySql is [${querySql}], query count is ${resultList.length}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Query is failed, querySql is [${querySql}], code is ${err.code}, message is ${err.message}`);
+ });
+ return resultList;
+ }
+
+ async queryByKey(primaryKey: string): Promise {
+ let result: DayInfo | null = null;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.query(predicates).then((resultSet: relationalStore.ResultSet) => {
+ if (resultSet.rowCount === 1) {
+ resultSet.goToFirstRow()
+ result = this.convertResultSet2Entity(resultSet)
+ hilog.info(0x0000, TAG,
+ `Query is successful, primaryKey is ${primaryKey}, query result is ${result.toString()}`);
+ } else {
+ hilog.error(0x0000, TAG,
+ `Query is failed, primaryKey is ${primaryKey}, query count is ${resultSet.rowCount}`);
+ }
+ resultSet.close();
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Query is failed, primaryKey is ${primaryKey}, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async updateByKey(date: string, targetTaskNum: number, finTaskNum: number): Promise {
+ let result: boolean = true;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, date);
+ let valueBucket: relationalStore.ValuesBucket = {
+ 'targetTaskNum': targetTaskNum,
+ 'finTaskNum': finTaskNum,
+ }
+ await RdbUtils.rdbStore?.update(valueBucket, predicates).then(async () => {
+ hilog.info(0x0000, TAG,
+ `Updated is successful, date is ${date}, targetTaskNum is ${targetTaskNum}, finTaskNum is ${finTaskNum}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Updated failed, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ convertEntity2ValuesBucket(data: DayInfo, excludePrimary: boolean): relationalStore.ValuesBucket {
+ let valueBucket = {} as relationalStore.ValuesBucket;
+ for (let item of COLUMNS_INFO) {
+ if (excludePrimary && item.primary) {
+ continue;
+ }
+ let name = item.name;
+ switch (name) {
+ case 'date':
+ valueBucket[name] = data.date;
+ break;
+ case 'targetTaskNum':
+ valueBucket[name] = data.targetTaskNum;
+ break;
+ case 'finTaskNum':
+ valueBucket[name] = data.finTaskNum;
+ break;
+ default:
+ break;
+ }
+ }
+ return valueBucket;
+ }
+
+ convertResultSet2Entity(resultSet: relationalStore.ResultSet): DayInfo {
+ const date = resultSet.getValue(resultSet.getColumnIndex('date')) as string;
+ const targetTaskNum = resultSet.getValue(resultSet.getColumnIndex('targetTaskNum')) as number;
+ const finTaskNum = resultSet.getValue(resultSet.getColumnIndex('finTaskNum')) as number;
+ return new DayInfo(date, targetTaskNum, finTaskNum);
+ }
+
+ /**
+ * initialize the information of the day.
+ * @param { string } date date of the day.
+ * @returns { Promise } whether it was successful or not.
+ */
+ async initDayInfo(date: string): Promise {
+ let result = -1;
+ let currentDayInfo: DayInfo | null = await this.queryByKey(date);
+ let taskListOfOpen: TaskInfo[] = await TaskInfoApi.queryOpenedTaskInfo();
+
+ if (currentDayInfo) {
+ // current day info exist, check day and task info Whether it is complete.
+ let queryDayTaskSql = `SELECT * FROM dayTaskInfo WHERE date = ${date} ORDER BY taskId`;
+ let dayTaskList: DayTaskInfo[] = await DayTaskInfoApi.query(queryDayTaskSql);
+ if (taskListOfOpen.length !== dayTaskList.length) {
+ // day and task info partially missing, identify missing information and insert it.
+ let exitsIndex: number[] = [];
+ for (let dayTask of dayTaskList) {
+ exitsIndex.push(dayTask.taskId);
+ }
+
+ let missInfoList: DayTaskInfo[] = [];
+ for (let taskOfOpen of taskListOfOpen) {
+ if (exitsIndex.includes(taskOfOpen.taskId)) {
+ continue;
+ }
+ missInfoList.push(taskOfOpen.convert2DefaultDayTask(date));
+ }
+
+ result = await DayTaskInfoApi.batchInsert(missInfoList);
+ if (result === -1) {
+ hilog.error(0x0000, TAG, `Additional current day and task info failed`);
+ return false;
+ }
+ hilog.info(0x0000, TAG, `Additional current day and task info successed`);
+ }
+ return true;
+ }
+
+ // Current day info not exist, it means that the application is opened for the first time that day, and all tasks are initialized to not done.
+ await TaskInfoApi.initAllTask();
+
+ // current day info not exist, generate the default information of the day and insert.
+ result = await this.insert(new DayInfo(date, 0, 0));
+ if (result === -1) {
+ hilog.error(0x0000, TAG, `Init current day info failed, date is ${date}`);
+ return false;
+ }
+ hilog.info(0x0000, TAG, `Init current day info successed, date is ${date}`);
+
+ // After initializing the current day's information, add the opened task information to the current day's task information.
+ let dayTaskInfoList: DayTaskInfo[] = [];
+ for (let taskOfOpen of taskListOfOpen) {
+ dayTaskInfoList.push(taskOfOpen.convert2DefaultDayTask(date));
+ }
+ result = await DayTaskInfoApi.batchInsert(dayTaskInfoList)
+ if (result === -1) {
+ hilog.error(0x0000, TAG, `Init current day of task info failed`);
+ return false;
+ }
+ hilog.info(0x0000, TAG, `Init current day of task info successed`);
+
+ // After the task of the day is successfully added, the information of the day is updated synchronously.
+ await this.updateDayByTaskStatus(date);
+
+ return true;
+ }
+
+ /**
+ * Update the information of the day based on the task information of the day.
+ * @param { string } date date of the day.
+ * @returns { Promise } whether it was successful or not.
+ */
+ async updateDayByTaskStatus(date: string): Promise {
+ let taskListOfOpen: DayTaskInfo[] = await DayTaskInfoApi.queryDayTaskInfo(date);
+ let finTaskNum = 0;
+ for (let taskOfOpen of taskListOfOpen) {
+ if (taskOfOpen.isDone) {
+ finTaskNum++;
+ }
+ }
+ let result = await this.updateByKey(date, taskListOfOpen.length, finTaskNum);
+ // Update service card information synchronously when tasks change.
+ FormUtils.updateForms();
+ return result;
+ }
+
+ /**
+ * Initialize the achievement preference information based on the information of the day.
+ * @param { string } date date of the day.
+ * @returns { Promise } whether it was successful or not.
+ */
+ async initAchievementStoreByDayInfo(date: string): Promise {
+ const currentDayInfo: DayInfo | null = await this.queryByKey(date);
+ if (currentDayInfo) {
+ // If the information of the day is already there, it means that the initialization is complete, and it will be returned directly.
+ return true;
+ }
+
+ // The achievement completion status is false on the day of initialization.
+ await PreferencesUtils.putPreference(Const.CURRENT_DAY_STATUS, String(false));
+
+ // If the previous day's information exists and the task is not fully completed, the reset consecutive days record is 0.
+ let lastDate = convertStr2Date(date);
+ lastDate.setDate(lastDate.getDate() - 1);
+ const lastDateStr = convertDate2Str(lastDate);
+ const lastDayInfo: DayInfo | null = await this.queryByKey(lastDateStr);
+ if (lastDayInfo && lastDayInfo.finTaskNum < lastDayInfo.targetTaskNum) {
+ await PreferencesUtils.putPreference(Const.CURRENT_CONSECUTIVE_DAYS, '0');
+ }
+ return true;
+ }
+}
+
+export default new DayInfoApi();
\ No newline at end of file
diff --git a/commons/common/src/main/ets/database/tables/DayTaskInfoApi.ets b/commons/common/src/main/ets/database/tables/DayTaskInfoApi.ets
new file mode 100644
index 0000000000000000000000000000000000000000..6424fcd91a262c0a54f1782e6107c8d43da411c2
--- /dev/null
+++ b/commons/common/src/main/ets/database/tables/DayTaskInfoApi.ets
@@ -0,0 +1,239 @@
+/*
+ *
+ * * 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 { relationalStore } from '@kit.ArkData';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import RdbUtils from '../RdbUtils';
+import { TableApi } from './TableApi';
+import ColumnInfo from '../../model/ColumnModel';
+import { RdbConstants } from '../../constants/RdbConstants';
+import { DayTaskInfo } from '../../model/database/DayTaskInfo';
+
+// hilog tag
+const TAG: string = 'DayTaskInfoApi';
+// this api table name
+const TABLE_NAME: string = RdbConstants.TABLE_OF_DAY_TASK;
+// this api columns info
+const COLUMNS_INFO: ColumnInfo[] = RdbConstants.TABLE_COLUMNS_MAPPING[TABLE_NAME];
+// this api columns name of primary key
+const PRIMARY_KEY: string = COLUMNS_INFO[0].name;
+
+class DayTaskInfoApi implements TableApi {
+ async insert(data: DayTaskInfo): Promise {
+ let result: number = -1;
+ const valueBucket = this.convertEntity2ValuesBucket(data, true);
+ await RdbUtils.rdbStore?.insert(TABLE_NAME, valueBucket,
+ relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then((rowId: number) => {
+ if (rowId === -1) {
+ hilog.error(0x0000, TAG, `Insert is failed, data is ${data.toString()}`);
+ }
+ hilog.info(0x0000, TAG, `Insert is successful, data is ${data.toString()}`);
+ result = rowId;
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Insert is failed, data is ${data.toString()}, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async batchInsert(dataList: DayTaskInfo[]): Promise {
+ let result: number = -1;
+ const valueBucketList: relationalStore.ValuesBucket[] = [];
+ for (let data of dataList) {
+ valueBucketList.push(this.convertEntity2ValuesBucket(data, true))
+ }
+ await RdbUtils.rdbStore?.batchInsert(TABLE_NAME, valueBucketList).then((count: number) => {
+ if (count === -1) {
+ hilog.error(0x0000, TAG, 'Batch insert is failed');
+ }
+ hilog.info(0x0000, TAG, 'Batch insert is successful');
+ result = count;
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Batch insert is failed, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async delete(primaryKey: number): Promise {
+ let result = true;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.delete(predicates).then((rows: number) => {
+ hilog.info(0x0000, TAG, `Delete is successful, primaryKey is ${primaryKey}, affected rows is ${rows}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Delete is failed, primaryKey is ${primaryKey}, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ async update(primaryKey: number, data: DayTaskInfo): Promise {
+ let result = true;
+ const valueBucket = this.convertEntity2ValuesBucket(data, true);
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.update(valueBucket, predicates,
+ relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then(async (rows: number) => {
+ hilog.info(0x0000, TAG,
+ `Updated is successful, primaryKey is [${primaryKey}], data is ${data.toString()}, affected rows is ${rows}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Updated is failed, primaryKey is [${primaryKey}], data is ${data.toString()}, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ async query(querySql: string): Promise {
+ let resultList: DayTaskInfo[] = [];
+ await RdbUtils.rdbStore?.querySql(querySql).then((resultSet: relationalStore.ResultSet) => {
+ while (resultSet.goToNextRow()) {
+ resultList.push(this.convertResultSet2Entity(resultSet));
+ }
+ resultSet.close();
+ hilog.info(0x0000, TAG, `Query is successful, querySql is [${querySql}], query count is ${resultList.length}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Query is failed, querySql is [${querySql}], code is ${err.code}, message is ${err.message}`);
+ });
+ return resultList;
+ }
+
+ async queryByKey(primaryKey: number): Promise {
+ let result: DayTaskInfo | null = null;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.query(predicates).then((resultSet: relationalStore.ResultSet) => {
+ if (resultSet.rowCount === 1) {
+ resultSet.goToFirstRow()
+ result = this.convertResultSet2Entity(resultSet)
+ hilog.info(0x0000, TAG,
+ `Query is successful, primaryKey is ${primaryKey}, query result is ${result.toString()}`);
+ } else {
+ hilog.info(0x0000, TAG,
+ `Query is failed, primaryKey is ${primaryKey}, query count is ${resultSet.rowCount}`);
+ }
+ resultSet.close();
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Query is failed, primaryKey is ${primaryKey}, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ convertEntity2ValuesBucket(data: DayTaskInfo, excludePrimary: boolean): relationalStore.ValuesBucket {
+ let valueBucket = {} as relationalStore.ValuesBucket;
+ for (let item of COLUMNS_INFO) {
+ if (excludePrimary && item.primary) {
+ continue;
+ }
+ let name = item.name;
+ switch (name) {
+ case 'id':
+ valueBucket[name] = data.id;
+ break;
+ case 'date':
+ valueBucket[name] = data.date;
+ break;
+ case 'taskId':
+ valueBucket[name] = data.taskId;
+ break;
+ case 'targetValue':
+ valueBucket[name] = data.targetValue;
+ break;
+ case 'finValue':
+ valueBucket[name] = data.finValue;
+ break;
+ case 'isDone':
+ valueBucket[name] = data.isDone;
+ break;
+ default:
+ break;
+ }
+ }
+ return valueBucket;
+ }
+
+ convertResultSet2Entity(resultSet: relationalStore.ResultSet): DayTaskInfo {
+ const id = resultSet.getValue(resultSet.getColumnIndex('id')) as number;
+ const date = resultSet.getValue(resultSet.getColumnIndex('date')) as string;
+ const taskId = resultSet.getValue(resultSet.getColumnIndex('taskId')) as number;
+ const targetValue = resultSet.getValue(resultSet.getColumnIndex('targetValue')) as string;
+ const finValue = resultSet.getValue(resultSet.getColumnIndex('finValue')) as string;
+ const isDone = resultSet.getValue(resultSet.getColumnIndex('isDone')) ? true : false;
+ return new DayTaskInfo(id, date, taskId, targetValue, finValue, isDone);
+ }
+
+ /**
+ * Query the primary key of the task information for the day based on the date and task ID.
+ * @param { string } date date of the day.
+ * @param { number } taskId id of the task.
+ * @returns { Promise } primary key of the task information.
+ */
+ async queryKeyByDateAndTaskId(date: string, taskId: number): Promise {
+ let result: number = -1;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo('date', date);
+ predicates.equalTo('taskId', taskId);
+ await RdbUtils.rdbStore?.query(predicates).then((resultSet: relationalStore.ResultSet) => {
+ if (resultSet.rowCount === 1) {
+ resultSet.goToFirstRow()
+ result = resultSet.getValue(resultSet.getColumnIndex('id')) as number
+ hilog.info(0x0000, TAG,
+ `Query is successful, date is ${date}, taskId is ${taskId}, query result is ${result}`);
+ } else {
+ hilog.error(0x0000, TAG,
+ `Query is failed, date is ${date}, taskId is ${taskId}, query count is ${resultSet.rowCount}`);
+ }
+ resultSet.close();
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Query is failed, date is ${date}, taskId is ${taskId}, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ /**
+ * query the list of opened task information.
+ * @returns { Promise } opened task list.
+ */
+ async queryDayTaskInfo(date: string): Promise {
+ let result: DayTaskInfo[] = [];
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo('date', date);
+ predicates.orderByAsc('taskId')
+ await RdbUtils.rdbStore?.query(predicates).then((resultSet: relationalStore.ResultSet) => {
+ if (resultSet.rowCount === 0) {
+ hilog.info(0x0000, TAG, `Query dayTaskInfo is failed, query result is empty`);
+ resultSet.close();
+ return;
+ }
+ while (resultSet.goToNextRow()) {
+ result.push(this.convertResultSet2Entity(resultSet));
+ }
+ resultSet.close();
+ hilog.info(0x0000, TAG, `Query dayTaskInfo is successful, query result is ${result.toString()}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Query dayTaskInfo is failed, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+}
+
+export default new DayTaskInfoApi();
\ No newline at end of file
diff --git a/commons/common/src/main/ets/database/tables/FormInfoApi.ets b/commons/common/src/main/ets/database/tables/FormInfoApi.ets
new file mode 100644
index 0000000000000000000000000000000000000000..5020958cb9d9c86f34c9e8a1ab93dbf8442ced64
--- /dev/null
+++ b/commons/common/src/main/ets/database/tables/FormInfoApi.ets
@@ -0,0 +1,199 @@
+/*
+ *
+ * * 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 { relationalStore } from '@kit.ArkData';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import RdbUtils from '../RdbUtils';
+import { TableApi } from './TableApi';
+import ColumnInfo from '../../model/ColumnModel';
+import { FormInfo } from '../../model/database/FormInfo';
+import { RdbConstants } from '../../constants/RdbConstants';
+
+// hilog tag.
+const TAG: string = 'FormInfoApi';
+// this api table name.
+const TABLE_NAME: string = RdbConstants.TABLE_OF_FORM;
+// this api columns info.
+const COLUMNS_INFO: ColumnInfo[] = RdbConstants.TABLE_COLUMNS_MAPPING[TABLE_NAME];
+// this api columns name of primary key.
+const PRIMARY_KEY: string = COLUMNS_INFO[0].name;
+
+class FormInfoApi implements TableApi {
+ async insert(data: FormInfo): Promise {
+ let result: number = -1;
+ const valueBucket = this.convertEntity2ValuesBucket(data, false);
+ await RdbUtils.rdbStore?.insert(TABLE_NAME, valueBucket,
+ relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then((rowId: number) => {
+ if (rowId === -1) {
+ hilog.error(0x0000, TAG, `Insert is failed, data is ${data.toString()}`);
+ }
+ hilog.info(0x0000, TAG, `Insert is successful, data is ${data.toString()}`);
+ result = rowId;
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Insert is failed, data is ${data.toString()}, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async batchInsert(dataList: FormInfo[]): Promise {
+ let result: number = -1;
+ const valueBucketList: relationalStore.ValuesBucket[] = [];
+ for (let data of dataList) {
+ valueBucketList.push(this.convertEntity2ValuesBucket(data, false))
+ }
+ await RdbUtils.rdbStore?.batchInsert(TABLE_NAME, valueBucketList).then((count: number) => {
+ if (count === -1) {
+ hilog.error(0x0000, TAG, 'Batch insert is failed');
+ }
+ hilog.info(0x0000, TAG, 'Batch insert is successful');
+ result = count;
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Batch insert is failed, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async delete(primaryKey: string): Promise {
+ let result = true;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.delete(predicates).then((rows: number) => {
+ hilog.info(0x0000, TAG, `Delete is successful, primaryKey is ${primaryKey}, affected rows is ${rows}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Delete is failed, primaryKey is ${primaryKey}, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ async update(primaryKey: string, data: FormInfo): Promise {
+ let result = true;
+ const valueBucket = this.convertEntity2ValuesBucket(data, true);
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.update(valueBucket, predicates,
+ relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then(async (rows: number) => {
+ hilog.info(0x0000, TAG,
+ `Updated is successful, primaryKey is ${primaryKey}, data is ${data.toString()}, affected rows is ${rows}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Updated is failed, primaryKey is ${primaryKey}, data is ${data.toString()}, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ async query(querySql: string): Promise {
+ let resultList: FormInfo[] = [];
+ await RdbUtils.rdbStore?.querySql(querySql).then((resultSet: relationalStore.ResultSet) => {
+ while (resultSet.goToNextRow()) {
+ resultList.push(this.convertResultSet2Entity(resultSet));
+ }
+ resultSet.close();
+ hilog.info(0x0000, TAG, `Query is successful, querySql is [${querySql}], query count is ${resultList.length}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Query is failed, querySql is [${querySql}], code is ${err.code}, message is ${err.message}`);
+ });
+ return resultList;
+ }
+
+ async queryByKey(primaryKey: string): Promise {
+ let result: FormInfo | null = null;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.query(predicates).then((resultSet: relationalStore.ResultSet) => {
+ if (resultSet.rowCount === 1) {
+ resultSet.goToFirstRow()
+ result = this.convertResultSet2Entity(resultSet)
+ hilog.info(0x0000, TAG,
+ `Query is successful, primaryKey is ${primaryKey}, query result is ${result.toString()}`);
+ } else {
+ hilog.error(0x0000, TAG,
+ `Query is failed, primaryKey is ${primaryKey}, query count is ${resultSet.rowCount}`);
+ }
+ resultSet.close();
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Query is failed, primaryKey is ${primaryKey}, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async updateByKey(date: string, targetTaskNum: number, finTaskNum: number): Promise {
+ let result: boolean = true;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, date);
+ let valueBucket: relationalStore.ValuesBucket = {
+ 'targetTaskNum': targetTaskNum,
+ 'finTaskNum': finTaskNum,
+ }
+ await RdbUtils.rdbStore?.update(valueBucket, predicates).then(async () => {
+ hilog.info(0x0000, TAG,
+ `Updated is successful, date is ${date}, targetTaskNum is ${targetTaskNum}, finTaskNum is ${finTaskNum}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Updated failed, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ convertEntity2ValuesBucket(data: FormInfo, excludePrimary: boolean): relationalStore.ValuesBucket {
+ let valueBucket = {} as relationalStore.ValuesBucket;
+ for (let item of COLUMNS_INFO) {
+ if (excludePrimary && item.primary) {
+ continue;
+ }
+ let name = item.name;
+ switch (name) {
+ case 'formId':
+ valueBucket[name] = data.formId;
+ break;
+ case 'formName':
+ valueBucket[name] = data.formName;
+ break;
+ case 'formDimension':
+ valueBucket[name] = data.formDimension;
+ break;
+ default:
+ break;
+ }
+ }
+ return valueBucket;
+ }
+
+ convertResultSet2Entity(resultSet: relationalStore.ResultSet): FormInfo {
+ const formId = resultSet.getValue(resultSet.getColumnIndex('formId')) as string;
+ const formName = resultSet.getValue(resultSet.getColumnIndex('formName')) as string;
+ const formDimension = resultSet.getValue(resultSet.getColumnIndex('formDimension')) as number;
+ return new FormInfo(formId, formName, formDimension);
+ }
+
+ /**
+ * query all service card information.
+ * @returns { Promise } all service card information.
+ */
+ async queryAll(): Promise {
+ const querySql = 'SELECT * FROM formInfo';
+ return this.query(querySql);
+ }
+}
+
+export default new FormInfoApi();
\ No newline at end of file
diff --git a/commons/common/src/main/ets/database/tables/TableApi.ets b/commons/common/src/main/ets/database/tables/TableApi.ets
new file mode 100644
index 0000000000000000000000000000000000000000..29dccc8eb56bd5ce8288639159c15c40c966c403
--- /dev/null
+++ b/commons/common/src/main/ets/database/tables/TableApi.ets
@@ -0,0 +1,80 @@
+/*
+ *
+ * * 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 { relationalStore } from '@kit.ArkData';
+
+type PrimaryKeyType = number | string;
+
+export interface TableApi {
+ /**
+ * The method of inserting a data information.
+ * @param { T } data data entities that need to be inserted.
+ * @returns { Promise } Returns primary key information for inserting data, and -1 if it fails.
+ */
+ insert(data: T): Promise;
+
+ /**
+ * The method of batch inserting data information.
+ * @param { T[] } dataList data entities List that need to be inserted.
+ * @returns { Promise } Returns rows count for inserting data, and -1 if it fails.
+ */
+ batchInsert(dataList: T[]): Promise;
+
+ /**
+ * The method of deleting a data information.
+ * @param { PrimaryKeyType } primaryKey primary key of row that need to be deleted.
+ * @returns { Promise } returns the deleted result.
+ */
+ delete(primaryKey: PrimaryKeyType): Promise;
+
+ /**
+ * The method of updating a data information.
+ * @param { PrimaryKeyType } primaryKey primary key of row that need to be updated.
+ * @param { T } data data entities that need to be updated.
+ * @returns { Promise } returns the updated result.
+ */
+ update(primaryKey: PrimaryKeyType, data: T): Promise;
+
+ /**
+ * The method of querying a data information.
+ * @param { string } querySql the query sql statement that needs to be executed.
+ * @returns { Promise } returns the list of data that were queried.
+ */
+ query(querySql: string): Promise;
+
+ /**
+ * The method of querying a data information by primary key.
+ * @param { PrimaryKeyType } primaryKey primary key of row that need to be queried.
+ * @returns { Promise } returns the data that were queried.
+ */
+ queryByKey(primaryKey: PrimaryKeyType): Promise;
+
+ /**
+ * convert entity information to a ValuesBucket type.
+ * @param { T } data T entity information.
+ * @param { boolean } excludePrimary Whether to exclude the conversion of the primary key.
+ * @returns { relationalStore.ValuesBucket } ValuesBucket of T.
+ */
+ convertEntity2ValuesBucket(data: T, excludePrimary: boolean): relationalStore.ValuesBucket;
+
+ /**
+ * convert ResultSet to entity information.
+ * @param { relationalStore.ResultSet } resultSet a database result set.
+ * @returns { T } T entity information.
+ */
+ convertResultSet2Entity(resultSet: relationalStore.ResultSet): T;
+}
\ No newline at end of file
diff --git a/commons/common/src/main/ets/database/tables/TaskInfoApi.ets b/commons/common/src/main/ets/database/tables/TaskInfoApi.ets
new file mode 100644
index 0000000000000000000000000000000000000000..42c4f86f0a916ec49b48b06e6dc1717a8c002c52
--- /dev/null
+++ b/commons/common/src/main/ets/database/tables/TaskInfoApi.ets
@@ -0,0 +1,431 @@
+/*
+ *
+ * * 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 { relationalStore } from '@kit.ArkData';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import RdbUtils from '../RdbUtils';
+import DayInfoApi from './DayInfoApi';
+import { TableApi } from './TableApi';
+import DayTaskInfoApi from './DayTaskInfoApi';
+import ColumnInfo from '../../model/ColumnModel';
+import { RdbConstants } from '../../constants/RdbConstants';
+import { taskBaseInfoList } from '../../model/TaskBaseModel';
+import { CommonConstants as Const } from '../../constants/CommonConstants';
+import { defaultTaskInfoList, TaskInfo } from '../../model/database/TaskInfo';
+
+// hilog tag
+const TAG: string = 'TaskInfoApi';
+// this api table name
+const TABLE_NAME: string = RdbConstants.TABLE_OF_TASK;
+// this api columns info
+const COLUMNS_INFO: ColumnInfo[] = RdbConstants.TABLE_COLUMNS_MAPPING[TABLE_NAME];
+// this api columns name of primary key
+const PRIMARY_KEY: string = COLUMNS_INFO[0].name;
+
+class TaskInfoApi implements TableApi {
+ async insert(data: TaskInfo): Promise {
+ let result: number = -1;
+ const valueBucket = this.convertEntity2ValuesBucket(data, false);
+ await RdbUtils.rdbStore?.insert(TABLE_NAME, valueBucket,
+ relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then((rowId: number) => {
+ if (rowId === -1) {
+ hilog.error(0x0000, TAG, `Insert is failed, data is ${data.toString()}`);
+ }
+ hilog.info(0x0000, TAG, `Insert is successful, data is ${data.toString()}`);
+ result = rowId;
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Insert is failed, data is ${data.toString()}, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async batchInsert(dataList: TaskInfo[]): Promise {
+ let result: number = -1;
+ const valueBucketList: relationalStore.ValuesBucket[] = [];
+ for (let data of dataList) {
+ valueBucketList.push(this.convertEntity2ValuesBucket(data, false))
+ }
+ await RdbUtils.rdbStore?.batchInsert(TABLE_NAME, valueBucketList).then((count: number) => {
+ if (count === -1) {
+ hilog.error(0x0000, TAG, 'Batch insert is failed');
+ }
+ hilog.info(0x0000, TAG, 'Batch insert is successful');
+ result = count;
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Batch insert is failed, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ async delete(primaryKey: number): Promise {
+ let result = true;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.delete(predicates).then((rows: number) => {
+ hilog.info(0x0000, TAG, `Delete is successful, primaryKey is ${primaryKey}, affected rows is ${rows}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Delete is failed, primaryKey is ${primaryKey}, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ async update(primaryKey: number, data: TaskInfo): Promise {
+ let result = true;
+ const valueBucket = this.convertEntity2ValuesBucket(data, true);
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.update(valueBucket, predicates,
+ relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then(async (rows: number) => {
+ hilog.info(0x0000, TAG,
+ `Updated is successful, primaryKey is ${primaryKey}, data is ${data.toString()}, affected rows is ${rows}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Updated is failed, primaryKey is ${primaryKey}, data is ${data.toString()}, code is ${err.code}, message is ${err.message}`);
+ result = false;
+ });
+ return result;
+ }
+
+ async query(querySql: string): Promise {
+ let resultList: TaskInfo[] = [];
+ await RdbUtils.rdbStore?.querySql(querySql).then((resultSet: relationalStore.ResultSet) => {
+ while (resultSet.goToNextRow()) {
+ resultList.push(this.convertResultSet2Entity(resultSet));
+ }
+ resultSet.close();
+ hilog.info(0x0000, TAG, `Query is successful, querySql is [${querySql}], query count is ${resultList.length}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Query is failed, querySql is [${querySql}], code is ${err.code}, message is ${err.message}`);
+ });
+ return resultList;
+ }
+
+ async queryByKey(primaryKey: number): Promise {
+ let result: TaskInfo | null = null;
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo(PRIMARY_KEY, primaryKey);
+ await RdbUtils.rdbStore?.query(predicates).then((resultSet: relationalStore.ResultSet) => {
+ if (resultSet.rowCount === 1) {
+ resultSet.goToFirstRow()
+ result = this.convertResultSet2Entity(resultSet)
+ hilog.info(0x0000, TAG,
+ `Query is successful, primaryKey is ${primaryKey}, query result is ${result.toString()}`);
+ } else {
+ hilog.error(0x0000, TAG, `Query is failed, primaryKey is ${primaryKey}, query count is ${resultSet.rowCount}`);
+ }
+ resultSet.close();
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Query is failed, primaryKey is ${primaryKey}, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ convertEntity2ValuesBucket(data: TaskInfo, excludePrimary: boolean): relationalStore.ValuesBucket {
+ let valueBucket = {} as relationalStore.ValuesBucket;
+ for (let item of COLUMNS_INFO) {
+ if (excludePrimary && item.primary) {
+ continue;
+ }
+ let name = item.name;
+ switch (name) {
+ case 'taskId':
+ valueBucket[name] = data.taskId;
+ break;
+ case 'isOpen':
+ valueBucket[name] = data.isOpen;
+ break;
+ case 'targetValue':
+ valueBucket[name] = data.targetValue;
+ break;
+ case 'finValue':
+ valueBucket[name] = data.finValue;
+ break;
+ case 'isDone':
+ valueBucket[name] = data.isDone;
+ break;
+ case 'isAlert':
+ valueBucket[name] = data.isAlert;
+ break;
+ case 'alertStartTime':
+ valueBucket[name] = data.alertStartTime;
+ break;
+ case 'alertEndTime':
+ valueBucket[name] = data.alertEndTime;
+ break;
+ case 'alertFrequency':
+ valueBucket[name] = data.alertFrequency;
+ break;
+ case 'reminderId':
+ valueBucket[name] = data.reminderId;
+ break;
+ default:
+ break;
+ }
+ }
+ return valueBucket;
+ }
+
+ convertResultSet2Entity(resultSet: relationalStore.ResultSet): TaskInfo {
+ const taskId = resultSet.getValue(resultSet.getColumnIndex('taskId')) as number;
+ const isOpen = resultSet.getValue(resultSet.getColumnIndex('isOpen')) ? true : false;
+ const targetValue = resultSet.getValue(resultSet.getColumnIndex('targetValue')) as string;
+ const finValue = resultSet.getValue(resultSet.getColumnIndex('finValue')) as string;
+ const isDone = resultSet.getValue(resultSet.getColumnIndex('isDone')) ? true : false;
+ const isAlert = resultSet.getValue(resultSet.getColumnIndex('isAlert')) ? true : false;
+ const alertStartTime = resultSet.getValue(resultSet.getColumnIndex('alertStartTime')) as string;
+ const alertEndTime = resultSet.getValue(resultSet.getColumnIndex('alertEndTime')) as string;
+ const alertFrequency = resultSet.getValue(resultSet.getColumnIndex('alertFrequency')) as string;
+ const reminderId = resultSet.getValue(resultSet.getColumnIndex('reminderId')) as number;
+ return new TaskInfo(taskId, isOpen, targetValue, finValue, isDone, isAlert, alertStartTime, alertEndTime,
+ alertFrequency, reminderId);
+ }
+
+ /**
+ * check if the default task exists and complete it.
+ * @returns { Promise } check if it was successful.
+ */
+ async checkDefaultTask(): Promise {
+ let taskList: TaskInfo[] = await this.query('SELECT * FROM taskInfo');
+ if (taskList.length === Const.TASK_NUM) {
+ return true;
+ }
+ let exitsIndex: number[] = [];
+ for (let task of taskList) {
+ exitsIndex.push(task.taskId);
+ }
+ let missInfoList: TaskInfo[] = [];
+ for (let index = 0; index < Const.TASK_NUM; index++) {
+ if (exitsIndex.includes(index)) {
+ continue;
+ }
+ missInfoList.push(defaultTaskInfoList[index]);
+ }
+ await this.batchInsert(missInfoList);
+ return true;
+ }
+
+ /**
+ * query the list of task information.
+ * @returns { Promise } task list.
+ */
+ async queryAllTaskInfo(): Promise {
+ let result: TaskInfo[] = [];
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.orderByAsc(PRIMARY_KEY)
+ await RdbUtils.rdbStore?.query(predicates).then((resultSet: relationalStore.ResultSet) => {
+ if (resultSet.rowCount === 0) {
+ hilog.info(0x0000, TAG, `Query all task is failed, query result is empty`);
+ resultSet.close();
+ return;
+ }
+ while (resultSet.goToNextRow()) {
+ result.push(this.convertResultSet2Entity(resultSet));
+ }
+ resultSet.close();
+ hilog.info(0x0000, TAG, `Query all task is successful, query result is ${result.toString()}`);
+ resultSet.close();
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Query all task is failed, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ /**
+ * query the list of opened task information.
+ * @returns { Promise } opened task list.
+ */
+ async queryOpenedTaskInfo(): Promise {
+ let result: TaskInfo[] = [];
+ let predicates = new relationalStore.RdbPredicates(TABLE_NAME);
+ predicates.equalTo('isOpen', true);
+ predicates.orderByAsc(PRIMARY_KEY)
+ await RdbUtils.rdbStore?.query(predicates).then((resultSet: relationalStore.ResultSet) => {
+ if (resultSet.rowCount === 0) {
+ hilog.info(0x0000, TAG, `Query opened task is failed, query result is empty`);
+ resultSet.close();
+ return;
+ }
+ while (resultSet.goToNextRow()) {
+ result.push(this.convertResultSet2Entity(resultSet));
+ }
+ resultSet.close();
+ hilog.info(0x0000, TAG, `Query opened task is successful, query result is ${result.toString()}`);
+ resultSet.close();
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG, `Query opened task is failed, code is ${err.code}, message is ${err.message}`);
+ });
+ return result;
+ }
+
+ /**
+ * initialize all tasks to an incomplete state.
+ * @returns { Promise } initialization succeeded.
+ */
+ async initAllTask(): Promise {
+ let initSql = `UPDATE ${TABLE_NAME} SET isDone = false, finValue = ""`
+ let result = await RdbUtils.executeSql(initSql);
+ if (result) {
+ hilog.info(0x0000, TAG, `Init all task is successful`);
+ } else {
+ hilog.error(0x0000, TAG, `Init all task is failed`);
+ }
+ return result;
+ }
+
+ /**
+ * Open the task and update the relevant information synchronously.
+ * @param { string } date date of the day.
+ * @param { number } taskId id of the task.
+ * @returns { Promise } whether it was successful or not.
+ */
+ async openTask(date: string, taskId: number): Promise {
+ // Determine whether the task is open.
+ let oldTaskInfo = await this.queryByKey(taskId);
+ if (!oldTaskInfo) {
+ return false;
+ }
+ if (oldTaskInfo.isOpen) {
+ hilog.info(0x0000, TAG, `The current task is opened and cannot be open, taskId is ${taskId}`);
+ return true;
+ }
+
+ // modify the current task to open.
+ let result = true;
+ let openTaskSql = `UPDATE ${TABLE_NAME} SET isOpen = true, finValue = "" WHERE taskId = ${taskId}`
+ result = await RdbUtils.executeSql(openTaskSql);
+ if (!result) {
+ return result;
+ }
+
+ // Synchronously add the task information records of the day.
+ await DayTaskInfoApi.queryKeyByDateAndTaskId(date, taskId).then(async (key: number) => {
+ if (key === -1) {
+ let taskInfo = await this.queryByKey(taskId);
+ if (!taskInfo) {
+ return;
+ }
+ await DayTaskInfoApi.insert(taskInfo.convert2DefaultDayTask(date));
+ }
+ })
+ if (!result) {
+ return result;
+ }
+
+ // Synchronously modify the number of information tasks for the day.
+ result = await DayInfoApi.updateDayByTaskStatus(date);
+
+ return result;
+ }
+
+ /**
+ * Open the task and update the relevant information synchronously.
+ * @param { string } date date of the day.
+ * @param { number } taskId id of the task.
+ * @returns { Promise } whether it was successful or not.
+ */
+ async closeTask(date: string, taskId: number): Promise {
+ // Determine whether the task is open.
+ let oldTaskInfo = await this.queryByKey(taskId);
+ if (!oldTaskInfo) {
+ return false;
+ }
+ if (!oldTaskInfo.isOpen) {
+ hilog.info(0x0000, TAG, `The current task is closed and cannot be close, taskId is ${taskId}`);
+ return true;
+ }
+
+ let result = true;
+ // modify the current task to close.
+ let openTaskSql = `UPDATE ${TABLE_NAME} SET isOpen = false WHERE taskId = ${taskId}`
+ result = await RdbUtils.executeSql(openTaskSql);
+ if (!result) {
+ return result;
+ }
+
+ // If this task is not done, synchronously delete the task information records of the day and taskId.
+ if (!oldTaskInfo.isDone) {
+ let key = await DayTaskInfoApi.queryKeyByDateAndTaskId(date, taskId);
+ result = await DayTaskInfoApi.delete(key);
+ if (!result) {
+ return result;
+ }
+ }
+
+ // Synchronously modify the number of information tasks for the day.
+ result = await DayInfoApi.updateDayByTaskStatus(date);
+
+ return result;
+ }
+
+ /**
+ * Clock the task and update the relevant information synchronously.
+ * @param { string } date date of the day.
+ * @param { number } taskId id of the task.
+ * @returns { Promise } whether it was successful or not.
+ */
+ async clockTask(date: string, taskId: number): Promise {
+ // Determine whether the task is done.
+ let taskInfo = await this.queryByKey(taskId);
+ if (!taskInfo) {
+ return false;
+ }
+ if (!taskInfo.isOpen) {
+ hilog.info(0x0000, TAG, `The current task is not open and cannot be clock, taskId is ${taskInfo.taskId}`);
+ return true;
+ }
+ if (taskInfo.isDone) {
+ hilog.info(0x0000, TAG, `The current task is done and cannot be clock, taskId is ${taskInfo.taskId}`);
+ return true;
+ }
+
+ let result = true;
+ /*
+ * Step 0 means that the task is a one-time task and can be completed directly.
+ * Otherwise, you need to add the step value to determine whether it is completed.
+ */
+ let step = taskBaseInfoList[taskId].step;
+ if (step === 0) {
+ taskInfo.finValue = taskInfo.targetValue;
+ taskInfo.isDone = true;
+ } else {
+ let finValue = Number(taskInfo.finValue) + step;
+ taskInfo.finValue = finValue.toString();
+ taskInfo.isDone = finValue >= Number(taskInfo.targetValue);
+ }
+
+ result = await this.update(taskId, taskInfo);
+ if (!result) {
+ return result;
+ }
+
+ let key = await DayTaskInfoApi.queryKeyByDateAndTaskId(date, taskId);
+ result = await DayTaskInfoApi.update(key, taskInfo.convert2DayTask(date));
+ if (!result) {
+ return result;
+ }
+ // Synchronously modify the information for the day info.
+ return await DayInfoApi.updateDayByTaskStatus(date);
+ }
+}
+
+export default new TaskInfoApi();
\ No newline at end of file
diff --git a/commons/common/src/main/ets/model/ColumnModel.ets b/commons/common/src/main/ets/model/ColumnModel.ets
new file mode 100644
index 0000000000000000000000000000000000000000..9a7456c78f00b1879ae6bb31f18f8a14968c2d56
--- /dev/null
+++ b/commons/common/src/main/ets/model/ColumnModel.ets
@@ -0,0 +1,38 @@
+/*
+ *
+ * * 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.
+ *
+ */
+
+/**
+ * Define field information to control and create tables.
+ */
+export default class ColumnInfo {
+ name: string;
+ type: string;
+ length?: number;
+ nullable?: boolean;
+ primary?: boolean;
+ autoincrement?: boolean;
+
+ constructor(name: string, type: string, length?: number, nullable?: boolean, primary?: boolean,
+ autoincrement?: boolean) {
+ this.name = name;
+ this.type = type;
+ this.primary = primary;
+ this.length = length;
+ this.nullable = nullable;
+ this.autoincrement = autoincrement;
+ }
+}
\ No newline at end of file
diff --git a/commons/common/src/main/ets/model/FormStorageModel.ets b/commons/common/src/main/ets/model/FormStorageModel.ets
new file mode 100644
index 0000000000000000000000000000000000000000..409fa57cf2dae09e4c0939a59b4c594e64730bc3
--- /dev/null
+++ b/commons/common/src/main/ets/model/FormStorageModel.ets
@@ -0,0 +1,32 @@
+/*
+ *
+ * * 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 { DayTaskInfo } from './database/DayTaskInfo';
+
+/**
+ * service card interaction data.
+ */
+export default class FormStorageModel {
+ // the total number of tasks for the day.
+ targetTaskNum?: number;
+ // the number of tasks completed on the day.
+ finTaskNum?: number;
+ // percentage of tasks completed on the same day.
+ percentage?: number;
+ // list of tasks for the day.
+ dayTaskInfos?: DayTaskInfo[];
+}
\ No newline at end of file
diff --git a/commons/common/src/main/ets/model/TaskBaseModel.ets b/commons/common/src/main/ets/model/TaskBaseModel.ets
new file mode 100644
index 0000000000000000000000000000000000000000..79de1ac20aa94a361ec88506775d18493bee7291
--- /dev/null
+++ b/commons/common/src/main/ets/model/TaskBaseModel.ets
@@ -0,0 +1,58 @@
+/*
+ *
+ * * 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 { CommonConstants as Const, PickerType } from '../constants/CommonConstants';
+
+export class TaskBaseInfo {
+ id: number;
+ name: Resource;
+ icon: Resource;
+ dialog: Resource;
+ unit: ResourceStr;
+ step: number;
+ limit: string;
+ pickerType: PickerType;
+ pickerRange: string[];
+
+ constructor(id: number, name: Resource, icon: Resource, dialog: Resource, unit: ResourceStr, step: number,
+ range: string, pickerType: PickerType, pickerRange: string[]) {
+ this.id = id;
+ this.name = name;
+ this.icon = icon;
+ this.dialog = dialog;
+ this.unit = unit;
+ this.step = step;
+ this.limit = range;
+ this.pickerType = pickerType;
+ this.pickerRange = pickerRange;
+ }
+}
+
+export const taskBaseInfoList: TaskBaseInfo[] = [
+ new TaskBaseInfo(0, $r('app.string.task_getup'), $r('app.media.ic_task_getup'),
+ $r('app.media.ic_dialog_getup'), '', 0, '06:00 - 09:00', PickerType.TIME, []),
+ new TaskBaseInfo(1, $r('app.string.task_drink'), $r('app.media.ic_task_drink'),
+ $r('app.media.ic_dialog_drink'), $r('app.string.unit_liter'), 0.5, '', PickerType.TEXT, Const.PICKER_RANGE_DRINK),
+ new TaskBaseInfo(2, $r('app.string.task_apple'), $r('app.media.ic_task_apple'),
+ $r('app.media.ic_dialog_apple'), $r('app.string.unit_number'), 1, '', PickerType.TEXT, Const.PICKER_RANGE_APPLE),
+ new TaskBaseInfo(3, $r('app.string.task_smile'), $r('app.media.ic_task_smile'),
+ $r('app.media.ic_dialog_smile'), $r('app.string.unit_times'), 0, '', PickerType.NONE, []),
+ new TaskBaseInfo(4, $r('app.string.task_brush'), $r('app.media.ic_task_brush'),
+ $r('app.media.ic_dialog_brush'), $r('app.string.unit_times'), 0, '', PickerType.NONE, []),
+ new TaskBaseInfo(5, $r('app.string.task_sleep'), $r('app.media.ic_task_sleep'),
+ $r('app.media.ic_dialog_sleep'), '', 0, '20:00 - 23:00', PickerType.TIME, [])
+]
\ No newline at end of file
diff --git a/commons/common/src/main/ets/model/database/DayInfo.ets b/commons/common/src/main/ets/model/database/DayInfo.ets
new file mode 100644
index 0000000000000000000000000000000000000000..6ff67477d74133b9da9449f289875153995b42bc
--- /dev/null
+++ b/commons/common/src/main/ets/model/database/DayInfo.ets
@@ -0,0 +1,55 @@
+/*
+ *
+ * * 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 { CommonConstants as Const } from '../../constants/CommonConstants';
+
+export class DayInfo {
+ date: string;
+ targetTaskNum: number;
+ finTaskNum: number;
+
+ constructor(date: string, targetTaskNum: number, finTaskNum: number) {
+ this.date = date;
+ this.targetTaskNum = targetTaskNum;
+ this.finTaskNum = finTaskNum;
+ }
+
+ toString(): string {
+ return `DayInfo[` +
+ `date: ${this.date}, ` +
+ `targetTaskNum: ${this.targetTaskNum}, ` +
+ `finTaskNum: ${this.finTaskNum}` +
+ `]`;
+ }
+
+ calculatePercentage(): number {
+ if (this.targetTaskNum === 0) {
+ return 0;
+ }
+ return Math.ceil(this.finTaskNum / this.targetTaskNum * Const.DEFAULT_100);
+ }
+
+ getProgressImg(): Resource {
+ if (this.finTaskNum === 0 || this.targetTaskNum === 0) {
+ return $r('app.media.ic_home_undone');
+ }
+ if (this.finTaskNum < this.targetTaskNum) {
+ return $r('app.media.ic_home_half_done');
+ }
+ return $r('app.media.ic_home_all_done');
+ }
+}
\ No newline at end of file
diff --git a/commons/common/src/main/ets/model/database/DayTaskInfo.ets b/commons/common/src/main/ets/model/database/DayTaskInfo.ets
new file mode 100644
index 0000000000000000000000000000000000000000..eafdb25159cad4c5873c849fb2a3257d8f3268e8
--- /dev/null
+++ b/commons/common/src/main/ets/model/database/DayTaskInfo.ets
@@ -0,0 +1,45 @@
+/*
+ *
+ * * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ */
+
+export class DayTaskInfo {
+ id: number;
+ date: string;
+ taskId: number;
+ targetValue: string;
+ finValue: string;
+ isDone: boolean;
+
+ constructor(id: number, date: string, taskId: number, targetValue: string, finValue: string, isDone: boolean) {
+ this.id = id;
+ this.date = date;
+ this.taskId = taskId;
+ this.targetValue = targetValue;
+ this.finValue = finValue;
+ this.isDone = isDone;
+ }
+
+ toString(): string {
+ return `DayTaskInfo[` +
+ `id: ${this.id}, ` +
+ `date: ${this.date}, ` +
+ `taskId: ${this.taskId}, ` +
+ `targetValue: ${this.targetValue}, ` +
+ `finValue: ${this.finValue}, ` +
+ `isDone: ${this.isDone}` +
+ `]`;
+ }
+}
\ No newline at end of file
diff --git a/commons/common/src/main/ets/model/database/FormInfo.ets b/commons/common/src/main/ets/model/database/FormInfo.ets
new file mode 100644
index 0000000000000000000000000000000000000000..e0a80a4da00059fdc5f42c244b826420f877f11b
--- /dev/null
+++ b/commons/common/src/main/ets/model/database/FormInfo.ets
@@ -0,0 +1,36 @@
+/*
+ *
+ * * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ */
+
+export class FormInfo {
+ formId: string;
+ formName: string;
+ formDimension: number;
+
+ constructor(formId: string, formName: string, formDimension: number) {
+ this.formId = formId;
+ this.formName = formName;
+ this.formDimension = formDimension;
+ }
+
+ toString(): string {
+ return `FormInfo[` +
+ `formId: ${this.formId}, ` +
+ `formName: ${this.formName}, ` +
+ `formDimension: ${this.formDimension}` +
+ `]`;
+ }
+}
\ No newline at end of file
diff --git a/commons/common/src/main/ets/model/database/TaskInfo.ets b/commons/common/src/main/ets/model/database/TaskInfo.ets
new file mode 100644
index 0000000000000000000000000000000000000000..f6e0c0941261fe842744f65e3fef14949c33d76b
--- /dev/null
+++ b/commons/common/src/main/ets/model/database/TaskInfo.ets
@@ -0,0 +1,105 @@
+/*
+ *
+ * * 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 { DayTaskInfo } from './DayTaskInfo';
+
+export class TaskInfo {
+ taskId: number;
+ isOpen: boolean;
+ targetValue: string;
+ finValue: string;
+ isDone: boolean;
+ isAlert: boolean;
+ alertStartTime: string;
+ alertEndTime: string;
+ alertFrequency: string;
+ reminderId: number;
+
+ constructor(taskId: number, isOpen: boolean, targetValue: string, finValue: string, isDone: boolean, isAlert: boolean,
+ alertStartTime: string, alertEndTime: string, alertFrequency: string, reminderId: number) {
+ this.taskId = taskId;
+ this.isOpen = isOpen;
+ this.targetValue = targetValue;
+ this.finValue = finValue;
+ this.isDone = isDone;
+ this.isAlert = isAlert;
+ this.alertStartTime = alertStartTime;
+ this.alertEndTime = alertEndTime;
+ this.alertFrequency = alertFrequency;
+ this.reminderId = reminderId
+ }
+
+ toString(): string {
+ return `TaskInfo[` +
+ `taskId: ${this.taskId}, ` +
+ `isOpen: ${this.isOpen}, ` +
+ `targetValue: ${this.targetValue}, ` +
+ `finValue: ${this.finValue}, ` +
+ `isDone: ${this.isDone}, ` +
+ `isAlert: ${this.isAlert}, ` +
+ `alertStartTime: ${this.alertStartTime}, ` +
+ `alertEndTime: ${this.alertEndTime}, ` +
+ `alertFrequency: ${this.alertFrequency}, ` +
+ `reminderId: ${this.reminderId}` +
+ `]`;
+ }
+
+ deepClone(): TaskInfo {
+ return new TaskInfo(
+ this.taskId,
+ this.isOpen,
+ this.targetValue,
+ this.finValue,
+ this.isDone,
+ this.isAlert,
+ this.alertStartTime,
+ this.alertEndTime,
+ this.alertFrequency,
+ this.reminderId
+ );
+ }
+
+ isTaskEqual(otherTask: TaskInfo): boolean {
+ return this.taskId === otherTask.taskId
+ && this.isOpen === otherTask.isOpen
+ && this.targetValue === otherTask.targetValue
+ && this.finValue === otherTask.finValue
+ && this.isDone === otherTask.isDone
+ && this.isAlert === otherTask.isAlert
+ && this.alertStartTime === otherTask.alertStartTime
+ && this.alertEndTime === otherTask.alertEndTime
+ && this.alertFrequency === otherTask.alertFrequency
+ && this.reminderId === otherTask.reminderId
+ }
+
+ convert2DayTask(date: string): DayTaskInfo {
+ return new DayTaskInfo(0, date, this.taskId, this.targetValue, this.finValue, this.isDone);
+ }
+
+ convert2DefaultDayTask(date: string): DayTaskInfo {
+ return new DayTaskInfo(0, date, this.taskId, this.targetValue, '', false);
+ }
+}
+
+export const defaultTaskInfoList: TaskInfo[] = [
+ new TaskInfo(0, false, '08:00', '', false, false, '', '', '', -1),
+ new TaskInfo(1, false, '1', '', false, false, '', '', '', -1),
+ new TaskInfo(2, false, '1', '', false, false, '', '', '', -1),
+ new TaskInfo(3, false, '1', '', false, false, '', '', '', -1),
+ new TaskInfo(4, false, '1', '', false, false, '', '', '', -1),
+ new TaskInfo(5, false, '23:00', '', false, false, '', '', '', -1)
+];
\ No newline at end of file
diff --git a/commons/common/src/main/ets/utils/FormUtils.ets b/commons/common/src/main/ets/utils/FormUtils.ets
new file mode 100644
index 0000000000000000000000000000000000000000..7b1614ec34574936f60b43127fe5447c7a655e02
--- /dev/null
+++ b/commons/common/src/main/ets/utils/FormUtils.ets
@@ -0,0 +1,129 @@
+/*
+ *
+ * * 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 { BusinessError } from '@kit.BasicServicesKit';
+import { formBindingData, formProvider } from '@kit.FormKit';
+import { convertDate2Str } from './Utils';
+import RdbUtils from '../database/RdbUtils';
+import { DayInfo } from '../model/database/DayInfo';
+import { FormInfo } from '../model/database/FormInfo';
+import DayInfoApi from '../database/tables/DayInfoApi';
+import FormInfoApi from '../database/tables/FormInfoApi';
+import FormStorageModel from '../model/FormStorageModel';
+import { DayTaskInfo } from '../model/database/DayTaskInfo';
+import DayTaskInfoApi from '../database/tables/DayTaskInfoApi';
+import { CommonConstants as Const } from '../constants/CommonConstants';
+
+// hilog tag.
+const TAG: string = 'FormUtils';
+
+class FormUtils {
+ /**
+ * Added service card information.
+ * @param { Context } context context.
+ * @param { FormInfo } formInfo service card info.
+ * @returns { void }
+ */
+ public insertFormData(context: Context, formInfo: FormInfo): void {
+ RdbUtils.createRdb(context).then(() => {
+ FormInfoApi.insert(formInfo).then((formId: number) => {
+ hilog.info(0x0000, TAG, `Insert formInfo successed, form info is ${formInfo.toString()}`);
+ this.updateForms();
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Insert formInfo failed, form info is ${formInfo.toString()}, code is ${err.code}, message is ${err.message}`);
+ });
+ });
+ }
+
+ /**
+ * Query and updated data displayed on the all service card.
+ * @returns { void }
+ */
+ public updateForms(): void {
+ FormInfoApi.queryAll().then((formInfos: FormInfo[]) => {
+ formInfos.forEach((item: FormInfo) => {
+ this.updateByFormName(item);
+ })
+ })
+ }
+
+ /**
+ * Update the displayed content based on the service card info.
+ * @param { FormInfo } formInfo service card info.
+ * @returns { void }
+ */
+ public updateByFormName(formInfo: FormInfo): void {
+ const date = convertDate2Str(new Date());
+ let updateDate: FormStorageModel = {};
+ if (formInfo.formName === Const.FORM_NAME_AGENCY &&
+ Number(formInfo.formDimension) === Const.DEFAULT_DIMENSION_2X4) {
+ DayTaskInfoApi.queryDayTaskInfo(date).then((dayTaskInfos: DayTaskInfo[]) => {
+ updateDate.dayTaskInfos = dayTaskInfos;
+ this.updateFormByDate(updateDate, formInfo);
+ })
+ } else if (formInfo.formName === Const.FORM_NAME_PROGRESS &&
+ Number(formInfo.formDimension) === Const.DEFAULT_DIMENSION_2X2) {
+ DayInfoApi.queryByKey(date).then((dayInfo: DayInfo | null) => {
+ if (!dayInfo) {
+ dayInfo = new DayInfo(date, 0, 0);
+ }
+ updateDate.targetTaskNum = dayInfo.targetTaskNum;
+ updateDate.finTaskNum = dayInfo.finTaskNum;
+ updateDate.percentage = dayInfo.calculatePercentage();
+ this.updateFormByDate(updateDate, formInfo);
+ })
+ }
+ }
+
+ /**
+ * Update the displayed content based on the service card info and date.
+ * @param { FormStorageModel } updateDate data that needs to be updated.
+ * @param { FormInfo } formInfo service card info.
+ * @returns { void }
+ */
+ private updateFormByDate(updateDate: FormStorageModel, formInfo: FormInfo) {
+ let formData = formBindingData.createFormBindingData(updateDate);
+ formProvider.updateForm(formInfo.formId, formData).then(() => {
+ hilog.info(0x0000, TAG,
+ `Form update successed, form info is ${formInfo.toString()}, formData is ${formData.data.toString()}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Form update failed, form info is ${formInfo.toString()}, formData is ${formData.data.toString()}, code is ${err.code}, message is ${err.message}`);
+ });
+ }
+
+ /**
+ * Deleted service card information by card id.
+ * @param { Context } context context.
+ * @param { string } formId service card id.
+ * @returns { void }
+ */
+ public deleteForm(context: Context, formId: string): void {
+ RdbUtils.createRdb(context).then(() => {
+ FormInfoApi.delete(formId).then(() => {
+ hilog.info(0x0000, TAG, `Delete formInfo successed, formId is ${formId}`);
+ }).catch((err: BusinessError) => {
+ hilog.error(0x0000, TAG,
+ `Delete formInfo failed, formId is ${formId}, code is ${err.code}, message is ${err.message}`);
+ });
+ })
+ }
+}
+
+export default new FormUtils();
\ No newline at end of file
diff --git a/commons/common/src/main/ets/utils/PreferencesUtils.ets b/commons/common/src/main/ets/utils/PreferencesUtils.ets
new file mode 100644
index 0000000000000000000000000000000000000000..29534b2183f7dafe02b95cfe91987d34ee886a51
--- /dev/null
+++ b/commons/common/src/main/ets/utils/PreferencesUtils.ets
@@ -0,0 +1,105 @@
+/*
+ *
+ * * 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 { preferences } from '@kit.ArkData';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { CommonConstants as Const } from '../constants/CommonConstants';
+
+// hilog tag
+const TAG: string = 'PreferencesUtils';
+// achievement store options
+const achievementStoreOptions: preferences.Options = { name: Const.ACHIEVEMENT_STORE };
+
+class PreferencesUtils {
+ // user preferences connect instances of achievement store.
+ storePreferences?: preferences.Preferences | null;
+
+ /**
+ * create a user preference instance of achievement store and initialize the relevant information.
+ * @param { Context } context context.
+ * @returns { Promise } A promise object.
+ */
+ async getPreferencesFromStorage(context: Context): Promise {
+ try {
+ this.storePreferences = await preferences.getPreferences(context, achievementStoreOptions)
+ hilog.info(0x0000, TAG, `Succeeded to load the preferences, options is ${Const.ACHIEVEMENT_STORE}`);
+
+ if (this.storePreferences) {
+ // initialize the relevant information
+ if (!this.storePreferences.hasSync(Const.MAX_CONSECUTIVE_DAYS)) {
+ await this.putPreference(Const.MAX_CONSECUTIVE_DAYS, '0');
+ }
+ if (!this.storePreferences.hasSync(Const.CURRENT_CONSECUTIVE_DAYS)) {
+ await this.putPreference(Const.CURRENT_CONSECUTIVE_DAYS, '0');
+ }
+ if (!this.storePreferences.hasSync(Const.CURRENT_DAY_STATUS)) {
+ await this.putPreference(Const.CURRENT_DAY_STATUS, String(false));
+ }
+ hilog.info(0x0000, TAG, `Succeeded to init the preferences info, options is ${Const.ACHIEVEMENT_STORE}`);
+ }
+ } catch (error) {
+ let err = error as BusinessError;
+ hilog.error(0x0000, TAG, `Failed to load the preferences, code is ${err.code}, message is ${err.message}`);
+ }
+ }
+
+ /**
+ * Sets an value for the key in the Preferences object and save the Preferences object to the file.
+ * @param { string } key Indicates the key of the preferences to modify.
+ * @param { string } value Indicates the value of the preferences.
+ * @returns { Promise } A promise object.
+ */
+ async putPreference(key: string, value: string): Promise {
+ if (!this.storePreferences) {
+ return;
+ }
+ try {
+ await this.storePreferences.put(key, value)
+ await this.storePreferences.flush()
+ hilog.info(0x0000, TAG, `Succeeded to put the preferences, key is ${key}, value is ${value}`);
+ } catch (error) {
+ let err = error as BusinessError;
+ hilog.error(0x0000, TAG,
+ `Failed to put the preferences, key is ${key}, value is ${value}, code is ${err.code}, message is ${err.message}`);
+ }
+ }
+
+ /**
+ * Obtains the value of a preferences in the ValueType format.
+ * @param { string } key Indicates the key of the preferences.
+ * @returns { Promise } The value matching the specified key if it is found;
+ */
+ async getPreference(key: string): Promise {
+ let value: string = ''
+ if (!this.storePreferences) {
+ return value;
+ }
+ try {
+ value = await this.storePreferences.get(key, 'default') as string;
+ hilog.info(0x0000, TAG, `Succeeded to get the preferences, key is ${key}, value is ${value}`);
+ } catch (error) {
+ let err = error as BusinessError;
+ hilog.error(0x0000, TAG,
+ `Failed to get the preferences, key is ${key}, code is ${err.code}, message is ${err.message}`);
+ return value;
+ }
+ return value;
+ }
+}
+
+export default new PreferencesUtils();
\ No newline at end of file
diff --git a/commons/common/src/main/ets/utils/PromptActionClass.ets b/commons/common/src/main/ets/utils/PromptActionClass.ets
new file mode 100644
index 0000000000000000000000000000000000000000..4b1beb3e531132d267da0d751735916bf67b8e52
--- /dev/null
+++ b/commons/common/src/main/ets/utils/PromptActionClass.ets
@@ -0,0 +1,95 @@
+/*
+ *
+ * * 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 { BusinessError } from '@kit.BasicServicesKit';
+import { ComponentContent, promptAction, UIContext } from '@kit.ArkUI';
+
+/**
+ * A global custom popup controller that does not rely on UI components.
+ */
+export class PromptActionClass {
+ static ctx: UIContext;
+ static contentNode: ComponentContent