已开启
feat: add SportsHealth 运动健康应用示例(纯仓颉实现) #421
stwilliam503388创建于 18 天前
feat: add SportsHealth 运动健康应用示例(纯仓颉实现) #421
已开启
共 102 个文件变更+3710-0
| @@ -0,0 +1,26 @@ | |||
| 1 | +/node_modules | ||
| 2 | +/oh_modules | ||
| 3 | +/local.properties | ||
| 4 | +/build-profile.json5 | ||
| 5 | +/build.log | ||
| 6 | +/nul | ||
| 7 | +/.idea | ||
| 8 | +**/build | ||
| 9 | +/.hvigor | ||
| 10 | +.cxx | ||
| 11 | +/.clangd | ||
| 12 | +/.clang-format | ||
| 13 | +/.clang-tidy | ||
| 14 | +**/.test | ||
| 15 | +/.appanalyzer | ||
| 16 | +/.agents/ | ||
| 17 | +/skills/ | ||
| 18 | +**/cj_res | ||
| 19 | +**/*.cj.macrocall | ||
| 20 | +**/ability_mainability_entry.cj | ||
| 21 | +**/module_**_entry.cj | ||
| 22 | +**/IDL_Dependencies_List~ | ||
| 23 | +**/build-script-cache | ||
| 24 | + | ||
| 25 | +# UI automation outputs (generated) | ||
| 26 | +/ui_capture_output*/ | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | +{ | ||
| 2 | + "app": { | ||
| 3 | + "bundleName": "com.example.sportshealthcangjieproject", | ||
| 4 | + "vendor": "example", | ||
| 5 | + "versionCode": 1000000, | ||
| 6 | + "versionName": "1.0.0", | ||
| 7 | + "icon": "$media:layered_image", | ||
| 8 | + "label": "$string:app_name" | ||
| 9 | + } | ||
| 10 | +} | ||
| @@ -0,0 +1,8 @@ | |||
| 1 | +{ | ||
| 2 | + "string": [ | ||
| 3 | + { | ||
| 4 | + "name": "app_name", | ||
| 5 | + "value": "SportsHealthCangjieProject" | ||
| 6 | + } | ||
| 7 | + ] | ||
| 8 | +} | ||
| @@ -0,0 +1,7 @@ | |||
| 1 | +{ | ||
| 2 | + "layered-image": | ||
| 3 | + { | ||
| 4 | + "background" : "$media:background", | ||
| 5 | + "foreground" : "$media:foreground" | ||
| 6 | + } | ||
| 7 | +} | ||
| @@ -0,0 +1,149 @@ | |||
| 1 | +# SportsHealth - 运动健康应用(纯仓颉语言实现) | ||
| 2 | + | ||
| 3 | +> 仓颉(Cangjie)语言实现的 HarmonyOS 运动健康应用,基于 ArkUI 声明式 UI 框架,演示 **Navigation + Tabs 多级导航** 在纯仓颉工程中的完整落地。 | ||
| 4 | + | ||
| 5 | +## 场景介绍 | ||
| 6 | + | ||
| 7 | +运动健康类应用是穿戴与手机生态中的高频场景。本示例实现了一个功能完整的运动健康 App:**5 个主 Tab + 18 个路由子页面**,覆盖健康数据概览、跑步记录、商城、内容发现、个人中心,并包含蓝牙穿戴设备绑定的完整页面流程(含运行时权限检测)。 | ||
| 8 | + | ||
| 9 | +| Tab | 页面 | 功能说明 | | ||
| 10 | +|-----|------|------| | ||
| 11 | +| 🏠 首页 | HomeTab | 健康数据概览,运动/睡眠/心率等指标卡片展示 | | ||
| 12 | +| 🏃 跑步 | RunTab | 跑步记录、运动数据展示 | | ||
| 13 | +| 🛒 商城 | ShopTab | 运动装备商品浏览 | | ||
| 14 | +| 🔍 发现 | FindTab | 内容发现/推荐 | | ||
| 15 | +| 👤 我的 | MineTab | 个人中心,汇聚全部子页面入口 | | ||
| 16 | + | ||
| 17 | +18 个路由子页面集中于 `subpages.cj`(1421 行):设置、添加设备、我的设备、设备扫描列表、个人信息、消息中心、健康报告、心率监测、运动圈子、版本更新、用户协议、隐私政策、关于、权限管理、推送设置、清理缓存、意见反馈等。 | ||
| 18 | + | ||
| 19 | +## 实现思路 | ||
| 20 | + | ||
| 21 | +### 1. Navigation + Tabs 组合导航 | ||
| 22 | + | ||
| 23 | +主页面在 `Navigation` 容器内部嵌套 `Tabs`,路由栈挂在 Navigation 之上,保证子页面路由返回后 Tab 状态不丢失: | ||
| 24 | + | ||
| 25 | +```cangjie | ||
| 26 | +@Entry | ||
| 27 | +@Component | ||
| 28 | +class EntryView { | ||
| 29 | + @Provide var stack: NavPathStack = NavPathStack() | ||
| 30 | + @State var currentIndex: Int32 = 0 | ||
| 31 | + var tabsController: TabsController = TabsController() | ||
| 32 | + | ||
| 33 | + func build() { | ||
| 34 | + Navigation(this.stack) { | ||
| 35 | + Tabs(barPosition: BarPosition.End, controller: this.tabsController, index: this.currentIndex) { | ||
| 36 | + TabContent() { HomeTab() }.tabBar(...) | ||
| 37 | + // ... 5 个 TabContent | ||
| 38 | + } | ||
| 39 | + } | ||
| 40 | + } | ||
| 41 | +} | ||
| 42 | +``` | ||
| 43 | + | ||
| 44 | +### 2. appPageMap 路由表集中映射 | ||
| 45 | + | ||
| 46 | +全部 18 个子页面路由通过 `@Builder appPageMap` 集中注册,按路由名分发到对应页面组件,新增页面只需在此处追加一个分支: | ||
| 47 | + | ||
| 48 | +```cangjie | ||
| 49 | +@Builder | ||
| 50 | +func appPageMap(name: String, param: Any) { | ||
| 51 | + if (name == "settings") { | ||
| 52 | + SettingsPage() | ||
| 53 | + } else if (name == "device_list") { | ||
| 54 | + DeviceListPage() | ||
| 55 | + } else if (name == "health_report") { | ||
| 56 | + HealthReportPage() | ||
| 57 | + } // ... 共 18 个路由分支 | ||
| 58 | + else { | ||
| 59 | + Column() { Text("Not Found") } // 兜底页 | ||
| 60 | + } | ||
| 61 | +} | ||
| 62 | +``` | ||
| 63 | + | ||
| 64 | +### 3. @Provide / @Consume 跨层级共享路由栈 | ||
| 65 | + | ||
| 66 | +入口处 `@Provide` 提供唯一的 `NavPathStack`,各 Tab 页面与子页面统一 `@Consume` 消费后调用 `pushPathByName` 完成跳转,无需逐层透传: | ||
| 67 | + | ||
| 68 | +```cangjie | ||
| 69 | +@Component | ||
| 70 | +class HomePage { | ||
| 71 | + @Consume var stack: NavPathStack | ||
| 72 | + | ||
| 73 | + func build() { | ||
| 74 | + // ... | ||
| 75 | + .onClick({ _: ClickEvent => | ||
| 76 | + this.stack.pushPathByName("my_devices", "") | ||
| 77 | + }) | ||
| 78 | + } | ||
| 79 | +} | ||
| 80 | +``` | ||
| 81 | + | ||
| 82 | +### 4. 运行时权限检测 | ||
| 83 | + | ||
| 84 | +`main_ability.cj` 在启动时申请计步/蓝牙/后台运行等权限;「添加设备」页通过 `atManager.checkAccessToken` 实时检测蓝牙权限状态并驱动 UI 刷新: | ||
| 85 | + | ||
| 86 | +```cangjie | ||
| 87 | +@State var bluetoothGranted: Bool = true | ||
| 88 | + | ||
| 89 | +func refreshBluetoothPermissionState(): Unit { | ||
| 90 | + // atManager.checkAccessToken(...) 逐项检测权限 | ||
| 91 | + this.bluetoothGranted = (atManager.checkAccessToken(tokenID, p) != GrantStatus.PermissionDenied) | ||
| 92 | +} | ||
| 93 | +``` | ||
| 94 | + | ||
| 95 | +### 5. UI 自动化友好设计 | ||
| 96 | + | ||
| 97 | +首页叠加了「不可见但可寻址」的点击热区(透明 Text),供 `ui_scenarios/` 下基于 hdc 的页面冒烟脚本精准定位,对齐 ArkTS 模板的自动化测试思路。 | ||
| 98 | + | ||
| 99 | +## 功能特性 | ||
| 100 | + | ||
| 101 | +- ✅ 底部 5 Tab 主导航(首页/跑步/商城/发现/我的) | ||
| 102 | +- ✅ Navigation 路由栈管理,18 个子页面集中注册 | ||
| 103 | +- ✅ @Provide / @Consume 跨层级状态传递 | ||
| 104 | +- ✅ 蓝牙穿戴设备绑定流程页面(扫描列表 → 添加 → 我的设备) | ||
| 105 | +- ✅ 运行时权限申请与状态检测 | ||
| 106 | +- ✅ 计步/心率/睡眠等健康数据卡片展示 | ||
| 107 | +- ✅ 基于 hdc 的 UI 冒烟自动化脚本(ui_scenarios/) | ||
| 108 | +- ✅ 全量文案走 `@r(app.string.xxx)` 资源引用 | ||
| 109 | + | ||
| 110 | +## 环境要求 | ||
| 111 | + | ||
| 112 | +- DevEco Studio(含 Cangjie 支持) | ||
| 113 | +- HarmonyOS SDK 6.0.2(22),API Level 22 | ||
| 114 | +- 仓颉语言插件(DevEco Studio 内安装) | ||
| 115 | + | ||
| 116 | +## 使用说明 | ||
| 117 | + | ||
| 118 | +1. 用 DevEco Studio 打开本目录,等待仓颉依赖解析完成 | ||
| 119 | +2. 选择模拟器或真机设备,点击 Run 运行 | ||
| 120 | +3. 底部 5 个 Tab 可自由切换;「我的」页面可进入全部子页面,体验设备扫描/绑定流程 | ||
| 121 | +4. 附带 `ui_scenarios/` UI 自动化脚本(基于 hdc 的冒烟导航脚本),可在真机上批量走查页面 | ||
| 122 | + | ||
| 123 | +## 工程目录 | ||
| 124 | + | ||
| 125 | +``` | ||
| 126 | +entry/src/main/cangjie/ | ||
| 127 | +├── index.cj # 入口:EntryView + Navigation/Tabs 容器 + appPageMap 路由表 | ||
| 128 | +├── main_ability.cj # Ability 生命周期、运行时权限申请 | ||
| 129 | +├── ability_stage.cj # Stage 模型配置 | ||
| 130 | +└── pages/ | ||
| 131 | + ├── home_tab.cj # 首页:健康数据概览(78 行) | ||
| 132 | + ├── run_tab.cj # 跑步:运动记录(160 行) | ||
| 133 | + ├── shop_tab.cj # 商城:商品浏览(236 行) | ||
| 134 | + ├── find_tab.cj # 发现:内容推荐(184 行) | ||
| 135 | + ├── mine_tab.cj # 我的:个人中心入口汇聚(162 行) | ||
| 136 | + └── subpages.cj # 18 个路由子页面集中实现(1421 行) | ||
| 137 | +``` | ||
| 138 | + | ||
| 139 | +## 难点与踩坑记录 | ||
| 140 | + | ||
| 141 | +- `Navigation` 与 `Tabs` 的嵌套顺序:路由栈必须挂在 Tabs 之上,否则子页面返回后 Tab 选中态丢失 | ||
| 142 | +- `@Provide`/`@Consume` 的变量名必须完全一致(`stack`),否则子组件消费失败、跳转无响应 | ||
| 143 | +- 仓颉侧 `NavPathStack` 的 `pushPathByName` 第二参数必传(空串占位),与 ArkTS 可选参数行为不同 | ||
| 144 | +- 子页面集中单文件(subpages.cj 1400+ 行)与拆分多文件的取舍:集中便于路由总览,拆分利于编译增量 | ||
| 145 | +- 仓颉包管理(cjpm.toml)与 oh-package 协作:依赖解析需等待 DevEco 同步完成再构建 | ||
| 146 | + | ||
| 147 | +## 原项目 | ||
| 148 | + | ||
| 149 | +本项目源自个人原创仓库:[SportsHealthCangjieProject](https://github.com/stwilliam503388-creator/SportsHealthCangjieProject) | ||
| @@ -0,0 +1,33 @@ | |||
| 1 | +{ | ||
| 2 | + "files": [ | ||
| 3 | + "**/*.ets", | ||
| 4 | + "**/*.cj" | ||
| 5 | + ], | ||
| 6 | + "ignore": [ | ||
| 7 | + "**/src/ohosTest/**/*", | ||
| 8 | + "**/src/test/**/*", | ||
| 9 | + "**/src/mock/**/*", | ||
| 10 | + "**/node_modules/**/*", | ||
| 11 | + "**/oh_modules/**/*", | ||
| 12 | + "**/build/**/*", | ||
| 13 | + "**/.preview/**/*" | ||
| 14 | + ], | ||
| 15 | + "ruleSet": [ | ||
| 16 | + "plugin:@performance/recommended", | ||
| 17 | + "plugin:@typescript-eslint/recommended" | ||
| 18 | + ], | ||
| 19 | + "rules": { | ||
| 20 | + "@security/no-unsafe-aes": "error", | ||
| 21 | + "@security/no-unsafe-hash": "error", | ||
| 22 | + "@security/no-unsafe-mac": "warn", | ||
| 23 | + "@security/no-unsafe-dh": "error", | ||
| 24 | + "@security/no-unsafe-dsa": "error", | ||
| 25 | + "@security/no-unsafe-ecdsa": "error", | ||
| 26 | + "@security/no-unsafe-rsa-encrypt": "error", | ||
| 27 | + "@security/no-unsafe-rsa-sign": "error", | ||
| 28 | + "@security/no-unsafe-rsa-key": "error", | ||
| 29 | + "@security/no-unsafe-dsa-key": "error", | ||
| 30 | + "@security/no-unsafe-dh-key": "error", | ||
| 31 | + "@security/no-unsafe-3des": "error" | ||
| 32 | + } | ||
| 33 | +} | ||
| @@ -0,0 +1,8 @@ | |||
| 1 | +/node_modules | ||
| 2 | +/oh_modules | ||
| 3 | +/.preview | ||
| 4 | +/build | ||
| 5 | +/.cxx | ||
| 6 | +/.test | ||
| 7 | +**/IDL_Dependencies_List~ | ||
| 8 | +**/build-script-cache | ||
| @@ -0,0 +1,24 @@ | |||
| 1 | +{ | ||
| 2 | + "apiType": "stageMode", | ||
| 3 | + "buildOption": { | ||
| 4 | + "cangjieOptions": { | ||
| 5 | + "path": "./cjpm.toml", | ||
| 6 | + "abiFilters": ["arm64-v8a", "x86_64"] | ||
| 7 | + }, | ||
| 8 | + "externalNativeOptions": { | ||
| 9 | + "abiFilters": ["arm64-v8a", "x86_64"] | ||
| 10 | + }, | ||
| 11 | + "nativeLib": { | ||
| 12 | + "filter": { | ||
| 13 | + "enableOverride": true | ||
| 14 | + } | ||
| 15 | + } | ||
| 16 | + }, | ||
| 17 | + "buildOptionSet": [ | ||
| 18 | + ], | ||
| 19 | + "targets": [ | ||
| 20 | + { | ||
| 21 | + "name": "default" | ||
| 22 | + } | ||
| 23 | + ] | ||
| 24 | +} | ||
| @@ -0,0 +1,38 @@ | |||
| 1 | +[package] | ||
| 2 | + cjc-version = "1.1.0" | ||
| 3 | + compile-option = "--dy-std --cfg=\"${COMPILE_CONDITION_ENTRY}\"" | ||
| 4 | + description = "CangjieUI Application" | ||
| 5 | + link-option = "" | ||
| 6 | + name = "ohos_app_cangjie_entry" | ||
| 7 | + output-type = "dynamic" | ||
| 8 | + src-dir = "./src/main/cangjie" | ||
| 9 | + target-dir = "" | ||
| 10 | + version = "1.0.0" | ||
| 11 | + package-configuration = {} | ||
| 12 | + scripts = {} | ||
| 13 | + | ||
| 14 | +[profile] | ||
| 15 | + [profile.build] | ||
| 16 | + incremental = true | ||
| 17 | + lto = "" | ||
| 18 | + [profile.build.combined] | ||
| 19 | + ohos_app_cangjie_entry = "dynamic" | ||
| 20 | + [profile.customized-option] | ||
| 21 | + debug = "-g -Woff all -Won apilevel-check" | ||
| 22 | + release = "--fast-math -O2 -s -Woff all -Won apilevel-check" | ||
| 23 | + [profile.test] | ||
| 24 | + | ||
| 25 | +[target.aarch64-linux-ohos] | ||
| 26 | + compile-option = "-B \"${DEVECO_CANGJIE_HOME}/build-tools/third_party/llvm/bin\" -B \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/clang/15.0.4/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/aarch64-linux-ohos\" --sysroot \"${DEVECO_OH_NATIVE_HOME}/sysroot\"" | ||
| 27 | +[target.aarch64-linux-ohos.bin-dependencies] | ||
| 28 | + path-option = ["${AARCH64_LIBS}", "${AARCH64_MACRO_LIBS}", "${AARCH64_KIT_LIBS}"] | ||
| 29 | + package-option = {} | ||
| 30 | + | ||
| 31 | +[target.x86_64-linux-ohos] | ||
| 32 | + compile-option = "-B \"${DEVECO_CANGJIE_HOME}/build-tools/third_party/llvm/bin\" -B \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/clang/15.0.4/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/x86_64-linux-ohos\" --sysroot \"${DEVECO_OH_NATIVE_HOME}/sysroot\"" | ||
| 33 | +[target.x86_64-linux-ohos.bin-dependencies] | ||
| 34 | + path-option = ["${X86_64_OHOS_LIBS}", "${X86_64_OHOS_MACRO_LIBS}", "${X86_64_OHOS_KIT_LIBS}"] | ||
| 35 | + | ||
| 36 | +[target.x86_64-unknown-windows-gnu.bin-dependencies] | ||
| 37 | + path-option = ["${X86_64_LIBS}", "${X86_64_MACRO_LIBS}"] | ||
| 38 | + package-option = {} | ||
| @@ -0,0 +1,6 @@ | |||
| 1 | +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; | ||
| 2 | + | ||
| 3 | +export default { | ||
| 4 | + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ | ||
| 5 | + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ | ||
| 6 | +} | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | +{ | ||
| 2 | + "name": "entry", | ||
| 3 | + "version": "1.0.0", | ||
| 4 | + "description": "Please describe the basic information.", | ||
| 5 | + "main": "", | ||
| 6 | + "author": "", | ||
| 7 | + "license": "", | ||
| 8 | + "dependencies": {} | ||
| 9 | +} | ||
| 10 | + | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | +package ohos_app_cangjie_entry | ||
| 2 | + | ||
| 3 | +import kit.AbilityKit.AbilityStage | ||
| 4 | +import kit.PerformanceAnalysisKit.Hilog | ||
| 5 | + | ||
| 6 | +class MyAbilityStage <: AbilityStage { | ||
| 7 | + public override func onCreate(): Unit { | ||
| 8 | + Hilog.info(1, "Cangjie", "MyAbilityStage onCreated.") | ||
| 9 | + } | ||
| 10 | +} | ||
| @@ -0,0 +1,175 @@ | |||
| 1 | +package ohos_app_cangjie_entry | ||
| 2 | + | ||
| 3 | +import kit.ArkUI.* | ||
| 4 | +import kit.LocalizationKit.* | ||
| 5 | +import ohos.arkui.state_macro_manage.* | ||
| 6 | + | ||
| 7 | +import ohos_app_cangjie_entry.pages.HomeTab | ||
| 8 | +import ohos_app_cangjie_entry.pages.RunTab | ||
| 9 | +import ohos_app_cangjie_entry.pages.FindTab | ||
| 10 | +import ohos_app_cangjie_entry.pages.ShopTab | ||
| 11 | +import ohos_app_cangjie_entry.pages.MineTab | ||
| 12 | +import ohos_app_cangjie_entry.pages.SettingsPage | ||
| 13 | +import ohos_app_cangjie_entry.pages.AddDevicePage | ||
| 14 | +import ohos_app_cangjie_entry.pages.MyDevicesPage | ||
| 15 | +import ohos_app_cangjie_entry.pages.DeviceListPage | ||
| 16 | +import ohos_app_cangjie_entry.pages.MyselfPage | ||
| 17 | +import ohos_app_cangjie_entry.pages.MessagePage | ||
| 18 | +import ohos_app_cangjie_entry.pages.HealthReportPage | ||
| 19 | +import ohos_app_cangjie_entry.pages.HeartMonitoringPage | ||
| 20 | +import ohos_app_cangjie_entry.pages.SecretPage | ||
| 21 | +import ohos_app_cangjie_entry.pages.CommonNewPage | ||
| 22 | +import ohos_app_cangjie_entry.pages.CheckUpdatesPage | ||
| 23 | +import ohos_app_cangjie_entry.pages.UserPolicyPage | ||
| 24 | +import ohos_app_cangjie_entry.pages.PrivacyPolicyPage | ||
| 25 | +import ohos_app_cangjie_entry.pages.AboutPage | ||
| 26 | +import ohos_app_cangjie_entry.pages.PermissionSettingsPage | ||
| 27 | +import ohos_app_cangjie_entry.pages.PushSettingsPage | ||
| 28 | +import ohos_app_cangjie_entry.pages.CacheClearPage | ||
| 29 | +import ohos_app_cangjie_entry.pages.FeedbackPage | ||
| 30 | + | ||
| 31 | +@Builder | ||
| 32 | +func appPageMap(name: String, param: Any) { | ||
| 33 | + if (name == "settings") { | ||
| 34 | + SettingsPage() | ||
| 35 | + } else if (name == "device_add") { | ||
| 36 | + AddDevicePage() | ||
| 37 | + } else if (name == "my_devices") { | ||
| 38 | + MyDevicesPage() | ||
| 39 | + } else if (name == "device_list") { | ||
| 40 | + DeviceListPage() | ||
| 41 | + } else if (name == "myself") { | ||
| 42 | + MyselfPage() | ||
| 43 | + } else if (name == "message") { | ||
| 44 | + MessagePage() | ||
| 45 | + } else if (name == "health_report") { | ||
| 46 | + HealthReportPage() | ||
| 47 | + } else if (name == "heart_monitoring") { | ||
| 48 | + HeartMonitoringPage() | ||
| 49 | + } else if (name == "secret") { | ||
| 50 | + SecretPage() | ||
| 51 | + } else if (name == "common_new") { | ||
| 52 | + CommonNewPage() | ||
| 53 | + } else if (name == "check_updates") { | ||
| 54 | + CheckUpdatesPage() | ||
| 55 | + } else if (name == "user_policy") { | ||
| 56 | + UserPolicyPage() | ||
| 57 | + } else if (name == "privacy_policy") { | ||
| 58 | + PrivacyPolicyPage() | ||
| 59 | + } else if (name == "about") { | ||
| 60 | + AboutPage() | ||
| 61 | + } else if (name == "permission_settings") { | ||
| 62 | + PermissionSettingsPage() | ||
| 63 | + } else if (name == "push_settings") { | ||
| 64 | + PushSettingsPage() | ||
| 65 | + } else if (name == "cache_clear") { | ||
| 66 | + CacheClearPage() | ||
| 67 | + } else if (name == "feedback") { | ||
| 68 | + FeedbackPage() | ||
| 69 | + } else { | ||
| 70 | + Column() { | ||
| 71 | + Text("Not Found") | ||
| 72 | + .fontSize(18) | ||
| 73 | + .fontWeight(FontWeight.W600) | ||
| 74 | + Text(name) | ||
| 75 | + .fontSize(14) | ||
| 76 | + .opacity(0.6) | ||
| 77 | + .margin(top: 4) | ||
| 78 | + } | ||
| 79 | + .padding(24) | ||
| 80 | + .width(100.percent) | ||
| 81 | + .height(100.percent) | ||
| 82 | + } | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +@Entry | ||
| 86 | +@Component | ||
| 87 | +class EntryView { | ||
| 88 | + @State var currentIndex: Int32 = 0 | ||
| 89 | + var tabsController: TabsController = TabsController() | ||
| 90 | + @Provide var stack: NavPathStack = NavPathStack() | ||
| 91 | + | ||
| 92 | + func tabIcon(index: Int32): ResourceStr { | ||
| 93 | + if (index == 0) { | ||
| 94 | + return @r(app.media.ic_public_home) | ||
| 95 | + } else if (index == 1) { | ||
| 96 | + return @r(app.media.ic_public_run) | ||
| 97 | + } else if (index == 2) { | ||
| 98 | + return @r(app.media.ic_public_find) | ||
| 99 | + } else if (index == 3) { | ||
| 100 | + return @r(app.media.ic_public_cart) | ||
| 101 | + } | ||
| 102 | + return @r(app.media.ic_public_person) | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + func tabIconSelected(index: Int32): ResourceStr { | ||
| 106 | + if (index == 0) { | ||
| 107 | + return @r(app.media.ic_public_home_sel) | ||
| 108 | + } else if (index == 1) { | ||
| 109 | + return @r(app.media.ic_public_run_sel) | ||
| 110 | + } else if (index == 2) { | ||
| 111 | + return @r(app.media.ic_public_find_sel) | ||
| 112 | + } else if (index == 3) { | ||
| 113 | + return @r(app.media.ic_public_cart_sel) | ||
| 114 | + } | ||
| 115 | + return @r(app.media.ic_public_person_sel) | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + @Builder | ||
| 119 | + func tabBuilder(index: Int32, title: AppResource) { | ||
| 120 | + Column() { | ||
| 121 | + Image(if (this.currentIndex == index) { this.tabIconSelected(index) } else { this.tabIcon(index) }) | ||
| 122 | + .width(24) | ||
| 123 | + .height(24) | ||
| 124 | + .objectFit(ImageFit.Contain) | ||
| 125 | + Text(title) | ||
| 126 | + .fontSize(10) | ||
| 127 | + .margin(top: 4) | ||
| 128 | + .fontColor(if (this.currentIndex == index) { 0xFFFB6522 } else { @r(app.color.tabTitleColor) }) | ||
| 129 | + } | ||
| 130 | + .justifyContent(FlexAlign.Center) | ||
| 131 | + .width(100.percent) | ||
| 132 | + .height(100.percent) | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + func build() { | ||
| 136 | + Navigation(this.stack) { | ||
| 137 | + Tabs(barPosition: BarPosition.End, controller: this.tabsController, index: this.currentIndex) { | ||
| 138 | + TabContent() { | ||
| 139 | + HomeTab() | ||
| 140 | + } | ||
| 141 | + .tabBar({=> bind(this.tabBuilder, this)(0, @r(app.string.tab_home)) }) | ||
| 142 | + | ||
| 143 | + TabContent() { | ||
| 144 | + RunTab() | ||
| 145 | + } | ||
| 146 | + .tabBar({=> bind(this.tabBuilder, this)(1, @r(app.string.tab_run)) }) | ||
| 147 | + | ||
| 148 | + TabContent() { | ||
| 149 | + FindTab() | ||
| 150 | + } | ||
| 151 | + .tabBar({=> bind(this.tabBuilder, this)(2, @r(app.string.tab_find)) }) | ||
| 152 | + | ||
| 153 | + TabContent() { | ||
| 154 | + ShopTab() | ||
| 155 | + } | ||
| 156 | + .tabBar({=> bind(this.tabBuilder, this)(3, @r(app.string.tab_shop)) }) | ||
| 157 | + | ||
| 158 | + TabContent() { | ||
| 159 | + MineTab() | ||
| 160 | + } | ||
| 161 | + .tabBar({=> bind(this.tabBuilder, this)(4, @r(app.string.tab_mine)) }) | ||
| 162 | + } | ||
| 163 | + .barHeight(56) | ||
| 164 | + .barMode(BarMode.Fixed) | ||
| 165 | + .scrollable(false) | ||
| 166 | + .onChange({ index: Int32 => | ||
| 167 | + this.currentIndex = index | ||
| 168 | + }) | ||
| 169 | + .width(100.percent) | ||
| 170 | + .height(100.percent) | ||
| 171 | + } | ||
| 172 | + .hideTitleBar(true) | ||
| 173 | + .navDestination(bind(appPageMap, this)) | ||
| 174 | + } | ||
| 175 | +} | ||
| @@ -0,0 +1,114 @@ | |||
| 1 | +package ohos_app_cangjie_entry | ||
| 2 | + | ||
| 3 | +import kit.PerformanceAnalysisKit.Hilog | ||
| 4 | +import kit.AbilityKit.Want | ||
| 5 | +import kit.AbilityKit.UIAbility | ||
| 6 | +import kit.AbilityKit.LaunchParam | ||
| 7 | +import kit.AbilityKit.LaunchReason | ||
| 8 | +import kit.ArkUI.* | ||
| 9 | +import ohos.arkui.state_macro_manage.* | ||
| 10 | +import ohos.business_exception.BusinessException | ||
| 11 | +import kit.AbilityKit.{AbilityAccessCtrl, BundleManager, BundleFlag, GrantStatus, Permissions, UIAbilityContext} | ||
| 12 | +import ohos.security.permission_request_result.PermissionRequestResult | ||
| 13 | + | ||
| 14 | +class MainAbility <: UIAbility { | ||
| 15 | + public init() { | ||
| 16 | + super() | ||
| 17 | + registerSelf() | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public override func onCreate(want: Want, launchParam: LaunchParam): Unit { | ||
| 21 | + Hilog.info(1, "Cangjie", "MainAbility OnCreated.${want.abilityName}") | ||
| 22 | + match (launchParam.launchReason) { | ||
| 23 | + case LaunchReason.StartAbility => Hilog.info(1, "Cangjie", "START_ABILITY") | ||
| 24 | + case _ => () | ||
| 25 | + } | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public override func onWindowStageCreate(windowStage: WindowStage): Unit { | ||
| 29 | + Hilog.info(1, "Cangjie", "MainAbility onWindowStageCreate.") | ||
| 30 | + | ||
| 31 | + // Align with ArkTS template: compute safe-area avoid heights and store in AppStorage. | ||
| 32 | + AppStorage.setOrCreate("windowStage", windowStage) | ||
| 33 | + AppStorage.setOrCreate<Int64>("topRectHeightPx", 0) | ||
| 34 | + AppStorage.setOrCreate<Int64>("bottomRectHeightPx", 0) | ||
| 35 | + | ||
| 36 | + try { | ||
| 37 | + let mainWindow: Window = windowStage.getMainWindow() | ||
| 38 | + mainWindow.setWindowLayoutFullScreen(true) | ||
| 39 | + | ||
| 40 | + let systemAvoidArea = mainWindow.getWindowAvoidArea(AvoidAreaType.TypeSystem) | ||
| 41 | + AppStorage.setOrCreate<Int64>( | ||
| 42 | + "topRectHeightPx", | ||
| 43 | + Int64(systemAvoidArea.topRect.height) | ||
| 44 | + ) | ||
| 45 | + | ||
| 46 | + let navAvoidArea = mainWindow.getWindowAvoidArea(AvoidAreaType.TypeNavigationIndicator) | ||
| 47 | + AppStorage.setOrCreate<Int64>( | ||
| 48 | + "bottomRectHeightPx", | ||
| 49 | + Int64(navAvoidArea.bottomRect.height) | ||
| 50 | + ) | ||
| 51 | + } catch (e: BusinessException) { | ||
| 52 | + Hilog.error(1, "Cangjie", "Compute avoid area failed. Code: ${e.code}, message: ${e.message}", "") | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + windowStage.loadContent("EntryView") | ||
| 56 | + | ||
| 57 | + // Request runtime permissions for in-app features (pedometer / bluetooth / background) | ||
| 58 | + // Note: per docs, requestPermissionsFromUser should be called after loadContent. | ||
| 59 | + this.requestInUsePermissions() | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + func requestInUsePermissions(): Unit { | ||
| 63 | + try { | ||
| 64 | + // Check current grant state first to avoid unnecessary prompts. | ||
| 65 | + let tokenID = BundleManager.getBundleInfoForSelf(BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION) | ||
| 66 | + .appInfo.accessTokenId | ||
| 67 | + let atManager = AbilityAccessCtrl.createAtManager() | ||
| 68 | + | ||
| 69 | + let permissionList: Array<Permissions> = [ | ||
| 70 | + "ohos.permission.ACTIVITY_MOTION", | ||
| 71 | + "ohos.permission.ACCESS_BLUETOOTH", | ||
| 72 | + "ohos.permission.KEEP_BACKGROUND_RUNNING" | ||
| 73 | + ] | ||
| 74 | + | ||
| 75 | + var needRequest: Bool = false | ||
| 76 | + for (p in permissionList) { | ||
| 77 | + if (atManager.checkAccessToken(tokenID, p) == GrantStatus.PermissionDenied) { | ||
| 78 | + needRequest = true | ||
| 79 | + break | ||
| 80 | + } | ||
| 81 | + } | ||
| 82 | + if (!needRequest) { | ||
| 83 | + return | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + var resultCallback = { | ||
| 87 | + errorCode: Option<BusinessException>, data: Option<PermissionRequestResult> => match (errorCode) { | ||
| 88 | + case Some(e) => Hilog.error(1, "Cangjie", "requestPermissionsFromUser failed, errcode: ${e.code}", "") | ||
| 89 | + case _ => | ||
| 90 | + match (data) { | ||
| 91 | + case Some(value) => | ||
| 92 | + // Avoid out-of-bounds when the array is empty. | ||
| 93 | + if (value.permissions.size > 0) { | ||
| 94 | + for (i in (0..(value.permissions.size - 1))) { | ||
| 95 | + if (value.authResults[i] == 0) { | ||
| 96 | + Hilog.info(1, "Cangjie", "permission granted: ${value.permissions[i]}") | ||
| 97 | + } else { | ||
| 98 | + Hilog.info(1, "Cangjie", "permission denied: ${value.permissions[i]}") | ||
| 99 | + } | ||
| 100 | + } | ||
| 101 | + } | ||
| 102 | + case _ => Hilog.error(1, "Cangjie", "requestPermissionsFromUser error: data is null", "") | ||
| 103 | + } | ||
| 104 | + } | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + atManager.requestPermissionsFromUser(this.context, permissionList, resultCallback) | ||
| 108 | + } catch (e: BusinessException) { | ||
| 109 | + Hilog.error(1, "Cangjie", "requestInUsePermissions BusinessException: ${e.code}, ${e.message}", "") | ||
| 110 | + } catch (e: Exception) { | ||
| 111 | + Hilog.error(1, "Cangjie", "requestInUsePermissions Exception: ${e}", "") | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | +} | ||
| @@ -0,0 +1,184 @@ | |||
| 1 | +package ohos_app_cangjie_entry.pages | ||
| 2 | + | ||
| 3 | +import kit.ArkUI.* | ||
| 4 | +import kit.LocalizationKit.* | ||
| 5 | +// Avoid name clash with ArkUI List | ||
| 6 | +import ohos.arkui.state_macro_manage.* | ||
| 7 | + | ||
| 8 | +@Component | ||
| 9 | +public class FindTab { | ||
| 10 | + func build() { | ||
| 11 | + FindPage() | ||
| 12 | + } | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +@Component | ||
| 16 | +class FindPage { | ||
| 17 | + @Consume var stack: NavPathStack | ||
| 18 | + | ||
| 19 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 20 | + | ||
| 21 | + @State var focusIndex: Int32 = 0 | ||
| 22 | + var controller: TabsController = TabsController() | ||
| 23 | + var scrollerForList: Scroller = Scroller() | ||
| 24 | + | ||
| 25 | + var tabArray: Array<AppResource> = [ | ||
| 26 | + @r(app.string.find_follow), | ||
| 27 | + @r(app.string.find_recommend), | ||
| 28 | + @r(app.string.find_activity), | ||
| 29 | + @r(app.string.find_knowledge) | ||
| 30 | + ] | ||
| 31 | + | ||
| 32 | + @Builder | ||
| 33 | + func topTab(tabName: AppResource, tabIndex: Int32) { | ||
| 34 | + Column() { | ||
| 35 | + Text(tabName) | ||
| 36 | + .fontSize(if (tabIndex == this.focusIndex) { 22 } else { 16 }) | ||
| 37 | + if (tabIndex == this.focusIndex) { | ||
| 38 | + Image(@r(app.media.a_lot_personal_run)) | ||
| 39 | + .width(10) | ||
| 40 | + .height(2) | ||
| 41 | + } | ||
| 42 | + } | ||
| 43 | + .width(70) | ||
| 44 | + .borderRadius(topLeft: 10, topRight: 10) | ||
| 45 | + .justifyContent(FlexAlign.Center) | ||
| 46 | + .onClick({ _: ClickEvent => | ||
| 47 | + this.controller.changeIndex(tabIndex) | ||
| 48 | + this.focusIndex = tabIndex | ||
| 49 | + }) | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + func build() { | ||
| 53 | + Column() { | ||
| 54 | + Row() { | ||
| 55 | + List(scroller: this.scrollerForList) { | ||
| 56 | + ForEach( | ||
| 57 | + this.tabArray, | ||
| 58 | + itemGeneratorFunc: { item: AppResource, idx: Int64 => | ||
| 59 | + ListItem() { | ||
| 60 | + this.topTab(item, Int32(idx.position())) | ||
| 61 | + } | ||
| 62 | + }, | ||
| 63 | + keyGeneratorFunc: { item: AppResource, idx: Int64 => return "${idx}" } | ||
| 64 | + ) | ||
| 65 | + } | ||
| 66 | + .listDirection(Axis.Horizontal) | ||
| 67 | + .scrollBar(BarState.Off) | ||
| 68 | + } | ||
| 69 | + .height(50) | ||
| 70 | + .justifyContent(FlexAlign.Start) | ||
| 71 | + .width(100.percent) | ||
| 72 | + | ||
| 73 | + Tabs(barPosition: BarPosition.Start, controller: this.controller) { | ||
| 74 | + TabContent() { | ||
| 75 | + Scroll() { | ||
| 76 | + Column(space: 10) { | ||
| 77 | + Row() { | ||
| 78 | + Image(@r(app.media.a_lot_personal_run)) | ||
| 79 | + .width(100.percent) | ||
| 80 | + .height(10.percent) | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + List(space: 10) { | ||
| 84 | + ListItem() { | ||
| 85 | + Column(space: 5) { | ||
| 86 | + Row() { | ||
| 87 | + Row(space: 5) { | ||
| 88 | + Image(@r(app.media.touxiang1)) | ||
| 89 | + .width(40) | ||
| 90 | + .height(40) | ||
| 91 | + .borderRadius(24) | ||
| 92 | + Text("吃肉不上头") | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + Button(@r(app.string.btn_follow)) | ||
| 96 | + .backgroundColor(0xFF50E5E5) | ||
| 97 | + .fontColor(Color.White) | ||
| 98 | + .width(60) | ||
| 99 | + .height(30) | ||
| 100 | + } | ||
| 101 | + .justifyContent(FlexAlign.SpaceBetween) | ||
| 102 | + .width(95.percent) | ||
| 103 | + | ||
| 104 | + Row() { | ||
| 105 | + Image(@r(app.media.a_lot_personal_run)) | ||
| 106 | + .width(100) | ||
| 107 | + .height(100) | ||
| 108 | + .borderRadius(10) | ||
| 109 | + Image(@r(app.media.bilik)) | ||
| 110 | + .width(100) | ||
| 111 | + .height(100) | ||
| 112 | + .borderRadius(10) | ||
| 113 | + Image(@r(app.media.figer)) | ||
| 114 | + .width(100) | ||
| 115 | + .height(100) | ||
| 116 | + .borderRadius(10) | ||
| 117 | + } | ||
| 118 | + .width(95.percent) | ||
| 119 | + .justifyContent(FlexAlign.SpaceBetween) | ||
| 120 | + } | ||
| 121 | + .width(100.percent) | ||
| 122 | + } | ||
| 123 | + } | ||
| 124 | + .height(80.percent) | ||
| 125 | + .scrollBar(BarState.Off) | ||
| 126 | + } | ||
| 127 | + .width(100.percent) | ||
| 128 | + .height(100.percent) | ||
| 129 | + } | ||
| 130 | + .scrollBar(BarState.Off) | ||
| 131 | + .width(100.percent) | ||
| 132 | + .height(100.percent) | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + TabContent() { | ||
| 136 | + Scroll() { | ||
| 137 | + Column() { | ||
| 138 | + Row() { | ||
| 139 | + Image(@r(app.media.men15)) | ||
| 140 | + .height(10.percent) | ||
| 141 | + .width(100.percent) | ||
| 142 | + } | ||
| 143 | + .width(100.percent) | ||
| 144 | + } | ||
| 145 | + .width(100.percent) | ||
| 146 | + .height(100.percent) | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + TabContent() { | ||
| 151 | + Column() { | ||
| 152 | + Text("我是页面 活动 的内容") | ||
| 153 | + .height(300) | ||
| 154 | + .width(100.percent) | ||
| 155 | + .fontSize(30) | ||
| 156 | + } | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + TabContent() { | ||
| 160 | + Column() { | ||
| 161 | + Text("我是页面 涨知识 的内容") | ||
| 162 | + .height(300) | ||
| 163 | + .width(100.percent) | ||
| 164 | + .fontSize(30) | ||
| 165 | + } | ||
| 166 | + } | ||
| 167 | + } | ||
| 168 | + .barMode(BarMode.Scrollable) | ||
| 169 | + .barHeight(0) | ||
| 170 | + .animationDuration(Some(100.0)) | ||
| 171 | + .onChange({ index: Int32 => | ||
| 172 | + this.focusIndex = index | ||
| 173 | + this.scrollerForList.scrollToIndex(index, smooth: true) | ||
| 174 | + }) | ||
| 175 | + .width(100.percent) | ||
| 176 | + .layoutWeight(1) | ||
| 177 | + } | ||
| 178 | + .alignItems(HorizontalAlign.Start) | ||
| 179 | + .width(100.percent) | ||
| 180 | + .height(100.percent) | ||
| 181 | + .margin(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 182 | + .backgroundColor(@r(app.color.primaryBgColor)) | ||
| 183 | + } | ||
| 184 | +} | ||
| @@ -0,0 +1,78 @@ | |||
| 1 | +package ohos_app_cangjie_entry.pages | ||
| 2 | + | ||
| 3 | +import kit.ArkUI.* | ||
| 4 | +import kit.LocalizationKit.* | ||
| 5 | +import ohos.arkui.state_macro_manage.* | ||
| 6 | + | ||
| 7 | +@Component | ||
| 8 | +public class HomeTab { | ||
| 9 | + func build() { | ||
| 10 | + HomePage() | ||
| 11 | + } | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +@Component | ||
| 15 | +class HomePage { | ||
| 16 | + @Consume var stack: NavPathStack | ||
| 17 | + | ||
| 18 | + func build() { | ||
| 19 | + Stack() { | ||
| 20 | + Column() { | ||
| 21 | + Stack() { | ||
| 22 | + Image(@r(app.media.img_3)) | ||
| 23 | + .width(100.percent) | ||
| 24 | + .margin(left: 40) | ||
| 25 | + // Invisible but addressable click target for UI automation. | ||
| 26 | + Text(@r(app.string.mine_personal_data)) | ||
| 27 | + .width(50) | ||
| 28 | + .height(50) | ||
| 29 | + .backgroundColor(Color.White) | ||
| 30 | + .margin(left: 300) | ||
| 31 | + .opacity(0.0) | ||
| 32 | + .onClick({ _: ClickEvent => | ||
| 33 | + // Align with ArkTS template: Home -> MyDeviceOne (device list) | ||
| 34 | + this.stack.pushPathByName("my_devices", "") | ||
| 35 | + }) | ||
| 36 | + } | ||
| 37 | + .margin(top: 30) | ||
| 38 | + | ||
| 39 | + Row() { | ||
| 40 | + Image(@r(app.media.img_16)) | ||
| 41 | + .height(21) | ||
| 42 | + } | ||
| 43 | + .margin(left: -220) | ||
| 44 | + | ||
| 45 | + Scroll() { | ||
| 46 | + Column() { | ||
| 47 | + Row() { | ||
| 48 | + Image(@r(app.media.img_6)) | ||
| 49 | + .width(166) | ||
| 50 | + .height(144) | ||
| 51 | + .margin(top: 33) | ||
| 52 | + Image(@r(app.media.img_9)) | ||
| 53 | + .width(36) | ||
| 54 | + .margin(left: 31) | ||
| 55 | + } | ||
| 56 | + .margin(left: 50) | ||
| 57 | + | ||
| 58 | + Image(@r(app.media.img_10)) | ||
| 59 | + .width(304) | ||
| 60 | + .margin(top: 20.5) | ||
| 61 | + Image(@r(app.media.img_17)) | ||
| 62 | + .width(328) | ||
| 63 | + .margin(top: 20.5) | ||
| 64 | + Image(@r(app.media.img_13)) | ||
| 65 | + .width(328) | ||
| 66 | + .margin(top: 12) | ||
| 67 | + Image(@r(app.media.img_14)) | ||
| 68 | + .width(328) | ||
| 69 | + .margin(top: 6.5) | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + .layoutWeight(1) | ||
| 73 | + .scrollBar(BarState.Off) | ||
| 74 | + } | ||
| 75 | + .backgroundColor(@r(app.color.primaryBgColor)) | ||
| 76 | + } | ||
| 77 | + } | ||
| 78 | +} | ||
| @@ -0,0 +1,162 @@ | |||
| 1 | +package ohos_app_cangjie_entry.pages | ||
| 2 | + | ||
| 3 | +import kit.ArkUI.* | ||
| 4 | +import kit.LocalizationKit.* | ||
| 5 | +import ohos.arkui.state_macro_manage.* | ||
| 6 | + | ||
| 7 | +@Component | ||
| 8 | +public class MineTab { | ||
| 9 | + func build() { | ||
| 10 | + MinePage() | ||
| 11 | + } | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +@Component | ||
| 15 | +class MinePage { | ||
| 16 | + @Consume var stack: NavPathStack | ||
| 17 | + | ||
| 18 | + // Use StorageLink (two-way) here to avoid StorageProp accessor restrictions. | ||
| 19 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 20 | + | ||
| 21 | + var mineItemIds: Array<String> = [ | ||
| 22 | + "1", | ||
| 23 | + "2", | ||
| 24 | + "3", | ||
| 25 | + "4", | ||
| 26 | + "5", | ||
| 27 | + "6", | ||
| 28 | + "7", | ||
| 29 | + "8", | ||
| 30 | + "9", | ||
| 31 | + "10", | ||
| 32 | + "11", | ||
| 33 | + "12", | ||
| 34 | + "13" | ||
| 35 | + ] | ||
| 36 | + | ||
| 37 | + var mineItemTitles: Array<AppResource> = [ | ||
| 38 | + @r(app.string.mine_personal_data), | ||
| 39 | + @r(app.string.mine_check_updates), | ||
| 40 | + @r(app.string.user_policy_about), | ||
| 41 | + @r(app.string.set_about), | ||
| 42 | + @r(app.string.personal_policy_about), | ||
| 43 | + @r(app.string.mine_about), | ||
| 44 | + @r(app.string.permission_about), | ||
| 45 | + @r(app.string.new_about), | ||
| 46 | + @r(app.string.new_ask_about), | ||
| 47 | + @r(app.string.cache_about), | ||
| 48 | + @r(app.string.opinion_about), | ||
| 49 | + @r(app.string.health_report), | ||
| 50 | + @r(app.string.heart_monitoring) | ||
| 51 | + ] | ||
| 52 | + | ||
| 53 | + func build() { | ||
| 54 | + Column() { | ||
| 55 | + Column() { | ||
| 56 | + Image(@r(app.media.ic_user)) | ||
| 57 | + .objectFit(ImageFit.Contain) | ||
| 58 | + .height(66) | ||
| 59 | + .width(66) | ||
| 60 | + .margin(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 61 | + | ||
| 62 | + Column() { | ||
| 63 | + Text("LV.7") | ||
| 64 | + .fontSize(12) | ||
| 65 | + .fontWeight(FontWeight.Bolder) | ||
| 66 | + .fontColor(@r(app.color.leveColor)) | ||
| 67 | + } | ||
| 68 | + .width(44) | ||
| 69 | + .height(16) | ||
| 70 | + .margin(top: -8) | ||
| 71 | + .borderRadius(5) | ||
| 72 | + .backgroundColor(@r(app.color.leveBgColor)) | ||
| 73 | + .justifyContent(FlexAlign.Center) | ||
| 74 | + | ||
| 75 | + Text("JoIin") | ||
| 76 | + .fontSize(20) | ||
| 77 | + .margin(bottom: 6) | ||
| 78 | + .fontWeight(FontWeight.Normal) | ||
| 79 | + .fontColor(@r(app.color.black)) | ||
| 80 | + .fontFamily(@r(app.string.Helvetica)) | ||
| 81 | + | ||
| 82 | + Text("这是一条简短地个人签") | ||
| 83 | + .fontSize(16) | ||
| 84 | + .fontWeight(FontWeight.Normal) | ||
| 85 | + .fontColor(@r(app.color.signatureColor)) | ||
| 86 | + .fontFamily(@r(app.string.PingFangSC_Regular)) | ||
| 87 | + } | ||
| 88 | + .width(100.percent) | ||
| 89 | + | ||
| 90 | + Scroll() { | ||
| 91 | + List() { | ||
| 92 | + ForEach( | ||
| 93 | + this.mineItemIds, | ||
| 94 | + itemGeneratorFunc: { itemId: String, idx: Int64 => | ||
| 95 | + ListItem() { | ||
| 96 | + Column() { | ||
| 97 | + Flex(justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center) { | ||
| 98 | + Text(this.mineItemTitles[idx.position()]) | ||
| 99 | + .fontSize(16) | ||
| 100 | + .height(40) | ||
| 101 | + Image(@r(app.media.ic_right_grey)) | ||
| 102 | + .objectFit(ImageFit.Contain) | ||
| 103 | + .height(12) | ||
| 104 | + .width(7) | ||
| 105 | + } | ||
| 106 | + .height(48) | ||
| 107 | + .onClick({ _: ClickEvent => | ||
| 108 | + if (idx.position() == 0) { | ||
| 109 | + // Align with ArkTS template: Mine -> DeviceListPageOne. | ||
| 110 | + this.stack.pushPathByName("device_list", "") | ||
| 111 | + } else if (idx.position() == 1) { | ||
| 112 | + this.stack.pushPathByName("check_updates", "") | ||
| 113 | + } else if (idx.position() == 2) { | ||
| 114 | + this.stack.pushPathByName("user_policy", "") | ||
| 115 | + } else if (idx.position() == 3) { | ||
| 116 | + this.stack.pushPathByName("settings", "") | ||
| 117 | + } else if (idx.position() == 4) { | ||
| 118 | + this.stack.pushPathByName("privacy_policy", "") | ||
| 119 | + } else if (idx.position() == 5) { | ||
| 120 | + this.stack.pushPathByName("about", "") | ||
| 121 | + } else if (idx.position() == 6) { | ||
| 122 | + this.stack.pushPathByName("permission_settings", "") | ||
| 123 | + } else if (idx.position() == 7) { | ||
| 124 | + this.stack.pushPathByName("push_settings", "") | ||
| 125 | + } else if (idx.position() == 8) { | ||
| 126 | + this.stack.pushPathByName("message", "") | ||
| 127 | + } else if (idx.position() == 9) { | ||
| 128 | + this.stack.pushPathByName("cache_clear", "") | ||
| 129 | + } else if (idx.position() == 10) { | ||
| 130 | + this.stack.pushPathByName("feedback", "") | ||
| 131 | + } else if (idx.position() == 11) { | ||
| 132 | + this.stack.pushPathByName("health_report", "") | ||
| 133 | + } else if (idx.position() == 12) { | ||
| 134 | + this.stack.pushPathByName("heart_monitoring", "") | ||
| 135 | + } | ||
| 136 | + }) | ||
| 137 | + | ||
| 138 | + Row() {} | ||
| 139 | + .height(1) | ||
| 140 | + .width(100.percent) | ||
| 141 | + .backgroundColor(@r(app.color.borderColor)) | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | + .backgroundColor(Color.White) | ||
| 145 | + .margin(left: 24, right: 24) | ||
| 146 | + .height(48) | ||
| 147 | + }, | ||
| 148 | + keyGeneratorFunc: { itemId: String, idx: Int64 => return "${idx}_${itemId}" } | ||
| 149 | + ) | ||
| 150 | + } | ||
| 151 | + .borderRadius(topLeft: 24, topRight: 24) | ||
| 152 | + .backgroundColor(Color.White) | ||
| 153 | + .clip(true) | ||
| 154 | + } | ||
| 155 | + .scrollBar(BarState.Off) | ||
| 156 | + .layoutWeight(1) | ||
| 157 | + } | ||
| 158 | + .height(100.percent) | ||
| 159 | + .width(100.percent) | ||
| 160 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 161 | + } | ||
| 162 | +} | ||
| @@ -0,0 +1,160 @@ | |||
| 1 | +package ohos_app_cangjie_entry.pages | ||
| 2 | + | ||
| 3 | +import kit.ArkUI.* | ||
| 4 | +import kit.LocalizationKit.* | ||
| 5 | +import ohos.arkui.state_macro_manage.* | ||
| 6 | +import kit.SensorServiceKit.* | ||
| 7 | +import kit.PerformanceAnalysisKit.Hilog | ||
| 8 | +import ohos.business_exception.BusinessException | ||
| 9 | +import ohos.callback_invoke.* | ||
| 10 | +import kit.AbilityKit.{AbilityAccessCtrl, BundleManager, BundleFlag, GrantStatus, Permissions} | ||
| 11 | + | ||
| 12 | +@Component | ||
| 13 | +public class RunTab { | ||
| 14 | + func build() { | ||
| 15 | + RunPage() | ||
| 16 | + } | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +@Component | ||
| 20 | +class RunPage { | ||
| 21 | + @State var stepNum: Int64 = 0 | ||
| 22 | + @State var pedometerSubscribed: Bool = false | ||
| 23 | + @State var motionGranted: Bool = true | ||
| 24 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 25 | + @StorageLink["bottomRectHeightPx"] var bottomRectHeightPx: Int64 = 0 | ||
| 26 | + | ||
| 27 | + func updateSteps(v: Int64) { | ||
| 28 | + this.stepNum = v | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + func refreshPermissionState(): Unit { | ||
| 32 | + try { | ||
| 33 | + let tokenID = BundleManager.getBundleInfoForSelf(BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION) | ||
| 34 | + .appInfo.accessTokenId | ||
| 35 | + let atManager = AbilityAccessCtrl.createAtManager() | ||
| 36 | + let p: Permissions = "ohos.permission.ACTIVITY_MOTION" | ||
| 37 | + this.motionGranted = (atManager.checkAccessToken(tokenID, p) != GrantStatus.PermissionDenied) | ||
| 38 | + } catch (e: Exception) { | ||
| 39 | + this.motionGranted = false | ||
| 40 | + } | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + func build() { | ||
| 44 | + Column() { | ||
| 45 | + Column() { | ||
| 46 | + Image(@r(app.media.img_36)) | ||
| 47 | + .width(328) | ||
| 48 | + .height(56) | ||
| 49 | + .margin(left: -10, top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 50 | + | ||
| 51 | + Stack() { | ||
| 52 | + Image(@r(app.media.img_37)) | ||
| 53 | + .width(100.percent) | ||
| 54 | + .margin(left: 0, top: 12) | ||
| 55 | + .height(56) | ||
| 56 | + Row() {} | ||
| 57 | + .width(100) | ||
| 58 | + .height(30) | ||
| 59 | + .margin(left: -220) | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + Stack() { | ||
| 63 | + Image(@r(app.media.img_43)) | ||
| 64 | + .width(100.percent) | ||
| 65 | + .margin(left: 0, top: 50) | ||
| 66 | + .height(415) | ||
| 67 | + | ||
| 68 | + Column() { | ||
| 69 | + Stack() { | ||
| 70 | + Image(@r(app.media.img_39)) | ||
| 71 | + .width(160) | ||
| 72 | + .margin(left: 0, top: 16) | ||
| 73 | + .height(102) | ||
| 74 | + | ||
| 75 | + Row() {} | ||
| 76 | + .width(200) | ||
| 77 | + .height(40) | ||
| 78 | + .margin(left: -5, top: -300) | ||
| 79 | + .backgroundColor(0xFFF1F3F5) | ||
| 80 | + | ||
| 81 | + Text("${this.stepNum}") | ||
| 82 | + .fontSize(40) | ||
| 83 | + .margin(left: 23, top: -300) | ||
| 84 | + .fontWeight(FontWeight.Bold) | ||
| 85 | + .width(100) | ||
| 86 | + | ||
| 87 | + Row() {} | ||
| 88 | + .width(120) | ||
| 89 | + .height(20) | ||
| 90 | + .margin(left: -5, top: -230) | ||
| 91 | + .backgroundColor(0xFFEAF4EC) | ||
| 92 | + | ||
| 93 | + Text("累计步数") | ||
| 94 | + .fontSize(15) | ||
| 95 | + .margin(left: 38, top: -230) | ||
| 96 | + .fontWeight(FontWeight.Bold) | ||
| 97 | + .width(100) | ||
| 98 | + .fontColor(0xFF999999) | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + if (!this.motionGranted) { | ||
| 102 | + Text("未授予运动与健身权限,步数可能为0") | ||
| 103 | + .fontSize(12) | ||
| 104 | + .opacity(0.7) | ||
| 105 | + .margin(top: 8) | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + Image(@r(app.media.img_40)) | ||
| 109 | + .width(216) | ||
| 110 | + .margin(left: 0, top: 16) | ||
| 111 | + .height(64) | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + Image(@r(app.media.img_41)) | ||
| 116 | + .width(328) | ||
| 117 | + .margin(left: 0, top: 28) | ||
| 118 | + .height(329) | ||
| 119 | + } | ||
| 120 | + } | ||
| 121 | + .height(100.percent) | ||
| 122 | + .width(100.percent) | ||
| 123 | + .padding(bottom: getUIContext().px2vp(this.bottomRectHeightPx.px) ?? 0.vp) | ||
| 124 | + .backgroundColor(0xFFF1F3F5) | ||
| 125 | + .onAppear({ => | ||
| 126 | + this.refreshPermissionState() | ||
| 127 | + if (!this.motionGranted) { | ||
| 128 | + return | ||
| 129 | + } | ||
| 130 | + if (this.pedometerSubscribed) { | ||
| 131 | + return | ||
| 132 | + } | ||
| 133 | + try { | ||
| 134 | + let callback = PedometerCallback(this) | ||
| 135 | + let options = Options(interval: IntervalOption.SensorNumber(100000000)) | ||
| 136 | + on(SensorId.Pedometer, callback, option: options) | ||
| 137 | + this.pedometerSubscribed = true | ||
| 138 | + } catch (e: BusinessException) { | ||
| 139 | + Hilog.error(0, "Cangjie", "Pedometer subscribe failed. Code: ${e.code}, message: ${e.message}", "") | ||
| 140 | + } | ||
| 141 | + }) | ||
| 142 | + } | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +class PedometerCallback <: Callback1Argument<PedometerResponse> { | ||
| 146 | + var owner: RunPage | ||
| 147 | + init(owner: RunPage) { | ||
| 148 | + this.owner = owner | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + public func invoke(err: ?BusinessException, arg: PedometerResponse): Unit { | ||
| 152 | + match (err) { | ||
| 153 | + case Some(e) => | ||
| 154 | + Hilog.error(0, "Cangjie", "Pedometer callback error. Code: ${e.code}, message: ${e.message}", "") | ||
| 155 | + return | ||
| 156 | + case _ => () | ||
| 157 | + } | ||
| 158 | + this.owner.updateSteps(arg.steps) | ||
| 159 | + } | ||
| 160 | +} | ||
| @@ -0,0 +1,236 @@ | |||
| 1 | +package ohos_app_cangjie_entry.pages | ||
| 2 | + | ||
| 3 | +import kit.ArkUI.* | ||
| 4 | +import kit.LocalizationKit.* | ||
| 5 | +import ohos.arkui.state_macro_manage.* | ||
| 6 | + | ||
| 7 | +@Component | ||
| 8 | +public class ShopTab { | ||
| 9 | + func build() { | ||
| 10 | + ShopPage() | ||
| 11 | + } | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +@Component | ||
| 15 | +class ShopPage { | ||
| 16 | + var swiperController: SwiperController = SwiperController() | ||
| 17 | + | ||
| 18 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 19 | + @StorageLink["bottomRectHeightPx"] var bottomRectHeightPx: Int64 = 0 | ||
| 20 | + | ||
| 21 | + var categoryTitles: Array<String> = [ | ||
| 22 | + "方便代餐", | ||
| 23 | + "控卡主食", | ||
| 24 | + "助燃嗨吃", | ||
| 25 | + "体脂秤", | ||
| 26 | + "运动装备", | ||
| 27 | + "低卡零食", | ||
| 28 | + "高蛋白", | ||
| 29 | + "咖啡冲调", | ||
| 30 | + "营养早餐", | ||
| 31 | + "更多" | ||
| 32 | + ] | ||
| 33 | + var categoryChars: Array<String> = ["烫", "滚", "减", "脂", "季", "领", "劵", "更", "优", "惠"] | ||
| 34 | + | ||
| 35 | + var recommendImages: Array<ResourceStr> = [ | ||
| 36 | + @r(app.media.recommendation1), | ||
| 37 | + @r(app.media.recommendation2), | ||
| 38 | + @r(app.media.recommendation3), | ||
| 39 | + @r(app.media.recommendation4), | ||
| 40 | + @r(app.media.recommendation5), | ||
| 41 | + @r(app.media.recommendation2), | ||
| 42 | + @r(app.media.recommendation2) | ||
| 43 | + ] | ||
| 44 | + var recommendTitles: Array<String> = [ | ||
| 45 | + "21天控卡减脂餐", | ||
| 46 | + "控卡代餐", | ||
| 47 | + "低脂零食", | ||
| 48 | + "体脂测量", | ||
| 49 | + "快乐燃脂", | ||
| 50 | + "21天控卡减脂餐", | ||
| 51 | + "21天控卡减脂餐" | ||
| 52 | + ] | ||
| 53 | + var recommendTags: Array<String> = [ | ||
| 54 | + "[ 热辣 ] 推荐", | ||
| 55 | + "控卡代餐", | ||
| 56 | + "低脂零食", | ||
| 57 | + "体脂测量", | ||
| 58 | + "快乐燃脂", | ||
| 59 | + "[热辣]推荐", | ||
| 60 | + "[热辣]推荐" | ||
| 61 | + ] | ||
| 62 | + var recommendBadges: Array<String> = [ | ||
| 63 | + "21天控卡减脂餐", | ||
| 64 | + "2件9折", | ||
| 65 | + "3件85折", | ||
| 66 | + "领劵立减5元", | ||
| 67 | + "领劵立减10元", | ||
| 68 | + "", | ||
| 69 | + "" | ||
| 70 | + ] | ||
| 71 | + | ||
| 72 | + @Builder | ||
| 73 | + func recommendCard(idx: Int64) { | ||
| 74 | + Stack(alignContent: if (idx.position() == 0) { Alignment.TopStart } else { Alignment.Start }) { | ||
| 75 | + Image(this.recommendImages[idx.position()]) | ||
| 76 | + .width(100.percent) | ||
| 77 | + .height(if (idx.position() == 0) { 200 } else { 100 }) | ||
| 78 | + .borderRadius(10) | ||
| 79 | + | ||
| 80 | + if (this.recommendBadges[idx.position()] != "") { | ||
| 81 | + Column(space: 10) { | ||
| 82 | + Text(this.recommendTags[idx.position()]) | ||
| 83 | + .fontSize(16) | ||
| 84 | + .fontColor(0xFF000000) | ||
| 85 | + | ||
| 86 | + Flex(justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center) { | ||
| 87 | + Text(this.recommendBadges[idx.position()]) | ||
| 88 | + .fontSize(13) | ||
| 89 | + .fontColor(Color.White) | ||
| 90 | + .textAlign(TextAlign.Center) | ||
| 91 | + .maxLines(1) | ||
| 92 | + .textOverflow(TextOverflow.Ellipsis) | ||
| 93 | + } | ||
| 94 | + .constraintSize(maxWidth: if (idx.position() == 0) { 112 } else { 88 }) | ||
| 95 | + .borderRadius(if (idx.position() == 0) { 14 } else { 13 }) | ||
| 96 | + .height(if (idx.position() == 0) { 28 } else { 26 }) | ||
| 97 | + .backgroundColor(0xFF206DF5) | ||
| 98 | + .padding(left: 12, right: 12) | ||
| 99 | + } | ||
| 100 | + .alignItems(HorizontalAlign.Start) | ||
| 101 | + .margin(left: if (idx.position() == 0) { 10 } else { 0 }, top: if (idx.position() == 0) { 10 } else { 0 }) | ||
| 102 | + .padding(left: if (idx.position() == 0) { 0 } else { 5 }, top: if (idx.position() == 0) { 0 } else { 5 }) | ||
| 103 | + } else { | ||
| 104 | + Column() { | ||
| 105 | + Text(this.recommendTags[idx.position()]) | ||
| 106 | + .fontSize(16) | ||
| 107 | + .fontColor(0xFF000000) | ||
| 108 | + Text(this.recommendTitles[idx.position()]) | ||
| 109 | + .fontSize(16) | ||
| 110 | + .fontColor(0xFF000000) | ||
| 111 | + } | ||
| 112 | + .alignItems(HorizontalAlign.Start) | ||
| 113 | + .padding(left: 5, top: 5) | ||
| 114 | + } | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + func build() { | ||
| 119 | + Column(space: 5) { | ||
| 120 | + Flex(justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center) { | ||
| 121 | + Text(@r(app.string.shop_title)) | ||
| 122 | + .fontSize(30) | ||
| 123 | + .fontWeight(FontWeight.Bold) | ||
| 124 | + .margin(left: 15) | ||
| 125 | + | ||
| 126 | + Row() { | ||
| 127 | + Image(@r(app.media.input_search)) | ||
| 128 | + .width(20) | ||
| 129 | + .height(20) | ||
| 130 | + Image(@r(app.media.startIcon)) | ||
| 131 | + .width(20) | ||
| 132 | + .height(20) | ||
| 133 | + } | ||
| 134 | + .margin(right: 15) | ||
| 135 | + } | ||
| 136 | + .margin(bottom: 10) | ||
| 137 | + | ||
| 138 | + // 对齐 ArkTS 模板:顶部安全区 | ||
| 139 | + .expandSafeArea(types: [SafeAreaType.System], edges: [SafeAreaEdge.Top]) | ||
| 140 | + | ||
| 141 | + Swiper(controller: this.swiperController) { | ||
| 142 | + Stack(alignContent: Alignment.Center) { | ||
| 143 | + Image(@r(app.media.bread)) | ||
| 144 | + .width(100.percent) | ||
| 145 | + .height(100.percent) | ||
| 146 | + Text("持久饱腹满足感") | ||
| 147 | + .fontSize(30) | ||
| 148 | + .fontWeight(FontWeight.W500) | ||
| 149 | + .fontColor(Color.White) | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + Text("0") | ||
| 153 | + .width(90.percent) | ||
| 154 | + .height(100.percent) | ||
| 155 | + .backgroundColor(Color.Gray) | ||
| 156 | + .textAlign(TextAlign.Center) | ||
| 157 | + .fontSize(30) | ||
| 158 | + | ||
| 159 | + Text("1") | ||
| 160 | + .width(90.percent) | ||
| 161 | + .height(100.percent) | ||
| 162 | + .backgroundColor(Color.Green) | ||
| 163 | + .textAlign(TextAlign.Center) | ||
| 164 | + .fontSize(30) | ||
| 165 | + | ||
| 166 | + Text("2") | ||
| 167 | + .width(90.percent) | ||
| 168 | + .height(100.percent) | ||
| 169 | + .backgroundColor(0xFFFFC0CB) | ||
| 170 | + .textAlign(TextAlign.Center) | ||
| 171 | + .fontSize(30) | ||
| 172 | + } | ||
| 173 | + .loop(true) | ||
| 174 | + .width(95.percent) | ||
| 175 | + .height(20.percent) | ||
| 176 | + .borderRadius(10) | ||
| 177 | + | ||
| 178 | + List(space: 5) { | ||
| 179 | + ForEach( | ||
| 180 | + this.categoryTitles, | ||
| 181 | + itemGeneratorFunc: { item: String, idx: Int64 => | ||
| 182 | + ListItem() { | ||
| 183 | + Column(space: 5) { | ||
| 184 | + Stack() { | ||
| 185 | + Image(@r(app.media.tang1)) | ||
| 186 | + .width(50) | ||
| 187 | + .height(50) | ||
| 188 | + .borderRadius(10) | ||
| 189 | + Text(this.categoryChars[idx.position()]) | ||
| 190 | + .fontSize(40) | ||
| 191 | + .fontColor(Color.White) | ||
| 192 | + } | ||
| 193 | + Text(item) | ||
| 194 | + .fontSize(14) | ||
| 195 | + } | ||
| 196 | + .alignItems(HorizontalAlign.Center) | ||
| 197 | + } | ||
| 198 | + }, | ||
| 199 | + keyGeneratorFunc: { item: String, idx: Int64 => return "${idx}_${item}" } | ||
| 200 | + ) | ||
| 201 | + } | ||
| 202 | + .width(90.percent) | ||
| 203 | + .lanes(5) | ||
| 204 | + | ||
| 205 | + // Replace Grid with scrollable 2-column masonry-like layout to match ArkTS WaterFlow placement & scrolling | ||
| 206 | + Scroll() { | ||
| 207 | + Row(space: 10) { | ||
| 208 | + Column(space: 5) { | ||
| 209 | + this.recommendCard(0) | ||
| 210 | + this.recommendCard(3) | ||
| 211 | + this.recommendCard(5) | ||
| 212 | + } | ||
| 213 | + .width(0) | ||
| 214 | + .layoutWeight(1) | ||
| 215 | + | ||
| 216 | + Column(space: 5) { | ||
| 217 | + this.recommendCard(1) | ||
| 218 | + this.recommendCard(2) | ||
| 219 | + this.recommendCard(4) | ||
| 220 | + this.recommendCard(6) | ||
| 221 | + } | ||
| 222 | + .width(0) | ||
| 223 | + .layoutWeight(1) | ||
| 224 | + } | ||
| 225 | + .width(100.percent) | ||
| 226 | + } | ||
| 227 | + .width(95.percent) | ||
| 228 | + .scrollBar(BarState.Off) | ||
| 229 | + .layoutWeight(1) | ||
| 230 | + } | ||
| 231 | + .width(100.percent) | ||
| 232 | + .height(100.percent) | ||
| 233 | + .padding(bottom: getUIContext().px2vp(this.bottomRectHeightPx.px) ?? 0.vp) | ||
| 234 | + .margin(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 235 | + } | ||
| 236 | +} | ||
| @@ -0,0 +1,1421 @@ | |||
| 1 | +package ohos_app_cangjie_entry.pages | ||
| 2 | + | ||
| 3 | +import kit.ArkUI.* | ||
| 4 | +import kit.LocalizationKit.* | ||
| 5 | +import ohos.arkui.state_macro_manage.* | ||
| 6 | +import ohos.web.webview.* | ||
| 7 | +import kit.AbilityKit.{AbilityAccessCtrl, BundleManager, BundleFlag, GrantStatus, Permissions} | ||
| 8 | +import std.sync.* | ||
| 9 | + | ||
| 10 | +@Component | ||
| 11 | +public class SettingsPage { | ||
| 12 | + @Consume var stack: NavPathStack | ||
| 13 | + | ||
| 14 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 15 | + | ||
| 16 | + func build() { | ||
| 17 | + NavDestination() { | ||
| 18 | + Column() { | ||
| 19 | + Scroll() { | ||
| 20 | + List() { | ||
| 21 | + ForEach( | ||
| 22 | + [ | ||
| 23 | + @r(app.string.set_count_danger), | ||
| 24 | + @r(app.string.count_style), | ||
| 25 | + @r(app.string.lanyan_style), | ||
| 26 | + @r(app.string.third_about), | ||
| 27 | + @r(app.string.user_personal_style), | ||
| 28 | + @r(app.string.back_count_style) | ||
| 29 | + ], | ||
| 30 | + itemGeneratorFunc: { title: AppResource, idx: Int64 => | ||
| 31 | + ListItem() { | ||
| 32 | + Column() { | ||
| 33 | + Flex(justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center) { | ||
| 34 | + Text(title) | ||
| 35 | + .fontSize(16) | ||
| 36 | + .height(40) | ||
| 37 | + Image(@r(app.media.ic_right_grey)) | ||
| 38 | + .objectFit(ImageFit.Contain) | ||
| 39 | + .height(12) | ||
| 40 | + .width(7) | ||
| 41 | + } | ||
| 42 | + .height(48) | ||
| 43 | + .onClick({ _: ClickEvent => | ||
| 44 | + if (idx.position() == 3) { | ||
| 45 | + this.stack.pushPathByName("common_new", "") | ||
| 46 | + } else if (idx.position() == 4) { | ||
| 47 | + this.stack.pushPathByName("secret", "") | ||
| 48 | + } | ||
| 49 | + }) | ||
| 50 | + | ||
| 51 | + Row() {} | ||
| 52 | + .height(1) | ||
| 53 | + .width(100.percent) | ||
| 54 | + .backgroundColor(@r(app.color.borderColor)) | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + .backgroundColor(Color.White) | ||
| 58 | + .margin(left: 24, right: 24) | ||
| 59 | + }, | ||
| 60 | + keyGeneratorFunc: { title: AppResource, idx: Int64 => return "${idx}" } | ||
| 61 | + ) | ||
| 62 | + } | ||
| 63 | + .backgroundColor(Color.White) | ||
| 64 | + .clip(true) | ||
| 65 | + } | ||
| 66 | + .scrollBar(BarState.Off) | ||
| 67 | + } | ||
| 68 | + .width(100.percent) | ||
| 69 | + .height(100.percent) | ||
| 70 | + } | ||
| 71 | + .title("设置") | ||
| 72 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 73 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 74 | + .onBackPressed({ => | ||
| 75 | + this.stack.pop() | ||
| 76 | + return true | ||
| 77 | + }) | ||
| 78 | + } | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +@Component | ||
| 82 | +public class AddDevicePage { | ||
| 83 | + @Consume var stack: NavPathStack | ||
| 84 | + | ||
| 85 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 86 | + | ||
| 87 | + // Align with ArkTS template AddDeviceDemoOne: show connecting state | ||
| 88 | + @State var connectingIdx: Int64 = -1 | ||
| 89 | + | ||
| 90 | + // Align with ArkTS template AddDeviceDemoOne behavior: a list of discovered devices | ||
| 91 | + // (Cangjie demo: mock list; keep UI & click behavior consistent) | ||
| 92 | + @State var foundDeviceNames: Array<String> = [] | ||
| 93 | + private var deviceBgs: Array<AppResource> = [ | ||
| 94 | + @r(app.media.img_26), | ||
| 95 | + @r(app.media.img_27), | ||
| 96 | + @r(app.media.img_28), | ||
| 97 | + @r(app.media.img_29), | ||
| 98 | + @r(app.media.img_30) | ||
| 99 | + ] | ||
| 100 | + | ||
| 101 | + @State var connectedIdx: Int64 = -1 | ||
| 102 | + | ||
| 103 | + @State var bluetoothGranted: Bool = true | ||
| 104 | + | ||
| 105 | + func refreshBluetoothPermissionState(): Unit { | ||
| 106 | + try { | ||
| 107 | + let tokenID = BundleManager.getBundleInfoForSelf(BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION) | ||
| 108 | + .appInfo.accessTokenId | ||
| 109 | + let atManager = AbilityAccessCtrl.createAtManager() | ||
| 110 | + let p: Permissions = "ohos.permission.ACCESS_BLUETOOTH" | ||
| 111 | + this.bluetoothGranted = (atManager.checkAccessToken(tokenID, p) != GrantStatus.PermissionDenied) | ||
| 112 | + } catch (e: Exception) { | ||
| 113 | + this.bluetoothGranted = false | ||
| 114 | + } | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + func ensureMockScanList() { | ||
| 118 | + if (this.foundDeviceNames.size > 0) { | ||
| 119 | + return | ||
| 120 | + } | ||
| 121 | + this.foundDeviceNames = [ | ||
| 122 | + "BLE Device A", | ||
| 123 | + "BLE Device B", | ||
| 124 | + "BLE Device C", | ||
| 125 | + "BLE Device D", | ||
| 126 | + "BLE Device E" | ||
| 127 | + ] | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + func build() { | ||
| 131 | + NavDestination() { | ||
| 132 | + Column() { | ||
| 133 | + Image(@r(app.media.img_25)) | ||
| 134 | + .width(140) | ||
| 135 | + .height(48) | ||
| 136 | + .margin(top: 16, left: -220) | ||
| 137 | + | ||
| 138 | + if (!this.bluetoothGranted) { | ||
| 139 | + Text("未授予蓝牙权限,无法连接设备") | ||
| 140 | + .fontSize(12) | ||
| 141 | + .opacity(0.7) | ||
| 142 | + .margin(top: 8, left: 24) | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + Scroll() { | ||
| 146 | + Column() { | ||
| 147 | + ForEach( | ||
| 148 | + this.foundDeviceNames, | ||
| 149 | + itemGeneratorFunc: { name: String, idx: Int64 => | ||
| 150 | + Stack(alignContent: Alignment.TopStart) { | ||
| 151 | + Image(this.deviceBgs[(idx.position() % Int64(this.deviceBgs.size)).position()]) | ||
| 152 | + .width(340) | ||
| 153 | + .height(64) | ||
| 154 | + .margin(top: 12, left: 10) | ||
| 155 | + | ||
| 156 | + Row() {} | ||
| 157 | + .width(200) | ||
| 158 | + .height(40) | ||
| 159 | + .backgroundColor(Color.White) | ||
| 160 | + .margin(top: 30, left: 60) | ||
| 161 | + | ||
| 162 | + Text(name) | ||
| 163 | + .fontSize(16) | ||
| 164 | + .fontWeight(FontWeight.Bold) | ||
| 165 | + .margin(top: 35, left: 65) | ||
| 166 | + | ||
| 167 | + Row() {} | ||
| 168 | + .backgroundColor(0xFFF3F3F3) | ||
| 169 | + .width(80) | ||
| 170 | + .height(31) | ||
| 171 | + .margin(top: 30, left: 260) | ||
| 172 | + .borderRadius(20) | ||
| 173 | + | ||
| 174 | + if (this.connectedIdx == idx.position()) { | ||
| 175 | + Text("已连接") | ||
| 176 | + .fontSize(14) | ||
| 177 | + .fontWeight(FontWeight.Bold) | ||
| 178 | + .fontColor(0xFFFB6522) | ||
| 179 | + .margin(top: 37, left: 278) | ||
| 180 | + .onClick({ _: ClickEvent => | ||
| 181 | + this.connectedIdx = -1 | ||
| 182 | + }) | ||
| 183 | + } else { | ||
| 184 | + if (this.connectingIdx == idx.position()) { | ||
| 185 | + // Cangjie demo: no BLE API; keep a manual connecting state. | ||
| 186 | + LoadingProgress() | ||
| 187 | + .height(40) | ||
| 188 | + .width(50) | ||
| 189 | + .margin(top: 27, left: 275) | ||
| 190 | + | ||
| 191 | + Row() {} | ||
| 192 | + .width(100) | ||
| 193 | + .height(50) | ||
| 194 | + .backgroundColor(Color.White) | ||
| 195 | + .margin(top: 20, left: 260) | ||
| 196 | + .opacity(0.0) | ||
| 197 | + .onClick({ _: ClickEvent => | ||
| 198 | + this.connectingIdx = -1 | ||
| 199 | + this.connectedIdx = idx.position() | ||
| 200 | + }) | ||
| 201 | + } else { | ||
| 202 | + Text("连接") | ||
| 203 | + .fontSize(14) | ||
| 204 | + .fontWeight(FontWeight.Bold) | ||
| 205 | + .fontColor(0xFFFB6522) | ||
| 206 | + .margin(top: 37, left: 285) | ||
| 207 | + .onClick({ _: ClickEvent => | ||
| 208 | + if (!this.bluetoothGranted) { | ||
| 209 | + return | ||
| 210 | + } | ||
| 211 | + this.connectingIdx = idx.position() | ||
| 212 | + }) | ||
| 213 | + } | ||
| 214 | + } | ||
| 215 | + } | ||
| 216 | + .margin(left: -30) | ||
| 217 | + }, | ||
| 218 | + keyGeneratorFunc: { name: String, idx: Int64 => return "${idx}_${name}" } | ||
| 219 | + ) | ||
| 220 | + | ||
| 221 | + Stack() { | ||
| 222 | + Image(@r(app.media.img_33)) | ||
| 223 | + .width(340) | ||
| 224 | + .height(40) | ||
| 225 | + .margin(top: 64, left: 10) | ||
| 226 | + Row() {} | ||
| 227 | + .width(100) | ||
| 228 | + .height(50) | ||
| 229 | + .backgroundColor(Color.White) | ||
| 230 | + .margin(top: 32, left: 240) | ||
| 231 | + .opacity(0.0) | ||
| 232 | + } | ||
| 233 | + } | ||
| 234 | + } | ||
| 235 | + .scrollBar(BarState.Off) | ||
| 236 | + } | ||
| 237 | + .width(100.percent) | ||
| 238 | + .height(100.percent) | ||
| 239 | + } | ||
| 240 | + .title("添加设备") | ||
| 241 | + // Align with ArkTS template: fixed top padding. | ||
| 242 | + .padding(top: 30) | ||
| 243 | + .backgroundColor(0xFFF1F3F5) | ||
| 244 | + .onAppear({ => | ||
| 245 | + this.refreshBluetoothPermissionState() | ||
| 246 | + this.ensureMockScanList() | ||
| 247 | + }) | ||
| 248 | + .onBackPressed({ => | ||
| 249 | + this.stack.pop() | ||
| 250 | + return true | ||
| 251 | + }) | ||
| 252 | + } | ||
| 253 | +} | ||
| 254 | + | ||
| 255 | +@Component | ||
| 256 | +public class MyDevicesPage { | ||
| 257 | + @Consume var stack: NavPathStack | ||
| 258 | + | ||
| 259 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 260 | + | ||
| 261 | + func build() { | ||
| 262 | + NavDestination() { | ||
| 263 | + Column() { | ||
| 264 | + Stack() { | ||
| 265 | + Image(@r(app.media.img_19)) | ||
| 266 | + .width(350) | ||
| 267 | + .margin(left: 0) | ||
| 268 | + Row() {} | ||
| 269 | + .width(50) | ||
| 270 | + .height(50) | ||
| 271 | + .backgroundColor(Color.White) | ||
| 272 | + .margin(left: -300) | ||
| 273 | + .opacity(0.0) | ||
| 274 | + .onClick({ _: ClickEvent => | ||
| 275 | + this.stack.pop() | ||
| 276 | + }) | ||
| 277 | + } | ||
| 278 | + .margin(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 279 | + | ||
| 280 | + Row() { | ||
| 281 | + Image(@r(app.media.img_20)) | ||
| 282 | + .width(200) | ||
| 283 | + } | ||
| 284 | + .margin(top: 39) | ||
| 285 | + | ||
| 286 | + Image(@r(app.media.img_21)) | ||
| 287 | + .width(219.66) | ||
| 288 | + .height(260) | ||
| 289 | + .margin(top: 52) | ||
| 290 | + | ||
| 291 | + Stack() { | ||
| 292 | + Image(@r(app.media.img_22)) | ||
| 293 | + .width(192) | ||
| 294 | + .height(40) | ||
| 295 | + | ||
| 296 | + // Invisible but addressable click target for UI automation. | ||
| 297 | + Text("添加设备") | ||
| 298 | + .width(192) | ||
| 299 | + .height(40) | ||
| 300 | + .opacity(0.0) | ||
| 301 | + .onClick({ _: ClickEvent => | ||
| 302 | + this.stack.pushPathByName("device_add", "") | ||
| 303 | + }) | ||
| 304 | + } | ||
| 305 | + .margin(top: 48) | ||
| 306 | + | ||
| 307 | + Image(@r(app.media.img_23)) | ||
| 308 | + .width(328) | ||
| 309 | + .height(56) | ||
| 310 | + .margin(top: 24) | ||
| 311 | + } | ||
| 312 | + .height(100.percent) | ||
| 313 | + .width(100.percent) | ||
| 314 | + .backgroundColor(0xFFF1F3F5) | ||
| 315 | + } | ||
| 316 | + .hideTitleBar(true) | ||
| 317 | + .onBackPressed({ => | ||
| 318 | + this.stack.pop() | ||
| 319 | + return true | ||
| 320 | + }) | ||
| 321 | + } | ||
| 322 | +} | ||
| 323 | + | ||
| 324 | +@Component | ||
| 325 | +public class DeviceListPage { | ||
| 326 | + @Consume var stack: NavPathStack | ||
| 327 | + | ||
| 328 | + func build() { | ||
| 329 | + NavDestination() { | ||
| 330 | + Column() { | ||
| 331 | + Column() { | ||
| 332 | + Stack() { | ||
| 333 | + Image(@r(app.media.img_19)) | ||
| 334 | + .width(328) | ||
| 335 | + .height(56) | ||
| 336 | + .margin(left: 0) | ||
| 337 | + Row() {} | ||
| 338 | + .width(50) | ||
| 339 | + .height(50) | ||
| 340 | + .backgroundColor(Color.White) | ||
| 341 | + .margin(left: -300) | ||
| 342 | + .opacity(0.0) | ||
| 343 | + .onClick({ _: ClickEvent => | ||
| 344 | + this.stack.pop() | ||
| 345 | + }) | ||
| 346 | + } | ||
| 347 | + .margin(top: 30) | ||
| 348 | + | ||
| 349 | + Image(@r(app.media.img_25)) | ||
| 350 | + .width(140) | ||
| 351 | + .height(48) | ||
| 352 | + .margin(top: 16, left: -1) | ||
| 353 | + | ||
| 354 | + Stack() { | ||
| 355 | + Image(@r(app.media.img_26)) | ||
| 356 | + .width(340) | ||
| 357 | + .height(64) | ||
| 358 | + .margin(top: 12, left: 10) | ||
| 359 | + Row() {} | ||
| 360 | + .width(100) | ||
| 361 | + .height(50) | ||
| 362 | + .backgroundColor(Color.White) | ||
| 363 | + .margin(top: 28, left: 240) | ||
| 364 | + .opacity(0.0) | ||
| 365 | + .onClick({ _: ClickEvent => | ||
| 366 | + this.stack.pushPathByName("device_add", "") | ||
| 367 | + }) | ||
| 368 | + } | ||
| 369 | + | ||
| 370 | + Stack() { | ||
| 371 | + Image(@r(app.media.img_27)) | ||
| 372 | + .width(340) | ||
| 373 | + .height(64) | ||
| 374 | + .margin(top: 12, left: 10) | ||
| 375 | + Row() {} | ||
| 376 | + .width(100) | ||
| 377 | + .height(50) | ||
| 378 | + .backgroundColor(Color.White) | ||
| 379 | + .margin(top: 28, left: 240) | ||
| 380 | + .opacity(0.0) | ||
| 381 | + .onClick({ _: ClickEvent => | ||
| 382 | + this.stack.pushPathByName("device_add", "") | ||
| 383 | + }) | ||
| 384 | + } | ||
| 385 | + | ||
| 386 | + Stack() { | ||
| 387 | + Image(@r(app.media.img_28)) | ||
| 388 | + .width(340) | ||
| 389 | + .height(64) | ||
| 390 | + .margin(top: 12, left: 10) | ||
| 391 | + Row() {} | ||
| 392 | + .width(100) | ||
| 393 | + .height(50) | ||
| 394 | + .backgroundColor(Color.White) | ||
| 395 | + .margin(top: 28, left: 240) | ||
| 396 | + .opacity(0.0) | ||
| 397 | + .onClick({ _: ClickEvent => | ||
| 398 | + this.stack.pushPathByName("device_add", "") | ||
| 399 | + }) | ||
| 400 | + } | ||
| 401 | + | ||
| 402 | + Stack() { | ||
| 403 | + Image(@r(app.media.img_29)) | ||
| 404 | + .width(340) | ||
| 405 | + .height(64) | ||
| 406 | + .margin(top: 12, left: 10) | ||
| 407 | + Row() {} | ||
| 408 | + .width(100) | ||
| 409 | + .height(50) | ||
| 410 | + .backgroundColor(Color.White) | ||
| 411 | + .margin(top: 28, left: 240) | ||
| 412 | + .opacity(0.0) | ||
| 413 | + .onClick({ _: ClickEvent => | ||
| 414 | + this.stack.pushPathByName("device_add", "") | ||
| 415 | + }) | ||
| 416 | + } | ||
| 417 | + | ||
| 418 | + Stack() { | ||
| 419 | + Image(@r(app.media.img_30)) | ||
| 420 | + .width(340) | ||
| 421 | + .height(64) | ||
| 422 | + .margin(top: 12, left: 10) | ||
| 423 | + Row() {} | ||
| 424 | + .width(100) | ||
| 425 | + .height(50) | ||
| 426 | + .backgroundColor(Color.White) | ||
| 427 | + .margin(top: 28, left: 240) | ||
| 428 | + .opacity(0.0) | ||
| 429 | + .onClick({ _: ClickEvent => | ||
| 430 | + this.stack.pushPathByName("device_add", "") | ||
| 431 | + }) | ||
| 432 | + } | ||
| 433 | + | ||
| 434 | + Stack() { | ||
| 435 | + Image(@r(app.media.img_31)) | ||
| 436 | + .width(340) | ||
| 437 | + .height(64) | ||
| 438 | + .margin(top: 12, left: 10) | ||
| 439 | + Row() {} | ||
| 440 | + .width(100) | ||
| 441 | + .height(50) | ||
| 442 | + .backgroundColor(Color.White) | ||
| 443 | + .margin(top: 28, left: 240) | ||
| 444 | + .opacity(0.0) | ||
| 445 | + .onClick({ _: ClickEvent => | ||
| 446 | + this.stack.pushPathByName("device_add", "") | ||
| 447 | + }) | ||
| 448 | + } | ||
| 449 | + | ||
| 450 | + Stack() { | ||
| 451 | + Image(@r(app.media.img_33)) | ||
| 452 | + .width(340) | ||
| 453 | + .height(40) | ||
| 454 | + .margin(top: 84, left: 10) | ||
| 455 | + } | ||
| 456 | + } | ||
| 457 | + .alignItems(HorizontalAlign.Start) | ||
| 458 | + .width(90.percent) | ||
| 459 | + } | ||
| 460 | + .height(100.percent) | ||
| 461 | + .width(100.percent) | ||
| 462 | + .backgroundColor(0xFFF1F3F5) | ||
| 463 | + } | ||
| 464 | + .hideTitleBar(true) | ||
| 465 | + .onBackPressed({ => | ||
| 466 | + this.stack.pop() | ||
| 467 | + return true | ||
| 468 | + }) | ||
| 469 | + } | ||
| 470 | +} | ||
| 471 | + | ||
| 472 | +@Component | ||
| 473 | +public class MyselfPage { | ||
| 474 | + @Consume var stack: NavPathStack | ||
| 475 | + | ||
| 476 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 477 | + | ||
| 478 | + func build() { | ||
| 479 | + NavDestination() { | ||
| 480 | + Scroll() { | ||
| 481 | + List() { | ||
| 482 | + ForEach( | ||
| 483 | + [ | ||
| 484 | + @r(app.string.third_about), | ||
| 485 | + @r(app.string.user_personal_style) | ||
| 486 | + ], | ||
| 487 | + itemGeneratorFunc: { title: AppResource, idx: Int64 => | ||
| 488 | + ListItem() { | ||
| 489 | + Column() { | ||
| 490 | + Flex(justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center) { | ||
| 491 | + Text(title) | ||
| 492 | + .fontSize(16) | ||
| 493 | + .height(40) | ||
| 494 | + Image(@r(app.media.ic_right_grey)) | ||
| 495 | + .objectFit(ImageFit.Contain) | ||
| 496 | + .height(12) | ||
| 497 | + .width(7) | ||
| 498 | + } | ||
| 499 | + .height(48) | ||
| 500 | + .onClick({ _: ClickEvent => | ||
| 501 | + if (idx.position() == 0) { | ||
| 502 | + this.stack.pushPathByName("common_new", "") | ||
| 503 | + } else if (idx.position() == 1) { | ||
| 504 | + this.stack.pushPathByName("secret", "") | ||
| 505 | + } | ||
| 506 | + }) | ||
| 507 | + | ||
| 508 | + Row() {} | ||
| 509 | + .height(1) | ||
| 510 | + .width(100.percent) | ||
| 511 | + .backgroundColor(@r(app.color.borderColor)) | ||
| 512 | + } | ||
| 513 | + } | ||
| 514 | + .backgroundColor(Color.White) | ||
| 515 | + .margin(left: 24, right: 24) | ||
| 516 | + }, | ||
| 517 | + keyGeneratorFunc: { title: AppResource, idx: Int64 => return "${idx}" } | ||
| 518 | + ) | ||
| 519 | + } | ||
| 520 | + .backgroundColor(Color.White) | ||
| 521 | + .clip(true) | ||
| 522 | + } | ||
| 523 | + .scrollBar(BarState.Off) | ||
| 524 | + .width(100.percent) | ||
| 525 | + .height(100.percent) | ||
| 526 | + } | ||
| 527 | + .title("隐私政策") | ||
| 528 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 529 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 530 | + .onBackPressed({ => | ||
| 531 | + this.stack.pop() | ||
| 532 | + return true | ||
| 533 | + }) | ||
| 534 | + } | ||
| 535 | +} | ||
| 536 | + | ||
| 537 | +@Component | ||
| 538 | +public class MessagePage { | ||
| 539 | + @Consume var stack: NavPathStack | ||
| 540 | + | ||
| 541 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 542 | + | ||
| 543 | + @State var currentIndex: Int32 = 0 | ||
| 544 | + | ||
| 545 | + var titles: Array<String> = ["个人消息", "客服消息"] | ||
| 546 | + | ||
| 547 | + func build() { | ||
| 548 | + NavDestination() { | ||
| 549 | + Column() { | ||
| 550 | + Tabs(barPosition: BarPosition.Start, index: this.currentIndex) { | ||
| 551 | + TabContent() { | ||
| 552 | + List(space: 5) { | ||
| 553 | + ListItem() { | ||
| 554 | + Row() { | ||
| 555 | + Row(space: 10) { | ||
| 556 | + Image(@r(app.media.app_icon)) | ||
| 557 | + .width(40) | ||
| 558 | + Column(space: 5) { | ||
| 559 | + Flex(justifyContent: FlexAlign.SpaceBetween) { | ||
| 560 | + Text("健康运动通知") | ||
| 561 | + Text("2021-06-08") | ||
| 562 | + .fontSize(12) | ||
| 563 | + } | ||
| 564 | + .width(80.percent) | ||
| 565 | + Text("欢迎来到健康运动") | ||
| 566 | + .fontSize(12) | ||
| 567 | + } | ||
| 568 | + .alignItems(HorizontalAlign.Start) | ||
| 569 | + } | ||
| 570 | + .margin(left: 20) | ||
| 571 | + } | ||
| 572 | + .width(100.percent) | ||
| 573 | + } | ||
| 574 | + .backgroundColor(Color.White) | ||
| 575 | + .width(100.percent) | ||
| 576 | + .height(70) | ||
| 577 | + | ||
| 578 | + ListItem() { | ||
| 579 | + Row() { | ||
| 580 | + Row(space: 10) { | ||
| 581 | + Image(@r(app.media.app_icon)) | ||
| 582 | + .width(40) | ||
| 583 | + Column(space: 5) { | ||
| 584 | + Flex(justifyContent: FlexAlign.SpaceBetween) { | ||
| 585 | + Text("监控通知") | ||
| 586 | + Text("2021-06-08") | ||
| 587 | + .fontSize(12) | ||
| 588 | + } | ||
| 589 | + .width(80.percent) | ||
| 590 | + Text("欢迎来到健康运动") | ||
| 591 | + .fontSize(12) | ||
| 592 | + } | ||
| 593 | + .alignItems(HorizontalAlign.Start) | ||
| 594 | + } | ||
| 595 | + .margin(left: 20) | ||
| 596 | + } | ||
| 597 | + .width(100.percent) | ||
| 598 | + } | ||
| 599 | + .backgroundColor(Color.White) | ||
| 600 | + .width(100.percent) | ||
| 601 | + .height(70) | ||
| 602 | + | ||
| 603 | + ListItem() { | ||
| 604 | + Row() { | ||
| 605 | + Row(space: 10) { | ||
| 606 | + Image(@r(app.media.app_icon)) | ||
| 607 | + .width(40) | ||
| 608 | + Column(space: 5) { | ||
| 609 | + Flex(justifyContent: FlexAlign.SpaceBetween) { | ||
| 610 | + Text("健康通知") | ||
| 611 | + Text("2021-06-08") | ||
| 612 | + .fontSize(12) | ||
| 613 | + } | ||
| 614 | + .width(80.percent) | ||
| 615 | + Text("欢迎来到健康运动") | ||
| 616 | + .fontSize(12) | ||
| 617 | + } | ||
| 618 | + .alignItems(HorizontalAlign.Start) | ||
| 619 | + } | ||
| 620 | + .margin(left: 20) | ||
| 621 | + } | ||
| 622 | + .width(100.percent) | ||
| 623 | + } | ||
| 624 | + .backgroundColor(Color.White) | ||
| 625 | + .width(100.percent) | ||
| 626 | + .height(70) | ||
| 627 | + } | ||
| 628 | + .width(100.percent) | ||
| 629 | + .height(98.percent) | ||
| 630 | + } | ||
| 631 | + .tabBar({=> | ||
| 632 | + Column() { | ||
| 633 | + Text(this.titles[0]) | ||
| 634 | + .fontColor(if (this.currentIndex == 0) { 0xFF1698CE } else { 0xFF6B6B6B }) | ||
| 635 | + } | ||
| 636 | + }) | ||
| 637 | + .backgroundColor(@r(app.color.primaryBgColor)) | ||
| 638 | + | ||
| 639 | + TabContent() { | ||
| 640 | + List() { | ||
| 641 | + ListItem() { | ||
| 642 | + Row() { | ||
| 643 | + Row(space: 10) { | ||
| 644 | + Image(@r(app.media.app_icon)) | ||
| 645 | + .width(40) | ||
| 646 | + Column(space: 5) { | ||
| 647 | + Flex(justifyContent: FlexAlign.SpaceBetween) { | ||
| 648 | + Text("健康通知") | ||
| 649 | + Text("2021-06-08") | ||
| 650 | + .fontSize(12) | ||
| 651 | + } | ||
| 652 | + .width(80.percent) | ||
| 653 | + Text("欢迎来到健康运动") | ||
| 654 | + .fontSize(12) | ||
| 655 | + } | ||
| 656 | + .alignItems(HorizontalAlign.Start) | ||
| 657 | + } | ||
| 658 | + .margin(left: 20) | ||
| 659 | + } | ||
| 660 | + .width(100.percent) | ||
| 661 | + } | ||
| 662 | + .backgroundColor(Color.White) | ||
| 663 | + .width(100.percent) | ||
| 664 | + .height(70) | ||
| 665 | + } | ||
| 666 | + .width(100.percent) | ||
| 667 | + .height(100.percent) | ||
| 668 | + .margin(top: 10) | ||
| 669 | + } | ||
| 670 | + .tabBar({=> | ||
| 671 | + Column() { | ||
| 672 | + Text(this.titles[1]) | ||
| 673 | + .fontColor(if (this.currentIndex == 1) { 0xFF1698CE } else { 0xFF6B6B6B }) | ||
| 674 | + } | ||
| 675 | + }) | ||
| 676 | + .backgroundColor(@r(app.color.primaryBgColor)) | ||
| 677 | + } | ||
| 678 | + .animationDuration(Some(0.0)) | ||
| 679 | + .backgroundColor(Color.White) | ||
| 680 | + .onChange({ index: Int32 => | ||
| 681 | + this.currentIndex = index | ||
| 682 | + }) | ||
| 683 | + } | ||
| 684 | + .width(100.percent) | ||
| 685 | + .height(100.percent) | ||
| 686 | + .backgroundColor(0xFFF3F2F7) | ||
| 687 | + } | ||
| 688 | + .title("消息中心") | ||
| 689 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 690 | + .onBackPressed({ => | ||
| 691 | + this.stack.pop() | ||
| 692 | + return true | ||
| 693 | + }) | ||
| 694 | + } | ||
| 695 | +} | ||
| 696 | + | ||
| 697 | +@Component | ||
| 698 | +public class CheckUpdatesPage { | ||
| 699 | + @Consume var stack: NavPathStack | ||
| 700 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 701 | + | ||
| 702 | + func build() { | ||
| 703 | + NavDestination() { | ||
| 704 | + Column() { | ||
| 705 | + Text("当前已是最新版本") | ||
| 706 | + .fontSize(16) | ||
| 707 | + .fontColor(0xFF333333) | ||
| 708 | + Text("无可用更新") | ||
| 709 | + .fontSize(12) | ||
| 710 | + .opacity(0.6) | ||
| 711 | + .margin(top: 8) | ||
| 712 | + } | ||
| 713 | + .width(100.percent) | ||
| 714 | + .height(100.percent) | ||
| 715 | + .justifyContent(FlexAlign.Center) | ||
| 716 | + } | ||
| 717 | + .title("检查更新") | ||
| 718 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 719 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 720 | + .onBackPressed({ => | ||
| 721 | + this.stack.pop() | ||
| 722 | + return true | ||
| 723 | + }) | ||
| 724 | + } | ||
| 725 | +} | ||
| 726 | + | ||
| 727 | +@Component | ||
| 728 | +public class UserPolicyPage { | ||
| 729 | + @Consume var stack: NavPathStack | ||
| 730 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 731 | + | ||
| 732 | + func build() { | ||
| 733 | + NavDestination() { | ||
| 734 | + Scroll() { | ||
| 735 | + Column(space: 12) { | ||
| 736 | + Text("用户协议") | ||
| 737 | + .fontSize(18) | ||
| 738 | + .fontWeight(FontWeight.W600) | ||
| 739 | + Text("本页面为示例占位,用于保证路由完整与运行不崩溃。") | ||
| 740 | + .fontSize(14) | ||
| 741 | + .opacity(0.7) | ||
| 742 | + Text("后续可替换为真实协议内容或加载 Web 页面。") | ||
| 743 | + .fontSize(14) | ||
| 744 | + .opacity(0.7) | ||
| 745 | + } | ||
| 746 | + .padding(24) | ||
| 747 | + .width(100.percent) | ||
| 748 | + } | ||
| 749 | + .scrollBar(BarState.Off) | ||
| 750 | + } | ||
| 751 | + .title("用户协议") | ||
| 752 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 753 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 754 | + .onBackPressed({ => | ||
| 755 | + this.stack.pop() | ||
| 756 | + return true | ||
| 757 | + }) | ||
| 758 | + } | ||
| 759 | +} | ||
| 760 | + | ||
| 761 | +@Component | ||
| 762 | +public class PrivacyPolicyPage { | ||
| 763 | + @Consume var stack: NavPathStack | ||
| 764 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 765 | + | ||
| 766 | + func build() { | ||
| 767 | + NavDestination() { | ||
| 768 | + Scroll() { | ||
| 769 | + Column(space: 12) { | ||
| 770 | + Text("隐私政策") | ||
| 771 | + .fontSize(18) | ||
| 772 | + .fontWeight(FontWeight.W600) | ||
| 773 | + Text("本页面为示例占位,用于保证路由完整与运行不崩溃。") | ||
| 774 | + .fontSize(14) | ||
| 775 | + .opacity(0.7) | ||
| 776 | + Text("后续可替换为真实隐私政策内容或加载 Web 页面。") | ||
| 777 | + .fontSize(14) | ||
| 778 | + .opacity(0.7) | ||
| 779 | + } | ||
| 780 | + .padding(24) | ||
| 781 | + .width(100.percent) | ||
| 782 | + } | ||
| 783 | + .scrollBar(BarState.Off) | ||
| 784 | + } | ||
| 785 | + .title("隐私政策") | ||
| 786 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 787 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 788 | + .onBackPressed({ => | ||
| 789 | + this.stack.pop() | ||
| 790 | + return true | ||
| 791 | + }) | ||
| 792 | + } | ||
| 793 | +} | ||
| 794 | + | ||
| 795 | +@Component | ||
| 796 | +public class AboutPage { | ||
| 797 | + @Consume var stack: NavPathStack | ||
| 798 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 799 | + | ||
| 800 | + func build() { | ||
| 801 | + NavDestination() { | ||
| 802 | + Column(space: 10) { | ||
| 803 | + Image(@r(app.media.app_icon)) | ||
| 804 | + .width(64) | ||
| 805 | + .height(64) | ||
| 806 | + .borderRadius(14) | ||
| 807 | + Text("SportsHealth") | ||
| 808 | + .fontSize(18) | ||
| 809 | + .fontWeight(FontWeight.W600) | ||
| 810 | + Text("版本 1.0.0") | ||
| 811 | + .fontSize(12) | ||
| 812 | + .opacity(0.6) | ||
| 813 | + } | ||
| 814 | + .width(100.percent) | ||
| 815 | + .height(100.percent) | ||
| 816 | + .justifyContent(FlexAlign.Center) | ||
| 817 | + } | ||
| 818 | + .title("关于") | ||
| 819 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 820 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 821 | + .onBackPressed({ => | ||
| 822 | + this.stack.pop() | ||
| 823 | + return true | ||
| 824 | + }) | ||
| 825 | + } | ||
| 826 | +} | ||
| 827 | + | ||
| 828 | +@Component | ||
| 829 | +public class PermissionSettingsPage { | ||
| 830 | + @Consume var stack: NavPathStack | ||
| 831 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 832 | + | ||
| 833 | + func build() { | ||
| 834 | + NavDestination() { | ||
| 835 | + Column(space: 12) { | ||
| 836 | + Text("如需修改权限,请前往系统设置中手动开启。") | ||
| 837 | + .fontSize(14) | ||
| 838 | + .opacity(0.7) | ||
| 839 | + Text("提示:若曾拒绝且不再询问,应用将无法再次弹窗申请。") | ||
| 840 | + .fontSize(12) | ||
| 841 | + .opacity(0.6) | ||
| 842 | + } | ||
| 843 | + .padding(24) | ||
| 844 | + .width(100.percent) | ||
| 845 | + .height(100.percent) | ||
| 846 | + } | ||
| 847 | + .title("权限设置") | ||
| 848 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 849 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 850 | + .onBackPressed({ => | ||
| 851 | + this.stack.pop() | ||
| 852 | + return true | ||
| 853 | + }) | ||
| 854 | + } | ||
| 855 | +} | ||
| 856 | + | ||
| 857 | +@Component | ||
| 858 | +public class PushSettingsPage { | ||
| 859 | + @Consume var stack: NavPathStack | ||
| 860 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 861 | + | ||
| 862 | + @State var enablePush: Bool = true | ||
| 863 | + | ||
| 864 | + func build() { | ||
| 865 | + NavDestination() { | ||
| 866 | + Column() { | ||
| 867 | + Row() { | ||
| 868 | + Text("推送消息") | ||
| 869 | + .fontSize(16) | ||
| 870 | + Toggle(ToggleType.Switch, isOn: this.enablePush) | ||
| 871 | + .onChange({ v: Bool => | ||
| 872 | + this.enablePush = v | ||
| 873 | + }) | ||
| 874 | + } | ||
| 875 | + .width(100.percent) | ||
| 876 | + .justifyContent(FlexAlign.SpaceBetween) | ||
| 877 | + .padding(24) | ||
| 878 | + } | ||
| 879 | + .width(100.percent) | ||
| 880 | + .height(100.percent) | ||
| 881 | + } | ||
| 882 | + .title("推送消息设置") | ||
| 883 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 884 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 885 | + .onBackPressed({ => | ||
| 886 | + this.stack.pop() | ||
| 887 | + return true | ||
| 888 | + }) | ||
| 889 | + } | ||
| 890 | +} | ||
| 891 | + | ||
| 892 | +@Component | ||
| 893 | +public class CacheClearPage { | ||
| 894 | + @Consume var stack: NavPathStack | ||
| 895 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 896 | + | ||
| 897 | + @State var cleared: Bool = false | ||
| 898 | + | ||
| 899 | + func build() { | ||
| 900 | + NavDestination() { | ||
| 901 | + Column(space: 16) { | ||
| 902 | + Text(if (this.cleared) { "已清理缓存" } else { "缓存大小:--" }) | ||
| 903 | + .fontSize(14) | ||
| 904 | + .opacity(0.7) | ||
| 905 | + Button("清理") | ||
| 906 | + .width(120) | ||
| 907 | + .height(40) | ||
| 908 | + .backgroundColor(0xFFFB6522) | ||
| 909 | + .fontColor(Color.White) | ||
| 910 | + .onClick({ _: ClickEvent => | ||
| 911 | + this.cleared = true | ||
| 912 | + }) | ||
| 913 | + } | ||
| 914 | + .padding(24) | ||
| 915 | + .width(100.percent) | ||
| 916 | + .height(100.percent) | ||
| 917 | + } | ||
| 918 | + .title("缓存清除") | ||
| 919 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 920 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 921 | + .onBackPressed({ => | ||
| 922 | + this.stack.pop() | ||
| 923 | + return true | ||
| 924 | + }) | ||
| 925 | + } | ||
| 926 | +} | ||
| 927 | + | ||
| 928 | +@Component | ||
| 929 | +public class FeedbackPage { | ||
| 930 | + @Consume var stack: NavPathStack | ||
| 931 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 932 | + | ||
| 933 | + @State var text: String = "" | ||
| 934 | + @State var submitted: Bool = false | ||
| 935 | + | ||
| 936 | + func build() { | ||
| 937 | + NavDestination() { | ||
| 938 | + Column(space: 12) { | ||
| 939 | + if (this.submitted) { | ||
| 940 | + Text("已提交,感谢反馈") | ||
| 941 | + .fontSize(16) | ||
| 942 | + } else { | ||
| 943 | + TextArea(text: this.text) | ||
| 944 | + .height(160) | ||
| 945 | + .onChange({ v: String => | ||
| 946 | + this.text = v | ||
| 947 | + }) | ||
| 948 | + Button("提交") | ||
| 949 | + .width(120) | ||
| 950 | + .height(40) | ||
| 951 | + .backgroundColor(0xFFFB6522) | ||
| 952 | + .fontColor(Color.White) | ||
| 953 | + .onClick({ _: ClickEvent => | ||
| 954 | + this.submitted = true | ||
| 955 | + }) | ||
| 956 | + } | ||
| 957 | + } | ||
| 958 | + .padding(24) | ||
| 959 | + .width(100.percent) | ||
| 960 | + .height(100.percent) | ||
| 961 | + } | ||
| 962 | + .title("意见反馈") | ||
| 963 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 964 | + .backgroundColor(@r(app.color.mineBgColor)) | ||
| 965 | + .onBackPressed({ => | ||
| 966 | + this.stack.pop() | ||
| 967 | + return true | ||
| 968 | + }) | ||
| 969 | + } | ||
| 970 | +} | ||
| 971 | + | ||
| 972 | +@Component | ||
| 973 | +public class HealthReportPage { | ||
| 974 | + @Consume var stack: NavPathStack | ||
| 975 | + | ||
| 976 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 977 | + | ||
| 978 | + func build() { | ||
| 979 | + NavDestination() { | ||
| 980 | + Column() { | ||
| 981 | + List(space: 5) { | ||
| 982 | + ListItem() { | ||
| 983 | + Row() { | ||
| 984 | + Row(space: 10) { | ||
| 985 | + Image(@r(app.media.app_icon)) | ||
| 986 | + .width(40) | ||
| 987 | + Column(space: 5) { | ||
| 988 | + Flex(justifyContent: FlexAlign.SpaceBetween) { | ||
| 989 | + Text("健康周报") | ||
| 990 | + Text("2021-06-08") | ||
| 991 | + .fontSize(12) | ||
| 992 | + } | ||
| 993 | + .width(80.percent) | ||
| 994 | + } | ||
| 995 | + .alignItems(HorizontalAlign.Start) | ||
| 996 | + } | ||
| 997 | + .margin(left: 20) | ||
| 998 | + } | ||
| 999 | + .width(100.percent) | ||
| 1000 | + } | ||
| 1001 | + .backgroundColor(Color.White) | ||
| 1002 | + .width(100.percent) | ||
| 1003 | + .height(70) | ||
| 1004 | + | ||
| 1005 | + ListItem() { | ||
| 1006 | + Row() { | ||
| 1007 | + Row(space: 10) { | ||
| 1008 | + Image(@r(app.media.app_icon)) | ||
| 1009 | + .width(40) | ||
| 1010 | + Column(space: 5) { | ||
| 1011 | + Flex(justifyContent: FlexAlign.SpaceBetween) { | ||
| 1012 | + Text("健康周报") | ||
| 1013 | + Text("2021-06-08") | ||
| 1014 | + .fontSize(12) | ||
| 1015 | + } | ||
| 1016 | + .width(80.percent) | ||
| 1017 | + } | ||
| 1018 | + .alignItems(HorizontalAlign.Start) | ||
| 1019 | + } | ||
| 1020 | + .margin(left: 20) | ||
| 1021 | + } | ||
| 1022 | + .width(100.percent) | ||
| 1023 | + } | ||
| 1024 | + .backgroundColor(Color.White) | ||
| 1025 | + .width(100.percent) | ||
| 1026 | + .height(70) | ||
| 1027 | + | ||
| 1028 | + ListItem() { | ||
| 1029 | + Row() { | ||
| 1030 | + Row(space: 10) { | ||
| 1031 | + Image(@r(app.media.app_icon)) | ||
| 1032 | + .width(40) | ||
| 1033 | + Column(space: 5) { | ||
| 1034 | + Flex(justifyContent: FlexAlign.SpaceBetween) { | ||
| 1035 | + Text("健康周报") | ||
| 1036 | + Text("2021-06-08") | ||
| 1037 | + .fontSize(12) | ||
| 1038 | + } | ||
| 1039 | + .width(80.percent) | ||
| 1040 | + } | ||
| 1041 | + .alignItems(HorizontalAlign.Start) | ||
| 1042 | + } | ||
| 1043 | + .margin(left: 20) | ||
| 1044 | + } | ||
| 1045 | + .width(100.percent) | ||
| 1046 | + } | ||
| 1047 | + .backgroundColor(Color.White) | ||
| 1048 | + .width(100.percent) | ||
| 1049 | + .height(70) | ||
| 1050 | + } | ||
| 1051 | + .width(100.percent) | ||
| 1052 | + .height(98.percent) | ||
| 1053 | + .margin(top: 10) | ||
| 1054 | + } | ||
| 1055 | + .width(100.percent) | ||
| 1056 | + .height(100.percent) | ||
| 1057 | + .backgroundColor(0xFFF3F2F7) | ||
| 1058 | + } | ||
| 1059 | + .title("健康报告") | ||
| 1060 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 1061 | + .onBackPressed({ => | ||
| 1062 | + this.stack.pop() | ||
| 1063 | + return true | ||
| 1064 | + }) | ||
| 1065 | + } | ||
| 1066 | +} | ||
| 1067 | + | ||
| 1068 | +@Component | ||
| 1069 | +public class HeartMonitoringPage { | ||
| 1070 | + @Consume var stack: NavPathStack | ||
| 1071 | + | ||
| 1072 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 1073 | + | ||
| 1074 | + @State var check: Int64 = 2 | ||
| 1075 | + var time: Array<String> = ["时", "日", "周", "月", "年"] | ||
| 1076 | + | ||
| 1077 | + func build() { | ||
| 1078 | + NavDestination() { | ||
| 1079 | + Column() { | ||
| 1080 | + Column() { | ||
| 1081 | + Row() { | ||
| 1082 | + ForEach( | ||
| 1083 | + this.time, | ||
| 1084 | + itemGeneratorFunc: { item: String, idx: Int64 => | ||
| 1085 | + Text(item) | ||
| 1086 | + .borderRadius(5) | ||
| 1087 | + .width(20.percent) | ||
| 1088 | + .height(100.percent) | ||
| 1089 | + .textAlign(TextAlign.Center) | ||
| 1090 | + .backgroundColor(if (idx.position() == this.check) { Color.White } else { Color.Transparent }) | ||
| 1091 | + .onClick({ _: ClickEvent => | ||
| 1092 | + this.check = idx.position() | ||
| 1093 | + }) | ||
| 1094 | + }, | ||
| 1095 | + keyGeneratorFunc: { item: String, idx: Int64 => return "${idx}" } | ||
| 1096 | + ) | ||
| 1097 | + } | ||
| 1098 | + .width(100.percent) | ||
| 1099 | + .height(30) | ||
| 1100 | + .justifyContent(FlexAlign.SpaceBetween) | ||
| 1101 | + .backgroundColor(0xFFE6E6E7) | ||
| 1102 | + .margin(top: 10) | ||
| 1103 | + .padding(2) | ||
| 1104 | + .borderRadius(5) | ||
| 1105 | + | ||
| 1106 | + Text("今天") | ||
| 1107 | + .width(100.percent) | ||
| 1108 | + .margin(top: 20, bottom: 30) | ||
| 1109 | + .fontWeight(FontWeight.W500) | ||
| 1110 | + .fontSize(16) | ||
| 1111 | + | ||
| 1112 | + Row() {} | ||
| 1113 | + .height(25.percent) | ||
| 1114 | + | ||
| 1115 | + Flex(justifyContent: FlexAlign.SpaceBetween) { | ||
| 1116 | + Text("最新") | ||
| 1117 | + .fontSize(13) | ||
| 1118 | + Text("75次/分") | ||
| 1119 | + .fontSize(13) | ||
| 1120 | + } | ||
| 1121 | + .width(100.percent) | ||
| 1122 | + .height(30) | ||
| 1123 | + .backgroundColor(0xFFE6E6E7) | ||
| 1124 | + .padding(left: 10, right: 10) | ||
| 1125 | + .borderRadius(10) | ||
| 1126 | + .margin(top: 30, bottom: 20) | ||
| 1127 | + | ||
| 1128 | + Text("显示更多心率数据") | ||
| 1129 | + .fontColor(0xFF007DFF) | ||
| 1130 | + .fontSize(14) | ||
| 1131 | + .margin(bottom: 30) | ||
| 1132 | + } | ||
| 1133 | + .backgroundColor(Color.White) | ||
| 1134 | + .padding(left: 15, right: 15) | ||
| 1135 | + | ||
| 1136 | + Column() { | ||
| 1137 | + Text("关于心率") | ||
| 1138 | + .width(100.percent) | ||
| 1139 | + .fontSize(18) | ||
| 1140 | + .fontWeight(FontWeight.W600) | ||
| 1141 | + Text("心率是指正常人安静状态下每分钟心跳的次数,也叫安静心率,一般为60~100次/分,可因年龄、性别或其他生理因素产生个体差异。 一般来说,年龄越小,心率越快,老年人心跳比年轻人慢,女性的心率比同龄男性快,这些都是正常的生理现象。") | ||
| 1142 | + .backgroundColor(Color.White) | ||
| 1143 | + .width(100.percent) | ||
| 1144 | + .padding(10) | ||
| 1145 | + .borderRadius(10) | ||
| 1146 | + .margin(top: 10) | ||
| 1147 | + } | ||
| 1148 | + .padding(20) | ||
| 1149 | + } | ||
| 1150 | + .width(100.percent) | ||
| 1151 | + .height(100.percent) | ||
| 1152 | + .backgroundColor(0xFFE6E6E7) | ||
| 1153 | + } | ||
| 1154 | + .title("心率") | ||
| 1155 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 1156 | + .onBackPressed({ => | ||
| 1157 | + this.stack.pop() | ||
| 1158 | + return true | ||
| 1159 | + }) | ||
| 1160 | + } | ||
| 1161 | +} | ||
| 1162 | + | ||
| 1163 | +@Component | ||
| 1164 | +public class SecretPage { | ||
| 1165 | + @Consume var stack: NavPathStack | ||
| 1166 | + private let controller: WebviewController = WebviewController() | ||
| 1167 | + | ||
| 1168 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 1169 | + | ||
| 1170 | + @State var loading: Bool = true | ||
| 1171 | + @State var showFallback: Bool = false | ||
| 1172 | + | ||
| 1173 | + private var loadTimeoutTimer: Option<Timer> = None | ||
| 1174 | + | ||
| 1175 | + func cancelLoadTimer(): Unit { | ||
| 1176 | + match (this.loadTimeoutTimer) { | ||
| 1177 | + case Some(t) => t.cancel() | ||
| 1178 | + case _ => () | ||
| 1179 | + } | ||
| 1180 | + this.loadTimeoutTimer = None | ||
| 1181 | + } | ||
| 1182 | + | ||
| 1183 | + func armLoadTimer(): Unit { | ||
| 1184 | + this.cancelLoadTimer() | ||
| 1185 | + this.loadTimeoutTimer = Some(Timer.once(10000 * Duration.millisecond) { | ||
| 1186 | + => launch { | ||
| 1187 | + if (this.loading && !this.showFallback) { | ||
| 1188 | + try { | ||
| 1189 | + this.controller.stop() | ||
| 1190 | + } catch (e: Exception) { | ||
| 1191 | + () | ||
| 1192 | + } | ||
| 1193 | + this.loading = false | ||
| 1194 | + this.showFallback = true | ||
| 1195 | + } | ||
| 1196 | + } | ||
| 1197 | + }) | ||
| 1198 | + } | ||
| 1199 | + | ||
| 1200 | + func build() { | ||
| 1201 | + NavDestination() { | ||
| 1202 | + Column() { | ||
| 1203 | + if (this.loading && !this.showFallback) { | ||
| 1204 | + Row(space: 8) { | ||
| 1205 | + LoadingProgress() | ||
| 1206 | + .width(16) | ||
| 1207 | + .height(16) | ||
| 1208 | + Text("正在加载...") | ||
| 1209 | + .fontSize(12) | ||
| 1210 | + .opacity(0.6) | ||
| 1211 | + Button("停止") | ||
| 1212 | + .height(28) | ||
| 1213 | + .backgroundColor(0xFFF3F2F7) | ||
| 1214 | + .onClick({ _: ClickEvent => | ||
| 1215 | + this.cancelLoadTimer() | ||
| 1216 | + this.controller.stop() | ||
| 1217 | + this.loading = false | ||
| 1218 | + this.showFallback = true | ||
| 1219 | + }) | ||
| 1220 | + } | ||
| 1221 | + .padding(left: 16, right: 16, top: 8, bottom: 8) | ||
| 1222 | + .width(100.percent) | ||
| 1223 | + .backgroundColor(0xFFFFFFFF) | ||
| 1224 | + } | ||
| 1225 | + | ||
| 1226 | + if (this.showFallback) { | ||
| 1227 | + Column(space: 12) { | ||
| 1228 | + Text("页面未加载完成") | ||
| 1229 | + .fontSize(16) | ||
| 1230 | + Text("你可以刷新重试,或返回") | ||
| 1231 | + .fontSize(12) | ||
| 1232 | + .opacity(0.6) | ||
| 1233 | + Row(space: 12) { | ||
| 1234 | + Button("刷新") | ||
| 1235 | + .width(120) | ||
| 1236 | + .height(40) | ||
| 1237 | + .backgroundColor(0xFFFB6522) | ||
| 1238 | + .fontColor(Color.White) | ||
| 1239 | + .onClick({ _: ClickEvent => | ||
| 1240 | + this.showFallback = false | ||
| 1241 | + this.loading = true | ||
| 1242 | + this.armLoadTimer() | ||
| 1243 | + this.controller.reload() | ||
| 1244 | + }) | ||
| 1245 | + Button("返回") | ||
| 1246 | + .width(120) | ||
| 1247 | + .height(40) | ||
| 1248 | + .backgroundColor(0xFFF3F2F7) | ||
| 1249 | + .onClick({ _: ClickEvent => | ||
| 1250 | + this.stack.pop() | ||
| 1251 | + }) | ||
| 1252 | + } | ||
| 1253 | + } | ||
| 1254 | + .padding(24) | ||
| 1255 | + .width(100.percent) | ||
| 1256 | + .height(100.percent) | ||
| 1257 | + .justifyContent(FlexAlign.Center) | ||
| 1258 | + .backgroundColor(0xFFFFFFFF) | ||
| 1259 | + } else { | ||
| 1260 | + Web(src: @rawfile("index.html"), controller: this.controller) | ||
| 1261 | + .width(100.percent) | ||
| 1262 | + .height(100.percent) | ||
| 1263 | + .onPageBegin({ evt => | ||
| 1264 | + this.loading = true | ||
| 1265 | + this.showFallback = false | ||
| 1266 | + this.armLoadTimer() | ||
| 1267 | + }) | ||
| 1268 | + .onPageEnd({ evt => | ||
| 1269 | + this.loading = false | ||
| 1270 | + this.showFallback = false | ||
| 1271 | + this.cancelLoadTimer() | ||
| 1272 | + }) | ||
| 1273 | + } | ||
| 1274 | + } | ||
| 1275 | + .width(100.percent) | ||
| 1276 | + .height(100.percent) | ||
| 1277 | + } | ||
| 1278 | + .title("用户隐私数据清单") | ||
| 1279 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 1280 | + .onAppear({ => | ||
| 1281 | + this.loading = true | ||
| 1282 | + this.showFallback = false | ||
| 1283 | + this.armLoadTimer() | ||
| 1284 | + }) | ||
| 1285 | + .onBackPressed({ => | ||
| 1286 | + this.cancelLoadTimer() | ||
| 1287 | + this.stack.pop() | ||
| 1288 | + return true | ||
| 1289 | + }) | ||
| 1290 | + } | ||
| 1291 | +} | ||
| 1292 | + | ||
| 1293 | +@Component | ||
| 1294 | +public class CommonNewPage { | ||
| 1295 | + @Consume var stack: NavPathStack | ||
| 1296 | + private let controller: WebviewController = WebviewController() | ||
| 1297 | + | ||
| 1298 | + @StorageLink["topRectHeightPx"] var topRectHeightPx: Int64 = 0 | ||
| 1299 | + | ||
| 1300 | + @State var loading: Bool = true | ||
| 1301 | + @State var showFallback: Bool = false | ||
| 1302 | + | ||
| 1303 | + private var loadTimeoutTimer: Option<Timer> = None | ||
| 1304 | + | ||
| 1305 | + func cancelLoadTimer(): Unit { | ||
| 1306 | + match (this.loadTimeoutTimer) { | ||
| 1307 | + case Some(t) => t.cancel() | ||
| 1308 | + case _ => () | ||
| 1309 | + } | ||
| 1310 | + this.loadTimeoutTimer = None | ||
| 1311 | + } | ||
| 1312 | + | ||
| 1313 | + func armLoadTimer(): Unit { | ||
| 1314 | + this.cancelLoadTimer() | ||
| 1315 | + this.loadTimeoutTimer = Some(Timer.once(10000 * Duration.millisecond) { | ||
| 1316 | + => launch { | ||
| 1317 | + if (this.loading && !this.showFallback) { | ||
| 1318 | + try { | ||
| 1319 | + this.controller.stop() | ||
| 1320 | + } catch (e: Exception) { | ||
| 1321 | + () | ||
| 1322 | + } | ||
| 1323 | + this.loading = false | ||
| 1324 | + this.showFallback = true | ||
| 1325 | + } | ||
| 1326 | + } | ||
| 1327 | + }) | ||
| 1328 | + } | ||
| 1329 | + | ||
| 1330 | + func build() { | ||
| 1331 | + NavDestination() { | ||
| 1332 | + Column() { | ||
| 1333 | + if (this.loading && !this.showFallback) { | ||
| 1334 | + Row(space: 8) { | ||
| 1335 | + LoadingProgress() | ||
| 1336 | + .width(16) | ||
| 1337 | + .height(16) | ||
| 1338 | + Text("正在加载...") | ||
| 1339 | + .fontSize(12) | ||
| 1340 | + .opacity(0.6) | ||
| 1341 | + Button("停止") | ||
| 1342 | + .height(28) | ||
| 1343 | + .backgroundColor(0xFFF3F2F7) | ||
| 1344 | + .onClick({ _: ClickEvent => | ||
| 1345 | + this.cancelLoadTimer() | ||
| 1346 | + this.controller.stop() | ||
| 1347 | + this.loading = false | ||
| 1348 | + this.showFallback = true | ||
| 1349 | + }) | ||
| 1350 | + } | ||
| 1351 | + .padding(left: 16, right: 16, top: 8, bottom: 8) | ||
| 1352 | + .width(100.percent) | ||
| 1353 | + .backgroundColor(0xFFFFFFFF) | ||
| 1354 | + } | ||
| 1355 | + | ||
| 1356 | + if (this.showFallback) { | ||
| 1357 | + Column(space: 12) { | ||
| 1358 | + Text("页面未加载完成") | ||
| 1359 | + .fontSize(16) | ||
| 1360 | + Text("你可以刷新重试,或返回") | ||
| 1361 | + .fontSize(12) | ||
| 1362 | + .opacity(0.6) | ||
| 1363 | + Row(space: 12) { | ||
| 1364 | + Button("刷新") | ||
| 1365 | + .width(120) | ||
| 1366 | + .height(40) | ||
| 1367 | + .backgroundColor(0xFFFB6522) | ||
| 1368 | + .fontColor(Color.White) | ||
| 1369 | + .onClick({ _: ClickEvent => | ||
| 1370 | + this.showFallback = false | ||
| 1371 | + this.loading = true | ||
| 1372 | + this.armLoadTimer() | ||
| 1373 | + this.controller.reload() | ||
| 1374 | + }) | ||
| 1375 | + Button("返回") | ||
| 1376 | + .width(120) | ||
| 1377 | + .height(40) | ||
| 1378 | + .backgroundColor(0xFFF3F2F7) | ||
| 1379 | + .onClick({ _: ClickEvent => | ||
| 1380 | + this.stack.pop() | ||
| 1381 | + }) | ||
| 1382 | + } | ||
| 1383 | + } | ||
| 1384 | + .padding(24) | ||
| 1385 | + .width(100.percent) | ||
| 1386 | + .height(100.percent) | ||
| 1387 | + .justifyContent(FlexAlign.Center) | ||
| 1388 | + .backgroundColor(0xFFFFFFFF) | ||
| 1389 | + } else { | ||
| 1390 | + Web(src: @rawfile("index.html"), controller: this.controller) | ||
| 1391 | + .width(100.percent) | ||
| 1392 | + .height(100.percent) | ||
| 1393 | + .onPageBegin({ evt => | ||
| 1394 | + this.loading = true | ||
| 1395 | + this.showFallback = false | ||
| 1396 | + this.armLoadTimer() | ||
| 1397 | + }) | ||
| 1398 | + .onPageEnd({ evt => | ||
| 1399 | + this.loading = false | ||
| 1400 | + this.showFallback = false | ||
| 1401 | + this.cancelLoadTimer() | ||
| 1402 | + }) | ||
| 1403 | + } | ||
| 1404 | + } | ||
| 1405 | + .width(100.percent) | ||
| 1406 | + .height(100.percent) | ||
| 1407 | + } | ||
| 1408 | + .title("第三方共享信息清单") | ||
| 1409 | + .padding(top: getUIContext().px2vp(this.topRectHeightPx.px) ?? 0.vp) | ||
| 1410 | + .onAppear({ => | ||
| 1411 | + this.loading = true | ||
| 1412 | + this.showFallback = false | ||
| 1413 | + this.armLoadTimer() | ||
| 1414 | + }) | ||
| 1415 | + .onBackPressed({ => | ||
| 1416 | + this.cancelLoadTimer() | ||
| 1417 | + this.stack.pop() | ||
| 1418 | + return true | ||
| 1419 | + }) | ||
| 1420 | + } | ||
| 1421 | +} | ||
| @@ -0,0 +1,68 @@ | |||
| 1 | +{ | ||
| 2 | + "module": { | ||
| 3 | + "name": "entry", | ||
| 4 | + "type": "entry", | ||
| 5 | + "description": "$string:module_desc", | ||
| 6 | + "mainElement": "EntryAbility", | ||
| 7 | + "deviceTypes": [ | ||
| 8 | + "phone" | ||
| 9 | + ], | ||
| 10 | + "deliveryWithInstall": true, | ||
| 11 | + "installationFree": false, | ||
| 12 | + "srcEntry": "ohos_app_cangjie_entry.MyAbilityStage", | ||
| 13 | + "abilities": [ | ||
| 14 | + { | ||
| 15 | + "name": "EntryAbility", | ||
| 16 | + "srcEntry": "ohos_app_cangjie_entry.MainAbility", | ||
| 17 | + "description": "$string:EntryAbility_desc", | ||
| 18 | + "icon": "$media:layered_image", | ||
| 19 | + "label": "$string:EntryAbility_label", | ||
| 20 | + "startWindowIcon": "$media:startIcon", | ||
| 21 | + "startWindowBackground": "$color:start_window_background", | ||
| 22 | + "exported": true, | ||
| 23 | + "skills": [ | ||
| 24 | + { | ||
| 25 | + "entities": [ | ||
| 26 | + "entity.system.home" | ||
| 27 | + ], | ||
| 28 | + "actions": [ | ||
| 29 | + "action.system.home" | ||
| 30 | + ] | ||
| 31 | + } | ||
| 32 | + ] | ||
| 33 | + } | ||
| 34 | + ], | ||
| 35 | + "requestPermissions": [ | ||
| 36 | + { | ||
| 37 | + "name": "ohos.permission.ACTIVITY_MOTION", | ||
| 38 | + "reason": "$string:permission_activity_motion_reason", | ||
| 39 | + "usedScene": { | ||
| 40 | + "abilities": [ | ||
| 41 | + "EntryAbility" | ||
| 42 | + ], | ||
| 43 | + "when": "inuse" | ||
| 44 | + } | ||
| 45 | + }, | ||
| 46 | + { | ||
| 47 | + "name": "ohos.permission.ACCESS_BLUETOOTH", | ||
| 48 | + "reason": "$string:permission_access_bluetooth_reason", | ||
| 49 | + "usedScene": { | ||
| 50 | + "abilities": [ | ||
| 51 | + "EntryAbility" | ||
| 52 | + ], | ||
| 53 | + "when": "inuse" | ||
| 54 | + } | ||
| 55 | + }, | ||
| 56 | + { | ||
| 57 | + "name": "ohos.permission.KEEP_BACKGROUND_RUNNING", | ||
| 58 | + "reason": "$string:permission_keep_background_running_reason", | ||
| 59 | + "usedScene": { | ||
| 60 | + "abilities": [ | ||
| 61 | + "EntryAbility" | ||
| 62 | + ], | ||
| 63 | + "when": "inuse" | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | + ] | ||
| 67 | + } | ||
| 68 | +} | ||
| @@ -0,0 +1,48 @@ | |||
| 1 | +{ | ||
| 2 | + "color": [ | ||
| 3 | + { | ||
| 4 | + "name": "start_window_background", | ||
| 5 | + "value": "#FFFFFF" | ||
| 6 | + }, | ||
| 7 | + { | ||
| 8 | + "name": "white", | ||
| 9 | + "value": "#FFFFFF" | ||
| 10 | + }, | ||
| 11 | + { | ||
| 12 | + "name": "black", | ||
| 13 | + "value": "#000000" | ||
| 14 | + }, | ||
| 15 | + { | ||
| 16 | + "name": "primaryBgColor", | ||
| 17 | + "value": "#F1F3F5" | ||
| 18 | + }, | ||
| 19 | + { | ||
| 20 | + "name": "titleColor", | ||
| 21 | + "value": "#182431" | ||
| 22 | + }, | ||
| 23 | + { | ||
| 24 | + "name": "signatureColor", | ||
| 25 | + "value": "#66686A" | ||
| 26 | + }, | ||
| 27 | + { | ||
| 28 | + "name": "tabTitleColor", | ||
| 29 | + "value": "#999999" | ||
| 30 | + }, | ||
| 31 | + { | ||
| 32 | + "name": "mineBgColor", | ||
| 33 | + "value": "#EDF2F5" | ||
| 34 | + }, | ||
| 35 | + { | ||
| 36 | + "name": "borderColor", | ||
| 37 | + "value": "#CCCCCC" | ||
| 38 | + }, | ||
| 39 | + { | ||
| 40 | + "name": "leveColor", | ||
| 41 | + "value": "#C99411" | ||
| 42 | + }, | ||
| 43 | + { | ||
| 44 | + "name": "leveBgColor", | ||
| 45 | + "value": "#D4E6F1" | ||
| 46 | + } | ||
| 47 | + ] | ||
| 48 | +} | ||