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; + static options: promptAction.BaseDialogOptions; + + static setContext(context: UIContext) { + PromptActionClass.ctx = context; + } + + static setContentNode(node: ComponentContent) { + PromptActionClass.contentNode = node; + } + + static setOptions(options: promptAction.BaseDialogOptions) { + PromptActionClass.options = options; + } + + /** + * Open the custom dialog. + */ + static openDialog() { + if (PromptActionClass.contentNode !== null) { + PromptActionClass.ctx.getPromptAction() + .openCustomDialog(PromptActionClass.contentNode, PromptActionClass.options) + .then(() => { + console.info('OpenCustomDialog complete.'); + }) + .catch((error: BusinessError) => { + let message = (error as BusinessError).message; + let code = (error as BusinessError).code; + console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`); + }) + } + } + + /** + * Close the custom dialog. + */ + static closeDialog() { + if (PromptActionClass.contentNode !== null) { + PromptActionClass.ctx.getPromptAction() + .closeCustomDialog(PromptActionClass.contentNode) + .then(() => { + console.info('CloseCustomDialog complete.'); + }) + .catch((error: BusinessError) => { + let message = (error as BusinessError).message; + let code = (error as BusinessError).code; + console.error(`CloseCustomDialog args error code is ${code}, message is ${message}`); + }) + } + } + + /** + * Update the custom dialog options. + * @param { promptAction.BaseDialogOptions } options options that need to be updated. + */ + static updateDialog(options: promptAction.BaseDialogOptions) { + if (PromptActionClass.contentNode !== null) { + PromptActionClass.ctx.getPromptAction() + .updateCustomDialog(PromptActionClass.contentNode, options) + .then(() => { + console.info('UpdateCustomDialog complete.'); + }) + .catch((error: BusinessError) => { + let message = (error as BusinessError).message; + let code = (error as BusinessError).code; + console.error(`UpdateCustomDialog args error code is ${code}, message is ${message}`); + }) + } + } +} \ No newline at end of file diff --git a/commons/common/src/main/ets/utils/Utils.ets b/commons/common/src/main/ets/utils/Utils.ets new file mode 100644 index 0000000000000000000000000000000000000000..444494e77601f23e7636a0f40ff9000a3666b9ad --- /dev/null +++ b/commons/common/src/main/ets/utils/Utils.ets @@ -0,0 +1,70 @@ +/* + * + * * Copyright (c) 2025 Huawei Device Co., Ltd. + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +import { i18n } from '@kit.LocalizationKit'; + +/** + * the formatted date is in standard format. + * @param { Date } date date that need to be formatted. + * @returns { string } date formatted. + */ +export function formatSystemTime(date: Date): string { + let formatOptions: Intl.DateTimeFormatOptions = { + dateStyle: 'full' + }; + let locale: string = i18n.System.getSystemLanguage(); + let dateFormat: Intl.DateTimeFormat = new Intl.DateTimeFormat(locale, formatOptions); + return dateFormat.format(date); +} + +/** + * convert date format to string format(YYYYMMDD). + * @param { Date } date date that need to be formatted. + * @returns { string } date formatted. + */ +export function convertDate2Str(date: Date): string { + const year = date.getFullYear(); + const month = ("0" + (date.getMonth() + 1)).slice(-2); + const day = ("0" + date.getDate()).slice(-2); + return `${year}${month}${day}`; +} + +/** + * convert string format(YYYYMMDD) format to date. + * @param { string } dateStr dateStr that need to be formatted. + * @returns { Date } dateStr formatted. + */ +export function convertStr2Date(dateStr: string): Date { + const year = parseInt(dateStr.substring(0, 4)); + const month = parseInt(dateStr.substring(4, 6)) - 1; + const day = parseInt(dateStr.substring(6, 8)); + return new Date(year, month, day); +} + +/** + * convert string format(HH:MM) format to date. + * @param { string } dateStr dateStr that need to be formatted. + * @returns { Date } dateStr formatted. + */ +export function convertTime2Date(value: string): Date { + const hourMin = value.split(':'); + const hour = parseInt(hourMin[0]); + const minute = parseInt(hourMin[1]); + let date = new Date(); + date.setHours(hour, minute); + return date; +} \ No newline at end of file diff --git a/commons/common/src/main/ets/utils/agent/AgentUtils.ets b/commons/common/src/main/ets/utils/agent/AgentUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..6a583cfa4569afe29968c97f9db5c4da9f91c484 --- /dev/null +++ b/commons/common/src/main/ets/utils/agent/AgentUtils.ets @@ -0,0 +1,94 @@ +/* + * + * * 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 { reminderAgentManager } from '@kit.BackgroundTasksKit'; +import { notificationManager } from '@kit.NotificationKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { TaskInfo } from '../../model/database/TaskInfo'; +import { TaskBaseInfo } from '../../model/TaskBaseModel'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const TAG: string = '[AgentUtils]'; + +export class AgentUtils { + static async creatAgent(taskInfo: TaskInfo, taskBaseInfo: TaskBaseInfo, uIContext: UIContext): Promise { + let targetReminderAgent = AgentUtils.agentConfigSet(taskInfo, taskBaseInfo, uIContext); + try { + let reminderId: number = await reminderAgentManager.publishReminder(targetReminderAgent); // 发布的提醒ID + hilog.info(0x0000, TAG, `Succeeded in publishing reminder. `); + return reminderId; + } catch (err) { + hilog.error(0x0000, TAG, `Failed to publish reminder. Code: ${err.code}, message: ${err.message}`); + } + return -1; + } + + static async updateAgent(taskInfo: TaskInfo, taskBaseInfo: TaskBaseInfo, uIContext: UIContext): Promise { + let targetReminderAgent = AgentUtils.agentConfigSet(taskInfo, taskBaseInfo, uIContext); + reminderAgentManager.updateReminder(taskInfo.reminderId, targetReminderAgent).then(() => { + console.info("update reminder succeed"); + }).catch((err: BusinessError) => { + console.error("promise err code:" + err.code + " message:" + err.message); + }); + } + + static deleteAgent(reminderId: number): void { + // The value of reminderId is obtained from the callback after the reminder agent is successfully published. + reminderAgentManager.cancelReminder(reminderId).then(() => { + hilog.info(0x0000, TAG, `Succeeded in canceling reminder. `); + }).catch((err: BusinessError) => { + hilog.error(0x0000, TAG, `Failed to cancel reminder. Code: ${err.code}, message: ${err.message}`); + }); + } + + static agentConfigSet(taskInfo: TaskInfo, taskBaseInfo: TaskBaseInfo, uIContext: UIContext) { + // Adjust the time format + let GeneratedDestructArray_1 = taskInfo.targetValue.split(':'); + let hourStr = GeneratedDestructArray_1[0]; + let minuteStr = GeneratedDestructArray_1[1]; + let displayText = uIContext.getHostContext()?.resourceManager.getStringSync(taskBaseInfo.name.id); + let targetReminderAgent: reminderAgentManager.ReminderRequestAlarm = { + reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM, + hour: parseInt(hourStr), + minute: parseInt(minuteStr), + daysOfWeek: [1, 2, 3, 4, 5, 6, 7], + actionButton: [ + { + title: 'close', + type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE + }, + { + title: 'snooze', + type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE + }, + ], + wantAgent: { + pkgName: 'com.huawei.healthylife', + abilityName: 'EntryAbility' + }, + ringDuration: 10, + snoozeTimes: 2, + timeInterval: 5 * 60, + title: displayText, + content: displayText, + expiredContent: 'this reminder has expired', + snoozeContent: 'remind later', + slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION + } + return targetReminderAgent; + } +} \ No newline at end of file diff --git a/commons/common/src/main/ets/utils/agent/RequestAuthorization.ets b/commons/common/src/main/ets/utils/agent/RequestAuthorization.ets new file mode 100644 index 0000000000000000000000000000000000000000..7165cd9869df9e05e59fe2e1205ed2174cf65242 --- /dev/null +++ b/commons/common/src/main/ets/utils/agent/RequestAuthorization.ets @@ -0,0 +1,53 @@ +/* + * + * * 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 { notificationManager } from '@kit.NotificationKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { common } from '@kit.AbilityKit'; + +const TAG: string = '[RequestAuthorization]'; +const DOMAIN_NUMBER: number = 0xFF00; + +export class RequestAuthorization { + static async requestNotification(uIContext: UIContext): Promise { + let isRequestSuccess = false; + let context = uIContext.getHostContext() as common.UIAbilityContext; + try { + let isEnabled = await notificationManager.isNotificationEnabled(); + if (isEnabled) { + return true; + } + } catch (err) { + hilog.error(DOMAIN_NUMBER, TAG, `isNotificationEnabled fail, code is ${err.code}, message is ${err.message}`); + } + + try { + await notificationManager.requestEnableNotification(context); + isRequestSuccess = true; + return isRequestSuccess; + } catch (err) { + if (1600004 == err.code) { + hilog.error(DOMAIN_NUMBER, TAG, + `[ANS] requestEnableNotification refused, code is ${err.code}, message is ${err.message}`); + } else { + hilog.error(DOMAIN_NUMBER, TAG, + `[ANS] requestEnableNotification failed, code is ${err.code}, message is ${err.message}`); + } + } + return isRequestSuccess; + } +} \ No newline at end of file diff --git a/commons/common/src/main/module.json5 b/commons/common/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..b0a3620ad9ed1f15bf8629cb21e1cf18b7142ce7 --- /dev/null +++ b/commons/common/src/main/module.json5 @@ -0,0 +1,9 @@ +{ + "module": { + "name": "common", + "type": "har", + "deviceTypes": [ + "phone" + ] + } +} diff --git a/commons/common/src/main/resources/base/element/color.json b/commons/common/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..c6c477e96d9ad066d112326b3deca542ef763959 --- /dev/null +++ b/commons/common/src/main/resources/base/element/color.json @@ -0,0 +1,24 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + }, + { + "name": "user_level_font_color", + "value": "#c99411" + }, + { + "name": "user_level_bg_color", + "value": "#d4e6f1" + }, + { + "name": "task_clock_bg_color", + "value": "#66FFFFFF" + }, + { + "name": "ach_bg_color", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/commons/common/src/main/resources/base/element/float.json b/commons/common/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..0599a006f5cc15ee63ee221a5a6ecd739bf3ff2e --- /dev/null +++ b/commons/common/src/main/resources/base/element/float.json @@ -0,0 +1,172 @@ +{ + "float": [ + { + "name": "default_0", + "value": "0vp" + }, + { + "name": "default_1", + "value": "1vp" + }, + { + "name": "default_2", + "value": "2vp" + }, + { + "name": "default_4", + "value": "4vp" + }, + { + "name": "default_5", + "value": "5vp" + }, + { + "name": "default_6", + "value": "6vp" + }, + { + "name": "default_8", + "value": "8vp" + }, + { + "name": "default_10", + "value": "10vp" + }, + { + "name": "default_12", + "value": "12vp" + }, + { + "name": "default_14", + "value": "14vp" + }, + { + "name": "default_16", + "value": "16vp" + }, + { + "name": "default_18", + "value": "18vp" + }, + { + "name": "default_20", + "value": "20vp" + }, + { + "name": "default_22", + "value": "22vp" + }, + { + "name": "default_24", + "value": "24vp" + }, + { + "name": "default_26", + "value": "26vp" + }, + { + "name": "default_28", + "value": "28vp" + }, + { + "name": "default_32", + "value": "32vp" + }, + { + "name": "default_36", + "value": "36vp" + }, + { + "name": "default_38", + "value": "38vp" + }, + { + "name": "default_40", + "value": "40vp" + }, + { + "name": "default_42", + "value": "42vp" + }, + { + "name": "default_44", + "value": "44vp" + }, + { + "name": "default_48", + "value": "48vp" + }, + { + "name": "default_56", + "value": "56vp" + }, + { + "name": "default_62", + "value": "62vp" + }, + { + "name": "default_66", + "value": "66vp" + }, + { + "name": "default_71", + "value": "71vp" + }, + { + "name": "default_72", + "value": "72vp" + }, + { + "name": "default_88", + "value": "88vp" + }, + { + "name": "default_100", + "value": "100vp" + }, + { + "name": "default_120", + "value": "120vp" + }, + { + "name": "default_132", + "value": "132vp" + }, + { + "name": "default_220", + "value": "220vp" + }, + { + "name": "default_267", + "value": "267vp" + }, + { + "name": "default_316", + "value": "316vp" + }, + { + "name": "default_451", + "value": "451vp" + }, + { + "name": "default_n8", + "value": "-8vp" + }, + { + "name": "letter_space_1", + "value": "0.1" + }, + { + "name": "letter_space_34", + "value": "3.4" + }, + { + "name": "opacity_4", + "value": "0.4" + }, + { + "name": "opacity_6", + "value": "0.6" + } + ] +} \ No newline at end of file diff --git a/commons/common/src/main/resources/base/element/string.json b/commons/common/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..6ade5e97bee18bf9e26c2ff4c295a3cbff5c4d60 --- /dev/null +++ b/commons/common/src/main/resources/base/element/string.json @@ -0,0 +1,204 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "健康生活" + }, + { + "name": "EntryAbility_desc", + "value": "运动总有新玩法" + }, + { + "name": "EntryAbility_label", + "value": "健康生活" + }, + { + "name": "internet_permission_reason", + "value": "Used to access the network to obtain map data." + }, + { + "name": "healthy_life", + "value": "健康生活" + }, + { + "name": "target_progress", + "value": "目标进度" + }, + { + "name": "task_list", + "value": "任务列表" + }, + { + "name": "week_shorthand_mon", + "value": "一" + }, + { + "name": "week_shorthand_tue", + "value": "二" + }, + { + "name": "week_shorthand_wed", + "value": "三" + }, + { + "name": "week_shorthand_thu", + "value": "四" + }, + { + "name": "week_shorthand_fri", + "value": "五" + }, + { + "name": "week_shorthand_sat", + "value": "六" + }, + { + "name": "week_shorthand_sun", + "value": "日" + }, + { + "name": "no_task", + "value": "暂无任务,请添加任务" + }, + { + "name": "task_getup", + "value": "早起" + }, + { + "name": "task_drink", + "value": "喝水" + }, + { + "name": "task_apple", + "value": "吃苹果" + }, + { + "name": "task_smile", + "value": "每日微笑" + }, + { + "name": "task_brush", + "value": "每日刷牙" + }, + { + "name": "task_sleep", + "value": "早睡" + }, + { + "name": "clock_in", + "value": "打卡" + }, + { + "name": "got_it", + "value": "知道了" + }, + { + "name": "was_done", + "value": "已完成" + }, + { + "name": "my_achievement", + "value": "我的成就" + }, + { + "name": "achievement_level", + "value": "连续%s天达成" + }, + { + "name": "mine_personal_data", + "value": "个人资料" + }, + { + "name": "mine_check_updates", + "value": "检查更新" + }, + { + "name": "mine_about", + "value": "关于" + }, + { + "name": "add_task", + "value": "添加任务" + }, + { + "name": "edit_task", + "value": "编辑任务" + }, + { + "name": "task_opened", + "value": "已开启" + }, + { + "name": "target_setting", + "value": "目标设置" + }, + { + "name": "complete", + "value": "完成" + }, + { + "name": "tab_home", + "value": "首页" + }, + { + "name": "tab_achievement", + "value": "成就" + }, + { + "name": "tab_mine", + "value": "我的" + }, + { + "name": "skip_ads", + "value": "跳过广告 %d s" + }, + { + "name": "privacy_title", + "value": "请在“健康生活”权限管理中开启“XX”权限" + }, + { + "name": "privacy_desc", + "value": "该功能支持XX…(功能介绍)" + }, + { + "name": "confirm", + "value": "确定" + }, + { + "name": "exit", + "value": "退出" + }, + { + "name": "cancel", + "value": "取消" + }, + { + "name": "user_name", + "value": "Jolin" + }, + { + "name": "user_level", + "value": "LV.7" + }, + { + "name": "user_signature", + "value": "这是一条简短的个人签名" + }, + { + "name": "unit_liter", + "value": "L" + }, + { + "name": "unit_number", + "value": "个" + }, + { + "name": "unit_times", + "value": "次" + }, + { + "name": "unit_pre_day", + "value": "/ 天" + } + ] +} \ No newline at end of file diff --git a/commons/common/src/main/resources/base/media/ic_ad_bg.png b/commons/common/src/main/resources/base/media/ic_ad_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..98765d68afca3af6744eac4f768e478e9d701629 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_ad_bg.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_30_off.png b/commons/common/src/main/resources/base/media/ic_badge_30_off.png new file mode 100644 index 0000000000000000000000000000000000000000..daa412d6212661e04d906c443547e58f3f4b3fc1 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_30_off.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_30_on.png b/commons/common/src/main/resources/base/media/ic_badge_30_on.png new file mode 100644 index 0000000000000000000000000000000000000000..94a43c7982b7fc15c831e9cf63b050c3aaf54c20 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_30_on.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_3_off.png b/commons/common/src/main/resources/base/media/ic_badge_3_off.png new file mode 100644 index 0000000000000000000000000000000000000000..7e4d421258f6856dd26f0fb9b019490b67555d7d Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_3_off.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_3_on.png b/commons/common/src/main/resources/base/media/ic_badge_3_on.png new file mode 100644 index 0000000000000000000000000000000000000000..74681dfbe4cd65ddf882c266a31cb412ca3517b5 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_3_on.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_50_off.png b/commons/common/src/main/resources/base/media/ic_badge_50_off.png new file mode 100644 index 0000000000000000000000000000000000000000..2f40e4b02ddd4e69360f2902863f528bfd8be9c4 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_50_off.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_50_on.png b/commons/common/src/main/resources/base/media/ic_badge_50_on.png new file mode 100644 index 0000000000000000000000000000000000000000..b4ddd6b8f37cf3dfd1da5b98ec98c528ceb25a1f Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_50_on.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_73_off.png b/commons/common/src/main/resources/base/media/ic_badge_73_off.png new file mode 100644 index 0000000000000000000000000000000000000000..be3359d7a77e35c665cba8908973918d84309e96 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_73_off.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_73_on.png b/commons/common/src/main/resources/base/media/ic_badge_73_on.png new file mode 100644 index 0000000000000000000000000000000000000000..47986eb9492e76c9e9dcd3839ed909a2a0fda959 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_73_on.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_7_off.png b/commons/common/src/main/resources/base/media/ic_badge_7_off.png new file mode 100644 index 0000000000000000000000000000000000000000..9181d82186e94cb1592c91ca1a30bdf4c248203a Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_7_off.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_7_on.png b/commons/common/src/main/resources/base/media/ic_badge_7_on.png new file mode 100644 index 0000000000000000000000000000000000000000..94730cf250b6ffb3c466e3c23b3a631f44b53c54 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_7_on.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_99_off.png b/commons/common/src/main/resources/base/media/ic_badge_99_off.png new file mode 100644 index 0000000000000000000000000000000000000000..7fef1f54115e6798a490fd076ca56a741d72795f Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_99_off.png differ diff --git a/commons/common/src/main/resources/base/media/ic_badge_99_on.png b/commons/common/src/main/resources/base/media/ic_badge_99_on.png new file mode 100644 index 0000000000000000000000000000000000000000..029d63b1c90410ca784bdb20eaea95916310e787 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_badge_99_on.png differ diff --git a/commons/common/src/main/resources/base/media/ic_card_apple.png b/commons/common/src/main/resources/base/media/ic_card_apple.png new file mode 100644 index 0000000000000000000000000000000000000000..ffe67b5f5aca85967a489e0c121f9846b05f16cf Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_card_apple.png differ diff --git a/commons/common/src/main/resources/base/media/ic_card_clean.png b/commons/common/src/main/resources/base/media/ic_card_clean.png new file mode 100644 index 0000000000000000000000000000000000000000..1e48101c40dbc70cc07fc8e07a0aef1ee43336f5 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_card_clean.png differ diff --git a/commons/common/src/main/resources/base/media/ic_card_drink.png b/commons/common/src/main/resources/base/media/ic_card_drink.png new file mode 100644 index 0000000000000000000000000000000000000000..3437ceaf4d02df944681204f99f546675359a997 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_card_drink.png differ diff --git a/commons/common/src/main/resources/base/media/ic_card_getup.png b/commons/common/src/main/resources/base/media/ic_card_getup.png new file mode 100644 index 0000000000000000000000000000000000000000..24fe815b062486f0633285bcca5a401e75ea43e2 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_card_getup.png differ diff --git a/commons/common/src/main/resources/base/media/ic_card_sleep.png b/commons/common/src/main/resources/base/media/ic_card_sleep.png new file mode 100644 index 0000000000000000000000000000000000000000..96a0386561648d99c44a703e7ecfd7ac92358c83 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_card_sleep.png differ diff --git a/commons/common/src/main/resources/base/media/ic_card_smile.png b/commons/common/src/main/resources/base/media/ic_card_smile.png new file mode 100644 index 0000000000000000000000000000000000000000..13bf25fc488edb56e3ab5e0f4336e79c80dcf5a5 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_card_smile.png differ diff --git a/commons/common/src/main/resources/base/media/ic_dialog_apple.png b/commons/common/src/main/resources/base/media/ic_dialog_apple.png new file mode 100644 index 0000000000000000000000000000000000000000..70ccc6781a58bc6f102bcab5d25d08dfb55b1af9 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_dialog_apple.png differ diff --git a/commons/common/src/main/resources/base/media/ic_dialog_brush.png b/commons/common/src/main/resources/base/media/ic_dialog_brush.png new file mode 100644 index 0000000000000000000000000000000000000000..bef2f60abc19599f2e9556b3f83c896b990946fe Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_dialog_brush.png differ diff --git a/commons/common/src/main/resources/base/media/ic_dialog_drink.png b/commons/common/src/main/resources/base/media/ic_dialog_drink.png new file mode 100644 index 0000000000000000000000000000000000000000..125c56b0e853b27bb4ef061063f06ba7c661dc79 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_dialog_drink.png differ diff --git a/commons/common/src/main/resources/base/media/ic_dialog_getup.png b/commons/common/src/main/resources/base/media/ic_dialog_getup.png new file mode 100644 index 0000000000000000000000000000000000000000..6a33e0967c0f14629787e8bbeaff90cd616b1b22 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_dialog_getup.png differ diff --git a/commons/common/src/main/resources/base/media/ic_dialog_sleep.png b/commons/common/src/main/resources/base/media/ic_dialog_sleep.png new file mode 100644 index 0000000000000000000000000000000000000000..4696e1b9a996f0d13cf4d3ef9bbb872a94607889 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_dialog_sleep.png differ diff --git a/commons/common/src/main/resources/base/media/ic_dialog_smile.png b/commons/common/src/main/resources/base/media/ic_dialog_smile.png new file mode 100644 index 0000000000000000000000000000000000000000..bd9528085a6c7613f48edf84b7d8d0a62193bddc Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_dialog_smile.png differ diff --git a/commons/common/src/main/resources/base/media/ic_home_add.png b/commons/common/src/main/resources/base/media/ic_home_add.png new file mode 100644 index 0000000000000000000000000000000000000000..e81f66c18ae36f8f5e33b03d4c33b9d64f6ec0b3 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_home_add.png differ diff --git a/commons/common/src/main/resources/base/media/ic_home_all_done.png b/commons/common/src/main/resources/base/media/ic_home_all_done.png new file mode 100644 index 0000000000000000000000000000000000000000..82c69baff73a852c42f3d8ade51e6ac0d9633d64 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_home_all_done.png differ diff --git a/commons/common/src/main/resources/base/media/ic_home_bg.png b/commons/common/src/main/resources/base/media/ic_home_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..c18bb7f8e5320397e5f13e886fab5ddf90db4592 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_home_bg.png differ diff --git a/commons/common/src/main/resources/base/media/ic_home_half_done.png b/commons/common/src/main/resources/base/media/ic_home_half_done.png new file mode 100644 index 0000000000000000000000000000000000000000..35ec2b8c0d50c1197985daf7d1ca5469a93e1471 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_home_half_done.png differ diff --git a/commons/common/src/main/resources/base/media/ic_home_undone.png b/commons/common/src/main/resources/base/media/ic_home_undone.png new file mode 100644 index 0000000000000000000000000000000000000000..2b73b1ceaf8ae7aafa99064cd20c7113010fa8a5 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_home_undone.png differ diff --git a/commons/common/src/main/resources/base/media/ic_mine_search.png b/commons/common/src/main/resources/base/media/ic_mine_search.png new file mode 100644 index 0000000000000000000000000000000000000000..da0971ca6640e2d56bec7a6ba54c1b9bbf99ebfd Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_mine_search.png differ diff --git a/commons/common/src/main/resources/base/media/ic_no_data.png b/commons/common/src/main/resources/base/media/ic_no_data.png new file mode 100644 index 0000000000000000000000000000000000000000..ccc344a5fdae357ee2874b5910594ac2c592400c Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_no_data.png differ diff --git a/commons/common/src/main/resources/base/media/ic_right_grey.png b/commons/common/src/main/resources/base/media/ic_right_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..bebf1f6df388728a4f1c251490db661e52386811 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_right_grey.png differ diff --git a/commons/common/src/main/resources/base/media/ic_splash_bg.png b/commons/common/src/main/resources/base/media/ic_splash_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..132b23fc08241f57d69a8b4bd12b0d149222cae4 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_splash_bg.png differ diff --git a/commons/common/src/main/resources/base/media/ic_tabs_achievement_normal.png b/commons/common/src/main/resources/base/media/ic_tabs_achievement_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..68e6b0efae9681b3bf68f9cd5aef3825a3190d9d Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_tabs_achievement_normal.png differ diff --git a/commons/common/src/main/resources/base/media/ic_tabs_achievement_sel.png b/commons/common/src/main/resources/base/media/ic_tabs_achievement_sel.png new file mode 100644 index 0000000000000000000000000000000000000000..7502e211315550a90b91445eb245cb5907ff1f11 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_tabs_achievement_sel.png differ diff --git a/commons/common/src/main/resources/base/media/ic_tabs_home_normal.png b/commons/common/src/main/resources/base/media/ic_tabs_home_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..100ecd622e3aa64f364b24b819fbf472ae85dfb7 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_tabs_home_normal.png differ diff --git a/commons/common/src/main/resources/base/media/ic_tabs_home_sel.png b/commons/common/src/main/resources/base/media/ic_tabs_home_sel.png new file mode 100644 index 0000000000000000000000000000000000000000..64eedcef13211b264cad947c6cc4b6edd01aa947 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_tabs_home_sel.png differ diff --git a/commons/common/src/main/resources/base/media/ic_tabs_mine_normal.png b/commons/common/src/main/resources/base/media/ic_tabs_mine_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..6b8e5da2d2a70950b118af7cb0dcd656e8732f51 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_tabs_mine_normal.png differ diff --git a/commons/common/src/main/resources/base/media/ic_tabs_mine_sel.png b/commons/common/src/main/resources/base/media/ic_tabs_mine_sel.png new file mode 100644 index 0000000000000000000000000000000000000000..384ff407988ddeaa18bc1368a443edba3c465f29 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_tabs_mine_sel.png differ diff --git a/commons/common/src/main/resources/base/media/ic_task_apple.png b/commons/common/src/main/resources/base/media/ic_task_apple.png new file mode 100644 index 0000000000000000000000000000000000000000..3c6004ed93adf34b8139d11a0da63b3e9e498156 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_task_apple.png differ diff --git a/commons/common/src/main/resources/base/media/ic_task_brush.png b/commons/common/src/main/resources/base/media/ic_task_brush.png new file mode 100644 index 0000000000000000000000000000000000000000..0f8b424f07bdc32dabf14ef407000809ed8fef21 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_task_brush.png differ diff --git a/commons/common/src/main/resources/base/media/ic_task_drink.png b/commons/common/src/main/resources/base/media/ic_task_drink.png new file mode 100644 index 0000000000000000000000000000000000000000..7774025a80d89922a92636da0f97a8fb5752a978 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_task_drink.png differ diff --git a/commons/common/src/main/resources/base/media/ic_task_getup.png b/commons/common/src/main/resources/base/media/ic_task_getup.png new file mode 100644 index 0000000000000000000000000000000000000000..893ba4dda21d4a25a75bb378cb6b7ea30a02766f Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_task_getup.png differ diff --git a/commons/common/src/main/resources/base/media/ic_task_sleep.png b/commons/common/src/main/resources/base/media/ic_task_sleep.png new file mode 100644 index 0000000000000000000000000000000000000000..ac92e3d3342ec630faf30223d6c49d4865f16cd9 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_task_sleep.png differ diff --git a/commons/common/src/main/resources/base/media/ic_task_smile.png b/commons/common/src/main/resources/base/media/ic_task_smile.png new file mode 100644 index 0000000000000000000000000000000000000000..6cf685dff8884f6aac8e8fbfc730cb837e30e986 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_task_smile.png differ diff --git a/commons/common/src/main/resources/base/media/ic_user.png b/commons/common/src/main/resources/base/media/ic_user.png new file mode 100644 index 0000000000000000000000000000000000000000..df4bc84f7e040530f6798cb82c2f8b07ab25cf28 Binary files /dev/null and b/commons/common/src/main/resources/base/media/ic_user.png differ diff --git a/commons/common/src/main/resources/base/media/icon_user.png b/commons/common/src/main/resources/base/media/icon_user.png new file mode 100644 index 0000000000000000000000000000000000000000..df4bc84f7e040530f6798cb82c2f8b07ab25cf28 Binary files /dev/null and b/commons/common/src/main/resources/base/media/icon_user.png differ diff --git a/commons/common/src/main/resources/base/media/logo.png b/commons/common/src/main/resources/base/media/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..87a9c3c4939d6188b231b6e108e80f885a879541 Binary files /dev/null and b/commons/common/src/main/resources/base/media/logo.png differ diff --git a/commons/common/src/main/resources/dark/element/color.json b/commons/common/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/commons/common/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/commons/common/src/main/resources/en_US/element/string.json b/commons/common/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..0bc5ff74be1072c36b2fb55af02504a2f7ed3e73 --- /dev/null +++ b/commons/common/src/main/resources/en_US/element/string.json @@ -0,0 +1,204 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "Healthy life" + }, + { + "name": "EntryAbility_desc", + "value": "There are always new ways to play sports" + }, + { + "name": "EntryAbility_label", + "value": "Healthy life" + }, + { + "name": "internet_permission_reason", + "value": "Used to access the network to obtain map data." + }, + { + "name": "healthy_life", + "value": "Healthy life" + }, + { + "name": "target_progress", + "value": "Target progress" + }, + { + "name": "task_list", + "value": "Task list" + }, + { + "name": "week_shorthand_mon", + "value": "Mon" + }, + { + "name": "week_shorthand_tue", + "value": "Tue" + }, + { + "name": "week_shorthand_wed", + "value": "Wed" + }, + { + "name": "week_shorthand_thu", + "value": "Thu" + }, + { + "name": "week_shorthand_fri", + "value": "Fri" + }, + { + "name": "week_shorthand_sat", + "value": "Sat" + }, + { + "name": "week_shorthand_sun", + "value": "Sun" + }, + { + "name": "no_task", + "value": "There are no tasks, please add tasks" + }, + { + "name": "task_getup", + "value": "Early riser" + }, + { + "name": "task_drink", + "value": "Drink" + }, + { + "name": "task_apple", + "value": "Eat apples" + }, + { + "name": "task_smile", + "value": "Smile every day" + }, + { + "name": "task_brush", + "value": "Brush your teeth daily" + }, + { + "name": "task_sleep", + "value": "Go to bed early" + }, + { + "name": "clock_in", + "value": "Clock" + }, + { + "name": "got_it", + "value": "Got it" + }, + { + "name": "was_done", + "value": "Was done" + }, + { + "name": "my_achievement", + "value": "My achievement" + }, + { + "name": "achievement_level", + "value": "Achieved for %s consecutive day" + }, + { + "name": "mine_personal_data", + "value": "Personal data" + }, + { + "name": "mine_check_updates", + "value": "Check updates" + }, + { + "name": "mine_about", + "value": "About" + }, + { + "name": "add_task", + "value": "Add task" + }, + { + "name": "edit_task", + "value": "Edit task" + }, + { + "name": "task_opened", + "value": "Opened" + }, + { + "name": "target_setting", + "value": "Target setting" + }, + { + "name": "complete", + "value": "Complete" + }, + { + "name": "tab_home", + "value": "Home" + }, + { + "name": "tab_achievement", + "value": "Achievement" + }, + { + "name": "tab_mine", + "value": "Mine" + }, + { + "name": "skip_ads", + "value": "Skip ads %d s" + }, + { + "name": "confirm", + "value": "Sure" + }, + { + "name": "exit", + "value": "Exit" + }, + { + "name": "privacy_title", + "value": "Please enable the \"XX\" permission in the \"Healthy Life\" permission management" + }, + { + "name": "privacy_desc", + "value": "The function supports XX... (Function introduction)" + }, + { + "name": "cancel", + "value": "Cancel" + }, + { + "name": "user_name", + "value": "Jolin" + }, + { + "name": "user_level", + "value": "LV.7" + }, + { + "name": "user_signature", + "value": "This is a short personal signature" + }, + { + "name": "unit_liter", + "value": "L" + }, + { + "name": "unit_number", + "value": "number" + }, + { + "name": "unit_times", + "value": "times" + }, + { + "name": "unit_pre_day", + "value": "/ day" + } + ] +} \ No newline at end of file diff --git a/commons/common/src/ohosTest/ets/test/Ability.test.ets b/commons/common/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/commons/common/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/commons/common/src/ohosTest/ets/test/List.test.ets b/commons/common/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/commons/common/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/commons/common/src/ohosTest/module.json5 b/commons/common/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..9e1206029559c8955aeb4814c8accac469dbfd7b --- /dev/null +++ b/commons/common/src/ohosTest/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "common_test", + "type": "feature", + "deviceTypes": [ + "default" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/commons/common/src/test/List.test.ets b/commons/common/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/commons/common/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/commons/common/src/test/LocalUnit.test.ets b/commons/common/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/commons/common/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/features/healthylife/.gitignore b/features/healthylife/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/features/healthylife/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/features/healthylife/BuildProfile.ets b/features/healthylife/BuildProfile.ets new file mode 100644 index 0000000000000000000000000000000000000000..3a501e5ddee8ea6d28961648fc7dd314a5304bd4 --- /dev/null +++ b/features/healthylife/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/features/healthylife/Index.ets b/features/healthylife/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..0898d7ee166c7093d8ffcb6b40a25a83e565f2bf --- /dev/null +++ b/features/healthylife/Index.ets @@ -0,0 +1 @@ +export { HealthyLifePage } from './src/main/ets/pages/HealthyLifePage'; \ No newline at end of file diff --git a/features/healthylife/build-profile.json5 b/features/healthylife/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..68c40f9141e428c02e8ecf13504dd02d2a76776d --- /dev/null +++ b/features/healthylife/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/features/healthylife/consumer-rules.txt b/features/healthylife/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/features/healthylife/hvigorfile.ts b/features/healthylife/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..805c5d7f6809c51cff0b4adcc1142979f8f864b6 --- /dev/null +++ b/features/healthylife/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/features/healthylife/obfuscation-rules.txt b/features/healthylife/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/features/healthylife/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/features/healthylife/oh-package-lock.json5 b/features/healthylife/oh-package-lock.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d8ab5600ab5a2698e41c3760e334e189c489368f --- /dev/null +++ b/features/healthylife/oh-package-lock.json5 @@ -0,0 +1,19 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "common@../../commons/common": "common@../../commons/common" + }, + "packages": { + "common@../../commons/common": { + "name": "common", + "version": "1.0.0", + "resolved": "", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/features/healthylife/oh-package.json5 b/features/healthylife/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..cc351a1959242e3463cdb696976c94df17606e8d --- /dev/null +++ b/features/healthylife/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "healthylife", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "common": "file:../../commons/common" + } +} diff --git a/features/healthylife/src/main/ets/healthylifeability/HealthylifeAbility.ets b/features/healthylife/src/main/ets/healthylifeability/HealthylifeAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..481760a9c4e9528a91b025cdcb0c1a69035fc5d3 --- /dev/null +++ b/features/healthylife/src/main/ets/healthylifeability/HealthylifeAbility.ets @@ -0,0 +1,60 @@ +/* + * + * * Copyright (c) 2025 Huawei Device Co., Ltd. + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class HealthylifeAbility extends UIAbility { + async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/model/AchievementModel.ets b/features/healthylife/src/main/ets/model/AchievementModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..3bae9cc2643957c932c776404a75061dcb5f8766 --- /dev/null +++ b/features/healthylife/src/main/ets/model/AchievementModel.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. + * + */ + +/** + * achievement default information. + */ +export interface AchievementItem { + level: number; + name: string; + iconOn: Resource; + iconOff: Resource; +} + +export const AchievementList: AchievementItem[] = [ + { + level: 3, + name: '3', + iconOn: $r('app.media.ic_badge_3_on'), + iconOff: $r('app.media.ic_badge_3_off') + }, + { + level: 7, + name: '7', + iconOn: $r('app.media.ic_badge_7_on'), + iconOff: $r('app.media.ic_badge_7_off') + }, + { + level: 30, + name: '30', + iconOn: $r('app.media.ic_badge_30_on'), + iconOff: $r('app.media.ic_badge_30_off') + }, + { + level: 50, + name: '50', + iconOn: $r('app.media.ic_badge_50_on'), + iconOff: $r('app.media.ic_badge_50_off') + }, + { + level: 73, + name: '73', + iconOn: $r('app.media.ic_badge_73_on'), + iconOff: $r('app.media.ic_badge_73_off') + }, + { + level: 99, + name: '99', + iconOn: $r('app.media.ic_badge_99_on'), + iconOff: $r('app.media.ic_badge_99_off') + } +] + +/** + * Gets the current maximum number of consecutive days to complete an achievement. + * @param { number } maxConsecutiveDays The maximum number of consecutive days completed before the achievement. + * @returns { number } If an achievement is achieved, the achievement index is returned, otherwise -1 is returned. + */ +export function getAchievementDoneIndex(maxConsecutiveDays: number): number { + for (let index = 0; index < AchievementList.length; index++) { + if (AchievementList[index].level === maxConsecutiveDays) { + return index; + } + } + return -1; +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/model/NavItemModel.ets b/features/healthylife/src/main/ets/model/NavItemModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..58c90dff300a75c2ec591b827daf497e1eefacc2 --- /dev/null +++ b/features/healthylife/src/main/ets/model/NavItemModel.ets @@ -0,0 +1,53 @@ +/* + * + * * 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. + * + */ + +/** + * Tab navigation default information. + */ +export interface NavItem { + icon: Resource; + icon_selected: Resource; + text: Resource; + id: number; +} + +export enum TabId { + HOME, + ACHIEVEMENT, + MINE +} + +export const NavList: NavItem[] = [ + { + icon: $r('app.media.ic_tabs_home_normal'), + icon_selected: $r('app.media.ic_tabs_home_sel'), + text: $r('app.string.tab_home'), + id: TabId.HOME + }, + { + icon: $r('app.media.ic_tabs_achievement_normal'), + icon_selected: $r('app.media.ic_tabs_achievement_sel'), + text: $r('app.string.tab_achievement'), + id: TabId.ACHIEVEMENT + }, + { + icon: $r('app.media.ic_tabs_mine_normal'), + icon_selected: $r('app.media.ic_tabs_mine_sel'), + text: $r('app.string.tab_mine'), + id: TabId.MINE + }, +] \ No newline at end of file diff --git a/features/healthylife/src/main/ets/pages/HealthyLifePage.ets b/features/healthylife/src/main/ets/pages/HealthyLifePage.ets new file mode 100644 index 0000000000000000000000000000000000000000..e3764f0f4ea50de961d11fe03463ad80fbd45fe6 --- /dev/null +++ b/features/healthylife/src/main/ets/pages/HealthyLifePage.ets @@ -0,0 +1,104 @@ +/* + * + * * 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 { HomeComponent } from '../views/HomeComponent'; +import { MineComponent } from '../views/MineComponent'; +import { NavList, TabId } from '../model/NavItemModel'; +import { CommonConstants as Const, convertDate2Str } from 'common'; +import { AchievementComponent } from '../views/AchievementComponent'; +import DayInfoApi from 'common/src/main/ets/database/tables/DayInfoApi'; +import { AchievementStore, initAchievementStore } from '../viewmodel/AchievementStore'; +import { HomeStore } from '../viewmodel/HomeStore'; +import { common } from '@kit.AbilityKit'; + +@Component +export struct HealthyLifePage { + @State currentIndex: number = 0; + @Provide achievementStore: AchievementStore = new AchievementStore(0, 0, false); + @Provide('pathStack') pageStack: NavPathStack = new NavPathStack(); + @Provide homeStore: HomeStore = new HomeStore([], -1, '', []); + private context = this.getUIContext()?.getHostContext() as common.UIAbilityContext; + private windowClass = this.context.windowStage.getMainWindowSync(); + + async aboutToAppear(): Promise { + await this.windowClass.setWindowLayoutFullScreen(true); + await this.windowClass.setWindowSystemBarProperties({ + statusBarColor: '#00000000', + statusBarContentColor: '#FF000000' + }) + await DayInfoApi.initDayInfo(convertDate2Str(new Date())); + this.achievementStore = await initAchievementStore(); + } + + build() { + Navigation(this.pageStack) { + Tabs({ barPosition: BarPosition.End }) { + TabContent() { + HomeComponent() + } + .tabBar(this.tabBuilder(TabId.HOME)) + + TabContent() { + AchievementComponent() + } + .tabBar(this.tabBuilder(TabId.ACHIEVEMENT)) + + TabContent() { + MineComponent() + } + .tabBar(this.tabBuilder(TabId.MINE)) + } + .scrollable(false) + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_1000) + .barWidth(Const.THOUSANDTH_940) + .vertical(false) + .padding({ + bottom: $r('app.float.default_20') + }) + .divider({ + strokeWidth: 1, + color: $r('sys.color.background_tertiary') + }) + .onChange((index) => { + this.currentIndex = index; + }) + } + .mode(NavigationMode.Stack) + } + + @Builder + tabBuilder(index: number) { + Column() { + Image(index === this.currentIndex ? NavList[index].icon_selected : NavList[index].icon) + .width($r('app.float.default_24')) + .height($r('app.float.default_24')) + .objectFit(ImageFit.Contain); + + Text(NavList[index].text) + .fontSize($r('app.float.default_10')) + .fontWeight(FontWeight.Regular) + .fontColor(this.currentIndex === index ? $r('sys.color.brand') : $r('sys.color.font_tertiary')) + .margin({ + top: $r('app.float.default_4') + }) + } + .justifyContent(FlexAlign.Center) + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_1000) + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/viewmodel/AchievementStore.ets b/features/healthylife/src/main/ets/viewmodel/AchievementStore.ets new file mode 100644 index 0000000000000000000000000000000000000000..c5a911066feebfb1d39b41fe66ec9d639ef471c9 --- /dev/null +++ b/features/healthylife/src/main/ets/viewmodel/AchievementStore.ets @@ -0,0 +1,82 @@ +/* + * + * * 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 { JSON } from '@kit.ArkTS'; +import { CommonConstants as Const } from 'common'; +import { getAchievementDoneIndex } from '../model/AchievementModel'; +import { TaskInfoDialogParams } from './dialog/TaskInfoDialogParams'; +import PreferencesUtils from 'common/src/main/ets/utils/PreferencesUtils'; +import { openAchievementCustomDialog } from '../views/dialog/AchievementDialog'; + +/** + * Global achievement information storage. + */ +@Observed +export class AchievementStore { + public maxConsecutiveDays: number; + public currentConsecutiveDays: number; + public currentDayStatus: boolean; + + constructor(maxConsecutiveDays: number, currentConsecutiveDays: number, currentDayStatus: boolean) { + this.maxConsecutiveDays = maxConsecutiveDays; + this.currentConsecutiveDays = currentConsecutiveDays; + this.currentDayStatus = currentDayStatus; + } +} + +/** + * Initialize global achievement information. + */ +export async function initAchievementStore() { + const maxConsecutiveDays = Number(await PreferencesUtils.getPreference(Const.MAX_CONSECUTIVE_DAYS)); + const currentConsecutiveDays = Number(await PreferencesUtils.getPreference(Const.CURRENT_CONSECUTIVE_DAYS)); + const currentDayStatusStr: string = await PreferencesUtils.getPreference(Const.CURRENT_DAY_STATUS); + const currentDayStatus = JSON.parse(currentDayStatusStr) as boolean; + return new AchievementStore(maxConsecutiveDays, currentConsecutiveDays, currentDayStatus) +} + +/** + * Update the information when the achievement information changes. + * @param { TaskInfoDialogParams } params Task info for clock custom dialog parameters. + */ +export async function updateAchievementStore(params: TaskInfoDialogParams) { + //Judge whether all tasks are completed that day, and return if not. + const dayInfo = params.homeStore.weekList[params.homeStore.selectedDay]; + if (dayInfo.finTaskNum < dayInfo.targetTaskNum || params.achievementStore.currentDayStatus) { + return; + } + + // If all tasks have been completed that day, the number of consecutive days of completion is added to the user preferences. + params.achievementStore.currentConsecutiveDays += 1; + await PreferencesUtils.putPreference(Const.CURRENT_CONSECUTIVE_DAYS, + String(params.achievementStore.currentConsecutiveDays)); + params.achievementStore.currentDayStatus = true; + await PreferencesUtils.putPreference(Const.CURRENT_DAY_STATUS, String(params.achievementStore.currentDayStatus)); + + // Determine whether the current number of consecutive days exceeds the maximum number of consecutive days. + if (params.achievementStore.currentConsecutiveDays > params.achievementStore.maxConsecutiveDays) { + // If it exceeds, synchronize the global storage and user preference content to update. + params.achievementStore.maxConsecutiveDays = params.achievementStore.currentConsecutiveDays; + await PreferencesUtils.putPreference(Const.MAX_CONSECUTIVE_DAYS, + String(params.achievementStore.maxConsecutiveDays)); + // Get the achievement for a specified number of days, and if so, open a pop-up window to display the achievement animation. + const achievementDoneIndex = getAchievementDoneIndex(params.achievementStore.maxConsecutiveDays); + if (achievementDoneIndex !== -1) { + openAchievementCustomDialog(params.uIContext, achievementDoneIndex); + } + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/viewmodel/HomeStore.ets b/features/healthylife/src/main/ets/viewmodel/HomeStore.ets new file mode 100644 index 0000000000000000000000000000000000000000..1c43ff1ee912e1f458e7ae5aa69fd61400636bf5 --- /dev/null +++ b/features/healthylife/src/main/ets/viewmodel/HomeStore.ets @@ -0,0 +1,122 @@ +/* + * + * * 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, convertDate2Str, DayInfo, DayTaskInfo +} from 'common'; +import DayInfoApi from 'common/src/main/ets/database/tables/DayInfoApi'; +import TaskInfoApi from 'common/src/main/ets/database/tables/TaskInfoApi'; +import DayTaskInfoApi from 'common/src/main/ets/database/tables/DayTaskInfoApi'; + +/** + * Global day and tasks information storage. + */ +@Observed +export class HomeStore { + // three consecutive weeks where the current date is located + public weekList: DayInfo[]; + public selectedDay: number; + public currentDate: string; + public taskList: DayTaskInfo[]; + + constructor(weekList: DayInfo[], selectedDay: number, currentDate: string, taskList: DayTaskInfo[]) { + this.weekList = weekList; + this.selectedDay = selectedDay; + this.currentDate = currentDate; + this.taskList = taskList; + } + + public checkCurrentDay(): boolean { + return convertDate2Str(new Date()) === this.currentDate; + } +} + +/** + * Initialize the global storage information of the day. + * @param { Date } date Date of the day. + * @returns { Promise } The global storage information. + */ +export async function initCurrentDateInfo(date: Date): Promise { + let dayOfWeek = date.getDay(); + let selectedDay = dayOfWeek === 0 ? 6 : dayOfWeek - 1; + let dateList: Date[] = getWeekDates(date, selectedDay); + let weekList: DayInfo[] = []; + for (let dateOfWeek of dateList) { + let dateStr = convertDate2Str(dateOfWeek); + let dayInfo = await DayInfoApi.queryByKey(dateStr); + if (dayInfo === null) { + // if this info is null, init to default value. + dayInfo = new DayInfo(dateStr, 0, 0); + } + weekList.push(dayInfo); + } + + let dateStr = convertDate2Str(date); + let taskList: DayTaskInfo[] = await DayTaskInfoApi.queryDayTaskInfo(dateStr); + if (taskList.length === 0 && date > new Date()) { + // If the task list is empty on the current day, the current date is greater than the system date, and the task information needs to be initialized and populated. + let openedTaskList = await TaskInfoApi.queryOpenedTaskInfo(); + for (let openedTask of openedTaskList) { + taskList.push(openedTask.convert2DefaultDayTask(dateStr)) + } + } + return new HomeStore(weekList, selectedDay + Const.WEEK_DAY_NUM, dateStr, taskList); +} + +/** + * Update the task list information in the global storage information. + * @param { HomeStore } homeStore The global storage information. + * @returns { Promise } A promise object. + */ +export async function updateTaskList(homeStore: HomeStore): Promise { + let dayInfo = await DayInfoApi.queryByKey(homeStore.currentDate); + if (dayInfo === null) { + // if this info is null, init to default value. + dayInfo = new DayInfo(homeStore.currentDate, 0, 0); + } + homeStore.weekList[homeStore.selectedDay] = dayInfo; + + let dateStr = homeStore.currentDate; + let taskList: DayTaskInfo[] = await DayTaskInfoApi.queryDayTaskInfo(dateStr); + if (taskList.length === 0) { + // If the task list is empty on the current day, the current date is greater than the system date, and the task information needs to be initialized and populated. + let openedTaskList = await TaskInfoApi.queryOpenedTaskInfo(); + for (let openedTask of openedTaskList) { + taskList.push(openedTask.convert2DefaultDayTask(dateStr)) + } + } + homeStore.taskList = taskList; +} + +/** + * Get a list of dates for three weeks before and after the day. + * @param { Date } inputDate Date of the day. + * @param { number } adjustDays The difference between the day and Monday. + * @returns { Date[] } The array of three weeks. + */ +function getWeekDates(inputDate: Date, adjustDays: number): Date[] { + const monday = new Date(inputDate); + monday.setDate(inputDate.getDate() - adjustDays - Const.WEEK_DAY_NUM); + + const dates: Date[] = []; + for (let i = 0; i < Const.WEEK_DAY_NUM * 3; i++) { + const currentDate = new Date(monday); + currentDate.setDate(monday.getDate() + i); + dates.push(currentDate); + } + return dates; +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/viewmodel/dialog/AchievementDialogParams.ets b/features/healthylife/src/main/ets/viewmodel/dialog/AchievementDialogParams.ets new file mode 100644 index 0000000000000000000000000000000000000000..fc508b2073c0270af295bed097ec5c9b8a1d670a --- /dev/null +++ b/features/healthylife/src/main/ets/viewmodel/dialog/AchievementDialogParams.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. + * + */ + +/** + * Achievement completion animation custom dialog parameters. + */ +export class AchievementDialogParams { + uiContext?: UIContext; + index: number = 3; + opacityValue: number = 0; + angle: number = 0; + scaleValue: number = 0; + + constructor(uiContext: UIContext, index: number, opacityValue: number, angle: number, scaleValue: number) { + this.uiContext = uiContext; + this.index = index; + this.opacityValue = opacityValue; + this.angle = angle; + this.scaleValue = scaleValue; + } +} + + + diff --git a/features/healthylife/src/main/ets/viewmodel/dialog/TargetSettingDialogParams.ets b/features/healthylife/src/main/ets/viewmodel/dialog/TargetSettingDialogParams.ets new file mode 100644 index 0000000000000000000000000000000000000000..7755fda23fe39e0a8eea6c4e1fc5a7e3cf120583 --- /dev/null +++ b/features/healthylife/src/main/ets/viewmodel/dialog/TargetSettingDialogParams.ets @@ -0,0 +1,40 @@ +/* + * + * * 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 { convertTime2Date, PickerType, TaskBaseInfo, TaskInfo } from 'common'; + +/** + * Task goal setting custom dialog parameters. + */ +export class TargetSettingDialogParams { + taskInfo: TaskInfo; + taskBaseInfo: TaskBaseInfo; + selectedValue: string; + startDate?: Date; + endDate?: Date; + + constructor(taskInfo: TaskInfo, taskBaseInfo: TaskBaseInfo) { + this.taskInfo = taskInfo; + this.taskBaseInfo = taskBaseInfo; + this.selectedValue = taskInfo.targetValue; + if (taskBaseInfo.pickerType === PickerType.TIME) { + const limitArr = taskBaseInfo.limit.split('-'); + this.startDate = convertTime2Date(limitArr[0]); + this.endDate = convertTime2Date(limitArr[1]); + } + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/viewmodel/dialog/TaskInfoDialogParams.ets b/features/healthylife/src/main/ets/viewmodel/dialog/TaskInfoDialogParams.ets new file mode 100644 index 0000000000000000000000000000000000000000..7bf92352ce79aa860510a6a72f8a78056bb92989 --- /dev/null +++ b/features/healthylife/src/main/ets/viewmodel/dialog/TaskInfoDialogParams.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. + * + */ + +import { DayTaskInfo } from 'common'; +import { HomeStore } from '../HomeStore'; +import { AchievementStore } from '../AchievementStore'; + +/** + * Task info for clock custom dialog parameters. + */ +export class TaskInfoDialogParams { + dayTaskInfo: DayTaskInfo; + homeStore: HomeStore; + achievementStore: AchievementStore; + uIContext: UIContext; + + constructor(dayTaskInfo: DayTaskInfo, homeStore: HomeStore, achievementStore: AchievementStore, + uIContext: UIContext) { + this.dayTaskInfo = dayTaskInfo; + this.homeStore = homeStore; + this.achievementStore = achievementStore; + this.uIContext = uIContext; + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/AchievementComponent.ets b/features/healthylife/src/main/ets/views/AchievementComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..4cfb6c4c5d9a25c2a11228997e0aec133032c5f0 --- /dev/null +++ b/features/healthylife/src/main/ets/views/AchievementComponent.ets @@ -0,0 +1,71 @@ +import { CommonConstants as Const } from 'common'; +import { AchievementStore } from '../viewmodel/AchievementStore'; +import { openAchievementCustomDialog } from './dialog/AchievementDialog'; +import { AchievementItem, AchievementList } from '../model/AchievementModel'; + +@Component +export struct AchievementComponent { + @Consume achievementStore: AchievementStore; + + build() { + Column() { + Text($r('app.string.my_achievement')) + .fontFamily($r(Const.HARMONY_HEI_TI_MEDIUM)) + .fontWeight(FontWeight.Regular) + .fontColor($r(`sys.color.font_on_primary`)) + .fontSize($r('app.float.default_24')) + .width(Const.THOUSANDTH_1000) + .height($r('app.float.default_56')) + .padding({ + left: Const.THOUSANDTH_66, + top: Const.THOUSANDTH_150 + }) + + Flex({ + direction: FlexDirection.Row, + wrap: FlexWrap.Wrap + }) { + ForEach(AchievementList, (item: AchievementItem, index: number) => { + this.achievementBuilder(index, item) + }, (item: AchievementItem, index: number) => index + JSON.stringify(item)) + } + .width(Const.THOUSANDTH_1000) + } + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_1000) + .backgroundColor($r('app.color.ach_bg_color')) + .justifyContent(FlexAlign.Start) + .padding({ + left: $r('app.float.default_10'), + right: $r('app.float.default_10'), + bottom: $r('app.float.default_10') + }) + } + + @Builder + achievementBuilder(index: number, item: AchievementItem) { + Column({ + space: $r('app.float.default_18') + }) { + Image(item.level <= this.achievementStore.maxConsecutiveDays ? item.iconOn : item.iconOff) + .width(Const.THOUSANDTH_1000) + .height($r('app.float.default_88')) + .objectFit(ImageFit.Contain) + + Text($r('app.string.achievement_level', item.name)) + .lineHeight($r('app.float.default_16')) + .fontSize($r('app.float.default_12')) + .fontColor($r(`sys.color.font_on_primary`)) + } + .width(Const.THOUSANDTH_333) + .padding({ + top: $r('app.float.default_38'), + bottom: $r('app.float.default_10') + }) + .onClick(() => { + if (item.level <= this.achievementStore.maxConsecutiveDays) { + openAchievementCustomDialog(this.getUIContext(), index); + } + }) + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/HomeComponent.ets b/features/healthylife/src/main/ets/views/HomeComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..6ebc24a149fb0020086d3ee6349d89fc52cf1b7d --- /dev/null +++ b/features/healthylife/src/main/ets/views/HomeComponent.ets @@ -0,0 +1,134 @@ +/* + * + * * 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 'common'; +import { HomeTopComponent } from './home/HomeTopComponent'; +import { TaskListComponent } from './home/TaskListComponent'; +import { AchievementStore } from '../viewmodel/AchievementStore'; +import { HomeStore, initCurrentDateInfo } from '../viewmodel/HomeStore'; + +const WHITE_COLOR_0X = 255; + +@Builder +export function homeBuilder() { + HomeComponent(); +} + +@Component +export struct HomeComponent { + @Consume('pathStack') pageStack: NavPathStack + @Consume homeStore: HomeStore; + @Consume achievementStore: AchievementStore; + @State naviAlpha: number = 0; + @State flag: boolean = false; + private scroller: Scroller = new Scroller(); + private yOffset: number = 0; + + // change navigator alpha when scrolling the Scroll component + onScrollAction() { + this.yOffset = this.scroller.currentOffset().yOffset; + if (this.yOffset > Const.DEFAULT_56) { + this.naviAlpha = 1; + } else { + this.naviAlpha = this.yOffset / Const.DEFAULT_56; + } + } + + async aboutToAppear() { + this.homeStore = await initCurrentDateInfo(new Date()); + this.flag = true; + } + + build() { + NavDestination() { + if (this.flag) { + Stack() { + // top and task list area of enable scroll + Scroll(this.scroller) { + Column() { + Text($r('app.string.healthy_life')) + .fontFamily($r(Const.HARMONY_HEI_TI_MEDIUM)) + .fontWeight(FontWeight.Regular) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_24')) + .width(Const.THOUSANDTH_1000) + .height($r('app.float.default_56')) + .padding({ + left: Const.THOUSANDTH_66 + }) + .position({ + x: $r('app.float.default_0'), + y: $r('app.float.default_48') + }) + .zIndex(Const.HOME_COMPONENT_Z_INDEX) + + HomeTopComponent() + + Text($r('app.string.task_list')) + .fontFamily(Const.HARMONY_HEI_TI_MEDIUM) + .fontWeight(FontWeight.Regular) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_16')) + .width(Const.THOUSANDTH_1000) + .padding({ + top: Const.THOUSANDTH_15, + bottom: Const.THOUSANDTH_15, + left: Const.THOUSANDTH_33 + }) + + TaskListComponent() + } + } + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_1000) + .align(Alignment.Top) + .scrollBar(BarState.Off) + .edgeEffect(EdgeEffect.Spring, { + alwaysEnabled: false, + effectEdge: EffectEdge.END + }) + .onWillScroll(() => { + this.onScrollAction(); + }) + + Button({ + type: ButtonType.Circle, + stateEffect: false + }) { + Image($r('app.media.ic_home_add')) + .borderRadius(Const.BORDER_RADIUS_PERCENT_50) + } + .width($r('app.float.default_48')) + .height($r('app.float.default_48')) + .position({ + x: Const.THOUSANDTH_830, + y: Const.THOUSANDTH_880 + }) + .zIndex(Const.HOME_COMPONENT_Z_INDEX) + .onClick(() => { + if (this.homeStore.checkCurrentDay()) { + this.pageStack.pushPathByName('AddTaskComponent', ''); + } + }) + } + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_1000) + .backgroundColor($r('sys.color.background_secondary')); + } + } + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/MineComponent.ets b/features/healthylife/src/main/ets/views/MineComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..22df9efcbca964dbab3c3177c8471ce141bf8118 --- /dev/null +++ b/features/healthylife/src/main/ets/views/MineComponent.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. + * + */ + +import { CommonConstants as Const } from 'common'; +import { MineMenuComponent } from './mine/MineMenuComponent'; +import { UserInfoComponent } from './mine/UserInfoComponent'; + +@Component +export struct MineComponent { + @Provide uiContext: UIContext = this.getUIContext(); + + build() { + Column() { + UserInfoComponent() + + MineMenuComponent() + } + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_1000) + .backgroundColor($r('sys.color.background_secondary')) + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/dialog/AchievementDialog.ets b/features/healthylife/src/main/ets/views/dialog/AchievementDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..de62e1d265c2e7169a72ed7108f1e504b2e765aa --- /dev/null +++ b/features/healthylife/src/main/ets/views/dialog/AchievementDialog.ets @@ -0,0 +1,75 @@ +/* + * + * * 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 { ComponentContent } from '@kit.ArkUI'; +import { AchievementList } from '../../model/AchievementModel'; +import { CommonConstants as Const, PromptActionClass } from 'common'; +import { AchievementDialogParams } from '../../viewmodel/dialog/AchievementDialogParams'; + +export function openAchievementCustomDialog(uIContext: UIContext, index: number): void { + PromptActionClass.setContext(uIContext); + let params = new AchievementDialogParams(uIContext, index, Const.ANIMATE_OPACITY_0, Const.ANIMATE_ANGLE_0, + Const.ANIMATE_SCALE_0); + let taskInfoDialogNode = new ComponentContent(uIContext, wrapBuilder(taskInfoDialogBuilder), params); + PromptActionClass.setContentNode(taskInfoDialogNode); + PromptActionClass.openDialog(); +} + +@Builder +export function taskInfoDialogBuilder(param: AchievementDialogParams) { + Column() { + Image(AchievementList[param.index].iconOn) + .width(Const.THOUSANDTH_560) + .height(Const.THOUSANDTH_400) + .objectFit(ImageFit.Contain) + + Text($r('app.string.achievement_level', AchievementList[param.index].name)) + .fontFamily(Const.HARMONY_HEI_TI) + .fontWeight(FontWeight.Regular) + .fontColor($r('sys.color.font_on_primary')) + .fontSize($r('app.float.default_24')) + .margin({ + top: $r('app.float.default_12') + }) + } + .height(Const.THOUSANDTH_333) + .width(Const.THOUSANDTH_500) + .justifyContent(FlexAlign.Center) + .scale({ + x: param.scaleValue, + y: param.scaleValue + }) + .rotate({ + x: Const.ANIMATE_ROTATE_0, + y: Const.ANIMATE_ROTATE_1, + z: Const.ANIMATE_ROTATE_0, + angle: param.angle + }) + .opacity(param.opacityValue) + .onAppear(() => { + param.uiContext?.animateTo({ + duration: Const.DURATION_800, + curve: Curve.EaseOut, + delay: Const.DURATION_100, + iterations: Const.ANIMATE_ITERATIONS_1 + }, () => { + PromptActionClass.contentNode.update( + new AchievementDialogParams(param.uiContext, param.index, Const.ANIMATE_OPACITY_1, Const.ANIMATE_ANGLE_360, + Const.ANIMATE_SCALE_1)); + }); + }) +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/dialog/TargetSettingDialog.ets b/features/healthylife/src/main/ets/views/dialog/TargetSettingDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..d501f6bdbf6cfd95bde48cc95a93cc197f74ef81 --- /dev/null +++ b/features/healthylife/src/main/ets/views/dialog/TargetSettingDialog.ets @@ -0,0 +1,112 @@ +/* + * + * * 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 { ComponentContent } from '@kit.ArkUI'; +import { + CommonConstants as Const, convertTime2Date, PickerType, PromptActionClass +} from 'common'; +import { TargetSettingDialogParams } from '../../viewmodel/dialog/TargetSettingDialogParams'; + +export function openTargetSettingCustomDialog(uIContext: UIContext, params: TargetSettingDialogParams): void { + PromptActionClass.setContext(uIContext); + params.selectedValue = params.taskInfo.targetValue; + let targetSettingDialogNode = new ComponentContent(uIContext, wrapBuilder(targetSettingDialogBuilder), params); + PromptActionClass.setContentNode(targetSettingDialogNode); + PromptActionClass.setOptions({ + alignment: DialogAlignment.Bottom, + offset: { + dx: Const.DEFAULT_0, + dy: Const.DEFAULT_NEGATIVE_20 + } + }) + PromptActionClass.openDialog(); +} + +@Builder +export function targetSettingDialogBuilder(params: TargetSettingDialogParams) { + Column() { + Row() { + Text($r('app.string.target_setting')) + .fontSize($r('app.float.default_20')) + .margin({ + right: $r('app.float.default_12') + }) + + if (params.taskBaseInfo.limit) { + Text(`(${params.taskBaseInfo.limit})`) + .fontSize($r('app.float.default_16')) + } + } + .width(Const.THOUSANDTH_1000) + .justifyContent(FlexAlign.Start) + + if (params.taskBaseInfo.pickerType === PickerType.TIME) { + TimePicker({ + selected: convertTime2Date(params.selectedValue), + format: TimePickerFormat.HOUR_MINUTE, + start: params.startDate, + end: params.endDate + }) + .height(Const.THOUSANDTH_800) + .useMilitaryTime(true) + .onChange((value: TimePickerResult) => { + if (value.hour != undefined && value.minute != undefined) { + params.selectedValue = `${("0" + value.hour).slice(-2)}:${("0" + value.minute).slice(-2)}`; + } + }) + } else if (params.taskBaseInfo.pickerType === PickerType.TEXT) { + TextPicker({ + range: params.taskBaseInfo.pickerRange, + value: params.selectedValue + }) + .width(Const.THOUSANDTH_900) + .height(Const.THOUSANDTH_800) + .onChange((value: string | string[]) => { + params.selectedValue = value as string; + }) + } + + Row() { + Text($r('app.string.cancel')) + .fontColor($r('sys.color.brand')) + .fontSize($r('app.float.default_20')) + .onClick(() => { + PromptActionClass.closeDialog(); + }) + + Text($r('app.string.confirm')) + .fontColor($r('sys.color.brand')) + .fontSize($r('app.float.default_20')) + .onClick(() => { + params.taskInfo.targetValue = params.selectedValue; + PromptActionClass.closeDialog(); + }) + } + .width(Const.THOUSANDTH_1000) + .height($r('app.float.default_28')) + .justifyContent(FlexAlign.SpaceAround) + .margin({ + bottom: $r('app.float.default_20') + }) + } + .backgroundColor($r('sys.color.background_primary')) + .height(Const.THOUSANDTH_500) + .justifyContent(FlexAlign.SpaceAround) + .borderRadius($r('app.float.default_24')) + .margin($r('app.float.default_18')) + .padding($r('app.float.default_12')) +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/dialog/TaskClockCustomDialog.ets b/features/healthylife/src/main/ets/views/dialog/TaskClockCustomDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..fd6b8e3ced6a63c943cf110004241e8673662c49 --- /dev/null +++ b/features/healthylife/src/main/ets/views/dialog/TaskClockCustomDialog.ets @@ -0,0 +1,101 @@ +/* + * + * * 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 { ComponentContent } from '@kit.ArkUI'; +import { updateTaskList } from '../../viewmodel/HomeStore'; +import TaskInfoApi from 'common/src/main/ets/database/tables/TaskInfoApi'; +import { updateAchievementStore } from '../../viewmodel/AchievementStore'; +import { TaskInfoDialogParams } from '../../viewmodel/dialog/TaskInfoDialogParams'; +import { CommonConstants as Const, PromptActionClass, taskBaseInfoList } from 'common'; + +export function openTaskClockCustomDialog(uIContext: UIContext, params: TaskInfoDialogParams): void { + PromptActionClass.setContext(uIContext); + let taskInfoDialogNode = new ComponentContent(uIContext, wrapBuilder(taskClockDialogBuilder), params); + PromptActionClass.setContentNode(taskInfoDialogNode); + PromptActionClass.openDialog(); +} + +@Builder +export function taskClockDialogBuilder(params: TaskInfoDialogParams) { + Column() { + Text(taskBaseInfoList[params.dayTaskInfo.taskId].name) + .fontFamily(Const.HARMONY_HEI_TI_BOLD) + .fontWeight(FontWeight.Bold) + .fontColor($r(`sys.color.font_on_primary`)) + .fontSize($r('app.float.default_22')) + .width(Const.THOUSANDTH_1000) + .margin({ + left: $r('app.float.default_12') + }) + .position({ + y: $r('app.float.default_267') + }); + + Button() { + Text(params.dayTaskInfo.isDone ? $r('app.string.was_done') : $r('app.string.clock_in')) + .fontFamily(Const.HARMONY_HEI_TI) + .fontWeight(FontWeight.Normal) + .fontColor($r(`sys.color.font_on_primary`)) + .fontSize($r('app.float.default_20')) + .height($r('app.float.default_42')); + } + .width($r('app.float.default_220')) + .borderRadius($r('app.float.default_24')) + .backgroundColor($r('app.color.task_clock_bg_color')) + .onClick(async () => { + await taskClock(params); + }); + + Text($r('app.string.got_it')) + .fontFamily(Const.HARMONY_HEI_TI) + .fontWeight(FontWeight.Regular) + .fontColor($r(`sys.color.font_on_primary`)) + .fontSize($r('app.float.default_14')) + .margin({ + top: $r('app.float.default_12') + }) + .onClick(() => { + PromptActionClass.closeDialog(); + }); + + } + .height($r('app.float.default_451')) + .width($r('app.float.default_316')) + .backgroundImage(taskBaseInfoList[params.dayTaskInfo.taskId].dialog, ImageRepeat.NoRepeat) + .backgroundImageSize({ + width: Const.THOUSANDTH_1000, + height: Const.THOUSANDTH_1000 + }) + .justifyContent(FlexAlign.End) + .padding({ + bottom: $r('app.float.default_12'), + left: $r('app.float.default_20') + }) +} + +async function taskClock(params: TaskInfoDialogParams) { + if (!params.dayTaskInfo.isDone) { + await TaskInfoApi.clockTask(params.dayTaskInfo.date, params.dayTaskInfo.taskId); + await updateTaskList(params.homeStore); + } + PromptActionClass.closeDialog(); + + // After the check-in is completed, determine whether all the tasks of the day are completed and update the achievement information. + if (!params.dayTaskInfo.isDone) { + await updateAchievementStore(params); + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/home/HomeTopComponent.ets b/features/healthylife/src/main/ets/views/home/HomeTopComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..9b0a601f082b3443ee32cf868551c7ee93627d4c --- /dev/null +++ b/features/healthylife/src/main/ets/views/home/HomeTopComponent.ets @@ -0,0 +1,84 @@ +/* + * + * * 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 { HomeStore } from '../../viewmodel/HomeStore'; +import { CommonConstants as Const, DayInfo } from 'common'; +import { WeekCalendarComponent } from './WeekCalendarComponent'; + +@Component +export struct HomeTopComponent { + @Consume @Watch('onDataChange') homeStore: HomeStore; + @State dayInfo: DayInfo = new DayInfo('', 0, 0); + + onDataChange() { + this.dayInfo = this.homeStore.weekList[this.homeStore.selectedDay]; + } + + aboutToAppear(): void { + this.onDataChange(); + } + + build() { + Column() { + Text($r('app.string.target_progress')) + .fontFamily(Const.HARMONY_HEI_TI_MEDIUM) + .fontWeight(FontWeight.Regular) + .fontColor($r(`sys.color.font_secondary`)) + .fontSize($r('app.float.default_16')) + .width(Const.THOUSANDTH_1000) + .padding({ + left: $r('app.float.default_24') + }) + + Row() { + Text(`${this.dayInfo.calculatePercentage()}`) + .fontFamily(Const.HARMONY_HEI_TI_BOLD) + .fontWeight(FontWeight.Bold) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_72')) + + Text(Const.PERCENT) + .fontFamily(Const.HARMONY_HEI_TI_MEDIUM) + .fontWeight(FontWeight.Regular) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_40')) + .width(Const.THOUSANDTH_1000) + .margin({ + top: $r('app.float.default_12'), + left: $r('app.float.default_8') + }) + } + .width(Const.THOUSANDTH_1000) + .padding({ + left: $r('app.float.default_24') + }) + + WeekCalendarComponent() + } + .height(Const.THOUSANDTH_500) + .backgroundImage($r('app.media.ic_home_bg')) + .backgroundImagePosition({ + x: $r('app.float.default_0'), + y: $r('app.float.default_0') + }) + .backgroundImageSize({ + width: Const.THOUSANDTH_1000, + height: Const.THOUSANDTH_900 + }) + .justifyContent(FlexAlign.End) + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/home/TaskListComponent.ets b/features/healthylife/src/main/ets/views/home/TaskListComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..6c16e67903bb124304720aed1d6ef6ad47ca131e --- /dev/null +++ b/features/healthylife/src/main/ets/views/home/TaskListComponent.ets @@ -0,0 +1,135 @@ +/* + * + * * 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 { HomeStore } from '../../viewmodel/HomeStore'; +import { AchievementStore } from '../../viewmodel/AchievementStore'; +import TaskInfoApi from 'common/src/main/ets/database/tables/TaskInfoApi'; +import { openTaskClockCustomDialog } from '../dialog/TaskClockCustomDialog'; +import { TaskInfoDialogParams } from '../../viewmodel/dialog/TaskInfoDialogParams'; +import { CommonConstants as Const, DayTaskInfo, taskBaseInfoList } from 'common'; + +@Component +export struct TaskListComponent { + @Consume('pathStack') pageStack: NavPathStack; + @Consume homeStore: HomeStore; + @Consume achievementStore: AchievementStore; + + build() { + if (this.homeStore.taskList.length > 0) { + Column() { + ForEach(this.homeStore.taskList, (item: DayTaskInfo, index: number) => { + this.taskCardBuilder(item, index) + }, (item: DayTaskInfo, index: number) => index + JSON.stringify(item)) + } + .width(Const.THOUSANDTH_1000) + .padding({ + top: Const.THOUSANDTH_15, + right: Const.THOUSANDTH_33, + left: Const.THOUSANDTH_33 + }) + } else { + Column() { + Image($r('app.media.ic_no_data')) + .width($r('app.float.default_132')) + .height($r('app.float.default_100')) + .margin({ + bottom: $r('app.float.default_8') + }) + + Text($r('app.string.no_task')) + .fontFamily(Const.HARMONY_HEI_TI_MEDIUM) + .fontWeight(FontWeight.Regular) + .fontColor($r(`sys.color.font_secondary`)) + .fontSize($r('app.float.default_14')) + } + .width(Const.THOUSANDTH_1000) + .margin({ + top: $r('app.float.default_48') + }) + } + } + + @Builder + taskCardBuilder(item: DayTaskInfo, _index: number) { + Row() { + Image(taskBaseInfoList[item.taskId].icon) + .width($r('app.float.default_36')) + .height($r('app.float.default_36')) + .objectFit(ImageFit.Contain) + .margin({ + right: $r('app.float.default_8') + }) + + Text(taskBaseInfoList[item.taskId].name) + .fontFamily(Const.HARMONY_HEI_TI_MEDIUM) + .fontWeight(FontWeight.Regular) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_16')) + + Blank() + + this.targetValueBuilder(item) + } + .width(Const.THOUSANDTH_1000) + .height($r('app.float.default_62')) + .borderRadius($r('app.float.default_24')) + .padding({ + left: Const.THOUSANDTH_50, + right: Const.THOUSANDTH_50 + }) + .backgroundColor($r('sys.color.background_primary')) + .margin({ + bottom: $r('app.float.default_12') + }) + .onClick(() => { + if (this.homeStore.checkCurrentDay()) { + let params = new TaskInfoDialogParams(item, this.homeStore, this.achievementStore, this.getUIContext()); + openTaskClockCustomDialog(this.getUIContext(), params); + } + }) + .gesture(LongPressGesture().onAction(async () => { + if (this.homeStore.checkCurrentDay()) { + let taskInfo = await TaskInfoApi.queryByKey(item.taskId); + this.pageStack.pushPath({ name: 'EditTaskComponent', param: taskInfo }); + } + })) + } + + @Builder + targetValueBuilder(item: DayTaskInfo) { + if (item.isDone) { + this.taskValueBuilder($r('app.string.was_done'), $r(`sys.color.font_primary`)) + } else { + Row() { + this.taskValueBuilder((item.finValue || `--`), $r(`sys.color.font_secondary`)) + + this.taskValueBuilder(` / ${item.targetValue} `, $r(`sys.color.font_primary`)) + + this.taskValueBuilder(taskBaseInfoList[item.taskId].unit, $r(`sys.color.font_primary`)) + } + } + } + + @Builder + taskValueBuilder(content: ResourceStr, fontColor: Resource) { + Text(content) + .fontFamily(Const.HARMONY_HEI_TI_MEDIUM) + .fontWeight(FontWeight.Regular) + .fontColor(fontColor) + .fontSize($r('app.float.default_16')) + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/home/WeekCalendarComponent.ets b/features/healthylife/src/main/ets/views/home/WeekCalendarComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..9a2d370947332fd20e88cd221b0a933853fcd86e --- /dev/null +++ b/features/healthylife/src/main/ets/views/home/WeekCalendarComponent.ets @@ -0,0 +1,177 @@ +/* + * + * * 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 { HomeStore, initCurrentDateInfo } from '../../viewmodel/HomeStore'; +import { CommonConstants as Const, convertStr2Date, DayInfo, formatSystemTime } from 'common'; + +@Component +export struct WeekCalendarComponent { + @Consume @Watch('onDataChange') homeStore: HomeStore; + @State showDateStr: string = ''; + @State currentWeekIndex: number = Const.WEEK_DAY_NUM; + showDate: Date = new Date(); + scroller: Scroller = new Scroller(); + + onDataChange() { + this.showDate = convertStr2Date(this.homeStore.currentDate); + this.showDateStr = formatSystemTime(this.showDate); + } + + private async refreshDateInfo(diffDay: number) { + this.showDate.setDate(this.showDate.getDate() + diffDay); + this.homeStore = await initCurrentDateInfo(this.showDate); + } + + /** + * Switch the week view, swipe left and right to the top and next week after exceeding a certain threshold. + */ + private weekChange() { + const center = this.scroller.currentOffset().xOffset; + if (center <= Const.WEEK_LEFT_LIMIT) { + this.animateToWeekAndRefresh(-Const.WEEK_DAY_NUM, 0); + } else if (center >= Const.WEEK_RIGHT_LIMIT) { + this.animateToWeekAndRefresh(Const.WEEK_DAY_NUM, Const.WEEK_DAY_NUM * 2); + } else { + this.getUIContext().animateTo({ + duration: Const.DURATION_200 + }, () => { + this.scroller.scrollToIndex(Const.WEEK_DAY_NUM); + }); + } + } + + private animateToWeekAndRefresh(diffDay: number, toIndex: number) { + this.getUIContext().animateTo({ + duration: Const.DURATION_100, + onFinish: async () => { + await this.refreshDateInfo(diffDay); + this.scroller.scrollToIndex(Const.WEEK_DAY_NUM); + } + }, () => { + this.scroller.scrollToIndex(toIndex); + }); + } + + aboutToAppear(): void { + this.onDataChange(); + } + + build() { + Column() { + Column() { + Row() { + this.symbolBuild($r('sys.symbol.chevron_left'), async () => { + await this.refreshDateInfo(-Const.WEEK_DAY_NUM); + }) + + Text(this.showDateStr) + .fontFamily(Const.HARMONY_HEI_TI_MEDIUM) + .fontWeight(FontWeight.Regular) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_14')) + .padding({ + left: $r('app.float.default_14'), + right: $r('app.float.default_14') + }) + + this.symbolBuild($r('sys.symbol.chevron_right'), async () => { + await this.refreshDateInfo(Const.WEEK_DAY_NUM); + }) + } + + List({ + scroller: this.scroller, + initialIndex: this.currentWeekIndex + }) { + ForEach(this.homeStore.weekList, (item: DayInfo, index: number) => { + this.dayOfWeekBuilder(item, index) + }, (item: DayInfo, index: number) => index + JSON.stringify(item)) + } + .scrollBar(BarState.Off) + .listDirection(Axis.Horizontal) + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_500) + .onScrollStop(() => { + this.weekChange(); + }) + } + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_1000) + .justifyContent(FlexAlign.SpaceEvenly) + .borderRadius($r('sys.float.radio_container_size')) + .backgroundColor($r('sys.color.background_primary')) + } + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_420) + .padding(Const.THOUSANDTH_33) + } + + @Builder + dayOfWeekBuilder(item: + DayInfo, index: + number + ) { + Column() { + Text(Const.WEEK_SHORTHAND_LIST[index % Const.WEEK_DAY_NUM]) + .fontFamily(Const.HARMONY_HEI_TI_MEDIUM) + .fontWeight(FontWeight.Regular) + .fontColor(this.homeStore.selectedDay === index ? $r('sys.color.brand') : $r('sys.color.font_secondary')) + .fontSize($r('app.float.default_12')) + + Divider() + .margin({ + top: $r('app.float.default_2'), + bottom: $r('app.float.default_4') + }) + .width($r('app.float.default_12')) + .color($r('sys.color.brand')) + .visibility(this.homeStore.selectedDay === index ? Visibility.Visible : Visibility.Hidden) + + Image(item.getProgressImg()) + .height($r('app.float.default_28')) + .objectFit(ImageFit.Contain) + .margin({ + top: Const.THOUSANDTH_80 + }) + } + .width(Const.PERCENTAGE_100_EXCEPT_7) + .justifyContent(FlexAlign.SpaceBetween) + .onClick(async () => { + if (this.homeStore.selectedDay === index) { + return; + } + const diffDay = index - this.homeStore.selectedDay; + await this.refreshDateInfo(diffDay); + }) + } + + @Builder + symbolBuild(symbolResource: + Resource, clickFun: + (event: ClickEvent) => void + ) { + SymbolGlyph(symbolResource) + .fontColor([$r('sys.color.icon_secondary')]) + .fontWeight(FontWeight.Normal) + .fontSize($r('app.float.default_18')) + .clickEffect({ + level: ClickEffectLevel.HEAVY, + scale: Const.SYMBOL_GLYPH_CLICK_SCALE + }) + .onClick(clickFun) + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/mine/MineMenuComponent.ets b/features/healthylife/src/main/ets/views/mine/MineMenuComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..1c04518d2ef8dbe983591fc5eaf6ad434304fa68 --- /dev/null +++ b/features/healthylife/src/main/ets/views/mine/MineMenuComponent.ets @@ -0,0 +1,72 @@ +/* + * + * * Copyright (c) 2025 Huawei Device Co., Ltd. + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +import { CommonConstants as Const } from 'common'; + +@Component +export struct MineMenuComponent { + build() { + List() { + ForEach(Const.DEFAULT_MENU_INFO, (item: Resource) => { + ListItem() { + Flex({ + justifyContent: FlexAlign.SpaceBetween, + alignItems: ItemAlign.Center + }) { + Text(item) + .fontSize($r('app.float.default_16')) + .height($r('app.float.default_40')) + + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor([$r('sys.color.icon_secondary')]) + .fontWeight(FontWeight.Normal) + .fontSize($r('app.float.default_24')) + } + } + .backgroundColor($r('sys.color.background_primary')) + .margin({ + left: $r('app.float.default_24'), + right: $r('app.float.default_24') + }) + .height($r('app.float.default_48')) + .border({ + width: { + bottom: $r('app.float.default_1') + }, + color: $r('sys.color.background_tertiary') + }) + }, (item: Resource, index: number) => index + JSON.stringify(item)); + } + .border({ + radius: { + topLeft: $r('app.float.default_24'), + topRight: $r('app.float.default_24') + } + }) + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_560) + .backgroundColor($r('sys.color.background_primary')) + .margin({ + top: $r('app.float.default_24') + }) + .padding({ + top: $r('app.float.default_16') + }) + .flexGrow(Const.FLEX_GROW_1) + .clip(true) + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/mine/UserInfoComponent.ets b/features/healthylife/src/main/ets/views/mine/UserInfoComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..d51323fb88ae01a16bb071f6baed003dfccfcfc9 --- /dev/null +++ b/features/healthylife/src/main/ets/views/mine/UserInfoComponent.ets @@ -0,0 +1,65 @@ +/* + * + * * 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 'common'; + +@Component +export struct UserInfoComponent { + build() { + Column() { + Image($r('app.media.ic_user')) + .objectFit(ImageFit.Contain) + .height($r('app.float.default_66')) + .width($r('app.float.default_66')) + .margin({ + top: $r('app.float.default_71') + }) + + Column() { + Text($r('app.string.user_level')) + .fontWeight(FontWeight.Bolder) + .fontColor($r('app.color.user_level_font_color')) + .fontSize($r('app.float.default_12')) + } + .width($r('app.float.default_44')) + .height($r('app.float.default_16')) + .margin({ + top: $r('app.float.default_n8') + }) + .border({ + radius: $r('app.float.default_5') + }) + .backgroundColor($r('app.color.user_level_bg_color')) + .justifyContent(FlexAlign.Center) + + Text($r('app.string.user_name')) + .fontFamily(Const.HELVETICA) + .fontWeight(FontWeight.Normal) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('app.float.default_20')) + .margin({ + bottom: $r('app.float.default_6') + }) + + Text($r('app.string.user_signature')) + .fontFamily(Const.PING_FANG_SC_REGULAR) + .fontWeight(FontWeight.Normal) + .fontColor($r('sys.color.font_secondary')) + .fontSize($r('app.float.default_16')) + } + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/task/AddTaskComponent.ets b/features/healthylife/src/main/ets/views/task/AddTaskComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..3133e0330bcc6e1a5d5b0b97f79d50f7244c76f6 --- /dev/null +++ b/features/healthylife/src/main/ets/views/task/AddTaskComponent.ets @@ -0,0 +1,104 @@ +/* + * + * * 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 TaskInfoApi from 'common/src/main/ets/database/tables/TaskInfoApi'; +import { CommonConstants as Const, taskBaseInfoList, TaskInfo } from 'common'; + +@Builder +export function addTaskBuilder() { + AddTaskComponent(); +} + +@Component +export struct AddTaskComponent { + @Consume('pathStack') pageStack: NavPathStack; + @State taskList: TaskInfo[] = []; + @State flag: boolean = false; + + async aboutToAppear(): Promise { + this.taskList = await TaskInfoApi.queryAllTaskInfo(); + this.flag = true; + } + + build() { + NavDestination() { + if (this.flag) { + Column({ + space: $r('app.float.default_2') + }) { + ForEach(this.taskList, (item: TaskInfo, index: number) => { + this.taskInfoBuilder(item, index) + }, (item: TaskInfo, index: number) => index + JSON.stringify(item)) + } + .width(Const.THOUSANDTH_940) + .justifyContent(FlexAlign.Center) + } + } + .size({ + width: Const.THOUSANDTH_1000, + height: Const.THOUSANDTH_1000 + }) + .backgroundColor($r('sys.color.background_secondary')) + .title($r('app.string.add_task')) + .padding({ + top: $r('app.float.default_36') + }) + } + + @Builder + taskInfoBuilder(item: TaskInfo, _index: number) { + Row() { + Image(taskBaseInfoList[item.taskId].icon) + .width($r('app.float.default_24')) + .height($r('app.float.default_24')) + .objectFit(ImageFit.Contain) + .margin({ + right: $r('app.float.default_8') + }) + + Text(taskBaseInfoList[item.taskId].name) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_20')) + + Blank(); + + Text($r('app.string.task_opened')) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_16')) + .margin({ + right: $r('app.float.default_8') + }) + .visibility(item.isOpen ? Visibility.Visible : Visibility.Hidden) + + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor([$r('sys.color.icon_secondary')]) + .fontWeight(FontWeight.Normal) + .fontSize($r('app.float.default_28')) + } + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_80) + .borderRadius($r('app.float.default_12')) + .padding({ + left: $r('app.float.default_12'), + right: $r('app.float.default_12') + }) + .backgroundColor($r('sys.color.background_primary')) + .onClick(() => { + this.pageStack.pushPath({ name: 'EditTaskComponent', param: item }); + }) + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/ets/views/task/EditTaskComponent.ets b/features/healthylife/src/main/ets/views/task/EditTaskComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..815b67304843a4220e3b8eede18fae46142431ad --- /dev/null +++ b/features/healthylife/src/main/ets/views/task/EditTaskComponent.ets @@ -0,0 +1,218 @@ +/* + * + * * 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 { HomeStore, updateTaskList } from '../../viewmodel/HomeStore'; +import TaskInfoApi from 'common/src/main/ets/database/tables/TaskInfoApi'; +import { openTargetSettingCustomDialog } from '../dialog/TargetSettingDialog'; +import DayTaskInfoApi from 'common/src/main/ets/database/tables/DayTaskInfoApi'; +import { TargetSettingDialogParams } from '../../viewmodel/dialog/TargetSettingDialogParams'; +import { + AgentUtils, + CommonConstants as Const, + PickerType, + RequestAuthorization, + TaskBaseInfo, + taskBaseInfoList, + TaskInfo +} from 'common'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const TAG: string = 'EditTaskComponent' + +@Builder +export function editTaskBuilder(_name: string, param: TaskInfo) { + EditTaskComponent({ + taskInfo: param, + originalInfo: param.deepClone(), + taskBaseInfo: taskBaseInfoList[param.taskId] + }); +} + +@Extend(Text) +function targetValueStyle(isOpen: boolean, pickerType: PickerType) { + .fontSize($r('app.float.default_16')) + .fontColor((isOpen && pickerType !== PickerType.NONE) ? $r('sys.color.font_primary') : $r('sys.color.font_tertiary')) + .padding({ + right: $r('app.float.default_6') + }) +} + +@Component +export struct EditTaskComponent { + @Consume('pathStack') pageStack: NavPathStack; + @Consume homeStore: HomeStore; + @Provide taskInfo: TaskInfo = new TaskInfo(0, false, '', '', false, false, '', '', '', -1); + @Provide taskBaseInfo: TaskBaseInfo = taskBaseInfoList[this.taskInfo.taskId]; + originalInfo: TaskInfo = this.taskInfo; + + async finishTaskEdit() { + if (this.originalInfo.isTaskEqual(this.taskInfo)) { + this.pageStack.clear(true); + return; + } + const statusUnchanged = + (this.originalInfo.isOpen && this.taskInfo.isOpen) || (!this.originalInfo.isOpen && !this.taskInfo.isOpen); + if (statusUnchanged) { + // the task status has not changed, and only the other information of the task needs to be updated. + await TaskInfoApi.update(this.taskInfo.taskId, this.taskInfo); + if (this.originalInfo.isOpen) { + // the current task is open, need to synchronize the update day task information. + await DayTaskInfoApi.queryKeyByDateAndTaskId(this.homeStore.currentDate, this.taskInfo.taskId) + .then(async (key: number) => { + await DayTaskInfoApi.update(key, this.taskInfo.convert2DayTask(this.homeStore.currentDate)); + }) + if (this.taskBaseInfo.unit === '') { + // update agent alerts + AgentUtils.updateAgent(this.taskInfo, this.taskBaseInfo, this.getUIContext()); + hilog.info(0x0000, TAG, `update reminderId successed, reminderId is ${this.taskInfo.reminderId}`); + } + } + } else { + // the task status has changed, need to update the task information before modifying the task status. + this.taskInfo.isOpen = this.originalInfo.isOpen; + if (!this.originalInfo.isTaskEqual(this.taskInfo)) { + await TaskInfoApi.update(this.taskInfo.taskId, this.taskInfo); + } + if (!this.taskInfo.isOpen) { + await TaskInfoApi.openTask(this.homeStore.currentDate, this.taskInfo.taskId); + if (this.taskBaseInfo.unit === '') { + let oldTaskInfo = await TaskInfoApi.queryByKey(this.taskInfo.taskId); + if (oldTaskInfo) { + let isRequestSuccess = await RequestAuthorization.requestNotification(this.getUIContext()); + if (isRequestSuccess) { + let reminderId = await AgentUtils.creatAgent(this.taskInfo, this.taskBaseInfo, this.getUIContext()); + // update reminderId + oldTaskInfo.reminderId = reminderId; + await TaskInfoApi.update(oldTaskInfo.taskId, oldTaskInfo); + hilog.info(0x0000, TAG, `create reminderId successed, reminderId is ${reminderId}`); + } + } + } + } else { + await TaskInfoApi.closeTask(this.homeStore.currentDate, this.taskInfo.taskId); + // Check the reminderId, if it exists, the proxy reminder needs to be turned off. + if (this.taskBaseInfo.unit === '') { + let taskInfo = await TaskInfoApi.queryByKey(this.taskInfo.taskId); + if (taskInfo && taskInfo.reminderId !== -1) { + AgentUtils.deleteAgent(taskInfo.reminderId); + hilog.info(0x0000, TAG, `delete reminderId successed, reminderId is ${taskInfo.reminderId}`); + } + } + } + } + await updateTaskList(this.homeStore); + this.pageStack.clear(true); + } + + build() { + NavDestination() { + Column({ + space: $r('app.float.default_2') + }) { + Row() { + Text(this.taskBaseInfo.name) + .fontWeight(FontWeight.Medium) + .fontSize($r('app.float.default_20')) + + Toggle({ + type: ToggleType.Switch, + isOn: this.taskInfo.isOpen + }) + .width($r('app.float.default_56')) + .height($r('app.float.default_32')) + .onChange((isOn) => { + this.taskInfo.isOpen = isOn; + }) + } + .width(Const.THOUSANDTH_940) + .justifyContent(FlexAlign.SpaceBetween) + .backgroundColor($r('sys.color.background_primary')) + .height($r('app.float.default_56')) + .borderRadius($r('app.float.default_10')) + .padding({ + left: $r('app.float.default_12'), + right: $r('app.float.default_12') + }) + + Row() { + Text($r('app.string.target_setting')) + .fontWeight(FontWeight.Medium) + .fontSize($r('app.float.default_20')) + + Blank() + + Text(this.taskInfo.targetValue) + .targetValueStyle(this.taskInfo.isOpen, this.taskBaseInfo.pickerType) + + if (this.taskBaseInfo.unit !== '') { + Text(this.taskBaseInfo.unit) + .targetValueStyle(this.taskInfo.isOpen, this.taskBaseInfo.pickerType) + + Text($r('app.string.unit_pre_day')) + .targetValueStyle(this.taskInfo.isOpen, this.taskBaseInfo.pickerType) + } + + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor([$r('sys.color.icon_secondary')]) + .fontSize($r('app.float.default_28')) + } + .width(Const.THOUSANDTH_940) + .height($r('app.float.default_56')) + .borderRadius($r('app.float.default_10')) + .backgroundColor($r('sys.color.background_primary')) + .justifyContent(FlexAlign.SpaceBetween) + .padding({ + left: $r('app.float.default_12'), + right: $r('app.float.default_12') + }) + .enabled(true) + .onClick(() => { + if (this.taskInfo.isOpen && this.taskBaseInfo.pickerType !== PickerType.NONE) { + let params = new TargetSettingDialogParams(this.taskInfo, this.taskBaseInfo); + openTargetSettingCustomDialog(this.getUIContext(), params); + } + }) + + Button() { + Text($r('app.string.complete')) + .fontSize($r('app.float.default_20')) + } + .width(Const.THOUSANDTH_800) + .height($r('app.float.default_48')) + .backgroundColor($r('sys.color.background_primary')) + .position({ + x: Const.THOUSANDTH_100, + y: Const.THOUSANDTH_800 + }) + .onClick(async () => { + await this.finishTaskEdit(); + }) + } + .width(Const.THOUSANDTH_1000) + .justifyContent(FlexAlign.Center) + } + .size({ + width: Const.THOUSANDTH_1000, + height: Const.THOUSANDTH_1000 + }) + .title($r('app.string.edit_task')) + .padding({ + top: $r('app.float.default_36') + }) + .backgroundColor($r('sys.color.background_secondary')) + } +} \ No newline at end of file diff --git a/features/healthylife/src/main/module.json5 b/features/healthylife/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6f4cb669b6ea1320e31c2669d5dc3cc49ddaf33a --- /dev/null +++ b/features/healthylife/src/main/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "healthylife", + "type": "har", + "description": "$string:module_desc", + "deviceTypes": [ + "phone" + ], + "installationFree": false, + "routerMap": "$profile:router_map" + } +} diff --git a/features/healthylife/src/main/resources/base/profile/main_pages.json b/features/healthylife/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..400e56a7582714d4cd313b33d6d30e91e723c1ed --- /dev/null +++ b/features/healthylife/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/HealthyLifePage" + ] +} diff --git a/features/healthylife/src/main/resources/base/profile/router_map.json b/features/healthylife/src/main/resources/base/profile/router_map.json new file mode 100644 index 0000000000000000000000000000000000000000..c3b3b9986e16b3d1f1ce7d2335ed7062a26c98ed --- /dev/null +++ b/features/healthylife/src/main/resources/base/profile/router_map.json @@ -0,0 +1,19 @@ +{ + "routerMap": [ + { + "name": "HomeComponent", + "pageSourceFile": "src/main/ets/views/HomeComponent.ets", + "buildFunction": "homeBuilder" + }, + { + "name": "AddTaskComponent", + "pageSourceFile": "src/main/ets/views/task/AddTaskComponent.ets", + "buildFunction": "addTaskBuilder" + }, + { + "name": "EditTaskComponent", + "pageSourceFile": "src/main/ets/views/task/EditTaskComponent.ets", + "buildFunction": "editTaskBuilder" + } + ] +} \ No newline at end of file diff --git a/features/healthylife/src/ohosTest/ets/test/Ability.test.ets b/features/healthylife/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/features/healthylife/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/features/healthylife/src/ohosTest/ets/test/List.test.ets b/features/healthylife/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/features/healthylife/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/features/healthylife/src/ohosTest/module.json5 b/features/healthylife/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..eb6d75f7fe2a0d9fac5831abfa27a6cc91b444a6 --- /dev/null +++ b/features/healthylife/src/ohosTest/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "healthylife_test", + "type": "feature", + "deviceTypes": [ + "default" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/features/healthylife/src/test/List.test.ets b/features/healthylife/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/features/healthylife/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/features/healthylife/src/test/LocalUnit.test.ets b/features/healthylife/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/features/healthylife/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7a7ab8914d8db6ab89758e185df5855dffe88d04 --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,23 @@ +{ + "modelVersion": "6.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..47113e2e36ecefde41c136272a0bd6ff745cffe4 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/oh-package-lock.json5 b/oh-package-lock.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6b264af261c4152dee3641068ee2fff156299e4b --- /dev/null +++ b/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", + "@ohos/hypium@1.0.24": "@ohos/hypium@1.0.24" + }, + "packages": { + "@ohos/hamock@1.0.0": { + "name": "", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.24": { + "name": "", + "version": "1.0.24", + "integrity": "sha512-3dCqc+BAR5LqEGG2Vtzi8O3r7ci/3fYU+FWjwvUobbfko7DUnXGOccaror0yYuUhJfXzFK0aZNMGSnXaTwEnbw==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.24.har", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c72aa05d549507e5ea6ce0bc0a8080c450508c7d --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "modelVersion": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.24", + "@ohos/hamock": "1.0.0" + } +} diff --git a/products/default/.gitignore b/products/default/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/products/default/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/products/default/build-profile.json5 b/products/default/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6bd6457a2fed846cc10698666fa1268219f1bee1 --- /dev/null +++ b/products/default/build-profile.json5 @@ -0,0 +1,33 @@ +{ + "apiType": "stageMode", + "buildOption": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/products/default/hvigorfile.ts b/products/default/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0e3a1ab98a91bc918d6404b2413111a5011f14a --- /dev/null +++ b/products/default/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/products/default/obfuscation-rules.txt b/products/default/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/products/default/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/products/default/oh-package-lock.json5 b/products/default/oh-package-lock.json5 new file mode 100644 index 0000000000000000000000000000000000000000..709713f0c402bc2d411c9ca4c55253cb0a09a121 --- /dev/null +++ b/products/default/oh-package-lock.json5 @@ -0,0 +1,29 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "common@../../commons/common": "common@../../commons/common", + "healthylife@../../features/healthylife": "healthylife@../../features/healthylife" + }, + "packages": { + "common@../../commons/common": { + "name": "common", + "version": "1.0.0", + "resolved": "", + "registryType": "local" + }, + "healthylife@../../features/healthylife": { + "name": "healthylife", + "version": "1.0.0", + "resolved": "", + "registryType": "local", + "dependencies": { + "common": "file:../../commons/common" + } + } + } +} \ No newline at end of file diff --git a/products/default/oh-package.json5 b/products/default/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0746e9ebf2c63284e9f58f0a9babb4ff0fbda031 --- /dev/null +++ b/products/default/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "default", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "common": "file:../../commons/common", + "healthylife":"file:../../features/healthylife" + } +} \ No newline at end of file diff --git a/products/default/src/main/ets/agency/pages/AgencyCard.ets b/products/default/src/main/ets/agency/pages/AgencyCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..1ca382cd5be8d0ce3b97bacf9295f179dc607b21 --- /dev/null +++ b/products/default/src/main/ets/agency/pages/AgencyCard.ets @@ -0,0 +1,206 @@ +/* + * + * * 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, taskBaseInfoList } from 'common'; + +let agencyStorage = new LocalStorage(); + +@Entry(agencyStorage) +@Component +struct AgencyCard { + @LocalStorageProp('dayTaskInfos') dayTaskInfos: DayTaskInfo[] = []; + /* + * The action type. + */ + readonly ACTION_TYPE: string = 'router'; + /* + * The ability name. + */ + readonly ABILITY_NAME: string = 'EntryAbility'; + /* + * The with percentage setting. + */ + readonly FULL_WIDTH_PERCENT: string = '100%'; + /* + * The height percentage setting. + */ + readonly FULL_HEIGHT_PERCENT: string = '100%'; + /* + * The empty image width percentage setting. + */ + readonly EMPTY_IMAGE_WIDTH: string = '31%'; + /* + * The agency component height percentage setting. + */ + readonly AGENCY_COMPONENT_HEIGHT: string = '30.5%'; + /* + * The empty image height percentage setting. + */ + readonly EMPTY_IMAGE_HEIGHT: string = '51%'; + /* + * The empty data text component margin top. + */ + readonly TEXT_MARGIN_TOP: string = '0.5%'; + /* + * The cross bar symbol stitching + */ + readonly CROSS_BAR_SYMBOL: string = '--'; + /* + * The slashes splicing. + */ + readonly SLASHES: string = '/'; + /* + * The target value splicing + */ + readonly TARGET_VALUE_SPLICING: string = this.CROSS_BAR_SYMBOL + this.SLASHES; + /** + * The list component space. + */ + readonly LIST_SPACE: number = 5; + /** + * The list component two lanes. + */ + readonly LIST_TWO_LANES: number = 2; + /** + * The empty data text opacity. + */ + readonly TEXT_OPACITY: number = 0.4; + /** + * The target text opacity. + */ + readonly TARGET_TEXT_OPACITY: number = 0.6; + /** + * The target text weight. + */ + readonly TARGET_TEXT_WEIGHT: number = 1; + /** + * The text slightly bold. + */ + readonly TEXT_SLIGHTLY_BOLD: number = 500; + + build() { + Column() { + if (this.dayTaskInfos.length > 0) { + List({ space: this.LIST_SPACE }) { + ForEach(this.dayTaskInfos, (taskItem: DayTaskInfo) => { + ListItem() { + this.AgencyComponent(taskItem) + } + .margin({ + right: $r("app.float.agency_item_margin") + }) + .borderRadius($r("app.float.agency_item_radius")) + .backgroundColor(Color.White) + }, (item: DayTaskInfo, index: number) => index + JSON.stringify(item)) + } + .padding({ + left: $r('app.float.agency_padding_left'), + top: $r('app.float.agency_padding_top'), + right: $r('app.float.agency_padding_right'), + bottom: $r('app.float.agency_padding_bottom') + }) + .lanes(this.LIST_TWO_LANES) + .backgroundColor($r("app.color.list_background_color")) + .width(this.FULL_WIDTH_PERCENT) + .height(this.FULL_HEIGHT_PERCENT) + } else { + this.AgencyNoData() + } + } + } + + @Builder + AgencyComponent(taskItem: DayTaskInfo) { + Row() { + Image(taskBaseInfoList[taskItem.taskId].icon) + .width($r('app.float.agency_image_size')) + .height($r('app.float.agency_image_size')) + .objectFit(ImageFit.Contain) + + Row() { + if (taskBaseInfoList[taskItem.taskId].step === 0) { + Text(taskItem.isDone ? taskItem.targetValue : this.TARGET_VALUE_SPLICING + taskItem.targetValue) + .fontWeight(FontWeight.Normal) + .fontColor($r("app.color.text_common_color")) + .fontSize($r("app.float.text_common_size")) + .opacity(this.TARGET_TEXT_OPACITY) + .textAlign(TextAlign.End) + } else { + Text(taskItem.finValue === '' ? this.CROSS_BAR_SYMBOL : taskItem.finValue) + .fontWeight(taskItem.finValue === '' ? FontWeight.Normal : this.TEXT_SLIGHTLY_BOLD) + .fontColor(taskItem.finValue === '' ? $r('app.color.hex_common_color') : $r('app.color.text_common_color')) + .fontSize(taskItem.finValue === '' ? $r("app.float.text_common_size") : $r('app.float.agency_text_bold')) + + Text(this.SLASHES + taskItem.targetValue) + .fontWeight(FontWeight.Normal) + .fontColor($r('app.color.hex_common_color')) + .fontSize($r("app.float.text_common_size")) + } + + Text(taskBaseInfoList[taskItem.taskId].unit) + .fontWeight(FontWeight.Normal) + .fontColor($r('app.color.hex_common_color')) + .fontSize($r("app.float.text_common_size")) + } + .layoutWeight(this.TARGET_TEXT_WEIGHT) + .justifyContent(FlexAlign.End) + + } + .padding({ + left: $r('app.float.agency_row_padding'), + right: $r('app.float.agency_row_padding') + }) + .width(this.FULL_WIDTH_PERCENT) + .height(this.AGENCY_COMPONENT_HEIGHT) + .onClick(() => { + this.jumpToAbility(); + }) + } + + @Builder + AgencyNoData() { + Column() { + Image($r('app.media.ic_no_data')) + .width(this.EMPTY_IMAGE_WIDTH) + .height(this.EMPTY_IMAGE_HEIGHT) + .objectFit(ImageFit.Contain) + + Text($r('app.string.agency_no_task')) + .fontWeight(FontWeight.Normal) + .fontColor(Color.White) + .fontSize($r('app.float.empty_data_size')) + .opacity(this.TEXT_OPACITY) + .margin({ + top: this.TEXT_MARGIN_TOP + }) + } + .justifyContent(FlexAlign.Center) + .width(this.FULL_WIDTH_PERCENT) + .height(this.FULL_HEIGHT_PERCENT) + .backgroundColor($r("app.color.no_data_background")) + .onClick(() => { + this.jumpToAbility(); + }) + } + + jumpToAbility() { + postCardAction(this, { + 'action': this.ACTION_TYPE, + 'abilityName': this.ABILITY_NAME + }); + } +} \ No newline at end of file diff --git a/products/default/src/main/ets/defaultformability/DefaultFormAbility.ets b/products/default/src/main/ets/defaultformability/DefaultFormAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..49cce8604a855fd5ed97a3e3a35f10489663ee5c --- /dev/null +++ b/products/default/src/main/ets/defaultformability/DefaultFormAbility.ets @@ -0,0 +1,68 @@ +/* + * + * * 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 { Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { formBindingData, FormExtensionAbility, formInfo } from '@kit.FormKit'; +import { FormInfo } from 'common'; +import FormUtils from 'common/src/main/ets/utils/FormUtils'; + +// hilog tag +const TAG = 'DefaultFormAbility'; + +export default class DefaultFormAbility extends FormExtensionAbility { + onAddForm(want: Want) { + if (!want || !want.parameters) { + hilog.error(0x0000, TAG, `FormAbility onAddForm want or want.parameters is undefined`); + return formBindingData.createFormBindingData(''); + } + // Obtain the current card information and add it to the card information base table. + let formId: string = want.parameters[formInfo.FormParam.IDENTITY_KEY] as string; + let formName: string = want.parameters[formInfo.FormParam.NAME_KEY] as string; + let formDimension: number = want.parameters[formInfo.FormParam.DIMENSION_KEY] as number; + let formInfoObj: FormInfo = new FormInfo(formId, formName, formDimension); + FormUtils.insertFormData(this.context, formInfoObj); + hilog.info(0x0000, TAG, `FormAbility onAddForm to init form is ${formName}`); + // Called to return a FormBindingData object. + const formData = ''; + return formBindingData.createFormBindingData(formData); + } + + onCastToNormalForm(formId: string) { + // Called when the form provider is notified that a temporary form is successfully + // converted to a normal form. + } + + onUpdateForm(formId: string) { + // Called to notify the form provider to update a specified form. + FormUtils.updateForms(); + } + + onFormEvent(formId: string, message: string) { + // Called when a specified message event defined by the form provider is triggered. + } + + onRemoveForm(formId: string) { + // Called to notify the form provider that a specified form has been destroyed. + FormUtils.deleteForm(this.context, formId); + } + + onAcquireFormState(want: Want) { + // Called to return a {@link FormState} object. + return formInfo.FormState.READY; + } +} \ No newline at end of file diff --git a/products/default/src/main/ets/entryability/EntryAbility.ets b/products/default/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..ac0c02cbe3241c6f4d8e5f76cf485928e22fdfa1 --- /dev/null +++ b/products/default/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,83 @@ +/* + * + * * 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 { window } from '@kit.ArkUI'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { convertDate2Str, RdbConstants } from 'common'; +import RdbUtils from 'common/src/main/ets/database/RdbUtils'; +import DayInfoApi from 'common/src/main/ets/database/tables/DayInfoApi'; +import TaskInfoApi from 'common/src/main/ets/database/tables/TaskInfoApi'; +import PreferencesUtils from 'common/src/main/ets/utils/PreferencesUtils'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + + // Init RDB connect and create tables + await RdbUtils.createRdb(this.context).then(() => { + for (let tableName of RdbConstants.TABLE_NAME_LIST) { + RdbUtils.createTable(tableName); + } + }); + await TaskInfoApi.checkDefaultTask(); + // Init preferences and get store + await PreferencesUtils.getPreferencesFromStorage(this.context); + await DayInfoApi.initAchievementStoreByDayInfo(convertDate2Str(new Date())); + + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + // Jump to the screen opening screen. + windowStage.loadContent('pages/SplashPage', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/products/default/src/main/ets/pages/AdvertisingPage.ets b/products/default/src/main/ets/pages/AdvertisingPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..dc5b315a31559ac5fa74884d94d2a93931006907 --- /dev/null +++ b/products/default/src/main/ets/pages/AdvertisingPage.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 { hilog } from '@kit.PerformanceAnalysisKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { CommonConstants as Const } from 'common'; + +// hilog tag. +const TAG: string = 'AdvertisingPage'; + +@Entry +@Component +struct AdvertisingPage { + @State duration: number = Const.AD_DURATION; + private intervalId: number = -1; + + goToHomePage() { + clearInterval(this.intervalId); + this.getUIContext().getRouter().replaceUrl({ url: 'pages/Index' }).catch((err: BusinessError) => { + hilog.error(0x0000, TAG, `Jump to index page failed, code is ${err.code},message is ${err.message}`); + }); + } + + aboutToAppear() { + this.intervalId = setInterval(() => { + if (this.duration > 0) { + this.duration -= 1; + } else { + this.goToHomePage(); + } + }, Const.DURATION_1000) + } + + build() { + Column() { + Row() { + Text($r('app.string.skip_ads', this.duration)) + .fontSize($r('app.float.default_12')) + .fontColor($r('sys.color.font_on_primary')) + .borderRadius($r('app.float.default_16')) + .letterSpacing($r('app.float.letter_space_1')) + .height($r('app.float.default_36')) + .backgroundColor('rgba(0,0,0,0.20)') + .border({ + color: $r('sys.color.font_on_primary'), + width: $r('app.float.default_1') + }) + .margin({ + top: $r('app.float.default_36') + }) + .padding($r('app.float.default_8')) + .onClick(() => this.goToHomePage()) + } + .width(Const.THOUSANDTH_900) + .justifyContent(FlexAlign.End) + + Row() { + Image($r('app.media.logo')) + .width($r('app.float.default_56')) + .height($r('app.float.default_56')) + .objectFit(ImageFit.Contain) + + Column({ + space: $r('app.float.default_4') + }) { + Text($r('app.string.EntryAbility_label')) + .fontFamily(Const.HARMONY_HEI_TI_BOLD) + .fontWeight(FontWeight.Bold) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('app.float.default_26')) + .letterSpacing($r('app.float.letter_space_1')) + + Text($r('app.string.EntryAbility_desc')) + .fontFamily(Const.HARMONY_HEI_TI) + .fontWeight(FontWeight.Normal) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('app.float.default_16')) + .letterSpacing($r('app.float.letter_space_34')) + .opacity($r('app.float.opacity_6')) + } + .alignItems(HorizontalAlign.Start) + .margin({ + left: $r('app.float.default_12') + }) + } + .height($r('app.float.default_100')) + .width(Const.THOUSANDTH_1000) + .justifyContent(FlexAlign.Center) + } + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_1000) + .backgroundImagePosition({ + x: $r('app.float.default_0'), + y: $r('app.float.default_0') + }) + .backgroundImage($r('app.media.ic_ad_bg')) + .backgroundImageSize({ + width: Const.THOUSANDTH_1000, + height: Const.THOUSANDTH_1000 + }) + .justifyContent(FlexAlign.SpaceBetween) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } +} \ No newline at end of file diff --git a/products/default/src/main/ets/pages/Index.ets b/products/default/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..8547dcc4445c0bb4763987084e93fe16687d9f90 --- /dev/null +++ b/products/default/src/main/ets/pages/Index.ets @@ -0,0 +1,28 @@ +/* + * + * * 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 { HealthyLifePage } from "healthylife"; + +@Entry +@Component +struct Index { + build() { + Column() { + HealthyLifePage() + } + } +} \ No newline at end of file diff --git a/products/default/src/main/ets/pages/SplashPage.ets b/products/default/src/main/ets/pages/SplashPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..ac8d78868eb784180af922393e27187493b728be --- /dev/null +++ b/products/default/src/main/ets/pages/SplashPage.ets @@ -0,0 +1,143 @@ +/* + * + * * 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 { common } from '@kit.AbilityKit'; +import { preferences } from '@kit.ArkData'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { CommonConstants as Const } from 'common'; +import UserPrivacyDialog from '../views/UserPrivacyDialog'; + +// hilog tag. +const TAG: string = 'SplashPage'; +// app preferences name +const H_STORE: string = 'healthAppStore'; +const IS_PRIVACY: string = 'isPrivacy'; + +@Entry +@Component +struct SplashPage { + context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext; + dialogController: CustomDialogController = new CustomDialogController({ + builder: UserPrivacyDialog( + { + cancel: () => { + this.exitApp(); + }, + confirm: () => { + this.onConfirm(); + } + }), + cancel: () => { + this.exitApp(); + }, + autoCancel: false, + alignment: DialogAlignment.Bottom, + offset: { + dx: $r('app.float.default_0'), + dy: $r('app.float.default_24') + } + }); + + onConfirm() { + preferences.getPreferences(this.context, H_STORE).then((res) => { + res.put(IS_PRIVACY, true).then(() => { + res.flush(); + hilog.info(0x0000, TAG, 'isPrivacy is put success'); + }).catch((err: BusinessError) => { + hilog.error(0x0000, TAG, `isPrivacy put failed, code is ${err.code},message is ${err.message}`); + }); + }).catch((err: BusinessError) => { + hilog.error(0x0000, TAG, `Get preferences failed, code is ${err.code},message is ${err.message}`); + }); + this.jumpAdPage(); + } + + exitApp() { + this.context.terminateSelf().catch((err: BusinessError) => { + hilog.error(0x0000, TAG, `Exit app failed, code is ${err.code},message is ${err.message}`); + }); + } + + jumpAdPage() { + setTimeout(() => { + this.getUIContext().getRouter().replaceUrl({ url: 'pages/AdvertisingPage' }).catch((err: BusinessError) => { + hilog.error(0x0000, TAG, `Jump to advertising page failed, code is ${err.code},message is ${err.message}`); + }); + }, Const.LAUNCHER_DELAY_TIME); + } + + aboutToAppear() { + preferences.getPreferences(this.context, H_STORE).then((res) => { + res.get(IS_PRIVACY, false).then((isPrivate) => { + if (isPrivate === true) { + this.jumpAdPage(); + } else { + this.dialogController.open(); + } + }); + }).catch((err: BusinessError) => { + hilog.error(0x0000, TAG, `Get preferences failed, code is ${err.code},message is ${err.message}`); + }) + } + + aboutToDisappear() { + clearTimeout(); + } + + build() { + Column() { + Image($r('app.media.logo')) + .width($r('app.float.default_120')) + .aspectRatio(Const.DEFAULT_ASPECT_RATIO) + .margin({ + top: $r('app.float.default_120') + }); + + Text($r('app.string.EntryAbility_label')) + .fontFamily(Const.HARMONY_HEI_TI_BOLD) + .fontWeight(FontWeight.Bold) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('app.float.default_24')) + .letterSpacing($r('app.float.letter_space_1')) + .margin({ + top: $r('app.float.default_20'), + bottom: $r('app.float.default_8') + }) + + Text($r('app.string.EntryAbility_desc')) + .fontFamily(Const.HARMONY_HEI_TI) + .fontWeight(FontWeight.Normal) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('app.float.default_16')) + .letterSpacing($r('app.float.letter_space_34')) + .opacity($r('app.float.opacity_6')) + } + .width(Const.THOUSANDTH_1000) + .height(Const.THOUSANDTH_1000) + .backgroundImagePosition({ + x: $r('app.float.default_0'), + y: $r('app.float.default_0') + }) + .backgroundImage($r('app.media.ic_splash_bg')) + .backgroundImageSize({ + width: Const.THOUSANDTH_1000, + height: Const.THOUSANDTH_1000 + }) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } +} \ No newline at end of file diff --git a/products/default/src/main/ets/progress/pages/ProgressCard.ets b/products/default/src/main/ets/progress/pages/ProgressCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..02f2e95fec3feb6188ad3661287dba3d37411721 --- /dev/null +++ b/products/default/src/main/ets/progress/pages/ProgressCard.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. + * + */ + +let progressStorage = new LocalStorage(); + +@Entry(progressStorage) +@Component +struct ProgressCard { + @LocalStorageProp('targetTaskNum') targetTaskNum: number = 0; + @LocalStorageProp('finTaskNum') finTaskNum: number = 0; + @LocalStorageProp('percentage') percentage: number = 0; + /* + * The action type. + */ + readonly ACTION_TYPE: string = 'router'; + /* + * The ability name. + */ + readonly ABILITY_NAME: string = 'EntryAbility'; + /* + * The with percentage setting. + */ + readonly FULL_WIDTH_PERCENT: string = '100%'; + /* + * The height percentage setting. + */ + readonly FULL_HEIGHT_PERCENT: string = '100%'; + /* + * The progress layout height percentage setting. + */ + readonly PROGRESS_LAYOUT_HEIGHT: string = '80%'; + /* + * The finish results layout percentage setting. + */ + readonly RESULTS_LAYOUT_HEIGHT: string = '20%'; + /* + * The percent sign. + */ + readonly PERCENTAGE: string = '%'; + /* + * The slashes splicing. + */ + readonly SLASHES: string = '/'; + /** + * The progress total to 100. + */ + readonly PROGRESS_TOTAL: number = 100; + /** + * The text slightly bold. + */ + readonly TEXT_SLIGHTLY_BOLD: number = 500; + + build() { + Column() { + Stack() { + Progress({ value: 0, total: this.PROGRESS_TOTAL, type: ProgressType.Ring }) + .value(this.percentage) + .width($r('app.float.progress_component_size')) + .height($r('app.float.progress_component_size')) + .backgroundColor($r('app.color.progress_background_color')) + .style({ strokeWidth: $r('app.float.progress_stroke_width') }) + + Row() { + Text(this.percentage.toString()) + .fontSize($r('app.float.percent_text_size')) + .fontColor(Color.Black) + .fontWeight(FontWeight.Normal) + + Text(this.PERCENTAGE) + .fontSize($r('app.float.percent_sign_size')) + .fontColor(Color.Black) + .fontWeight(this.TEXT_SLIGHTLY_BOLD) + .margin({ top: $r('app.float.percent_sign_margin') }) + } + .justifyContent(FlexAlign.Center) + } + .height(this.PROGRESS_LAYOUT_HEIGHT) + + Row() { + Text(this.finTaskNum.toString()) + .fontColor($r('app.color.numerator_text_color')) + .fontSize($r('app.float.numerator_text_size')) + .lineHeight($r('app.float.numerator_line_height')) + .fontWeight(FontWeight.Normal) + + Text(this.SLASHES + this.targetTaskNum) + .fontColor($r('app.color.denominator_text_color')) + .fontSize($r('app.float.denominator_text_size')) + .lineHeight($r('app.float.denominator_line_height')) + .fontWeight(FontWeight.Normal) + } + .height(this.RESULTS_LAYOUT_HEIGHT) + } + .backgroundColor($r("app.color.progress_column_color")) + .width(this.FULL_WIDTH_PERCENT) + .height(this.FULL_HEIGHT_PERCENT) + .onClick(() => { + postCardAction(this, { + 'action': this.ACTION_TYPE, + 'abilityName': this.ABILITY_NAME + }); + }) + } +} \ No newline at end of file diff --git a/products/default/src/main/ets/views/UserPrivacyDialog.ets b/products/default/src/main/ets/views/UserPrivacyDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..036aab2e6ccd0c2d94fb8d3f5583c1052a970813 --- /dev/null +++ b/products/default/src/main/ets/views/UserPrivacyDialog.ets @@ -0,0 +1,83 @@ +/* + * + * * 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 'common'; + +@CustomDialog +export default struct UserPrivacyDialog { + controller: CustomDialogController = new CustomDialogController({ builder: '' }); + cancel: Function = () => { + }; + confirm: Function = () => { + }; + + build() { + Column() { + Text($r('app.string.privacy_title')) + .fontFamily(Const.HARMONY_HEI_TI) + .fontWeight(FontWeight.Normal) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_14')) + .width(Const.THOUSANDTH_1000) + .lineHeight($r('app.float.default_20')) + .margin({ + top: $r('app.float.default_8') + }) + + Text($r('app.string.privacy_desc')) + .fontFamily(Const.HARMONY_HEI_TI) + .fontWeight(FontWeight.Normal) + .fontColor($r(`sys.color.font_primary`)) + .fontSize($r('app.float.default_14')) + .width(Const.THOUSANDTH_1000) + .lineHeight($r('app.float.default_20')) + .margin({ + top: $r('app.float.default_8') + }) + .opacity($r('app.float.opacity_6')) + + Row() { + Button($r('app.string.cancel')) + .backgroundColor($r('sys.color.background_primary')) + .fontColor($r('sys.color.comp_background_emphasize')) + .onClick(() => { + this.controller.close(); + this.cancel(); + }) + + Divider() + .vertical(true) + .height($r('app.float.default_22')) + .opacity($r('app.float.opacity_6')) + + Button($r('app.string.confirm')) + .backgroundColor($r('sys.color.background_primary')) + .fontColor($r('sys.color.comp_background_emphasize')) + .onClick(() => { + this.controller.close(); + this.confirm(); + }) + } + .width(Const.THOUSANDTH_1000) + .margin({ + top: $r('app.float.default_22') + }) + .justifyContent(FlexAlign.SpaceEvenly) + } + .padding($r('app.float.default_16')) + } +} \ No newline at end of file diff --git a/products/default/src/main/module.json5 b/products/default/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c0e0cb4928a9c5e215f150cd533c7ed4c7886e9d --- /dev/null +++ b/products/default/src/main/module.json5 @@ -0,0 +1,62 @@ +{ + "module": { + "name": "default", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "ohos.want.action.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "DefaultFormAbility", + "srcEntry": "./ets/defaultformability/DefaultFormAbility.ets", + "label": "$string:DefaultFormAbility_label", + "description": "$string:DefaultFormAbility_desc", + "type": "form", + "metadata": [ + { + "name": "ohos.extension.form", + "resource": "$profile:form_config" + } + ] + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.PUBLISH_AGENT_REMINDER", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + } + ] + } +} \ No newline at end of file diff --git a/products/default/src/main/resources/base/element/color.json b/products/default/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..f8dddb7079fc6f50843a4588d00b5f5cbd81feb1 --- /dev/null +++ b/products/default/src/main/resources/base/element/color.json @@ -0,0 +1,40 @@ +{ + "color": [ + { + "name": "item_title_font", + "value": "#E6000000" + }, + { + "name": "progress_background_color", + "value": "#08000000" + }, + { + "name": "numerator_text_color", + "value": "#007DFF" + }, + { + "name": "denominator_text_color", + "value": "#72787E" + }, + { + "name": "progress_column_color", + "value": "#0d000000" + }, + { + "name": "list_background_color", + "value": "#33000000" + }, + { + "name": "no_data_background", + "value": "#cc727272" + }, + { + "name": "text_common_color", + "value": "#182431" + }, + { + "name": "hex_common_color", + "value": "#99182431" + } + ] +} \ No newline at end of file diff --git a/products/default/src/main/resources/base/element/float.json b/products/default/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..fc3ee163ebd24b4322971ed4b6aac91ce07f5ae3 --- /dev/null +++ b/products/default/src/main/resources/base/element/float.json @@ -0,0 +1,88 @@ +{ + "float": [ + { + "name": "font_size", + "value": "14fp" + }, + { + "name": "progress_component_size", + "value": "108vp" + }, + { + "name": "progress_stroke_width", + "value": "13vp" + }, + { + "name": "percent_text_size", + "value": "27fp" + }, + { + "name": "percent_sign_size", + "value": "13fp" + }, + { + "name": "percent_sign_margin", + "value": "10vp" + }, + { + "name": "numerator_text_size", + "value": "15fp" + }, + { + "name": "numerator_line_height", + "value": "19vp" + }, + { + "name": "denominator_text_size", + "value": "15fp" + }, + { + "name": "denominator_line_height", + "value": "19vp" + }, + { + "name": "agency_item_margin", + "value": "6vp" + }, + { + "name": "agency_item_radius", + "value": "6vp" + }, + { + "name": "agency_padding_left", + "value": "12vp" + }, + { + "name": "agency_padding_top", + "value": "10vp" + }, + { + "name": "agency_padding_right", + "value": "6vp" + }, + { + "name": "agency_padding_bottom", + "value": "10vp" + }, + { + "name": "empty_data_size", + "value": "16fp" + }, + { + "name": "agency_image_size", + "value": "26vp" + }, + { + "name": "text_common_size", + "value": "14fp" + }, + { + "name": "agency_text_bold", + "value": "22fp" + }, + { + "name": "agency_row_padding", + "value": "15vp" + } + ] +} \ No newline at end of file diff --git a/products/default/src/main/resources/base/element/string.json b/products/default/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..89c33a642d4131bff1b60761b9bb969ace47f046 --- /dev/null +++ b/products/default/src/main/resources/base/element/string.json @@ -0,0 +1,32 @@ +{ + "string": [ + { + "name": "DefaultFormAbility_desc", + "value": "健康生活-服务卡片" + }, + { + "name": "DefaultFormAbility_label", + "value": "健康生活-服务卡片-标签" + }, + { + "name": "progress_desc", + "value": "服务卡片" + }, + { + "name": "progress_display_name", + "value": "服务卡片" + }, + { + "name": "agency_desc", + "value": "服务卡片" + }, + { + "name": "agency_display_name", + "value": "服务卡片" + }, + { + "name": "agency_no_task", + "value": "请前往健康生活添加任务" + } + ] +} \ No newline at end of file diff --git a/products/default/src/main/resources/base/media/background.png b/products/default/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/products/default/src/main/resources/base/media/background.png differ diff --git a/products/default/src/main/resources/base/media/foreground.png b/products/default/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/products/default/src/main/resources/base/media/foreground.png differ diff --git a/products/default/src/main/resources/base/media/layered_image.json b/products/default/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/products/default/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/products/default/src/main/resources/base/media/startIcon.png b/products/default/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/products/default/src/main/resources/base/media/startIcon.png differ diff --git a/products/default/src/main/resources/base/profile/form_config.json b/products/default/src/main/resources/base/profile/form_config.json new file mode 100644 index 0000000000000000000000000000000000000000..41df7da34e9abf3b794f9e726f532535d1377ce3 --- /dev/null +++ b/products/default/src/main/resources/base/profile/form_config.json @@ -0,0 +1,46 @@ +{ + "forms": [ + { + "name": "progress", + "displayName": "$string:progress_display_name", + "description": "$string:progress_desc", + "src": "./ets/progress/pages/ProgressCard.ets", + "uiSyntax": "arkts", + "window": { + "designWidth": 720, + "autoDesignWidth": true + }, + "colorMode": "auto", + "isDynamic": true, + "isDefault": true, + "updateEnabled": false, + "scheduledUpdateTime": "10:30", + "updateDuration": 1, + "defaultDimension": "2*2", + "supportDimensions": [ + "2*2" + ] + }, + { + "name": "agency", + "displayName": "$string:agency_display_name", + "description": "$string:agency_desc", + "src": "./ets/agency/pages/AgencyCard.ets", + "uiSyntax": "arkts", + "window": { + "designWidth": 720, + "autoDesignWidth": true + }, + "colorMode": "auto", + "isDynamic": true, + "isDefault": false, + "updateEnabled": false, + "scheduledUpdateTime": "10:30", + "updateDuration": 1, + "defaultDimension": "2*4", + "supportDimensions": [ + "2*4" + ] + } + ] +} \ No newline at end of file diff --git a/products/default/src/main/resources/base/profile/main_pages.json b/products/default/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..ed10334d862ef286bc2c0be486f197d9e49ce47c --- /dev/null +++ b/products/default/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,7 @@ +{ + "src": [ + "pages/Index", + "pages/SplashPage", + "pages/AdvertisingPage" + ] +} diff --git a/products/default/src/main/resources/en_US/element/string.json b/products/default/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..cb135084640741f3092dc5f270ffc8a35d852f23 --- /dev/null +++ b/products/default/src/main/resources/en_US/element/string.json @@ -0,0 +1,32 @@ +{ + "string": [ + { + "name": "DefaultFormAbility_desc", + "value": "healthy living service card" + }, + { + "name": "DefaultFormAbility_label", + "value": "healthy living service cards labels" + }, + { + "name": "progress_desc", + "value": "service card" + }, + { + "name": "progress_display_name", + "value": "service card" + }, + { + "name": "agency_desc", + "value": "service card" + }, + { + "name": "agency_display_name", + "value": "service card" + }, + { + "name": "agency_no_task", + "value": "Go to Healthy Living to add tasks" + } + ] +} \ No newline at end of file diff --git a/products/default/src/mock/mock-config.json5 b/products/default/src/mock/mock-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7a73a41bfdf76d6f793007240d80983a52f15f97 --- /dev/null +++ b/products/default/src/mock/mock-config.json5 @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/products/default/src/ohosTest/ets/test/Ability.test.ets b/products/default/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/products/default/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/products/default/src/ohosTest/ets/test/List.test.ets b/products/default/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/products/default/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/products/default/src/ohosTest/module.json5 b/products/default/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ca09309aac10100cee33f3d5aa81ec05ae40149d --- /dev/null +++ b/products/default/src/ohosTest/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "default_test", + "type": "feature", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} \ No newline at end of file diff --git a/products/default/src/test/List.test.ets b/products/default/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/products/default/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/products/default/src/test/LocalUnit.test.ets b/products/default/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/products/default/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/screenshots/healthyfile1.png b/screenshots/healthyfile1.png new file mode 100644 index 0000000000000000000000000000000000000000..03c3f269bed237a089f16ead9e0c905b238ba04b Binary files /dev/null and b/screenshots/healthyfile1.png differ diff --git a/screenshots/healthyfile2.png b/screenshots/healthyfile2.png new file mode 100644 index 0000000000000000000000000000000000000000..5234e5e36db15f2a01cd0c6b2b868a701db8e597 Binary files /dev/null and b/screenshots/healthyfile2.png differ diff --git a/screenshots/healthyfile3.png b/screenshots/healthyfile3.png new file mode 100644 index 0000000000000000000000000000000000000000..1c6030c2610497ed35ab865f4a5351fd29321713 Binary files /dev/null and b/screenshots/healthyfile3.png differ diff --git a/screenshots/healthyfile4.png b/screenshots/healthyfile4.png new file mode 100644 index 0000000000000000000000000000000000000000..08dfaa62f5dd79f75b427847d4daa0fc035dd575 Binary files /dev/null and b/screenshots/healthyfile4.png differ diff --git a/screenshots/healthyfile5.png b/screenshots/healthyfile5.png new file mode 100644 index 0000000000000000000000000000000000000000..e333bd2ebd264c60759c4fa7e0a5212e194c7f4b Binary files /dev/null and b/screenshots/healthyfile5.png differ diff --git a/screenshots/healthyfile6.png b/screenshots/healthyfile6.png new file mode 100644 index 0000000000000000000000000000000000000000..876bcdf786aef87d3328cea221d1cc6909aa7e90 Binary files /dev/null and b/screenshots/healthyfile6.png differ