Compare commits
5 Commits
4d96ab0912
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 00bce1b6ab | |||
| 230180febf | |||
| 696b728c78 | |||
| 22340b09fa | |||
| d9d45d983b |
34
CHANGELOG.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# 更新记录
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
**机型覆盖 17 → 55 款**
|
||||||
|
|
||||||
|
新增 38 款圆屏、Connect IQ ≥ 4.2 的机型,包括 fenix 7 Pro 全系、Venu 2/3S、
|
||||||
|
Approach S50/S70、Descent G2/Mk3、D2 系列、Instinct 3 AMOLED、FR255S/265S/570/955 等。
|
||||||
|
机型列表与资源路径改由 `tools/gen_devices.py` 扫描本机 SDK 生成,不再手写。
|
||||||
|
|
||||||
|
新增两档图标尺寸(218px、360px)。218px 上设计稿的天气图标只有 14×14,
|
||||||
|
太阳与云之间的暗缝不足一个像素,会糊成一块 —— 该尺寸改用专画的简化标记。
|
||||||
|
|
||||||
|
**常亮模式收紧**
|
||||||
|
|
||||||
|
砍掉整个进度环,时间/日期由 55% 压暗到 38%。实测点亮像素数 10.4% → 9.7%,
|
||||||
|
平均亮度 3.0% → 2.5%(普通模式分别是 27.6% / 14.1%)。
|
||||||
|
|
||||||
|
**其它**
|
||||||
|
|
||||||
|
- 补 LICENSE(MIT,仅覆盖代码;`design/` 下的设计稿版权归第三方,见文件内说明)
|
||||||
|
- `UserProfile.getProfile()` 加异常保护
|
||||||
|
- 版本号 1.0.0 → 1.1.0
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
按 `watchface-kit` 设计稿 1:1 还原的多指标数据表盘。
|
||||||
|
|
||||||
|
- 156 项可选数据字段,编号对齐 watchface.io 目录
|
||||||
|
- 七套主题,配色逐根刻度实测自设计稿
|
||||||
|
- 常亮(AOD)模式,含防烧屏位移
|
||||||
|
- 简体中文 / 英文双语,表盘文字亦本地化
|
||||||
|
- OpenWeatherMap 接入(用户自备 API key,后台定时拉取)
|
||||||
|
- 系统 Complication 接入(日历、训练状态、完赛预测、第三方 App)
|
||||||
29
LICENSE
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 ericwyuan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
注意:本许可只覆盖**本仓库的代码**。
|
||||||
|
|
||||||
|
design/ 目录下的设计稿(watchface-*.svg、watchface-tokens.json、rebuild.py)
|
||||||
|
来自第三方 watchface-kit,版权归其原作者所有,不在本许可范围内。
|
||||||
|
表盘的视觉呈现是对那套设计的 1:1 还原 —— 若要发布到 Connect IQ Store 或以
|
||||||
|
其它形式分发,需先取得原作者授权,或将视觉部分替换为原创设计。
|
||||||
100
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Fenix 8 V3 — Garmin 数据表盘
|
# Fenix 8 V3 — Garmin 数据表盘
|
||||||
|
|
||||||
按 `watchface-kit` 设计稿 1:1 还原的多指标数据表盘。设计画布 **500×500**,渲染时按屏宽等比缩放 `s = screenWidth / 500`,支持圆形 AMOLED(Fenix 8 / Epix 2 / Venu 3 等)与圆屏 MIP(Fenix 7 / Enduro 3 / FR255 等)共 17 款设备。
|
按 `watchface-kit` 设计稿 1:1 还原的多指标数据表盘。设计画布 **500×500**,渲染时按屏宽等比缩放 `s = screenWidth / 500`,支持圆形 AMOLED 与圆屏 MIP 共 **55 款**设备(Fenix 7/8 全系含 Pro、Epix 2、Enduro 3、Venu 2/3、FR 系列、Approach S50/S70、Descent、D2、Instinct 3 AMOLED 等)。
|
||||||
|
|
||||||
- **SDK**:Connect IQ SDK 9.1.0(`monkeyc` 9.1.0,`minSdkVersion="4.2.0"`)
|
- **SDK**:Connect IQ SDK 9.1.0(`monkeyc` 9.1.0,`minSdkVersion="4.2.0"`)
|
||||||
- **语言**:Monkey C;界面文案含英文与简体中文
|
- **语言**:Monkey C;界面文案含英文与简体中文
|
||||||
@@ -24,6 +24,7 @@ fenix8v3-watchface/
|
|||||||
├── tools/
|
├── tools/
|
||||||
│ ├── gen_themes.py # design/*.svg -> source/Themes.mc
|
│ ├── gen_themes.py # design/*.svg -> source/Themes.mc
|
||||||
│ ├── gen_fields.py # 字段表 -> FieldTable.mc + settings.xml + 两种语言文案
|
│ ├── gen_fields.py # 字段表 -> FieldTable.mc + settings.xml + 两种语言文案
|
||||||
|
│ ├── gen_devices.py # 扫描 SDK -> manifest 机型列表 + monkey.jungle
|
||||||
│ ├── gen_icons.py # design/*.svg -> 各尺寸图标位图 + 启动图标
|
│ ├── gen_icons.py # design/*.svg -> 各尺寸图标位图 + 启动图标
|
||||||
│ └── gen_preview_svg.py # source/Themes.mc -> preview-themes.svg + preview.html
|
│ └── gen_preview_svg.py # source/Themes.mc -> preview-themes.svg + preview.html
|
||||||
├── source/
|
├── source/
|
||||||
@@ -34,6 +35,7 @@ fenix8v3-watchface/
|
|||||||
│ ├── Fields.mc # 125 项数据字段的取值与格式化
|
│ ├── Fields.mc # 125 项数据字段的取值与格式化
|
||||||
│ ├── Solar.mc # 日出/晨昏蒙影/黄金蓝调时刻的太阳高度角计算
|
│ ├── Solar.mc # 日出/晨昏蒙影/黄金蓝调时刻的太阳高度角计算
|
||||||
│ ├── Owm.mc # OpenWeatherMap 缓存读取
|
│ ├── Owm.mc # OpenWeatherMap 缓存读取
|
||||||
|
│ ├── Comp.mc # 系统 / 第三方 Complication 桥接
|
||||||
│ ├── Fenix8V3Background.mc # 后台服务:拉取 OWM
|
│ ├── Fenix8V3Background.mc # 后台服务:拉取 OWM
|
||||||
│ ├── Settings.mc # 所有设置项的读取与默认值
|
│ ├── Settings.mc # 所有设置项的读取与默认值
|
||||||
│ ├── FieldTable.mc # ⚠️ 自动生成:字段 id 表
|
│ ├── FieldTable.mc # ⚠️ 自动生成:字段 id 表
|
||||||
@@ -42,16 +44,21 @@ fenix8v3-watchface/
|
|||||||
├── resources-chn/ # 简体中文文案
|
├── resources-chn/ # 简体中文文案
|
||||||
├── resources-icons-<宽度>/ # ⚠️ 自动生成:按屏幕尺寸光栅化的图标
|
├── resources-icons-<宽度>/ # ⚠️ 自动生成:按屏幕尺寸光栅化的图标
|
||||||
├── resources-launcher-<尺寸>/ # ⚠️ 自动生成:各尺寸启动图标
|
├── resources-launcher-<尺寸>/ # ⚠️ 自动生成:各尺寸启动图标
|
||||||
|
├── docs/ # 上架素材:商店文案、图标、各机型截图
|
||||||
└── bin/ # 编译产物(已 gitignore)
|
└── bin/ # 编译产物(已 gitignore)
|
||||||
```
|
```
|
||||||
|
|
||||||
重新生成全部派生文件:
|
重新生成全部派生文件:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 tools/gen_themes.py && python3 tools/gen_fields.py \
|
python3 tools/gen_devices.py \
|
||||||
|
&& python3 tools/gen_themes.py && python3 tools/gen_fields.py \
|
||||||
&& python3 tools/gen_icons.py && python3 tools/gen_preview_svg.py
|
&& python3 tools/gen_icons.py && python3 tools/gen_preview_svg.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`gen_devices.py` 要先跑 —— 它决定了需要哪些图标尺寸,`gen_icons.py` 的
|
||||||
|
`WIDTHS` / `LAUNCHER` 两张表必须跟它对齐,少一档就会有机型编译不过。
|
||||||
|
|
||||||
三份渲染实现(表盘 / SVG 预览 / HTML 预览)只有 **一份颜色数据**:`Themes.mc`。预览脚本直接解析它,不会与固件漂移。字段表、设置项、两种语言的文案同样只有 `tools/gen_fields.py` 里那一张表。
|
三份渲染实现(表盘 / SVG 预览 / HTML 预览)只有 **一份颜色数据**:`Themes.mc`。预览脚本直接解析它,不会与固件漂移。字段表、设置项、两种语言的文案同样只有 `tools/gen_fields.py` 里那一张表。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -181,21 +188,48 @@ python3 tools/gen_themes.py && python3 tools/gen_preview_svg.py
|
|||||||
|
|
||||||
### 数据字段
|
### 数据字段
|
||||||
|
|
||||||
四个数据位共用一张 **125 项** 的字段表,编号沿用 [watchface.io/docs/datafields](https://watchface.io/docs/datafields),两边可以直接对照。底部两格的小标签跟随所选字段自动变化,不需要单独配置。
|
四个数据位共用一张 **156 项** 的字段表,编号沿用 [watchface.io/docs/datafields](https://watchface.io/docs/datafields),两边可以直接对照。底部两格的小标签跟随所选字段自动变化,不需要单独配置。
|
||||||
|
|
||||||
| 分类 | 字段 |
|
| 分类 | 字段 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 日期时间 | 秒(前导零两种)、时间、UTC 时间、第二时区 1/2、星期月日、星期日、月日、日、星期(简写/全称)、月份(名称/数字)、上午下午、周数(ISO / 日历) |
|
| 日期时间 | 秒(前导零两种)、时间、UTC 时间、第二时区 1/2、星期月日、星期日、月日、日、星期(简写/全称)、月份(名称/数字)、上午下午、周数(ISO / 日历) |
|
||||||
| 活动 | 步数、步数取整、剩余步数、卡路里、活动卡路里、活动分钟(总/中强度/高强度/本周)、距离、上楼层数、下楼层数、爬升高度、推行距离、推行次数、**本周与近 7 天累计**(步数/卡路里/距离/楼层/活动分钟) |
|
| 活动 | 步数、步数取整、剩余步数、卡路里、活动卡路里、活动分钟(总/中强度/高强度/本周)、距离、上楼层数、下楼层数、爬升高度、推行距离、推行次数、**本周 / 近 7 天 / 近 28 天累计**(步数/卡路里/距离/楼层/活动分钟) |
|
||||||
| 身体 | 心率、静息心率、压力指数、血氧、呼吸频率、身体电量、活动条(正/反向)、体重、BMI、恢复时间、最大摄氧量(跑/骑) |
|
| 身体 | 心率、静息心率、压力指数、血氧、呼吸频率、身体电量、活动条(正/反向)、体重、BMI、恢复时间、最大摄氧量(跑/骑) |
|
||||||
| 系统 | 电量、剩余天数、电量+天数、闹钟、通知、勿扰、蓝牙、太阳能强度 |
|
| 系统 | 电量、剩余天数、电量+天数、闹钟、通知、勿扰、蓝牙、太阳能强度 |
|
||||||
| 环境 | 海拔、海平面气压、环境气压、日出、日落、下一太阳事件、距太阳事件、月龄、月球照明、民用/航海/天文晨昏蒙影(各 2 项)、黄金时刻(晨/昏)、蓝调时刻(晨/昏) |
|
| 环境 | 海拔、海平面气压、环境气压、日出、日落、下一太阳事件、距太阳事件、月龄、月球照明、民用/航海/天文晨昏蒙影(各 2 项)、黄金时刻(晨/昏)、蓝调时刻(晨/昏) |
|
||||||
| 天气 | 气温、最高/最低、体感、最高、最低、湿度、降水概率、风速、风向角、风向、更新时间、海平面气压、紫外线、能见度、露点 |
|
| 天气 | 气温、最高/最低、体感、最高、最低、湿度、降水概率、风速、风向角、风向、更新时间、海平面气压、紫外线、能见度、露点 |
|
||||||
| OpenWeatherMap | 气温、体感、最高/最低、天气描述(简/详)、湿度、气压、能见度、风速、风向角、风向、阵风、1h 降雨、1h 降雪、云量、降水概率、露点、明日与后日气温及高低温、城市名、更新时间(共 25 项) |
|
| OpenWeatherMap | 气温、体感、最高/最低、天气描述(简/详)、湿度、气压、能见度、风速、风向角、风向、阵风、1h 降雨、1h 降雪、云量、降水概率、露点、明日与后日气温及高低温、城市名、更新时间、**空气质量(AQI 等级 + 描述 + PM2.5 + PM10 + CO)**(共 30 项) |
|
||||||
| 自定义 | 自定义文字 1 / 2 / 3 |
|
| 系统 Complication | 下一个日程、训练状态、完赛预测(5k/10k/半马/全马)、配速预测(同上四项)、最近高尔夫得分、本周跑步距离、本周骑行距离、第三方 App 1–5 |
|
||||||
|
| 自定义 | 自定义文字 1 / 2 / 3、自定义时间格式、日期格式 1 / 2、事件倒计时 |
|
||||||
|
|
||||||
加字段:在 `tools/gen_fields.py` 的 `FIELDS` 表里加一行,跑一遍生成器,再到 `source/Fields.mc` 的 `value()` 里补一个分支。设置项、两种语言的文案、表盘标签会自动跟上。
|
加字段:在 `tools/gen_fields.py` 的 `FIELDS` 表里加一行,跑一遍生成器,再到 `source/Fields.mc` 的 `value()` 里补一个分支。设置项、两种语言的文案、表盘标签会自动跟上。
|
||||||
|
|
||||||
|
### 系统 Complication
|
||||||
|
|
||||||
|
`Toybox.Complications`(CIQ 4.2+,17 款设备全都有)让表盘可以订阅系统和**其它 App 发布**的复杂功能。这条路解锁了一批原本以为做不到的字段:
|
||||||
|
|
||||||
|
- **日历事件** —— CIQ 确实没有日历 API,但系统把「下一个日程」作为 complication 发布了出来
|
||||||
|
- **训练状态、完赛/配速预测、最近高尔夫得分** —— `ActivityMonitor.Info` 和 `UserProfile` 上都没有,这里有
|
||||||
|
- **本周跑步 / 骑行距离** —— 这是**按运动分类**的,`ActivityMonitor` 的按天历史做不出来
|
||||||
|
- **第三方 App 1–5** —— CGM、补水、行情这类 App 发布的复杂功能,按序号取
|
||||||
|
|
||||||
|
需要 `ComplicationSubscriber` 权限。第三方 App 那五个槽位按「有值且有短标签」过滤后按序号取,避免序号随手表上装了什么 App 而漂移。
|
||||||
|
|
||||||
|
### 自定义格式与倒计时
|
||||||
|
|
||||||
|
时间/日期格式串用单字母占位符(设置项能填的长度有限,这样中英文都好写):
|
||||||
|
|
||||||
|
| 占位符 | 含义 | 占位符 | 含义 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `h` / `H` | 小时(跟随 12/24 制)/ 补零 | `D` | 日 |
|
||||||
|
| `m` / `s` | 分钟 / 秒(补零) | `M` / `N` | 月(数字 / 名称) |
|
||||||
|
| `a` | 上午/下午(24 小时制下为空) | `Y` / `y` | 四位年 / 两位年 |
|
||||||
|
| | | `w` | 星期 |
|
||||||
|
|
||||||
|
其它字符原样输出,所以 `D/M` → `10/9`,`w N D` → `周四 9月 10`。
|
||||||
|
|
||||||
|
事件倒计时填 `YYYY-MM-DD`,显示还剩几天。
|
||||||
|
|
||||||
### 秒针与后台天气
|
### 秒针与后台天气
|
||||||
|
|
||||||
**秒**(850/869)通过 `onPartialUpdate` 每秒重绘:只有当某个数据位真的选了秒时才请求这个回调,并且只在该格的裁剪区内重绘,不动整个表盘。常亮的 AMOLED 机型不参与——那时数据位本来就不显示。
|
**秒**(850/869)通过 `onPartialUpdate` 每秒重绘:只有当某个数据位真的选了秒时才请求这个回调,并且只在该格的裁剪区内重绘,不动整个表盘。常亮的 AMOLED 机型不参与——那时数据位本来就不显示。
|
||||||
@@ -208,14 +242,14 @@ python3 tools/gen_themes.py && python3 tools/gen_preview_svg.py
|
|||||||
|
|
||||||
| 没做的 | 原因 |
|
| 没做的 | 原因 |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| 各运动项目的 WTD / 近 7 天 / 本月 / 近 28 天 汇总(约 80 项) | Connect IQ 只有 `ActivityMonitor.getHistory()` 的按天总计(步数/卡路里/距离/楼层/活动分钟),**没有**按运动分类的历史。所以通用的本周/近 7 天累计已经做了,按跑步/骑行/游泳分开的做不了——那些在 GreenBlack 那边是手机侧与 Garmin Connect 集成同步来的 |
|
| 按运动细分的多数汇总(游泳、自定义运动 1/2 的距离/时长/次数,约 60 项) | `ActivityMonitor.getHistory()` 只有按天总计,没有按运动分类的历史。**跑步和骑行的本周距离例外**——系统把这两项作为 complication 发布了,已经做了;游泳和自定义运动没有对应的 complication 类型 |
|
||||||
|
| 日历事件、计时器 | 日历已通过 Complication 做了。**计时器**仍然做不了:表盘拿不到触摸输入,没法启停 |
|
||||||
| StormGlass.io 潮汐(5 项) | 需要额外的付费 API key,且与本表盘的定位无关 |
|
| StormGlass.io 潮汐(5 项) | 需要额外的付费 API key,且与本表盘的定位无关 |
|
||||||
| 第三方 App 复杂功能(CGM、Hydration、QuoteGlance 等 12 项) | 需要用户装了对应的 App,并通过 Complications API 订阅 |
|
| 第三方 App 复杂功能(CGM、Hydration、QuoteGlance 等 12 项) | 需要用户装了对应的 App,并通过 Complications API 订阅 |
|
||||||
| 日历事件、计时器、倒计时 | 日历不对 CIQ 开放;表盘拿不到触摸输入,没法做启停 |
|
| 睡眠得分 | `ActivityMonitor.Info` 上没有,也没有对应的 complication 类型(SDK 9.1.0 实测)。训练状态与完赛/配速预测已改由 Complication 提供 |
|
||||||
| 睡眠得分、训练状态、完赛/配速预测 | `ActivityMonitor.Info` 与 `UserProfile` 上没有这些字段(SDK 9.1.0 实测) |
|
|
||||||
| 高尔夫成绩 | 无对应 API |
|
| 高尔夫成绩 | 无对应 API |
|
||||||
| 标记类字段(Marker,约 35 项) | 那是给刻度型表盘做环形标记用的,本表盘的进度环用的是自己的指标体系 |
|
| 标记类字段(Marker,约 35 项) | 那是给刻度型表盘做环形标记用的,本表盘的进度环用的是自己的指标体系 |
|
||||||
| OWM 的 AQI 与 UVI | 免费档的 `/data/2.5/weather` 不返回;要 One Call 3.0 订阅。露点已用 Magnus 公式从温湿度算出 |
|
| OWM 的 UVI | 要 One Call 3.0 订阅。**AQI 已经做了**——空气质量走 `/data/2.5/air_pollution`,那个接口在免费档就开放。露点用 Magnus 公式从温湿度算出 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -238,7 +272,7 @@ python3 tools/gen_themes.py && python3 tools/gen_preview_svg.py
|
|||||||
|
|
||||||
### 权限
|
### 权限
|
||||||
|
|
||||||
`manifest.xml` 声明四项:`UserProfile`(静息心率、体重、VO2max)、`SensorHistory`(血氧、身体电量)、`Communications` 与 `Background`(OpenWeatherMap)。用户安装时会看到授权提示。
|
`manifest.xml` 声明五项:`UserProfile`(静息心率、体重、VO2max)、`SensorHistory`(血氧、身体电量)、`Communications` 与 `Background`(OpenWeatherMap)、`ComplicationSubscriber`(日历、训练状态、第三方 App)。用户安装时会看到授权提示。
|
||||||
|
|
||||||
> `Sensor` 权限**不能**用于 `type="watchface"`(除非同时申请 `Background`),编译器会直接拒绝。所以海拔与气压走 `Activity.Info` 而不是 `Sensor.getInfo()`。
|
> `Sensor` 权限**不能**用于 `type="watchface"`(除非同时申请 `Background`),编译器会直接拒绝。所以海拔与气压走 `Activity.Info` 而不是 `Sensor.getInfo()`。
|
||||||
|
|
||||||
@@ -255,9 +289,18 @@ AMOLED 机型在常亮模式下必须压低点亮像素,否则既费电又有
|
|||||||
- 时间与日期压暗到 55%,其余数据位全部隐藏
|
- 时间与日期压暗到 55%,其余数据位全部隐藏
|
||||||
- 整个画面按分钟在 ±4px 内游走,避免固定像素长期点亮
|
- 整个画面按分钟在 ±4px 内游走,避免固定像素长期点亮
|
||||||
|
|
||||||
只在 `DeviceSettings.requiresBurnInProtection` 为真时启用;MIP 机型(Fenix 7 / Enduro 3 等)不受影响,照常全量绘制。
|
只在 `DeviceSettings.requiresBurnInProtection` 为真时启用;MIP 机型不受影响,照常全量绘制。
|
||||||
|
|
||||||
模拟器可用 `Settings → Display Mode` 切换验证。
|
实测(fenix847mm,取表盘内区):
|
||||||
|
|
||||||
|
| | 点亮像素数 | 平均亮度(功耗代理) |
|
||||||
|
|---|---|---|
|
||||||
|
| 普通模式 | 27.6% | 14.1% |
|
||||||
|
| 常亮模式 | **9.7%** | **2.5%** |
|
||||||
|
|
||||||
|
常亮下砍掉的东西:整条日期带底色、**整个进度环**、四个数据位与电池;保留的时间、冒号、日期压暗到 38%。
|
||||||
|
|
||||||
|
两个口径要分清:「点亮像素数」把每个抗锯齿边缘像素都算成亮的,对大号数字表盘天然不友好;AMOLED 的功耗更接近各子像素亮度之和,也就是表里的「平均亮度」。本实现两个口径都留了余量。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -300,7 +343,9 @@ for d in enduro3 fenix843mm fenix847mm fenix8pro47mm fenix8solar47mm fenix8solar
|
|||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
屏幕分辨率跨度较大(fenix7s 240×240 至 fenix847mm / venu3 454×454),全部按 `s = width/500` 缩放。
|
屏幕分辨率跨度较大(FR255S 218×218 至 454×454 共 8 档),全部按 `s = width/500` 缩放。
|
||||||
|
|
||||||
|
机型列表和 `monkey.jungle` 由 `tools/gen_devices.py` 扫描本机已安装的 SDK 设备生成:筛选条件是「支持 watchFace + 圆屏 + CIQ ≥ 4.2」(Complications 需要 4.2)。**注意结果取决于本机装了哪些设备**,跑之前先确认 SDK 设备装全了,脚本会打印找到的数量。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -313,7 +358,22 @@ monkeydo bin/Fenix8V3_fenix847mm.prg fenix847mm # 载入表盘
|
|||||||
|
|
||||||
非 release 编译会同时产出 `bin/*-settings.json`,可在模拟器 `View → Settings` 载入以实时切换主题与指标。注意模拟器会**持久化**已保存的设置,改 `properties.xml` 的默认值对已运行过的实例不生效,需先重置应用数据。
|
非 release 编译会同时产出 `bin/*-settings.json`,可在模拟器 `View → Settings` 载入以实时切换主题与指标。注意模拟器会**持久化**已保存的设置,改 `properties.xml` 的默认值对已运行过的实例不生效,需先重置应用数据。
|
||||||
|
|
||||||
真机侧载:用 Garmin Express 或 Connect 手机 App 导入对应机型的 `bin/Fenix8V3_<device>.prg`,或把 `.prg` 拷入手表 `/GARMIN/Apps/`。
|
### 侧载到真机
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tools/install.sh # 默认 enduro3
|
||||||
|
tools/install.sh fenix847mm # 指定机型,id 见 manifest.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本会编译 release 版并自动找已挂载的 GARMIN 卷复制过去。找不到卷时会打印
|
||||||
|
`.prg` 的路径和排查提示。
|
||||||
|
|
||||||
|
手动做的话就是:编译出 `bin/Fenix8V3_<机型>.prg`,拷进手表的 `GARMIN/APPS/`,
|
||||||
|
推出后拔线,表上长按 UP/MENU → 表盘 → 选 Fenix 8 V3。再长按 MENU 进表盘设置。
|
||||||
|
|
||||||
|
> macOS 上 fenix 7 之后的机型多数走 MTP,Finder 看不到这个盘。装
|
||||||
|
> [OpenMTP](https://openmtp.ganeshrvel.com)(免费)或 Android File Transfer
|
||||||
|
> 就能拖文件了。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -336,6 +396,16 @@ python3 tools/gen_preview_svg.py
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 12.5 上架素材
|
||||||
|
|
||||||
|
商店后台要填的文案、图标和截图都在 `docs/`,见 `docs/README.md`。
|
||||||
|
|
||||||
|
截图是模拟器的真实渲染(Garmin 内置字体),八个屏宽各一张,外加七套主题各一张。
|
||||||
|
其中**进度环是摆拍的** —— 模拟器灌不进步数和距离,环会全灰,具体说明在
|
||||||
|
`docs/README.md` 里写清楚了。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 13. 设计稿版权
|
## 13. 设计稿版权
|
||||||
|
|
||||||
`design/` 内的 SVG、tokens 与 `rebuild.py` 来自 `watchface-kit`,版权归原作者所有。本仓库是按该设计稿实现的 Monkey C 表盘。
|
`design/` 内的 SVG、tokens 与 `rebuild.py` 来自 `watchface-kit`,版权归原作者所有。本仓库是按该设计稿实现的 Monkey C 表盘。
|
||||||
|
|||||||
72
docs/README.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# 上架素材
|
||||||
|
|
||||||
|
Connect IQ Store 后台要填的东西都在这个目录里。代码不依赖这里的任何文件。
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/
|
||||||
|
├── store-listing-zh.md 中文商店文案(名称 / 简介 / 详细描述 / 关键词 / 权限说明)
|
||||||
|
├── store-listing-en.md 英文商店文案
|
||||||
|
├── store-icon-1024.png 商店图标(大)
|
||||||
|
├── store-icon-512.png 商店图标(小)
|
||||||
|
└── screenshots/
|
||||||
|
├── 218x218.png ~ 454x454.png 八个屏宽各一张
|
||||||
|
└── theme-01-ember.png ~ theme-07-kelp.png 七套主题(454px)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 截图是怎么来的
|
||||||
|
|
||||||
|
全部是 **Connect IQ 模拟器的真实渲染**,不是网页预览图 —— 字体是 Garmin 内置的
|
||||||
|
Roboto / Bionic,和真机一致。`preview-themes.svg` 那张用的是网页字体,**不要**拿去
|
||||||
|
当商店截图。
|
||||||
|
|
||||||
|
每个屏宽各挑了一台代表机型:
|
||||||
|
|
||||||
|
| 尺寸 | 机型 | 尺寸 | 机型 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 218 | FR255S | 360 | Venu 2S |
|
||||||
|
| 240 | fenix 7S | 390 | Vivoactive 5 |
|
||||||
|
| 260 | fenix 7 | 416 | fenix 8 43mm |
|
||||||
|
| 280 | Enduro 3 | 454 | fenix 8 47mm |
|
||||||
|
|
||||||
|
截图里显示的数据位是 **日出 / 最高最低气温 / 心率 / 身体电量** ——
|
||||||
|
这四项在模拟器里有真实取值,不是默认配置(默认是日出 / 高低温 / 距离 / 步数)。
|
||||||
|
换掉后两项是因为模拟器灌不进步数和距离,默认配置截出来底部是 `0.0` 和 `0`。
|
||||||
|
|
||||||
|
### ⚠️ 进度环是摆拍的
|
||||||
|
|
||||||
|
模拟器的「Edit Activity Monitor Info」对话框只能改活动分钟、恢复时间、呼吸和
|
||||||
|
压力,**改不了步数 / 距离 / 卡路里**(那几个输入框是 Java 自绘的表格,
|
||||||
|
脚本也够不着)。所以进度环在模拟器里永远是全灰的,截图没法看。
|
||||||
|
|
||||||
|
截图构建把四段弧固定成了设计稿的填充状态(右上 21/21、左上 10/21、
|
||||||
|
右下 9/21、左下 14/21)。**除进度环外,画面上每个数字都是模拟器的真实取值**,
|
||||||
|
没有伪造。
|
||||||
|
|
||||||
|
如果要出完全真实的截图,把 `.prg` 侧载到真机戴一天再拍。
|
||||||
|
|
||||||
|
## 重新生成
|
||||||
|
|
||||||
|
图标:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/gen_icons.py # 顺带产出 store-icon 用的同款视觉
|
||||||
|
```
|
||||||
|
|
||||||
|
截图:本目录下的 PNG 是用模拟器抓的,流程见提交记录里的 `shot.sh` / `theme.sh`
|
||||||
|
(脚本在临时目录,没有入库 —— 它们依赖具体的窗口位置,换台机器就要重调)。
|
||||||
|
手工重做的话:编译 → `monkeydo` 载入 → 截模拟器窗口 → 按日期带定位裁出表盘圆形区。
|
||||||
|
|
||||||
|
## 后台还要填但不在这里的
|
||||||
|
|
||||||
|
- **分类**:Watch Faces
|
||||||
|
- **支持的机型**:55 款,以 `manifest.xml` 的 `<iq:products>` 为准
|
||||||
|
- **价格**:免费 / 付费自行决定
|
||||||
|
- **隐私政策**:如果启用 OpenWeatherMap,需要说明会把**坐标**发给
|
||||||
|
`api.openweathermap.org`。不填 key 时完全不联网,这一点建议在描述里写清楚
|
||||||
|
- **联系邮箱 / 支持链接**
|
||||||
|
|
||||||
|
## ⚠️ 发布前必须先解决
|
||||||
|
|
||||||
|
表盘的视觉是对第三方 `watchface-kit` 设计稿的 1:1 还原(见仓库根目录的
|
||||||
|
`LICENSE` 与 `README.md` 第 13 节)。**以自己的名义上架前,需要先取得原作者
|
||||||
|
授权,或把视觉部分替换成原创设计。** 这是法律问题,不是技术问题。
|
||||||
BIN
docs/screenshots/218x218.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/screenshots/240x240.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/screenshots/260x260.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/screenshots/280x280.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/screenshots/360x360.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
docs/screenshots/390x390.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
docs/screenshots/416x416.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
docs/screenshots/454x454.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
docs/screenshots/theme-01-ember.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
docs/screenshots/theme-02-aurora.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
docs/screenshots/theme-03-brass.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
docs/screenshots/theme-04-voltage.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
docs/screenshots/theme-05-reef.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
docs/screenshots/theme-06-acid.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
docs/screenshots/theme-07-kelp.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
docs/store-icon-1024.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
docs/store-icon-512.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
90
docs/store-listing-en.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Connect IQ Store listing (English)
|
||||||
|
|
||||||
|
## App name
|
||||||
|
|
||||||
|
```
|
||||||
|
Fenix 8 V3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Short description
|
||||||
|
|
||||||
|
```
|
||||||
|
A dense data watch face: four progress arcs, four configurable slots, 156 data
|
||||||
|
fields to choose from, seven colour themes.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Full description
|
||||||
|
|
||||||
|
```
|
||||||
|
Fenix 8 V3 packs a lot of information onto the dial without making it feel
|
||||||
|
crowded.
|
||||||
|
|
||||||
|
■ Four progress arcs
|
||||||
|
The rim is split into four independent arcs. Each one tracks a metric of your
|
||||||
|
choosing - steps, calories, distance, floors, move bar, active minutes or body
|
||||||
|
battery - so a glance tells you where the day stands. Every tick is
|
||||||
|
individually coloured, so the arc shifts hue as it fills.
|
||||||
|
|
||||||
|
■ Four data slots, 156 fields
|
||||||
|
Top-left, top-right, bottom-left and bottom-right. Each can show any of:
|
||||||
|
|
||||||
|
· Date & time seconds, UTC, two alternate time zones, week number
|
||||||
|
(ISO or calendar), custom time and date formats
|
||||||
|
· Activity steps, calories, distance, floors, ascent, active minutes,
|
||||||
|
plus week-to-date, 7-day and 28-day totals
|
||||||
|
· Body heart rate, resting HR, stress, pulse ox, respiration,
|
||||||
|
body battery, weight, BMI, recovery time, VO2 max
|
||||||
|
· System battery, battery in days, alarms, notifications, do not
|
||||||
|
disturb, bluetooth, solar intensity
|
||||||
|
· Environment altitude, pressure, sunrise/sunset, next sun event, moon age
|
||||||
|
and illumination, civil/nautical/astronomical twilight,
|
||||||
|
golden hour, blue hour
|
||||||
|
· Weather temperature, feels-like, high/low, humidity, precipitation,
|
||||||
|
wind speed and direction, UV index, visibility, dew point
|
||||||
|
· From watch next calendar event, training status, race and pace
|
||||||
|
predictors (5k/10k/half/marathon), weekly run and bike
|
||||||
|
distance, and complications published by your other apps
|
||||||
|
· Custom three custom text lines, event countdown
|
||||||
|
|
||||||
|
The captions under the bottom two slots follow whatever you pick - no separate
|
||||||
|
setting to keep in sync.
|
||||||
|
|
||||||
|
■ Seven themes
|
||||||
|
Ember, Aurora, Brass, Voltage, Reef, Acid and Kelp. Each carries its own arc
|
||||||
|
gradient, date band colours and time-digit gradient.
|
||||||
|
|
||||||
|
■ Always-on display
|
||||||
|
On AMOLED watches the face switches to a low-power layout while asleep: the
|
||||||
|
date band fill and the progress ring are dropped, the time and date are dimmed,
|
||||||
|
and the whole layout drifts a few pixels each minute to avoid burn-in.
|
||||||
|
|
||||||
|
■ OpenWeatherMap (optional)
|
||||||
|
Add your own OpenWeatherMap API key for 30 further weather fields, including
|
||||||
|
air quality (AQI, PM2.5, PM10, CO) and the next two days' forecast. Without a
|
||||||
|
key the watch face never touches the network.
|
||||||
|
|
||||||
|
■ Also
|
||||||
|
· English and Simplified Chinese - weekday, month and field captions all follow
|
||||||
|
the watch language
|
||||||
|
· Temperature unit can be forced to Celsius or Fahrenheit; distance and
|
||||||
|
12/24-hour follow the watch
|
||||||
|
· The bluetooth glyph dims when the phone is out of range
|
||||||
|
```
|
||||||
|
|
||||||
|
## Keywords / tags
|
||||||
|
|
||||||
|
```
|
||||||
|
data field, progress ring, complications, always-on, AMOLED, weather,
|
||||||
|
heart rate, body battery, sunrise sunset, chinese
|
||||||
|
```
|
||||||
|
|
||||||
|
## Permissions to explain to users
|
||||||
|
|
||||||
|
```
|
||||||
|
· UserProfile - resting heart rate, weight, VO2 max
|
||||||
|
· SensorHistory - pulse ox, body battery
|
||||||
|
· Communications + Background - only used to fetch weather, and only when an
|
||||||
|
OpenWeatherMap API key has been entered
|
||||||
|
· ComplicationSubscriber - calendar, training status, and data published by
|
||||||
|
other apps
|
||||||
|
```
|
||||||
75
docs/store-listing-zh.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Connect IQ Store 上架文案(简体中文)
|
||||||
|
|
||||||
|
## 应用名称
|
||||||
|
|
||||||
|
```
|
||||||
|
Fenix 8 V3
|
||||||
|
```
|
||||||
|
|
||||||
|
## 一句话简介(短描述)
|
||||||
|
|
||||||
|
```
|
||||||
|
多指标数据表盘:四段进度环 + 四个可自由配置的数据位,156 项数据任选,七套配色。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 详细描述
|
||||||
|
|
||||||
|
```
|
||||||
|
Fenix 8 V3 是一款信息密度高、但不拥挤的数据表盘。
|
||||||
|
|
||||||
|
■ 四段进度环
|
||||||
|
表盘外圈是四段独立的进度弧,各自绑定一项指标(步数、卡路里、距离、楼层、
|
||||||
|
动动条、活动分钟、身体电量),一眼就能看出今天的完成度。每根刻度的颜色都
|
||||||
|
是渐变的,进度越高颜色越靠近弧段末端的色相。
|
||||||
|
|
||||||
|
■ 四个数据位,156 项数据任选
|
||||||
|
左上、右上、左下、右下四格,每格都能从 156 项数据里任选:
|
||||||
|
|
||||||
|
· 时间日期 秒、UTC、第二时区 ×2、周数(ISO / 日历)、自定义时间与日期格式
|
||||||
|
· 活动 步数、卡路里、距离、楼层、爬升、活动分钟,以及本周 / 近 7 天 /
|
||||||
|
近 28 天累计
|
||||||
|
· 身体 心率、静息心率、压力、血氧、呼吸、身体电量、体重、BMI、
|
||||||
|
恢复时间、最大摄氧量
|
||||||
|
· 系统 电量、剩余天数、闹钟、通知、勿扰、蓝牙、太阳能强度
|
||||||
|
· 环境 海拔、气压、日出日落、下一太阳事件、月龄月相,
|
||||||
|
以及民用 / 航海 / 天文晨昏蒙影、黄金时刻、蓝调时刻
|
||||||
|
· 天气 气温、体感、最高最低、湿度、降水概率、风速风向、紫外线、
|
||||||
|
能见度、露点
|
||||||
|
· 手表数据 下一个日程、训练状态、完赛与配速预测(5k/10k/半马/全马)、
|
||||||
|
本周跑步与骑行距离、以及其它 App 发布的复杂功能
|
||||||
|
· 自定义 三段自定义文字、事件倒计时
|
||||||
|
|
||||||
|
底部两格的小标签会跟着所选数据自动变化,不用另外设置。
|
||||||
|
|
||||||
|
■ 七套配色
|
||||||
|
余烬、极光、黄铜、电压、珊瑚礁、酸绿、海藻。每套都有自己的进度环渐变、
|
||||||
|
日期带配色和时间数字渐变。
|
||||||
|
|
||||||
|
■ 常亮显示
|
||||||
|
AMOLED 机型支持常亮。常亮时自动切换到低功耗版面:去掉日期带底色和进度环,
|
||||||
|
时间与日期压暗,整个画面每分钟在几个像素内轻微游走以避免烧屏。
|
||||||
|
|
||||||
|
■ OpenWeatherMap(选用)
|
||||||
|
填入自己的 OpenWeatherMap API key 后,可额外获得 30 项天气数据,包括空气质量
|
||||||
|
(AQI、PM2.5、PM10、CO)与未来两日预报。不填则完全不联网。
|
||||||
|
|
||||||
|
■ 其它
|
||||||
|
· 简体中文 / 英文双语,表盘上的星期、月份、数据标签都会跟随手表语言
|
||||||
|
· 温度单位可强制摄氏或华氏,距离与时间制式跟随手表设置
|
||||||
|
· 手机断连时蓝牙图标自动压暗
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键词 / 标签
|
||||||
|
|
||||||
|
```
|
||||||
|
数据表盘, 进度环, 数据字段, 常亮, AMOLED, 天气, 心率, 身体电量, 日出日落, 中文
|
||||||
|
```
|
||||||
|
|
||||||
|
## 需要向用户说明的权限
|
||||||
|
|
||||||
|
```
|
||||||
|
· 用户档案 —— 用于静息心率、体重、最大摄氧量
|
||||||
|
· 传感器历史 —— 用于血氧、身体电量
|
||||||
|
· 通信与后台 —— 仅在填写了 OpenWeatherMap API key 时用于拉取天气
|
||||||
|
· 复杂功能订阅 —— 用于日程、训练状态、以及其它 App 发布的数据
|
||||||
|
```
|
||||||
57
manifest.xml
@@ -7,26 +7,64 @@
|
|||||||
minSdkVersion="4.2.0"
|
minSdkVersion="4.2.0"
|
||||||
name="@Strings.AppName"
|
name="@Strings.AppName"
|
||||||
type="watchface"
|
type="watchface"
|
||||||
version="1.0.0">
|
version="1.1.0">
|
||||||
|
|
||||||
<iq:products>
|
<iq:products>
|
||||||
|
<iq:product id="approachs50"/>
|
||||||
|
<iq:product id="approachs7042mm"/>
|
||||||
|
<iq:product id="approachs7047mm"/>
|
||||||
|
<iq:product id="d2airx10"/>
|
||||||
|
<iq:product id="d2mach1"/>
|
||||||
|
<iq:product id="d2mach2"/>
|
||||||
|
<iq:product id="descentg2"/>
|
||||||
|
<iq:product id="descentmk343mm"/>
|
||||||
|
<iq:product id="descentmk351mm"/>
|
||||||
<iq:product id="enduro3"/>
|
<iq:product id="enduro3"/>
|
||||||
|
<iq:product id="epix2"/>
|
||||||
|
<iq:product id="epix2pro42mm"/>
|
||||||
|
<iq:product id="epix2pro47mm"/>
|
||||||
|
<iq:product id="epix2pro51mm"/>
|
||||||
|
<iq:product id="fenix7"/>
|
||||||
|
<iq:product id="fenix7pro"/>
|
||||||
|
<iq:product id="fenix7pronowifi"/>
|
||||||
|
<iq:product id="fenix7s"/>
|
||||||
|
<iq:product id="fenix7spro"/>
|
||||||
|
<iq:product id="fenix7x"/>
|
||||||
|
<iq:product id="fenix7xpro"/>
|
||||||
|
<iq:product id="fenix7xpronowifi"/>
|
||||||
<iq:product id="fenix843mm"/>
|
<iq:product id="fenix843mm"/>
|
||||||
<iq:product id="fenix847mm"/>
|
<iq:product id="fenix847mm"/>
|
||||||
<iq:product id="fenix8pro47mm"/>
|
<iq:product id="fenix8pro47mm"/>
|
||||||
<iq:product id="fenix8solar47mm"/>
|
<iq:product id="fenix8solar47mm"/>
|
||||||
<iq:product id="fenix8solar51mm"/>
|
<iq:product id="fenix8solar51mm"/>
|
||||||
<iq:product id="epix2"/>
|
<iq:product id="fenixe"/>
|
||||||
<iq:product id="epix2pro47mm"/>
|
<iq:product id="fr165"/>
|
||||||
<iq:product id="epix2pro51mm"/>
|
<iq:product id="fr165m"/>
|
||||||
<iq:product id="fenix7"/>
|
|
||||||
<iq:product id="fenix7x"/>
|
|
||||||
<iq:product id="fenix7s"/>
|
|
||||||
<iq:product id="fr965"/>
|
|
||||||
<iq:product id="fr265"/>
|
|
||||||
<iq:product id="fr255"/>
|
<iq:product id="fr255"/>
|
||||||
|
<iq:product id="fr255m"/>
|
||||||
|
<iq:product id="fr255s"/>
|
||||||
|
<iq:product id="fr255sm"/>
|
||||||
|
<iq:product id="fr265"/>
|
||||||
|
<iq:product id="fr265s"/>
|
||||||
|
<iq:product id="fr57042mm"/>
|
||||||
|
<iq:product id="fr57047mm"/>
|
||||||
|
<iq:product id="fr955"/>
|
||||||
|
<iq:product id="fr965"/>
|
||||||
|
<iq:product id="fr970"/>
|
||||||
|
<iq:product id="instinct3amoled45mm"/>
|
||||||
|
<iq:product id="instinct3amoled50mm"/>
|
||||||
|
<iq:product id="instinctcrossoveramoled"/>
|
||||||
|
<iq:product id="marq2"/>
|
||||||
|
<iq:product id="marq2aviator"/>
|
||||||
|
<iq:product id="venu2"/>
|
||||||
|
<iq:product id="venu2plus"/>
|
||||||
|
<iq:product id="venu2s"/>
|
||||||
<iq:product id="venu3"/>
|
<iq:product id="venu3"/>
|
||||||
|
<iq:product id="venu3s"/>
|
||||||
|
<iq:product id="venu441mm"/>
|
||||||
|
<iq:product id="venu445mm"/>
|
||||||
<iq:product id="vivoactive5"/>
|
<iq:product id="vivoactive5"/>
|
||||||
|
<iq:product id="vivoactive6"/>
|
||||||
</iq:products>
|
</iq:products>
|
||||||
|
|
||||||
<iq:languages>
|
<iq:languages>
|
||||||
@@ -40,6 +78,7 @@
|
|||||||
<iq:uses-permission id="UserProfile"/>
|
<iq:uses-permission id="UserProfile"/>
|
||||||
<iq:uses-permission id="Communications"/>
|
<iq:uses-permission id="Communications"/>
|
||||||
<iq:uses-permission id="Background"/>
|
<iq:uses-permission id="Background"/>
|
||||||
|
<iq:uses-permission id="ComplicationSubscriber"/>
|
||||||
<iq:uses-permission id="SensorHistory"/>
|
<iq:uses-permission id="SensorHistory"/>
|
||||||
</iq:permissions>
|
</iq:permissions>
|
||||||
|
|
||||||
|
|||||||
@@ -2,31 +2,68 @@ project.manifest = manifest.xml
|
|||||||
|
|
||||||
base.sourcePath = source
|
base.sourcePath = source
|
||||||
|
|
||||||
# resources-chn holds the Simplified Chinese variant of every string; the
|
# resources-chn 是简体中文文案;目录名的 -chn 后缀就是语言标记。
|
||||||
# -chn suffix is what tells monkeyc which language it belongs to.
|
|
||||||
base.resourcePath = resources;resources-chn
|
base.resourcePath = resources;resources-chn
|
||||||
|
|
||||||
# Two more resource trees per device, both generated by tools/gen_icons.py:
|
# 下面每台设备再挂两棵资源树,都由 tools/gen_icons.py 生成:
|
||||||
# resources-icons-<screen width> sunrise/sunset/weather rasterised from the
|
# resources-icons-<屏宽> 按该屏幕像素网格光栅化的日出/日落/天气图标
|
||||||
# design paths for that exact pixel grid
|
# resources-launcher-<尺寸> 该机型要求尺寸的启动图标
|
||||||
# resources-launcher-<size> launcher icon at the size the device asks for
|
# 这一整段由 tools/gen_devices.py 生成,不要手改。
|
||||||
|
|
||||||
|
approachs50.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-56
|
||||||
|
approachs7042mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60
|
||||||
|
approachs7047mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-70
|
||||||
|
d2airx10.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-70
|
||||||
|
d2mach1.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
||||||
|
d2mach2.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
||||||
|
descentg2.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60
|
||||||
|
descentmk343mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60
|
||||||
|
descentmk351mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-60
|
||||||
enduro3.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
enduro3.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
||||||
epix2.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
epix2.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
||||||
|
epix2pro42mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60
|
||||||
epix2pro47mm.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
epix2pro47mm.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
||||||
epix2pro51mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-60
|
epix2pro51mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-60
|
||||||
fenix7.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
fenix7.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
||||||
|
fenix7pro.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
||||||
|
fenix7pronowifi.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
||||||
fenix7s.resourcePath = $(base.resourcePath);resources-icons-240;resources-launcher-40
|
fenix7s.resourcePath = $(base.resourcePath);resources-icons-240;resources-launcher-40
|
||||||
|
fenix7spro.resourcePath = $(base.resourcePath);resources-icons-240;resources-launcher-40
|
||||||
fenix7x.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
fenix7x.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
||||||
|
fenix7xpro.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
||||||
|
fenix7xpronowifi.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
||||||
fenix843mm.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
fenix843mm.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
||||||
fenix847mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
fenix847mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
||||||
fenix8pro47mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
fenix8pro47mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
||||||
fenix8solar47mm.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
fenix8solar47mm.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
||||||
fenix8solar51mm.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
fenix8solar51mm.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
||||||
|
fenixe.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
||||||
|
fr165.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54
|
||||||
|
fr165m.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54
|
||||||
fr255.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
fr255.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
||||||
|
fr255m.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
||||||
|
fr255s.resourcePath = $(base.resourcePath);resources-icons-218;resources-launcher-40
|
||||||
|
fr255sm.resourcePath = $(base.resourcePath);resources-icons-218;resources-launcher-40
|
||||||
fr265.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
fr265.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
||||||
|
fr265s.resourcePath = $(base.resourcePath);resources-icons-360;resources-launcher-60
|
||||||
|
fr57042mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54
|
||||||
|
fr57047mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
||||||
|
fr955.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
||||||
fr965.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
fr965.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
||||||
|
fr970.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
||||||
|
instinct3amoled45mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60
|
||||||
|
instinct3amoled50mm.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
||||||
|
instinctcrossoveramoled.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-38
|
||||||
|
marq2.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60
|
||||||
|
marq2aviator.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60
|
||||||
|
venu2.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-70
|
||||||
|
venu2plus.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-70
|
||||||
|
venu2s.resourcePath = $(base.resourcePath);resources-icons-360;resources-launcher-61
|
||||||
venu3.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-70
|
venu3.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-70
|
||||||
|
venu3s.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-70
|
||||||
|
venu441mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54
|
||||||
|
venu445mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
||||||
vivoactive5.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-56
|
vivoactive5.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-56
|
||||||
|
vivoactive6.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54
|
||||||
|
|
||||||
base.excludeAnnotations = release
|
base.excludeAnnotations = release
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
<string id="FldSeconds">秒</string>
|
<string id="FldSeconds">秒</string>
|
||||||
<string id="FldSeconds0">秒 (前导零)</string>
|
<string id="FldSeconds0">秒 (前导零)</string>
|
||||||
<string id="FldWeekCal">周数</string>
|
<string id="FldWeekCal">周数</string>
|
||||||
|
<string id="FldCustomTime">自定义时间格式</string>
|
||||||
|
<string id="FldDateFmt1">日期格式 1</string>
|
||||||
|
<string id="FldDateFmt2">日期格式 2</string>
|
||||||
|
<string id="FldCountdown">事件倒计时</string>
|
||||||
<string id="FldSteps">步数</string>
|
<string id="FldSteps">步数</string>
|
||||||
<string id="FldStepsRound">步数 (取整)</string>
|
<string id="FldStepsRound">步数 (取整)</string>
|
||||||
<string id="FldStepsToGo">剩余步数</string>
|
<string id="FldStepsToGo">剩余步数</string>
|
||||||
@@ -42,6 +46,10 @@
|
|||||||
<string id="FldFloorsWeek">本周楼层</string>
|
<string id="FldFloorsWeek">本周楼层</string>
|
||||||
<string id="FldFloors7d">近 7 天楼层</string>
|
<string id="FldFloors7d">近 7 天楼层</string>
|
||||||
<string id="FldActiveMin7d">近 7 天活动分钟</string>
|
<string id="FldActiveMin7d">近 7 天活动分钟</string>
|
||||||
|
<string id="FldSteps28d">近 28 天步数</string>
|
||||||
|
<string id="FldCal28d">近 28 天卡路里</string>
|
||||||
|
<string id="FldDist28d">近 28 天距离</string>
|
||||||
|
<string id="FldFloors28d">近 28 天楼层</string>
|
||||||
<string id="FldHeartRate">心率</string>
|
<string id="FldHeartRate">心率</string>
|
||||||
<string id="FldRestingHr">静息心率</string>
|
<string id="FldRestingHr">静息心率</string>
|
||||||
<string id="FldStress">压力指数</string>
|
<string id="FldStress">压力指数</string>
|
||||||
@@ -97,6 +105,24 @@
|
|||||||
<string id="FldUvi">紫外线指数</string>
|
<string id="FldUvi">紫外线指数</string>
|
||||||
<string id="FldVisibility">能见度</string>
|
<string id="FldVisibility">能见度</string>
|
||||||
<string id="FldDewPoint">露点</string>
|
<string id="FldDewPoint">露点</string>
|
||||||
|
<string id="FldNextCal">下一个日程</string>
|
||||||
|
<string id="FldTrainStatus">训练状态</string>
|
||||||
|
<string id="FldRace5k">5k 完赛预测</string>
|
||||||
|
<string id="FldRace10k">10k 完赛预测</string>
|
||||||
|
<string id="FldRaceHalf">半马完赛预测</string>
|
||||||
|
<string id="FldRaceFull">全马完赛预测</string>
|
||||||
|
<string id="FldPace5k">5k 配速预测</string>
|
||||||
|
<string id="FldPace10k">10k 配速预测</string>
|
||||||
|
<string id="FldPaceHalf">半马配速预测</string>
|
||||||
|
<string id="FldPaceFull">全马配速预测</string>
|
||||||
|
<string id="FldGolfScore">最近高尔夫得分</string>
|
||||||
|
<string id="FldRunWeek">本周跑步距离</string>
|
||||||
|
<string id="FldBikeWeek">本周骑行距离</string>
|
||||||
|
<string id="FldApp1">第三方 App 1</string>
|
||||||
|
<string id="FldApp2">第三方 App 2</string>
|
||||||
|
<string id="FldApp3">第三方 App 3</string>
|
||||||
|
<string id="FldApp4">第三方 App 4</string>
|
||||||
|
<string id="FldApp5">第三方 App 5</string>
|
||||||
<string id="FldOwmTemp">OWM 气温</string>
|
<string id="FldOwmTemp">OWM 气温</string>
|
||||||
<string id="FldOwmMinMax">OWM 最高/最低</string>
|
<string id="FldOwmMinMax">OWM 最高/最低</string>
|
||||||
<string id="FldOwmFeels">OWM 体感温度</string>
|
<string id="FldOwmFeels">OWM 体感温度</string>
|
||||||
@@ -120,6 +146,11 @@
|
|||||||
<string id="FldOwmMinMaxTmrw">OWM 明日高低温</string>
|
<string id="FldOwmMinMaxTmrw">OWM 明日高低温</string>
|
||||||
<string id="FldOwmDayAfter">OWM 后日气温</string>
|
<string id="FldOwmDayAfter">OWM 后日气温</string>
|
||||||
<string id="FldOwmMinMaxD2">OWM 后日高低温</string>
|
<string id="FldOwmMinMaxD2">OWM 后日高低温</string>
|
||||||
|
<string id="FldOwmAqi">OWM 空气质量</string>
|
||||||
|
<string id="FldOwmAqiText">OWM 空气质量描述</string>
|
||||||
|
<string id="FldOwmPm25">OWM PM2.5</string>
|
||||||
|
<string id="FldOwmPm10">OWM PM10</string>
|
||||||
|
<string id="FldOwmCo">OWM 一氧化碳</string>
|
||||||
<string id="FldOwmCity">OWM 城市</string>
|
<string id="FldOwmCity">OWM 城市</string>
|
||||||
<string id="FldOwmUpdated">OWM 更新时间</string>
|
<string id="FldOwmUpdated">OWM 更新时间</string>
|
||||||
<string id="FldCustom1">自定义文字 1</string>
|
<string id="FldCustom1">自定义文字 1</string>
|
||||||
@@ -127,7 +158,7 @@
|
|||||||
<string id="FldCustom3">自定义文字 3</string>
|
<string id="FldCustom3">自定义文字 3</string>
|
||||||
|
|
||||||
<!-- On-face captions, in FieldTable.IDS order. -->
|
<!-- On-face captions, in FieldTable.IDS order. -->
|
||||||
<string id="FieldLabels">,时间,UTC,时区1,时区2,日期,日期,日期,日,星期,星期,月,月,,周,秒,秒,周,步数,步数,还差,千卡,活动,分钟,中强度,高强度,周分钟,距离,楼层,下楼,爬升,推行,推行,周步数,7天,周千卡,7天,周距离,7天,周楼层,7天,7天,心率,静息,压力,血氧,呼吸,体能,活动,活动,体重,BMI,恢复,摄氧,摄氧,电量,天数,电量,闹钟,通知,勿扰,蓝牙,太阳能,海拔,气压,气压,日出,日落,太阳,太阳,月龄,月相,民用晨,航海晨,天文晨,蓝调晨,黄金晨,黄金昏,蓝调昏,民用昏,航海昏,天文昏,气温,高低温,体感,最高,最低,湿度,降水,风速,风向,风向,更新,气压,紫外线,能见度,露点,气温,高低温,体感,最高,最低,天气,天气,湿度,气压,能见度,风速,风向,风向,阵风,降雨,降雪,云量,降水,露点,明日,明日,后日,后日,城市,更新,,,</string>
|
<string id="FieldLabels">,时间,UTC,时区1,时区2,日期,日期,日期,日,星期,星期,月,月,,周,秒,秒,周,时间,日期,日期,倒计时,步数,步数,还差,千卡,活动,分钟,中强度,高强度,周分钟,距离,楼层,下楼,爬升,推行,推行,周步数,7天,周千卡,7天,周距离,7天,周楼层,7天,7天,28天,28天,28天,28天,心率,静息,压力,血氧,呼吸,体能,活动,活动,体重,BMI,恢复,摄氧,摄氧,电量,天数,电量,闹钟,通知,勿扰,蓝牙,太阳能,海拔,气压,气压,日出,日落,太阳,太阳,月龄,月相,民用晨,航海晨,天文晨,蓝调晨,黄金晨,黄金昏,蓝调昏,民用昏,航海昏,天文昏,气温,高低温,体感,最高,最低,湿度,降水,风速,风向,风向,更新,气压,紫外线,能见度,露点,日程,训练,5K,10K,半马,全马,5K配速,10K配速,半马配速,全马配速,高尔夫,跑步,骑行,App1,App2,App3,App4,App5,气温,高低温,体感,最高,最低,天气,天气,湿度,气压,能见度,风速,风向,风向,阵风,降雨,降雪,云量,降水,露点,明日,明日,后日,后日,空气,空气,PM2.5,PM10,CO,城市,更新,,,</string>
|
||||||
|
|
||||||
<string id="MetricSteps">步数</string>
|
<string id="MetricSteps">步数</string>
|
||||||
<string id="MetricCalories">卡路里</string>
|
<string id="MetricCalories">卡路里</string>
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
<string id="SettingTempUnit">温度单位</string>
|
<string id="SettingTempUnit">温度单位</string>
|
||||||
<string id="SettingAltOffset1">第二时区 1(相对本地分钟数)</string>
|
<string id="SettingAltOffset1">第二时区 1(相对本地分钟数)</string>
|
||||||
<string id="SettingAltOffset2">第二时区 2(相对本地分钟数)</string>
|
<string id="SettingAltOffset2">第二时区 2(相对本地分钟数)</string>
|
||||||
|
<string id="SettingCountdownDate">倒计时目标日期(YYYY-MM-DD)</string>
|
||||||
|
<string id="SettingTimeFmt">自定义时间格式(h m a)</string>
|
||||||
|
<string id="SettingDateFmt1">日期格式 1(D M Y w)</string>
|
||||||
|
<string id="SettingDateFmt2">日期格式 2(D M Y w)</string>
|
||||||
<string id="SettingOwmKey">OpenWeatherMap API 密钥</string>
|
<string id="SettingOwmKey">OpenWeatherMap API 密钥</string>
|
||||||
<string id="SettingCustom1">自定义文字 1</string>
|
<string id="SettingCustom1">自定义文字 1</string>
|
||||||
<string id="SettingCustom2">自定义文字 2</string>
|
<string id="SettingCustom2">自定义文字 2</string>
|
||||||
|
|||||||
5
resources-icons-218/drawables/drawables.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<drawables>
|
||||||
|
<bitmap id="IconSunrise" filename="sunrise.png" packingFormat="png"/>
|
||||||
|
<bitmap id="IconSunset" filename="sunset.png" packingFormat="png"/>
|
||||||
|
<bitmap id="IconWeather" filename="weather.png" packingFormat="png"/>
|
||||||
|
</drawables>
|
||||||
BIN
resources-icons-218/drawables/sunrise.png
Normal file
|
After Width: | Height: | Size: 151 B |
BIN
resources-icons-218/drawables/sunset.png
Normal file
|
After Width: | Height: | Size: 148 B |
BIN
resources-icons-218/drawables/weather.png
Normal file
|
After Width: | Height: | Size: 148 B |
|
Before Width: | Height: | Size: 157 B After Width: | Height: | Size: 153 B |
5
resources-icons-360/drawables/drawables.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<drawables>
|
||||||
|
<bitmap id="IconSunrise" filename="sunrise.png" packingFormat="png"/>
|
||||||
|
<bitmap id="IconSunset" filename="sunset.png" packingFormat="png"/>
|
||||||
|
<bitmap id="IconWeather" filename="weather.png" packingFormat="png"/>
|
||||||
|
</drawables>
|
||||||
BIN
resources-icons-360/drawables/sunrise.png
Normal file
|
After Width: | Height: | Size: 579 B |
BIN
resources-icons-360/drawables/sunset.png
Normal file
|
After Width: | Height: | Size: 583 B |
BIN
resources-icons-360/drawables/weather.png
Normal file
|
After Width: | Height: | Size: 621 B |
3
resources-launcher-38/drawables/drawables.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<drawables>
|
||||||
|
<bitmap id="LauncherIcon" filename="launcher_icon.png"/>
|
||||||
|
</drawables>
|
||||||
BIN
resources-launcher-38/drawables/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
3
resources-launcher-54/drawables/drawables.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<drawables>
|
||||||
|
<bitmap id="LauncherIcon" filename="launcher_icon.png"/>
|
||||||
|
</drawables>
|
||||||
BIN
resources-launcher-54/drawables/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
3
resources-launcher-61/drawables/drawables.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<drawables>
|
||||||
|
<bitmap id="LauncherIcon" filename="launcher_icon.png"/>
|
||||||
|
</drawables>
|
||||||
BIN
resources-launcher-61/drawables/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
@@ -13,6 +13,10 @@
|
|||||||
<property id="TempUnit" type="number">0</property>
|
<property id="TempUnit" type="number">0</property>
|
||||||
<property id="AltOffset1" type="number">0</property>
|
<property id="AltOffset1" type="number">0</property>
|
||||||
<property id="AltOffset2" type="number">0</property>
|
<property id="AltOffset2" type="number">0</property>
|
||||||
|
<property id="CountdownDate" type="string"></property>
|
||||||
|
<property id="TimeFmt" type="string">h:m a</property>
|
||||||
|
<property id="DateFmt1" type="string">D/M</property>
|
||||||
|
<property id="DateFmt2" type="string">w D M</property>
|
||||||
<property id="OwmKey" type="string"></property>
|
<property id="OwmKey" type="string"></property>
|
||||||
<property id="Custom1" type="string"></property>
|
<property id="Custom1" type="string"></property>
|
||||||
<property id="Custom2" type="string"></property>
|
<property id="Custom2" type="string"></property>
|
||||||
|
|||||||
@@ -32,6 +32,10 @@
|
|||||||
<listEntry value="850">@Strings.FldSeconds</listEntry>
|
<listEntry value="850">@Strings.FldSeconds</listEntry>
|
||||||
<listEntry value="869">@Strings.FldSeconds0</listEntry>
|
<listEntry value="869">@Strings.FldSeconds0</listEntry>
|
||||||
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
||||||
|
<listEntry value="861">@Strings.FldCustomTime</listEntry>
|
||||||
|
<listEntry value="862">@Strings.FldDateFmt1</listEntry>
|
||||||
|
<listEntry value="863">@Strings.FldDateFmt2</listEntry>
|
||||||
|
<listEntry value="735">@Strings.FldCountdown</listEntry>
|
||||||
<listEntry value="1">@Strings.FldSteps</listEntry>
|
<listEntry value="1">@Strings.FldSteps</listEntry>
|
||||||
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
||||||
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
||||||
@@ -56,6 +60,10 @@
|
|||||||
<listEntry value="203">@Strings.FldFloorsWeek</listEntry>
|
<listEntry value="203">@Strings.FldFloorsWeek</listEntry>
|
||||||
<listEntry value="204">@Strings.FldFloors7d</listEntry>
|
<listEntry value="204">@Strings.FldFloors7d</listEntry>
|
||||||
<listEntry value="200">@Strings.FldActiveMin7d</listEntry>
|
<listEntry value="200">@Strings.FldActiveMin7d</listEntry>
|
||||||
|
<listEntry value="828">@Strings.FldSteps28d</listEntry>
|
||||||
|
<listEntry value="832">@Strings.FldCal28d</listEntry>
|
||||||
|
<listEntry value="836">@Strings.FldDist28d</listEntry>
|
||||||
|
<listEntry value="840">@Strings.FldFloors28d</listEntry>
|
||||||
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
||||||
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
||||||
<listEntry value="12">@Strings.FldStress</listEntry>
|
<listEntry value="12">@Strings.FldStress</listEntry>
|
||||||
@@ -111,6 +119,24 @@
|
|||||||
<listEntry value="313">@Strings.FldUvi</listEntry>
|
<listEntry value="313">@Strings.FldUvi</listEntry>
|
||||||
<listEntry value="314">@Strings.FldVisibility</listEntry>
|
<listEntry value="314">@Strings.FldVisibility</listEntry>
|
||||||
<listEntry value="315">@Strings.FldDewPoint</listEntry>
|
<listEntry value="315">@Strings.FldDewPoint</listEntry>
|
||||||
|
<listEntry value="612">@Strings.FldNextCal</listEntry>
|
||||||
|
<listEntry value="626">@Strings.FldTrainStatus</listEntry>
|
||||||
|
<listEntry value="627">@Strings.FldRace5k</listEntry>
|
||||||
|
<listEntry value="628">@Strings.FldRace10k</listEntry>
|
||||||
|
<listEntry value="629">@Strings.FldRaceHalf</listEntry>
|
||||||
|
<listEntry value="630">@Strings.FldRaceFull</listEntry>
|
||||||
|
<listEntry value="631">@Strings.FldPace5k</listEntry>
|
||||||
|
<listEntry value="632">@Strings.FldPace10k</listEntry>
|
||||||
|
<listEntry value="633">@Strings.FldPaceHalf</listEntry>
|
||||||
|
<listEntry value="634">@Strings.FldPaceFull</listEntry>
|
||||||
|
<listEntry value="641">@Strings.FldGolfScore</listEntry>
|
||||||
|
<listEntry value="619">@Strings.FldRunWeek</listEntry>
|
||||||
|
<listEntry value="620">@Strings.FldBikeWeek</listEntry>
|
||||||
|
<listEntry value="695">@Strings.FldApp1</listEntry>
|
||||||
|
<listEntry value="696">@Strings.FldApp2</listEntry>
|
||||||
|
<listEntry value="697">@Strings.FldApp3</listEntry>
|
||||||
|
<listEntry value="698">@Strings.FldApp4</listEntry>
|
||||||
|
<listEntry value="699">@Strings.FldApp5</listEntry>
|
||||||
<listEntry value="500">@Strings.FldOwmTemp</listEntry>
|
<listEntry value="500">@Strings.FldOwmTemp</listEntry>
|
||||||
<listEntry value="515">@Strings.FldOwmMinMax</listEntry>
|
<listEntry value="515">@Strings.FldOwmMinMax</listEntry>
|
||||||
<listEntry value="503">@Strings.FldOwmFeels</listEntry>
|
<listEntry value="503">@Strings.FldOwmFeels</listEntry>
|
||||||
@@ -134,6 +160,11 @@
|
|||||||
<listEntry value="522">@Strings.FldOwmMinMaxTmrw</listEntry>
|
<listEntry value="522">@Strings.FldOwmMinMaxTmrw</listEntry>
|
||||||
<listEntry value="535">@Strings.FldOwmDayAfter</listEntry>
|
<listEntry value="535">@Strings.FldOwmDayAfter</listEntry>
|
||||||
<listEntry value="523">@Strings.FldOwmMinMaxD2</listEntry>
|
<listEntry value="523">@Strings.FldOwmMinMaxD2</listEntry>
|
||||||
|
<listEntry value="538">@Strings.FldOwmAqi</listEntry>
|
||||||
|
<listEntry value="539">@Strings.FldOwmAqiText</listEntry>
|
||||||
|
<listEntry value="540">@Strings.FldOwmPm25</listEntry>
|
||||||
|
<listEntry value="541">@Strings.FldOwmPm10</listEntry>
|
||||||
|
<listEntry value="542">@Strings.FldOwmCo</listEntry>
|
||||||
<listEntry value="516">@Strings.FldOwmCity</listEntry>
|
<listEntry value="516">@Strings.FldOwmCity</listEntry>
|
||||||
<listEntry value="517">@Strings.FldOwmUpdated</listEntry>
|
<listEntry value="517">@Strings.FldOwmUpdated</listEntry>
|
||||||
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
||||||
@@ -162,6 +193,10 @@
|
|||||||
<listEntry value="850">@Strings.FldSeconds</listEntry>
|
<listEntry value="850">@Strings.FldSeconds</listEntry>
|
||||||
<listEntry value="869">@Strings.FldSeconds0</listEntry>
|
<listEntry value="869">@Strings.FldSeconds0</listEntry>
|
||||||
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
||||||
|
<listEntry value="861">@Strings.FldCustomTime</listEntry>
|
||||||
|
<listEntry value="862">@Strings.FldDateFmt1</listEntry>
|
||||||
|
<listEntry value="863">@Strings.FldDateFmt2</listEntry>
|
||||||
|
<listEntry value="735">@Strings.FldCountdown</listEntry>
|
||||||
<listEntry value="1">@Strings.FldSteps</listEntry>
|
<listEntry value="1">@Strings.FldSteps</listEntry>
|
||||||
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
||||||
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
||||||
@@ -186,6 +221,10 @@
|
|||||||
<listEntry value="203">@Strings.FldFloorsWeek</listEntry>
|
<listEntry value="203">@Strings.FldFloorsWeek</listEntry>
|
||||||
<listEntry value="204">@Strings.FldFloors7d</listEntry>
|
<listEntry value="204">@Strings.FldFloors7d</listEntry>
|
||||||
<listEntry value="200">@Strings.FldActiveMin7d</listEntry>
|
<listEntry value="200">@Strings.FldActiveMin7d</listEntry>
|
||||||
|
<listEntry value="828">@Strings.FldSteps28d</listEntry>
|
||||||
|
<listEntry value="832">@Strings.FldCal28d</listEntry>
|
||||||
|
<listEntry value="836">@Strings.FldDist28d</listEntry>
|
||||||
|
<listEntry value="840">@Strings.FldFloors28d</listEntry>
|
||||||
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
||||||
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
||||||
<listEntry value="12">@Strings.FldStress</listEntry>
|
<listEntry value="12">@Strings.FldStress</listEntry>
|
||||||
@@ -241,6 +280,24 @@
|
|||||||
<listEntry value="313">@Strings.FldUvi</listEntry>
|
<listEntry value="313">@Strings.FldUvi</listEntry>
|
||||||
<listEntry value="314">@Strings.FldVisibility</listEntry>
|
<listEntry value="314">@Strings.FldVisibility</listEntry>
|
||||||
<listEntry value="315">@Strings.FldDewPoint</listEntry>
|
<listEntry value="315">@Strings.FldDewPoint</listEntry>
|
||||||
|
<listEntry value="612">@Strings.FldNextCal</listEntry>
|
||||||
|
<listEntry value="626">@Strings.FldTrainStatus</listEntry>
|
||||||
|
<listEntry value="627">@Strings.FldRace5k</listEntry>
|
||||||
|
<listEntry value="628">@Strings.FldRace10k</listEntry>
|
||||||
|
<listEntry value="629">@Strings.FldRaceHalf</listEntry>
|
||||||
|
<listEntry value="630">@Strings.FldRaceFull</listEntry>
|
||||||
|
<listEntry value="631">@Strings.FldPace5k</listEntry>
|
||||||
|
<listEntry value="632">@Strings.FldPace10k</listEntry>
|
||||||
|
<listEntry value="633">@Strings.FldPaceHalf</listEntry>
|
||||||
|
<listEntry value="634">@Strings.FldPaceFull</listEntry>
|
||||||
|
<listEntry value="641">@Strings.FldGolfScore</listEntry>
|
||||||
|
<listEntry value="619">@Strings.FldRunWeek</listEntry>
|
||||||
|
<listEntry value="620">@Strings.FldBikeWeek</listEntry>
|
||||||
|
<listEntry value="695">@Strings.FldApp1</listEntry>
|
||||||
|
<listEntry value="696">@Strings.FldApp2</listEntry>
|
||||||
|
<listEntry value="697">@Strings.FldApp3</listEntry>
|
||||||
|
<listEntry value="698">@Strings.FldApp4</listEntry>
|
||||||
|
<listEntry value="699">@Strings.FldApp5</listEntry>
|
||||||
<listEntry value="500">@Strings.FldOwmTemp</listEntry>
|
<listEntry value="500">@Strings.FldOwmTemp</listEntry>
|
||||||
<listEntry value="515">@Strings.FldOwmMinMax</listEntry>
|
<listEntry value="515">@Strings.FldOwmMinMax</listEntry>
|
||||||
<listEntry value="503">@Strings.FldOwmFeels</listEntry>
|
<listEntry value="503">@Strings.FldOwmFeels</listEntry>
|
||||||
@@ -264,6 +321,11 @@
|
|||||||
<listEntry value="522">@Strings.FldOwmMinMaxTmrw</listEntry>
|
<listEntry value="522">@Strings.FldOwmMinMaxTmrw</listEntry>
|
||||||
<listEntry value="535">@Strings.FldOwmDayAfter</listEntry>
|
<listEntry value="535">@Strings.FldOwmDayAfter</listEntry>
|
||||||
<listEntry value="523">@Strings.FldOwmMinMaxD2</listEntry>
|
<listEntry value="523">@Strings.FldOwmMinMaxD2</listEntry>
|
||||||
|
<listEntry value="538">@Strings.FldOwmAqi</listEntry>
|
||||||
|
<listEntry value="539">@Strings.FldOwmAqiText</listEntry>
|
||||||
|
<listEntry value="540">@Strings.FldOwmPm25</listEntry>
|
||||||
|
<listEntry value="541">@Strings.FldOwmPm10</listEntry>
|
||||||
|
<listEntry value="542">@Strings.FldOwmCo</listEntry>
|
||||||
<listEntry value="516">@Strings.FldOwmCity</listEntry>
|
<listEntry value="516">@Strings.FldOwmCity</listEntry>
|
||||||
<listEntry value="517">@Strings.FldOwmUpdated</listEntry>
|
<listEntry value="517">@Strings.FldOwmUpdated</listEntry>
|
||||||
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
||||||
@@ -292,6 +354,10 @@
|
|||||||
<listEntry value="850">@Strings.FldSeconds</listEntry>
|
<listEntry value="850">@Strings.FldSeconds</listEntry>
|
||||||
<listEntry value="869">@Strings.FldSeconds0</listEntry>
|
<listEntry value="869">@Strings.FldSeconds0</listEntry>
|
||||||
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
||||||
|
<listEntry value="861">@Strings.FldCustomTime</listEntry>
|
||||||
|
<listEntry value="862">@Strings.FldDateFmt1</listEntry>
|
||||||
|
<listEntry value="863">@Strings.FldDateFmt2</listEntry>
|
||||||
|
<listEntry value="735">@Strings.FldCountdown</listEntry>
|
||||||
<listEntry value="1">@Strings.FldSteps</listEntry>
|
<listEntry value="1">@Strings.FldSteps</listEntry>
|
||||||
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
||||||
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
||||||
@@ -316,6 +382,10 @@
|
|||||||
<listEntry value="203">@Strings.FldFloorsWeek</listEntry>
|
<listEntry value="203">@Strings.FldFloorsWeek</listEntry>
|
||||||
<listEntry value="204">@Strings.FldFloors7d</listEntry>
|
<listEntry value="204">@Strings.FldFloors7d</listEntry>
|
||||||
<listEntry value="200">@Strings.FldActiveMin7d</listEntry>
|
<listEntry value="200">@Strings.FldActiveMin7d</listEntry>
|
||||||
|
<listEntry value="828">@Strings.FldSteps28d</listEntry>
|
||||||
|
<listEntry value="832">@Strings.FldCal28d</listEntry>
|
||||||
|
<listEntry value="836">@Strings.FldDist28d</listEntry>
|
||||||
|
<listEntry value="840">@Strings.FldFloors28d</listEntry>
|
||||||
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
||||||
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
||||||
<listEntry value="12">@Strings.FldStress</listEntry>
|
<listEntry value="12">@Strings.FldStress</listEntry>
|
||||||
@@ -371,6 +441,24 @@
|
|||||||
<listEntry value="313">@Strings.FldUvi</listEntry>
|
<listEntry value="313">@Strings.FldUvi</listEntry>
|
||||||
<listEntry value="314">@Strings.FldVisibility</listEntry>
|
<listEntry value="314">@Strings.FldVisibility</listEntry>
|
||||||
<listEntry value="315">@Strings.FldDewPoint</listEntry>
|
<listEntry value="315">@Strings.FldDewPoint</listEntry>
|
||||||
|
<listEntry value="612">@Strings.FldNextCal</listEntry>
|
||||||
|
<listEntry value="626">@Strings.FldTrainStatus</listEntry>
|
||||||
|
<listEntry value="627">@Strings.FldRace5k</listEntry>
|
||||||
|
<listEntry value="628">@Strings.FldRace10k</listEntry>
|
||||||
|
<listEntry value="629">@Strings.FldRaceHalf</listEntry>
|
||||||
|
<listEntry value="630">@Strings.FldRaceFull</listEntry>
|
||||||
|
<listEntry value="631">@Strings.FldPace5k</listEntry>
|
||||||
|
<listEntry value="632">@Strings.FldPace10k</listEntry>
|
||||||
|
<listEntry value="633">@Strings.FldPaceHalf</listEntry>
|
||||||
|
<listEntry value="634">@Strings.FldPaceFull</listEntry>
|
||||||
|
<listEntry value="641">@Strings.FldGolfScore</listEntry>
|
||||||
|
<listEntry value="619">@Strings.FldRunWeek</listEntry>
|
||||||
|
<listEntry value="620">@Strings.FldBikeWeek</listEntry>
|
||||||
|
<listEntry value="695">@Strings.FldApp1</listEntry>
|
||||||
|
<listEntry value="696">@Strings.FldApp2</listEntry>
|
||||||
|
<listEntry value="697">@Strings.FldApp3</listEntry>
|
||||||
|
<listEntry value="698">@Strings.FldApp4</listEntry>
|
||||||
|
<listEntry value="699">@Strings.FldApp5</listEntry>
|
||||||
<listEntry value="500">@Strings.FldOwmTemp</listEntry>
|
<listEntry value="500">@Strings.FldOwmTemp</listEntry>
|
||||||
<listEntry value="515">@Strings.FldOwmMinMax</listEntry>
|
<listEntry value="515">@Strings.FldOwmMinMax</listEntry>
|
||||||
<listEntry value="503">@Strings.FldOwmFeels</listEntry>
|
<listEntry value="503">@Strings.FldOwmFeels</listEntry>
|
||||||
@@ -394,6 +482,11 @@
|
|||||||
<listEntry value="522">@Strings.FldOwmMinMaxTmrw</listEntry>
|
<listEntry value="522">@Strings.FldOwmMinMaxTmrw</listEntry>
|
||||||
<listEntry value="535">@Strings.FldOwmDayAfter</listEntry>
|
<listEntry value="535">@Strings.FldOwmDayAfter</listEntry>
|
||||||
<listEntry value="523">@Strings.FldOwmMinMaxD2</listEntry>
|
<listEntry value="523">@Strings.FldOwmMinMaxD2</listEntry>
|
||||||
|
<listEntry value="538">@Strings.FldOwmAqi</listEntry>
|
||||||
|
<listEntry value="539">@Strings.FldOwmAqiText</listEntry>
|
||||||
|
<listEntry value="540">@Strings.FldOwmPm25</listEntry>
|
||||||
|
<listEntry value="541">@Strings.FldOwmPm10</listEntry>
|
||||||
|
<listEntry value="542">@Strings.FldOwmCo</listEntry>
|
||||||
<listEntry value="516">@Strings.FldOwmCity</listEntry>
|
<listEntry value="516">@Strings.FldOwmCity</listEntry>
|
||||||
<listEntry value="517">@Strings.FldOwmUpdated</listEntry>
|
<listEntry value="517">@Strings.FldOwmUpdated</listEntry>
|
||||||
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
||||||
@@ -422,6 +515,10 @@
|
|||||||
<listEntry value="850">@Strings.FldSeconds</listEntry>
|
<listEntry value="850">@Strings.FldSeconds</listEntry>
|
||||||
<listEntry value="869">@Strings.FldSeconds0</listEntry>
|
<listEntry value="869">@Strings.FldSeconds0</listEntry>
|
||||||
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
||||||
|
<listEntry value="861">@Strings.FldCustomTime</listEntry>
|
||||||
|
<listEntry value="862">@Strings.FldDateFmt1</listEntry>
|
||||||
|
<listEntry value="863">@Strings.FldDateFmt2</listEntry>
|
||||||
|
<listEntry value="735">@Strings.FldCountdown</listEntry>
|
||||||
<listEntry value="1">@Strings.FldSteps</listEntry>
|
<listEntry value="1">@Strings.FldSteps</listEntry>
|
||||||
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
||||||
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
||||||
@@ -446,6 +543,10 @@
|
|||||||
<listEntry value="203">@Strings.FldFloorsWeek</listEntry>
|
<listEntry value="203">@Strings.FldFloorsWeek</listEntry>
|
||||||
<listEntry value="204">@Strings.FldFloors7d</listEntry>
|
<listEntry value="204">@Strings.FldFloors7d</listEntry>
|
||||||
<listEntry value="200">@Strings.FldActiveMin7d</listEntry>
|
<listEntry value="200">@Strings.FldActiveMin7d</listEntry>
|
||||||
|
<listEntry value="828">@Strings.FldSteps28d</listEntry>
|
||||||
|
<listEntry value="832">@Strings.FldCal28d</listEntry>
|
||||||
|
<listEntry value="836">@Strings.FldDist28d</listEntry>
|
||||||
|
<listEntry value="840">@Strings.FldFloors28d</listEntry>
|
||||||
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
||||||
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
||||||
<listEntry value="12">@Strings.FldStress</listEntry>
|
<listEntry value="12">@Strings.FldStress</listEntry>
|
||||||
@@ -501,6 +602,24 @@
|
|||||||
<listEntry value="313">@Strings.FldUvi</listEntry>
|
<listEntry value="313">@Strings.FldUvi</listEntry>
|
||||||
<listEntry value="314">@Strings.FldVisibility</listEntry>
|
<listEntry value="314">@Strings.FldVisibility</listEntry>
|
||||||
<listEntry value="315">@Strings.FldDewPoint</listEntry>
|
<listEntry value="315">@Strings.FldDewPoint</listEntry>
|
||||||
|
<listEntry value="612">@Strings.FldNextCal</listEntry>
|
||||||
|
<listEntry value="626">@Strings.FldTrainStatus</listEntry>
|
||||||
|
<listEntry value="627">@Strings.FldRace5k</listEntry>
|
||||||
|
<listEntry value="628">@Strings.FldRace10k</listEntry>
|
||||||
|
<listEntry value="629">@Strings.FldRaceHalf</listEntry>
|
||||||
|
<listEntry value="630">@Strings.FldRaceFull</listEntry>
|
||||||
|
<listEntry value="631">@Strings.FldPace5k</listEntry>
|
||||||
|
<listEntry value="632">@Strings.FldPace10k</listEntry>
|
||||||
|
<listEntry value="633">@Strings.FldPaceHalf</listEntry>
|
||||||
|
<listEntry value="634">@Strings.FldPaceFull</listEntry>
|
||||||
|
<listEntry value="641">@Strings.FldGolfScore</listEntry>
|
||||||
|
<listEntry value="619">@Strings.FldRunWeek</listEntry>
|
||||||
|
<listEntry value="620">@Strings.FldBikeWeek</listEntry>
|
||||||
|
<listEntry value="695">@Strings.FldApp1</listEntry>
|
||||||
|
<listEntry value="696">@Strings.FldApp2</listEntry>
|
||||||
|
<listEntry value="697">@Strings.FldApp3</listEntry>
|
||||||
|
<listEntry value="698">@Strings.FldApp4</listEntry>
|
||||||
|
<listEntry value="699">@Strings.FldApp5</listEntry>
|
||||||
<listEntry value="500">@Strings.FldOwmTemp</listEntry>
|
<listEntry value="500">@Strings.FldOwmTemp</listEntry>
|
||||||
<listEntry value="515">@Strings.FldOwmMinMax</listEntry>
|
<listEntry value="515">@Strings.FldOwmMinMax</listEntry>
|
||||||
<listEntry value="503">@Strings.FldOwmFeels</listEntry>
|
<listEntry value="503">@Strings.FldOwmFeels</listEntry>
|
||||||
@@ -524,6 +643,11 @@
|
|||||||
<listEntry value="522">@Strings.FldOwmMinMaxTmrw</listEntry>
|
<listEntry value="522">@Strings.FldOwmMinMaxTmrw</listEntry>
|
||||||
<listEntry value="535">@Strings.FldOwmDayAfter</listEntry>
|
<listEntry value="535">@Strings.FldOwmDayAfter</listEntry>
|
||||||
<listEntry value="523">@Strings.FldOwmMinMaxD2</listEntry>
|
<listEntry value="523">@Strings.FldOwmMinMaxD2</listEntry>
|
||||||
|
<listEntry value="538">@Strings.FldOwmAqi</listEntry>
|
||||||
|
<listEntry value="539">@Strings.FldOwmAqiText</listEntry>
|
||||||
|
<listEntry value="540">@Strings.FldOwmPm25</listEntry>
|
||||||
|
<listEntry value="541">@Strings.FldOwmPm10</listEntry>
|
||||||
|
<listEntry value="542">@Strings.FldOwmCo</listEntry>
|
||||||
<listEntry value="516">@Strings.FldOwmCity</listEntry>
|
<listEntry value="516">@Strings.FldOwmCity</listEntry>
|
||||||
<listEntry value="517">@Strings.FldOwmUpdated</listEntry>
|
<listEntry value="517">@Strings.FldOwmUpdated</listEntry>
|
||||||
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
||||||
@@ -597,6 +721,22 @@
|
|||||||
<settingConfig type="numeric" min="-720" max="840"/>
|
<settingConfig type="numeric" min="-720" max="840"/>
|
||||||
</setting>
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.CountdownDate" title="@Strings.SettingCountdownDate">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="10"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.TimeFmt" title="@Strings.SettingTimeFmt">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="16"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.DateFmt1" title="@Strings.SettingDateFmt1">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="16"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.DateFmt2" title="@Strings.SettingDateFmt2">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="16"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
<setting propertyKey="@Properties.OwmKey" title="@Strings.SettingOwmKey">
|
<setting propertyKey="@Properties.OwmKey" title="@Strings.SettingOwmKey">
|
||||||
<settingConfig type="alphaNumeric" maxLength="40"/>
|
<settingConfig type="alphaNumeric" maxLength="40"/>
|
||||||
</setting>
|
</setting>
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
<string id="FldSeconds">Seconds</string>
|
<string id="FldSeconds">Seconds</string>
|
||||||
<string id="FldSeconds0">Seconds (09)</string>
|
<string id="FldSeconds0">Seconds (09)</string>
|
||||||
<string id="FldWeekCal">Week Number</string>
|
<string id="FldWeekCal">Week Number</string>
|
||||||
|
<string id="FldCustomTime">Custom Time Format</string>
|
||||||
|
<string id="FldDateFmt1">Date Format 1</string>
|
||||||
|
<string id="FldDateFmt2">Date Format 2</string>
|
||||||
|
<string id="FldCountdown">Event Countdown</string>
|
||||||
<string id="FldSteps">Steps</string>
|
<string id="FldSteps">Steps</string>
|
||||||
<string id="FldStepsRound">Steps Rounded</string>
|
<string id="FldStepsRound">Steps Rounded</string>
|
||||||
<string id="FldStepsToGo">Steps to Go</string>
|
<string id="FldStepsToGo">Steps to Go</string>
|
||||||
@@ -42,6 +46,10 @@
|
|||||||
<string id="FldFloorsWeek">Floors (Week to Date)</string>
|
<string id="FldFloorsWeek">Floors (Week to Date)</string>
|
||||||
<string id="FldFloors7d">Floors (Last 7 Days)</string>
|
<string id="FldFloors7d">Floors (Last 7 Days)</string>
|
||||||
<string id="FldActiveMin7d">Active Minutes (7 Days)</string>
|
<string id="FldActiveMin7d">Active Minutes (7 Days)</string>
|
||||||
|
<string id="FldSteps28d">Steps (28 Days)</string>
|
||||||
|
<string id="FldCal28d">Calories (28 Days)</string>
|
||||||
|
<string id="FldDist28d">Distance (28 Days)</string>
|
||||||
|
<string id="FldFloors28d">Floors (28 Days)</string>
|
||||||
<string id="FldHeartRate">Heart Rate</string>
|
<string id="FldHeartRate">Heart Rate</string>
|
||||||
<string id="FldRestingHr">Resting Heart Rate</string>
|
<string id="FldRestingHr">Resting Heart Rate</string>
|
||||||
<string id="FldStress">Stress Score</string>
|
<string id="FldStress">Stress Score</string>
|
||||||
@@ -97,6 +105,24 @@
|
|||||||
<string id="FldUvi">UV Index</string>
|
<string id="FldUvi">UV Index</string>
|
||||||
<string id="FldVisibility">Visibility</string>
|
<string id="FldVisibility">Visibility</string>
|
||||||
<string id="FldDewPoint">Dew Point</string>
|
<string id="FldDewPoint">Dew Point</string>
|
||||||
|
<string id="FldNextCal">Next Calendar Event</string>
|
||||||
|
<string id="FldTrainStatus">Training Status</string>
|
||||||
|
<string id="FldRace5k">Race Time (5k)</string>
|
||||||
|
<string id="FldRace10k">Race Time (10k)</string>
|
||||||
|
<string id="FldRaceHalf">Race Time (Half)</string>
|
||||||
|
<string id="FldRaceFull">Race Time (Marathon)</string>
|
||||||
|
<string id="FldPace5k">Pace (5k)</string>
|
||||||
|
<string id="FldPace10k">Pace (10k)</string>
|
||||||
|
<string id="FldPaceHalf">Pace (Half)</string>
|
||||||
|
<string id="FldPaceFull">Pace (Marathon)</string>
|
||||||
|
<string id="FldGolfScore">Last Golf Round Score</string>
|
||||||
|
<string id="FldRunWeek">Run Distance (Week)</string>
|
||||||
|
<string id="FldBikeWeek">Bike Distance (Week)</string>
|
||||||
|
<string id="FldApp1">App 1 (on watch)</string>
|
||||||
|
<string id="FldApp2">App 2 (on watch)</string>
|
||||||
|
<string id="FldApp3">App 3 (on watch)</string>
|
||||||
|
<string id="FldApp4">App 4 (on watch)</string>
|
||||||
|
<string id="FldApp5">App 5 (on watch)</string>
|
||||||
<string id="FldOwmTemp">OWM Temperature</string>
|
<string id="FldOwmTemp">OWM Temperature</string>
|
||||||
<string id="FldOwmMinMax">OWM Max/Min Temp</string>
|
<string id="FldOwmMinMax">OWM Max/Min Temp</string>
|
||||||
<string id="FldOwmFeels">OWM Feels Like</string>
|
<string id="FldOwmFeels">OWM Feels Like</string>
|
||||||
@@ -120,6 +146,11 @@
|
|||||||
<string id="FldOwmMinMaxTmrw">OWM Min/Max Tomorrow</string>
|
<string id="FldOwmMinMaxTmrw">OWM Min/Max Tomorrow</string>
|
||||||
<string id="FldOwmDayAfter">OWM Temp Day After</string>
|
<string id="FldOwmDayAfter">OWM Temp Day After</string>
|
||||||
<string id="FldOwmMinMaxD2">OWM Min/Max Day After</string>
|
<string id="FldOwmMinMaxD2">OWM Min/Max Day After</string>
|
||||||
|
<string id="FldOwmAqi">OWM Air Quality (AQI)</string>
|
||||||
|
<string id="FldOwmAqiText">OWM AQI Description</string>
|
||||||
|
<string id="FldOwmPm25">OWM PM2.5</string>
|
||||||
|
<string id="FldOwmPm10">OWM PM10</string>
|
||||||
|
<string id="FldOwmCo">OWM CO</string>
|
||||||
<string id="FldOwmCity">OWM City Name</string>
|
<string id="FldOwmCity">OWM City Name</string>
|
||||||
<string id="FldOwmUpdated">OWM Update Time</string>
|
<string id="FldOwmUpdated">OWM Update Time</string>
|
||||||
<string id="FldCustom1">Custom Text 1</string>
|
<string id="FldCustom1">Custom Text 1</string>
|
||||||
@@ -127,7 +158,7 @@
|
|||||||
<string id="FldCustom3">Custom Text 3</string>
|
<string id="FldCustom3">Custom Text 3</string>
|
||||||
|
|
||||||
<!-- On-face captions, in FieldTable.IDS order. -->
|
<!-- On-face captions, in FieldTable.IDS order. -->
|
||||||
<string id="FieldLabels">,TIME,UTC,TZ1,TZ2,DATE,DATE,DATE,DAY,DAY,DAY,MON,MON,,WEEK,SEC,SEC,WEEK,STEP,STEP,TO GO,KCAL,ACAL,MIN,MOD,VIG,WEEK,DST,FLR,DESC,ASC,PUSH,PUSH,WEEK,7D,WEEK,7D,WEEK,7D,WEEK,7D,7D,HR,RHR,STRS,SPO2,RESP,BODY,MOVE,MOVE,WT,BMI,RCVR,VO2,VO2,BATT,DAYS,BATT,ALRM,MSG,DND,BT,SOLR,ALT,hPa,hPa,RISE,SET,SUN,SUN,MOON,MOON,CIVIL,NAUT,ASTR,BLUE,GOLD,GOLD,BLUE,CIVIL,NAUT,ASTR,TEMP,HI/LO,FEEL,HIGH,LOW,HUM,RAIN,WIND,WIND,WIND,WX,hPa,UVI,VIS,DEW,TEMP,HI/LO,FEEL,HIGH,LOW,WX,WX,HUM,hPa,VIS,WIND,WIND,WIND,GUST,RAIN,SNOW,CLD,RAIN,DEW,TMRW,TMRW,D+2,D+2,CITY,OWM,,,</string>
|
<string id="FieldLabels">,TIME,UTC,TZ1,TZ2,DATE,DATE,DATE,DAY,DAY,DAY,MON,MON,,WEEK,SEC,SEC,WEEK,TIME,DATE,DATE,DAYS,STEP,STEP,TO GO,KCAL,ACAL,MIN,MOD,VIG,WEEK,DST,FLR,DESC,ASC,PUSH,PUSH,WEEK,7D,WEEK,7D,WEEK,7D,WEEK,7D,7D,28D,28D,28D,28D,HR,RHR,STRS,SPO2,RESP,BODY,MOVE,MOVE,WT,BMI,RCVR,VO2,VO2,BATT,DAYS,BATT,ALRM,MSG,DND,BT,SOLR,ALT,hPa,hPa,RISE,SET,SUN,SUN,MOON,MOON,CIVIL,NAUT,ASTR,BLUE,GOLD,GOLD,BLUE,CIVIL,NAUT,ASTR,TEMP,HI/LO,FEEL,HIGH,LOW,HUM,RAIN,WIND,WIND,WIND,WX,hPa,UVI,VIS,DEW,CAL,TRN,5K,10K,HM,FM,5K,10K,HM,FM,GOLF,RUN,BIKE,APP1,APP2,APP3,APP4,APP5,TEMP,HI/LO,FEEL,HIGH,LOW,WX,WX,HUM,hPa,VIS,WIND,WIND,WIND,GUST,RAIN,SNOW,CLD,RAIN,DEW,TMRW,TMRW,D+2,D+2,AQI,AQI,PM2.5,PM10,CO,CITY,OWM,,,</string>
|
||||||
|
|
||||||
<string id="MetricSteps">Steps</string>
|
<string id="MetricSteps">Steps</string>
|
||||||
<string id="MetricCalories">Calories</string>
|
<string id="MetricCalories">Calories</string>
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
<string id="SettingTempUnit">Temperature Unit</string>
|
<string id="SettingTempUnit">Temperature Unit</string>
|
||||||
<string id="SettingAltOffset1">Alternate Time 1 (minutes from local)</string>
|
<string id="SettingAltOffset1">Alternate Time 1 (minutes from local)</string>
|
||||||
<string id="SettingAltOffset2">Alternate Time 2 (minutes from local)</string>
|
<string id="SettingAltOffset2">Alternate Time 2 (minutes from local)</string>
|
||||||
|
<string id="SettingCountdownDate">Countdown Target (YYYY-MM-DD)</string>
|
||||||
|
<string id="SettingTimeFmt">Custom Time Format (h m a)</string>
|
||||||
|
<string id="SettingDateFmt1">Date Format 1 (D M Y w)</string>
|
||||||
|
<string id="SettingDateFmt2">Date Format 2 (D M Y w)</string>
|
||||||
<string id="SettingOwmKey">OpenWeatherMap API Key</string>
|
<string id="SettingOwmKey">OpenWeatherMap API Key</string>
|
||||||
<string id="SettingCustom1">Custom Text 1</string>
|
<string id="SettingCustom1">Custom Text 1</string>
|
||||||
<string id="SettingCustom2">Custom Text 2</string>
|
<string id="SettingCustom2">Custom Text 2</string>
|
||||||
|
|||||||
184
source/Comp.mc
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import Toybox.Lang;
|
||||||
|
import Toybox.Complications;
|
||||||
|
import Toybox.System;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 系统 Complication 桥接
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Toybox.Complications(CIQ 4.2+,本项目支持的 17 款设备全都有)让表盘可以订阅
|
||||||
|
// 系统和**其它 App 发布**的复杂功能数据。
|
||||||
|
//
|
||||||
|
// 这个模块解锁了一批我原本判断「表盘做不到」的字段 —— 当时只查了
|
||||||
|
// ActivityMonitor / UserProfile / Weather,漏掉了这条路:
|
||||||
|
//
|
||||||
|
// 日历事件 CIQ 确实没有日历 API,但系统把「下一个日程」作为
|
||||||
|
// complication 发布出来了
|
||||||
|
// 训练状态 ActivityMonitor.Info 上没有,这里有
|
||||||
|
// 完赛/配速预测 同上,5k/10k/半马/全马各两套
|
||||||
|
// 本周跑步/骑行距离 这是**按运动分类**的,ActivityMonitor 的按天历史做不出来
|
||||||
|
// 高尔夫成绩 无其它来源
|
||||||
|
// 第三方 App CGM、补水、行情这类 App 发布的复杂功能,
|
||||||
|
// 通过迭代器按序号取(对应目录里的 App 1..App 5)
|
||||||
|
//
|
||||||
|
// 取值策略:每次绘制现查(getComplication 很轻量),不做订阅回调 ——
|
||||||
|
// 表盘每分钟才重绘一次,订阅的实时性没有意义,反而要维护状态。
|
||||||
|
// ============================================================================
|
||||||
|
module Comp {
|
||||||
|
|
||||||
|
// 字段编号 → complication 类型。
|
||||||
|
// 返回 null 表示这个编号不归本模块管。
|
||||||
|
function typeFor(id as Number) {
|
||||||
|
if (id == 612) { return Complications.COMPLICATION_TYPE_CALENDAR_EVENTS; }
|
||||||
|
if (id == 626) { return Complications.COMPLICATION_TYPE_TRAINING_STATUS; }
|
||||||
|
if (id == 627) { return Complications.COMPLICATION_TYPE_RACE_PREDICTOR_5K; }
|
||||||
|
if (id == 628) { return Complications.COMPLICATION_TYPE_RACE_PREDICTOR_10K; }
|
||||||
|
if (id == 629) {
|
||||||
|
return Complications.COMPLICATION_TYPE_RACE_PREDICTOR_HALF_MARATHON;
|
||||||
|
}
|
||||||
|
if (id == 630) {
|
||||||
|
return Complications.COMPLICATION_TYPE_RACE_PREDICTOR_MARATHON;
|
||||||
|
}
|
||||||
|
if (id == 631) { return Complications.COMPLICATION_TYPE_RACE_PACE_PREDICTOR_5K; }
|
||||||
|
if (id == 632) { return Complications.COMPLICATION_TYPE_RACE_PACE_PREDICTOR_10K; }
|
||||||
|
if (id == 633) {
|
||||||
|
return Complications.COMPLICATION_TYPE_RACE_PACE_PREDICTOR_HALF_MARATHON;
|
||||||
|
}
|
||||||
|
if (id == 634) {
|
||||||
|
return Complications.COMPLICATION_TYPE_RACE_PACE_PREDICTOR_MARATHON;
|
||||||
|
}
|
||||||
|
if (id == 641) {
|
||||||
|
return Complications.COMPLICATION_TYPE_LAST_GOLF_ROUND_SCORE;
|
||||||
|
}
|
||||||
|
if (id == 619) {
|
||||||
|
return Complications.COMPLICATION_TYPE_WEEKLY_RUN_DISTANCE;
|
||||||
|
}
|
||||||
|
if (id == 620) {
|
||||||
|
return Complications.COMPLICATION_TYPE_WEEKLY_BIKE_DISTANCE;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三方 App 列表的每帧缓存。
|
||||||
|
//
|
||||||
|
// 枚举一次要走完整个迭代器,四个数据位如果都选了 App 槽就会走四遍。
|
||||||
|
// 表盘每分钟才画一次,本来不算大开销,但 onUpdate 有看门狗时限,
|
||||||
|
// 能省则省 —— 所以每帧只枚举一次,由 view 在绘制开始时调 beginFrame() 失效。
|
||||||
|
var mApps as Array<Complications.Complication>?;
|
||||||
|
|
||||||
|
function beginFrame() as Void {
|
||||||
|
mApps = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 695..699 是「第三方 App 1..5」,不对应固定类型,靠迭代器按序号取。
|
||||||
|
function isAppSlot(id as Number) as Boolean {
|
||||||
|
return id >= 695 && id <= 699;
|
||||||
|
}
|
||||||
|
|
||||||
|
function owns(id as Number) as Boolean {
|
||||||
|
return isAppSlot(id) || typeFor(id) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按类型取一个 complication 对象;取不到返回 null。
|
||||||
|
// 用 try 包住是因为设备不支持某个类型时 getComplication 会抛,而不是返回
|
||||||
|
// null —— 和本项目里其它 CIQ API 的老毛病一样。
|
||||||
|
function byType(type) as Complications.Complication? {
|
||||||
|
try {
|
||||||
|
return Complications.getComplication(new Complications.Id(type));
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取第 n 个**第三方发布**的 complication(n 从 0 起)。
|
||||||
|
//
|
||||||
|
// 迭代器里既有系统自带的,也有别的 App 发布的。这里只保留「有短标签且有值」
|
||||||
|
// 的项 —— 没装对应 App 时那些是空壳,全留着的话序号会随手表上装了什么 App
|
||||||
|
// 而漂移,用户设好的第 3 个明天可能就变成别的东西了。
|
||||||
|
//
|
||||||
|
// Complication 的成员是 complicationId / value / unit / shortLabel /
|
||||||
|
// longLabel / ranges(没有 label,别写错)。
|
||||||
|
function nthApp(n as Number) as Complications.Complication? {
|
||||||
|
if (mApps == null) {
|
||||||
|
var list = [] as Array<Complications.Complication>;
|
||||||
|
try {
|
||||||
|
var it = Complications.getComplications();
|
||||||
|
if (it != null) {
|
||||||
|
var c = it.next();
|
||||||
|
while (c != null) {
|
||||||
|
if (c.value != null && c.shortLabel != null
|
||||||
|
&& c.shortLabel instanceof Lang.String) {
|
||||||
|
list.add(c);
|
||||||
|
}
|
||||||
|
c = it.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 设备不支持枚举时就当作一个都没有。
|
||||||
|
}
|
||||||
|
mApps = list;
|
||||||
|
}
|
||||||
|
return (n >= 0 && n < mApps.size()) ? mApps[n] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把 complication 的值转成可显示的字符串。
|
||||||
|
//
|
||||||
|
// value 的类型随 complication 而变(数字、字符串都有),unit 有时带单位词。
|
||||||
|
// 完赛/配速预测的 value 是**秒数**,直接印出来是一串没意义的数字,
|
||||||
|
// 所以单独格式化成 h:mm:ss / mm:ss。
|
||||||
|
function render(c as Complications.Complication?, id as Number) as String? {
|
||||||
|
if (c == null || c.value == null) { return null; }
|
||||||
|
var v = c.value;
|
||||||
|
if (isTimeSeconds(id) && !(v instanceof Lang.String)) {
|
||||||
|
return hms(v.toNumber());
|
||||||
|
}
|
||||||
|
// 浮点直接 toString 会印成 "0.000000",统一保留一位小数。
|
||||||
|
if (v instanceof Lang.Float || v instanceof Lang.Double) {
|
||||||
|
return trimUnit(v.toFloat().format("%.1f"), c);
|
||||||
|
}
|
||||||
|
var s = v.toString();
|
||||||
|
// 单位词能明显帮上忙时才拼(比如距离的 km),纯数字类就不拼了。
|
||||||
|
//
|
||||||
|
// ⚠️ unit 的类型不保证是 String —— 周跑步距离那种返回的不是字符串,
|
||||||
|
// 直接调 .length() 会抛 "Could not find symbol 'length'"。
|
||||||
|
// 所以先用 instanceof 判一次再说。
|
||||||
|
if (c.unit != null && c.unit instanceof Lang.String) {
|
||||||
|
var u = c.unit as String;
|
||||||
|
if (u.length() > 0 && u.length() <= 3) { s = s + u; }
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把单位词拼到已格式化好的数值后面(如果 unit 确实是个短字符串)。
|
||||||
|
function trimUnit(s as String, c as Complications.Complication) as String {
|
||||||
|
if (c.unit != null && c.unit instanceof Lang.String) {
|
||||||
|
var u = c.unit as String;
|
||||||
|
if (u.length() > 0 && u.length() <= 3) { return s + u; }
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完赛预测(627–630)与配速预测(631–634)都是秒。
|
||||||
|
function isTimeSeconds(id as Number) as Boolean {
|
||||||
|
return id >= 627 && id <= 634;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 秒 → h:mm:ss,不足一小时则 m:ss。配速通常是后者。
|
||||||
|
function hms(total as Number) as String {
|
||||||
|
if (total <= 0) { return "--"; }
|
||||||
|
var h = total / 3600;
|
||||||
|
var m = (total % 3600) / 60;
|
||||||
|
var s = total % 60;
|
||||||
|
if (h > 0) {
|
||||||
|
return h.format("%d") + ":" + m.format("%02d") + ":" + s.format("%02d");
|
||||||
|
}
|
||||||
|
return m.format("%d") + ":" + s.format("%02d");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字段取值总入口。不属于本模块的编号返回 null。
|
||||||
|
function value(id as Number) as String? {
|
||||||
|
if (!owns(id)) { return null; }
|
||||||
|
var c = isAppSlot(id) ? nthApp(id - 695) : byType(typeFor(id));
|
||||||
|
var out = render(c, id);
|
||||||
|
return out == null ? "--" : out;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,9 +13,10 @@ import Toybox.Position;
|
|||||||
// HTTP 请求。Connect IQ 的方案是**后台服务**:系统按定时器唤醒一个独立进程,
|
// HTTP 请求。Connect IQ 的方案是**后台服务**:系统按定时器唤醒一个独立进程,
|
||||||
// 在里面发请求,把结果通过 Background.exit() 交回前台,前台再写进 Storage。
|
// 在里面发请求,把结果通过 Background.exit() 交回前台,前台再写进 Storage。
|
||||||
//
|
//
|
||||||
// 每次唤醒发两个请求:
|
// 每次唤醒发三个请求:
|
||||||
// 1. 当前天气 /data/2.5/weather
|
// 1. 当前天气 /data/2.5/weather
|
||||||
// 2. 短期预报 /data/2.5/forecast?cnt=16(约 48 小时,够算明日/后日高低温)
|
// 2. 短期预报 /data/2.5/forecast?cnt=16(约 48 小时,够算明日/后日高低温)
|
||||||
|
// 3. 空气质量 /data/2.5/air_pollution(AQI / PM2.5 / PM10 / CO)
|
||||||
//
|
//
|
||||||
// ⚠️ 预报接口必须带 cnt 限制条数。后台进程的内存预算非常小(约 32KB),
|
// ⚠️ 预报接口必须带 cnt 限制条数。后台进程的内存预算非常小(约 32KB),
|
||||||
// 完整的 5 天 / 3 小时预报解析出来能有几十 KB,会直接爆掉。
|
// 完整的 5 天 / 3 小时预报解析出来能有几十 KB,会直接爆掉。
|
||||||
@@ -49,7 +50,7 @@ class Fenix8V3Background extends System.ServiceDelegate {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mResult = {};
|
mResult = {};
|
||||||
mPending = 2;
|
mPending = 3; // 当前天气 + 短预报 + 空气质量
|
||||||
// 一律用 units=metric 请求,单位换算统一放到 Fields 里按用户设置做,
|
// 一律用 units=metric 请求,单位换算统一放到 Fields 里按用户设置做,
|
||||||
// 这样切换单位不用重新联网。
|
// 这样切换单位不用重新联网。
|
||||||
var common = {"lat" => lat, "lon" => lon,
|
var common = {"lat" => lat, "lon" => lon,
|
||||||
@@ -66,6 +67,12 @@ class Fenix8V3Background extends System.ServiceDelegate {
|
|||||||
Communications.makeWebRequest(
|
Communications.makeWebRequest(
|
||||||
"https://api.openweathermap.org/data/2.5/forecast",
|
"https://api.openweathermap.org/data/2.5/forecast",
|
||||||
fc, opts, method(:onForecast));
|
fc, opts, method(:onForecast));
|
||||||
|
// 空气质量走独立接口。它在免费档里是开放的(One Call 才要订阅),
|
||||||
|
// 所以 AQI / PM2.5 / PM10 / CO 这几项不用付费也能有。
|
||||||
|
Communications.makeWebRequest(
|
||||||
|
"https://api.openweathermap.org/data/2.5/air_pollution",
|
||||||
|
{"lat" => lat, "lon" => lon, "appid" => Owm.key()},
|
||||||
|
opts, method(:onAir));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前天气的回调。OWM 的 JSON 是嵌套的,逐层取值时每层都要判空 ——
|
// 当前天气的回调。OWM 的 JSON 是嵌套的,逐层取值时每层都要判空 ——
|
||||||
@@ -143,6 +150,25 @@ class Fenix8V3Background extends System.ServiceDelegate {
|
|||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 空气质量的回调。响应形如 { list: [ { main: {aqi}, components: {...} } ] }。
|
||||||
|
function onAir(code as Number, body as Dictionary?) as Void {
|
||||||
|
if (code == 200 && body != null) {
|
||||||
|
var list = body.get("list") as Array?;
|
||||||
|
if (list != null && list.size() > 0) {
|
||||||
|
var e = list[0] as Dictionary;
|
||||||
|
var main = e.get("main") as Dictionary?;
|
||||||
|
if (main != null) { put("aqi", main.get("aqi")); }
|
||||||
|
var comp = e.get("components") as Dictionary?;
|
||||||
|
if (comp != null) {
|
||||||
|
put("pm2_5", comp.get("pm2_5"));
|
||||||
|
put("pm10", comp.get("pm10"));
|
||||||
|
put("co", comp.get("co"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
// 距 Unix 纪元的整天数,用来给预报条目分桶。
|
// 距 Unix 纪元的整天数,用来给预报条目分桶。
|
||||||
function dayNumber(unix as Number) as Number {
|
function dayNumber(unix as Number) as Number {
|
||||||
return unix / 86400;
|
return unix / 86400;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Fields;
|
|||||||
import Layout;
|
import Layout;
|
||||||
import Icons;
|
import Icons;
|
||||||
import Settings;
|
import Settings;
|
||||||
|
import Comp;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 表盘主体
|
// 表盘主体
|
||||||
@@ -279,45 +280,25 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
// ------------------------------- 常亮模式 ------------------------------
|
// ------------------------------- 常亮模式 ------------------------------
|
||||||
// 低功耗画面。相比正常画面砍掉的东西:
|
// 低功耗画面。相比正常画面砍掉的东西:
|
||||||
// - 日期带底色(整条亮橙色横幅是最费电的元素,直接不画)
|
// - 日期带底色(整条亮橙色横幅是最费电的元素,直接不画)
|
||||||
// - 未点亮的刻度(只画有数据的那部分,且压暗到 45%)
|
// - **整个进度环**
|
||||||
|
// - 时间、冒号、日期压暗到 38%
|
||||||
|
//
|
||||||
|
// 度量口径要注意:「点亮像素数」把每个抗锯齿边缘像素都算成亮的,和实际
|
||||||
|
// 功耗对不上;AMOLED 功耗更接近各子像素亮度之和(平均亮度)。
|
||||||
|
// 本实现两个口径都留了余量,实测数字见 README 的常亮小节。
|
||||||
// - 四个数据位、电池(全部隐藏)
|
// - 四个数据位、电池(全部隐藏)
|
||||||
// 保留的时间和日期压暗到 55%。
|
// 保留的时间和日期压暗到 55%。
|
||||||
//
|
//
|
||||||
// 另外整个画面按分钟在 ±4 像素内游走,避免同一个像素长期点亮导致烧屏。
|
// 另外整个画面按分钟在 ±4 像素内游走,避免同一个像素长期点亮导致烧屏。
|
||||||
|
// pTR/pTL/pBR/pBL 现在用不上(常亮下不画环),保留形参是为了以后要把环
|
||||||
|
// 加回来时不必改调用点。
|
||||||
function drawAmbient(dc as Dc, ti as Number, ds as System.DeviceSettings?,
|
function drawAmbient(dc as Dc, ti as Number, ds as System.DeviceSettings?,
|
||||||
pTR as Float, pTL as Float, pBR as Float, pBL as Float) as Void {
|
pTR as Float, pTL as Float, pBR as Float, pBL as Float) as Void {
|
||||||
var g = Gregorian.info(Time.now(), Time.FORMAT_SHORT);
|
var g = Gregorian.info(Time.now(), Time.FORMAT_SHORT);
|
||||||
var ox = ((g.min % 5) - 2) * 2.0 / Layout.scale; // ±4 设备像素,换算回设计像素
|
var ox = ((g.min % 5) - 2) * 2.0 / Layout.scale; // ±4 设备像素,换算回设计像素
|
||||||
var oy = ((g.min / 5) % 5 - 2) * 2.0 / Layout.scale;
|
var oy = ((g.min / 5) % 5 - 2) * 2.0 / Layout.scale;
|
||||||
|
|
||||||
var top = Themes.RINGTOP[ti] as Array<Number>;
|
// 进度环在常亮下不画 —— 它是点亮像素数的大头。
|
||||||
var bot = Themes.RINGBOTTOM[ti] as Array<Number>;
|
|
||||||
var litTR = (pTR * Layout.TOP_N).toNumber();
|
|
||||||
var litTL = (pTL * Layout.TOP_N).toNumber();
|
|
||||||
var litBR = (pBR * Layout.BOT_N).toNumber();
|
|
||||||
var litBL = (pBL * Layout.BOT_N).toNumber();
|
|
||||||
for (var i = 0; i < Layout.TOP_N; i++) {
|
|
||||||
var a = Layout.TOP_PH + Layout.TOP_PITCH * i;
|
|
||||||
if (i >= Layout.TOP_N - litTR) {
|
|
||||||
dc.setColor(dim(top[i], 0.45), Graphics.COLOR_TRANSPARENT);
|
|
||||||
tick(dc, a, Layout.TOP_HW);
|
|
||||||
}
|
|
||||||
if (i >= Layout.TOP_N - litTL) {
|
|
||||||
dc.setColor(dim(top[i], 0.45), Graphics.COLOR_TRANSPARENT);
|
|
||||||
tick(dc, -a, Layout.TOP_HW);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var i = 0; i < Layout.BOT_N; i++) {
|
|
||||||
var a = Layout.BOT_PH + Layout.BOT_PITCH * i;
|
|
||||||
if (i < litBL) {
|
|
||||||
dc.setColor(dim(bot[i], 0.45), Graphics.COLOR_TRANSPARENT);
|
|
||||||
tick(dc, 180.0 + a, Layout.BOT_HW);
|
|
||||||
}
|
|
||||||
if (i < litBR) {
|
|
||||||
dc.setColor(dim(bot[i], 0.45), Graphics.COLOR_TRANSPARENT);
|
|
||||||
tick(dc, 180.0 - a, Layout.BOT_HW);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var h = g.hour;
|
var h = g.hour;
|
||||||
if (ds == null || !ds.is24Hour) {
|
if (ds == null || !ds.is24Hour) {
|
||||||
@@ -327,17 +308,17 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
var mins = Themes.MINUTES[ti] as Array<Number>;
|
var mins = Themes.MINUTES[ti] as Array<Number>;
|
||||||
Layout.text(dc, Layout.HOURS_R + ox, Layout.TIME_CY + oy, Layout.fontTime,
|
Layout.text(dc, Layout.HOURS_R + ox, Layout.TIME_CY + oy, Layout.fontTime,
|
||||||
h.format("%d"), Graphics.TEXT_JUSTIFY_RIGHT,
|
h.format("%d"), Graphics.TEXT_JUSTIFY_RIGHT,
|
||||||
dim(hours[hours.size() / 2], 0.55));
|
dim(hours[hours.size() / 2], 0.38));
|
||||||
Layout.text(dc, Layout.MINUTES_L + ox, Layout.TIME_CY + oy, Layout.fontTime,
|
Layout.text(dc, Layout.MINUTES_L + ox, Layout.TIME_CY + oy, Layout.fontTime,
|
||||||
g.min.format("%02d"), Graphics.TEXT_JUSTIFY_LEFT,
|
g.min.format("%02d"), Graphics.TEXT_JUSTIFY_LEFT,
|
||||||
dim(mins[mins.size() / 2], 0.55));
|
dim(mins[mins.size() / 2], 0.38));
|
||||||
dc.setColor(dim(Themes.COLON[ti] as Number, 0.55), Graphics.COLOR_TRANSPARENT);
|
dc.setColor(dim(Themes.COLON[ti] as Number, 0.38), Graphics.COLOR_TRANSPARENT);
|
||||||
dc.fillRectangle(Layout.px(Layout.COLON_X + ox), Layout.px(Layout.COLON_Y1 + oy),
|
dc.fillRectangle(Layout.px(Layout.COLON_X + ox), Layout.px(Layout.COLON_Y1 + oy),
|
||||||
Layout.px(Layout.COLON_W), Layout.px(Layout.COLON_H));
|
Layout.px(Layout.COLON_W), Layout.px(Layout.COLON_H));
|
||||||
dc.fillRectangle(Layout.px(Layout.COLON_X + ox), Layout.px(Layout.COLON_Y2 + oy),
|
dc.fillRectangle(Layout.px(Layout.COLON_X + ox), Layout.px(Layout.COLON_Y2 + oy),
|
||||||
Layout.px(Layout.COLON_W), Layout.px(Layout.COLON_H));
|
Layout.px(Layout.COLON_W), Layout.px(Layout.COLON_H));
|
||||||
|
|
||||||
var band = dim(Themes.ACCENT[ti] as Number, 0.55);
|
var band = dim(Themes.ACCENT[ti] as Number, 0.38);
|
||||||
Layout.text(dc, Layout.DOW_CX + ox, Layout.BAND_CY + oy, Layout.fontBand,
|
Layout.text(dc, Layout.DOW_CX + ox, Layout.BAND_CY + oy, Layout.fontBand,
|
||||||
Fields.dow(g), Graphics.TEXT_JUSTIFY_CENTER, band);
|
Fields.dow(g), Graphics.TEXT_JUSTIFY_CENTER, band);
|
||||||
Layout.text(dc, Layout.MD_CX + ox, Layout.BAND_CY + oy, Layout.fontBand,
|
Layout.text(dc, Layout.MD_CX + ox, Layout.BAND_CY + oy, Layout.fontBand,
|
||||||
@@ -429,6 +410,7 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
var ti = Settings.themeIndex(Themes.NAMES.size());
|
var ti = Settings.themeIndex(Themes.NAMES.size());
|
||||||
var ds = System.getDeviceSettings();
|
var ds = System.getDeviceSettings();
|
||||||
var ctx = new Fields.Ctx(ds, Settings.tempUnit());
|
var ctx = new Fields.Ctx(ds, Settings.tempUnit());
|
||||||
|
Comp.beginFrame(); // 让第三方 App 列表这一帧只枚举一次
|
||||||
var info = ctx.info();
|
var info = ctx.info();
|
||||||
var stats = ctx.stats();
|
var stats = ctx.stats();
|
||||||
var level = 0.0f;
|
var level = 0.0f;
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ module FieldTable {
|
|||||||
// 全部字段 id,顺序与 FieldLabels 资源串(逗号分隔的标签表)严格一致 ——
|
// 全部字段 id,顺序与 FieldLabels 资源串(逗号分隔的标签表)严格一致 ——
|
||||||
// Fields.label() 就是靠这个下标去取对应标签的。
|
// Fields.label() 就是靠这个下标去取对应标签的。
|
||||||
const IDS = [
|
const IDS = [
|
||||||
0,860,880,864,865,851,872,852,855,870,856,853,868,858,859,850,869,873,1,14,707,2,
|
0,860,880,864,865,851,872,852,855,870,856,853,868,858,859,850,869,873,861,862,863,
|
||||||
603,150,152,154,151,3,4,5,6,9,10,219,220,211,212,213,214,203,204,200,618,276,12,635,
|
735,1,14,707,2,603,150,152,154,151,3,4,5,6,9,10,219,220,211,212,213,214,203,204,200,
|
||||||
11,623,8,15,281,742,13,278,277,250,251,257,253,255,254,256,252,615,616,733,712,717,
|
828,832,836,840,618,276,12,635,11,623,8,15,281,742,13,278,277,250,251,257,253,255,
|
||||||
700,731,706,732,710,709,708,711,714,715,718,719,720,721,306,639,300,301,302,305,304,
|
254,256,252,615,616,733,712,717,700,731,706,732,710,709,708,711,714,715,718,719,720,
|
||||||
308,307,310,309,312,313,314,315,500,515,503,504,505,507,502,501,506,509,510,511,518,
|
721,306,639,300,301,302,305,304,308,307,310,309,312,313,314,315,612,626,627,628,629,
|
||||||
512,513,532,536,537,530,534,522,535,523,516,517,703,704,705
|
630,631,632,633,634,641,619,620,695,696,697,698,699,500,515,503,504,505,507,502,501,
|
||||||
|
506,509,510,511,518,512,513,532,536,537,530,534,522,535,523,538,539,540,541,542,516,
|
||||||
|
517,703,704,705
|
||||||
];
|
];
|
||||||
|
|
||||||
// 由字段 id 反查它在 IDS 里的下标;找不到返回 0(对应「关闭」)。
|
// 由字段 id 反查它在 IDS 里的下标;找不到返回 0(对应「关闭」)。
|
||||||
|
|||||||
137
source/Fields.mc
@@ -15,6 +15,7 @@ import FieldTable;
|
|||||||
import Settings;
|
import Settings;
|
||||||
import Solar;
|
import Solar;
|
||||||
import Owm;
|
import Owm;
|
||||||
|
import Comp;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 数据字段
|
// 数据字段
|
||||||
@@ -80,6 +81,89 @@ module Fields {
|
|||||||
return Lang.format(mDateFmt, [month(g), g.day.format("%d")]);
|
return Lang.format(mDateFmt, [month(g), g.day.format("%d")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按用户填的格式串拼时间/日期。
|
||||||
|
//
|
||||||
|
// 用单字母占位符,因为设置项里能填的长度有限,而且这样中英文都好写:
|
||||||
|
// h 小时(12/24 跟随手表) H 小时补零
|
||||||
|
// m 分钟补零 s 秒补零
|
||||||
|
// a 上午/下午(24 小时制下为空)
|
||||||
|
// D 日 M 月(数字) N 月(名称)
|
||||||
|
// Y 四位年 y 两位年 w 星期
|
||||||
|
// 其它字符原样输出,所以 "D/M" → "10/9","w N D" → "周四 9月 10"。
|
||||||
|
function applyFormat(fmt as String, g as Gregorian.Info, c as Ctx) as String {
|
||||||
|
var out = "";
|
||||||
|
var chars = fmt.toCharArray();
|
||||||
|
for (var i = 0; i < chars.size(); i++) {
|
||||||
|
var ch = chars[i];
|
||||||
|
if (ch == 'h') {
|
||||||
|
var hh = g.hour;
|
||||||
|
if (!c.is24) {
|
||||||
|
if (hh == 0) { hh = 12; } else if (hh > 12) { hh = hh - 12; }
|
||||||
|
}
|
||||||
|
out += hh.format("%d");
|
||||||
|
} else if (ch == 'H') {
|
||||||
|
out += g.hour.format("%02d");
|
||||||
|
} else if (ch == 'm') {
|
||||||
|
out += g.min.format("%02d");
|
||||||
|
} else if (ch == 's') {
|
||||||
|
out += g.sec.format("%02d");
|
||||||
|
} else if (ch == 'a') {
|
||||||
|
out += c.is24 ? "" : meridiem(g);
|
||||||
|
} else if (ch == 'D') {
|
||||||
|
out += g.day.format("%d");
|
||||||
|
} else if (ch == 'M') {
|
||||||
|
out += g.month.format("%d");
|
||||||
|
} else if (ch == 'N') {
|
||||||
|
out += month(g);
|
||||||
|
} else if (ch == 'Y') {
|
||||||
|
out += g.year.format("%d");
|
||||||
|
} else if (ch == 'y') {
|
||||||
|
out += (g.year % 100).format("%02d");
|
||||||
|
} else if (ch == 'w') {
|
||||||
|
out += dow(g);
|
||||||
|
} else {
|
||||||
|
out += ch.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 距目标日期还有几天。目标日期是设置里填的 YYYY-MM-DD 字符串。
|
||||||
|
// 解析失败(没填/格式不对)返回 null,字段显示 "--"。
|
||||||
|
function daysUntil(target as String, g as Gregorian.Info) as Number? {
|
||||||
|
if (target.length() < 8) { return null; }
|
||||||
|
var parts = splitOn(target, '-');
|
||||||
|
if (parts.size() < 3) { return null; }
|
||||||
|
var y = parts[0].toNumber();
|
||||||
|
var mo = parts[1].toNumber();
|
||||||
|
var d = parts[2].toNumber();
|
||||||
|
if (y == null || mo == null || d == null) { return null; }
|
||||||
|
// 都换算成「儒略日式」的绝对天数再相减,省得处理跨月跨年。
|
||||||
|
return absDay(y, mo, d) - absDay(g.year, g.month, g.day);
|
||||||
|
}
|
||||||
|
|
||||||
|
function absDay(y as Number, m as Number, d as Number) as Number {
|
||||||
|
var yy = y;
|
||||||
|
var mm = m;
|
||||||
|
if (mm <= 2) { yy = yy - 1; mm = mm + 12; }
|
||||||
|
var a = yy / 100;
|
||||||
|
var b = 2 - a + a / 4;
|
||||||
|
return (365.25 * (yy + 4716)).toNumber() + (30.6001 * (mm + 1)).toNumber()
|
||||||
|
+ d + b - 1524;
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitOn(str as String, sep as Char) as Array<String> {
|
||||||
|
var out = [] as Array<String>;
|
||||||
|
var cur = "";
|
||||||
|
var chars = str.toCharArray();
|
||||||
|
for (var i = 0; i < chars.size(); i++) {
|
||||||
|
if (chars[i] == sep) { out.add(cur); cur = ""; }
|
||||||
|
else { cur += chars[i].toString(); }
|
||||||
|
}
|
||||||
|
out.add(cur);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// 英文出 "10",中文出 "10日"。
|
// 英文出 "10",中文出 "10日"。
|
||||||
// 后缀带在本地化的日期格式串里,所以这里把月份传空串即可。
|
// 后缀带在本地化的日期格式串里,所以这里把月份传空串即可。
|
||||||
function dayOnly(g as Gregorian.Info) as String {
|
function dayOnly(g as Gregorian.Info) as String {
|
||||||
@@ -160,8 +244,18 @@ module Fields {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 本项目已经被这个 API 家族坑过三次(Properties.getValue、
|
||||||
|
// deleteTemporalEvent、Complication.unit),一律按「声明了权限也可能抛」
|
||||||
|
// 来防。取不到就当作没有用户档案,相关字段显示 "--"。
|
||||||
function profile() as UserProfile.Profile? {
|
function profile() as UserProfile.Profile? {
|
||||||
if (!mGotProf) { mProf = UserProfile.getProfile(); mGotProf = true; }
|
if (!mGotProf) {
|
||||||
|
try {
|
||||||
|
mProf = UserProfile.getProfile();
|
||||||
|
} catch (e) {
|
||||||
|
mProf = null;
|
||||||
|
}
|
||||||
|
mGotProf = true;
|
||||||
|
}
|
||||||
return mProf;
|
return mProf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,6 +469,15 @@ module Fields {
|
|||||||
// view 只在有格子选了秒时才去请求那个回调。
|
// view 只在有格子选了秒时才去请求那个回调。
|
||||||
if (id == 850) { return c.greg.sec.format("%d"); } // 秒
|
if (id == 850) { return c.greg.sec.format("%d"); } // 秒
|
||||||
if (id == 869) { return c.greg.sec.format("%02d"); } // 秒 (前导零)
|
if (id == 869) { return c.greg.sec.format("%02d"); } // 秒 (前导零)
|
||||||
|
// 自定义格式:格式串来自设置,占位符说明见 applyFormat
|
||||||
|
if (id == 861) { return applyFormat(Settings.text("TimeFmt"), c.greg, c); }
|
||||||
|
if (id == 862) { return applyFormat(Settings.text("DateFmt1"), c.greg, c); }
|
||||||
|
if (id == 863) { return applyFormat(Settings.text("DateFmt2"), c.greg, c); }
|
||||||
|
if (id == 735) { // 事件倒计时
|
||||||
|
var left = daysUntil(Settings.text("CountdownDate"), c.greg);
|
||||||
|
if (left == null) { return DASH; }
|
||||||
|
return left.format("%d");
|
||||||
|
} // 秒 (前导零)
|
||||||
if (id == 870) { return dow(c.greg); } // 星期简写
|
if (id == 870) { return dow(c.greg); } // 星期简写
|
||||||
if (id == 856) { return dow(c.greg); } // 星期全称
|
if (id == 856) { return dow(c.greg); } // 星期全称
|
||||||
if (id == 853) { return month(c.greg); } // 月份
|
if (id == 853) { return month(c.greg); } // 月份
|
||||||
@@ -450,6 +553,14 @@ module Fields {
|
|||||||
return (id == 213) ? dist(v, c) : rounded(v);
|
return (id == 213) ? dist(v, c) : rounded(v);
|
||||||
}
|
}
|
||||||
// 近 7 天步数 / 近 7 天卡路里 / 近 7 天距离 / 近 7 天楼层 / 近 7 天活动分钟
|
// 近 7 天步数 / 近 7 天卡路里 / 近 7 天距离 / 近 7 天楼层 / 近 7 天活动分钟
|
||||||
|
if (id == 828 || id == 832 || id == 836 || id == 840) {
|
||||||
|
// 近 28 天。getHistory 能返回多少天由固件决定,sumHistory 内部会按
|
||||||
|
// 实际条数截断,所以拿不到 28 天时是「有多少算多少」而不是报错。
|
||||||
|
var w28 = (id == 828) ? 0 : ((id == 832) ? 1 : ((id == 836) ? 2 : 3));
|
||||||
|
var v28 = sumHistory(c, w28, 27);
|
||||||
|
if (v28 == null) { return DASH; }
|
||||||
|
return (id == 836) ? dist(v28, c) : rounded(v28);
|
||||||
|
}
|
||||||
if (id == 220 || id == 212 || id == 214 || id == 204 || id == 200) {
|
if (id == 220 || id == 212 || id == 214 || id == 204 || id == 200) {
|
||||||
var which = (id == 220) ? 0 : ((id == 212) ? 1
|
var which = (id == 220) ? 0 : ((id == 212) ? 1
|
||||||
: ((id == 214) ? 2 : ((id == 204) ? 3 : 4)));
|
: ((id == 214) ? 2 : ((id == 204) ? 3 : 4)));
|
||||||
@@ -760,6 +871,18 @@ module Fields {
|
|||||||
if (id == 523) { // OWM 后日高低温
|
if (id == 523) { // OWM 后日高低温
|
||||||
return temp(Owm.get("hi2"), c) + "/" + temp(Owm.get("lo2"), c);
|
return temp(Owm.get("hi2"), c) + "/" + temp(Owm.get("lo2"), c);
|
||||||
}
|
}
|
||||||
|
if (id == 538 || id == 539) { // 空气质量指数 / 描述
|
||||||
|
var aqi = Owm.get("aqi");
|
||||||
|
if (aqi == null) { return DASH; }
|
||||||
|
if (id == 538) { return num(aqi); }
|
||||||
|
// OWM 的 AQI 是 1..5 的等级,不是欧美常见的 0..500 数值。
|
||||||
|
var names = ["", "优", "良", "中", "差", "很差"];
|
||||||
|
var k = aqi.toNumber();
|
||||||
|
return (k >= 1 && k <= 5) ? names[k] : DASH;
|
||||||
|
}
|
||||||
|
if (id == 540) { return oneDp(Owm.get("pm2_5")); } // PM2.5
|
||||||
|
if (id == 541) { return oneDp(Owm.get("pm10")); } // PM10
|
||||||
|
if (id == 542) { return oneDp(Owm.get("co")); } // 一氧化碳
|
||||||
if (id == 516) { return str(Owm.get("city")); } // OWM 城市
|
if (id == 516) { return str(Owm.get("city")); } // OWM 城市
|
||||||
if (id == 517) { // OWM 更新时间
|
if (id == 517) { // OWM 更新时间
|
||||||
var dt = Owm.get("dt");
|
var dt = Owm.get("dt");
|
||||||
@@ -768,10 +891,21 @@ module Fields {
|
|||||||
return DASH;
|
return DASH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function oneDp(v) as String {
|
||||||
|
return v == null ? DASH : v.toFloat().format("%.1f");
|
||||||
|
}
|
||||||
|
|
||||||
function str(v) as String {
|
function str(v) as String {
|
||||||
return v == null ? DASH : v.toString();
|
return v == null ? DASH : v.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 系统 Complication ----
|
||||||
|
// 日历、训练状态、完赛/配速预测、按运动的周距离、以及第三方 App 发布的
|
||||||
|
// 复杂功能。全部由 Comp.mc 经 Toybox.Complications 取得。
|
||||||
|
function complication(id as Number, c as Ctx) as String? {
|
||||||
|
return Comp.value(id);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 自定义文字 ----
|
// ---- 自定义文字 ----
|
||||||
// 用户在设置里自己填的三段文字。
|
// 用户在设置里自己填的三段文字。
|
||||||
function custom(id as Number, c as Ctx) as String? {
|
function custom(id as Number, c as Ctx) as String? {
|
||||||
@@ -791,6 +925,7 @@ module Fields {
|
|||||||
if (v == null) { v = system(id, c); }
|
if (v == null) { v = system(id, c); }
|
||||||
if (v == null) { v = environment(id, c); }
|
if (v == null) { v = environment(id, c); }
|
||||||
if (v == null) { v = weather(id, c); }
|
if (v == null) { v = weather(id, c); }
|
||||||
|
if (v == null) { v = complication(id, c); }
|
||||||
if (v == null) { v = owm(id, c); }
|
if (v == null) { v = owm(id, c); }
|
||||||
if (v == null) { v = custom(id, c); }
|
if (v == null) { v = custom(id, c); }
|
||||||
return v == null ? "" : v;
|
return v == null ? "" : v;
|
||||||
|
|||||||
118
tools/gen_devices.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""扫描本机安装的 Connect IQ 设备,重写 manifest 的机型列表和 monkey.jungle。
|
||||||
|
|
||||||
|
筛选条件(三条都要满足):
|
||||||
|
1. 支持 watchFace 类型
|
||||||
|
2. Connect IQ >= 4.2 —— Toybox.Complications 是这个版本引入的,
|
||||||
|
本表盘的日历/训练状态/第三方 App 字段依赖它
|
||||||
|
3. 圆屏(deviceFamily 以 round- 开头)
|
||||||
|
|
||||||
|
python3 tools/gen_devices.py
|
||||||
|
|
||||||
|
改写两处:
|
||||||
|
manifest.xml <iq:products> 整块
|
||||||
|
monkey.jungle 每台设备的 resourcePath(图标按屏宽、启动图标按各自要求的尺寸)
|
||||||
|
|
||||||
|
⚠️ 结果取决于本机 SDK 里**装了哪些设备**。如果某台机器上少装了几款,
|
||||||
|
跑一遍会把它们从 manifest 里删掉。所以脚本会打印找到的数量,
|
||||||
|
提交前确认一下数字对不对(当前应为 55 款)。
|
||||||
|
|
||||||
|
图标资源的尺寸集合由本脚本算出后交给 tools/gen_icons.py 使用,
|
||||||
|
两者的 WIDTHS / LAUNCHER 必须一致,所以改完这里要跟着跑一遍 gen_icons.py。
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.dirname(HERE)
|
||||||
|
DEV = os.path.expanduser(
|
||||||
|
"~/Library/Application Support/Garmin/ConnectIQ/Devices")
|
||||||
|
|
||||||
|
|
||||||
|
def eligible():
|
||||||
|
"""返回 [(设备id, 屏宽, 启动图标尺寸, 是否支持alpha), ...],按 id 排序。"""
|
||||||
|
out = []
|
||||||
|
if not os.path.isdir(DEV):
|
||||||
|
raise SystemExit("找不到设备目录:%s" % DEV)
|
||||||
|
for d in sorted(os.listdir(DEV)):
|
||||||
|
p = os.path.join(DEV, d, "compiler.json")
|
||||||
|
if not os.path.exists(p):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
c = json.load(open(p))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
types = {t["type"] for t in c.get("appTypes", []) if isinstance(t, dict)}
|
||||||
|
if "watchFace" not in types:
|
||||||
|
continue
|
||||||
|
if not str(c.get("deviceFamily", "")).startswith("round"):
|
||||||
|
continue
|
||||||
|
vers = [str(pn.get("connectIQVersion", "0"))
|
||||||
|
for pn in c.get("partNumbers", [])]
|
||||||
|
if not any(_ciq_ok(v) for v in vers):
|
||||||
|
continue
|
||||||
|
li = c.get("launcherIcon", {})
|
||||||
|
out.append((d, c["resolution"]["width"], li.get("width"),
|
||||||
|
bool(c.get("alphaBlendingSupport"))))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _ciq_ok(v):
|
||||||
|
try:
|
||||||
|
a = [int(x) for x in v.split(".")[:2]]
|
||||||
|
return a[0] > 4 or (a[0] == 4 and a[1] >= 2)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def write_manifest(devs):
|
||||||
|
p = os.path.join(ROOT, "manifest.xml")
|
||||||
|
s = open(p).read()
|
||||||
|
body = "\n".join(' <iq:product id="%s"/>' % d for d, _, _, _ in devs)
|
||||||
|
block = " <iq:products>\n%s\n </iq:products>" % body
|
||||||
|
s = re.sub(r" <iq:products>.*?</iq:products>", block, s, flags=re.S)
|
||||||
|
open(p, "w").write(s)
|
||||||
|
print("manifest.xml: 写入 %d 款机型" % len(devs))
|
||||||
|
|
||||||
|
|
||||||
|
def write_jungle(devs):
|
||||||
|
L = ["project.manifest = manifest.xml", "",
|
||||||
|
"base.sourcePath = source", "",
|
||||||
|
"# resources-chn 是简体中文文案;目录名的 -chn 后缀就是语言标记。",
|
||||||
|
"base.resourcePath = resources;resources-chn", "",
|
||||||
|
"# 下面每台设备再挂两棵资源树,都由 tools/gen_icons.py 生成:",
|
||||||
|
"# resources-icons-<屏宽> 按该屏幕像素网格光栅化的日出/日落/天气图标",
|
||||||
|
"# resources-launcher-<尺寸> 该机型要求尺寸的启动图标",
|
||||||
|
"# 这一整段由 tools/gen_devices.py 生成,不要手改。",
|
||||||
|
""]
|
||||||
|
for d, w, li, _ in devs:
|
||||||
|
L.append("%s.resourcePath = $(base.resourcePath)"
|
||||||
|
";resources-icons-%d;resources-launcher-%d" % (d, w, li))
|
||||||
|
L += ["", "base.excludeAnnotations = release"]
|
||||||
|
open(os.path.join(ROOT, "monkey.jungle"), "w").write("\n".join(L) + "\n")
|
||||||
|
print("monkey.jungle: 写入 %d 条资源路径" % len(devs))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
devs = eligible()
|
||||||
|
if not devs:
|
||||||
|
raise SystemExit("一款符合条件的机型都没找到,请检查 SDK 设备是否已安装")
|
||||||
|
widths = defaultdict(set)
|
||||||
|
for _, w, _, alpha in devs:
|
||||||
|
widths[w].add(alpha)
|
||||||
|
print("符合条件的机型:%d 款" % len(devs))
|
||||||
|
print("需要的图标屏宽:%s" % sorted(widths))
|
||||||
|
print("需要的启动图标尺寸:%s" % sorted({li for _, _, li, _ in devs}))
|
||||||
|
for w in sorted(widths):
|
||||||
|
if len(widths[w]) > 1:
|
||||||
|
print(" ⚠️ %dpx 同时存在支持/不支持 alpha 的机型,"
|
||||||
|
"图标按硬边生成" % w)
|
||||||
|
write_manifest(devs)
|
||||||
|
write_jungle(devs)
|
||||||
|
print("\n接着跑:python3 tools/gen_icons.py")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -47,6 +47,10 @@ FIELDS = [
|
|||||||
(850, "Seconds", "Seconds", "秒", "SEC", "秒"),
|
(850, "Seconds", "Seconds", "秒", "SEC", "秒"),
|
||||||
(869, "Seconds0", "Seconds (09)", "秒 (前导零)", "SEC", "秒"),
|
(869, "Seconds0", "Seconds (09)", "秒 (前导零)", "SEC", "秒"),
|
||||||
(873, "WeekCal", "Week Number", "周数", "WEEK", "周"),
|
(873, "WeekCal", "Week Number", "周数", "WEEK", "周"),
|
||||||
|
(861, "CustomTime", "Custom Time Format", "自定义时间格式", "TIME", "时间"),
|
||||||
|
(862, "DateFmt1", "Date Format 1", "日期格式 1", "DATE", "日期"),
|
||||||
|
(863, "DateFmt2", "Date Format 2", "日期格式 2", "DATE", "日期"),
|
||||||
|
(735, "Countdown", "Event Countdown", "事件倒计时", "DAYS", "倒计时"),
|
||||||
|
|
||||||
# ---- activity -------------------------------------------------------
|
# ---- activity -------------------------------------------------------
|
||||||
(1, "Steps", "Steps", "步数", "STEP", "步数"),
|
(1, "Steps", "Steps", "步数", "STEP", "步数"),
|
||||||
@@ -73,6 +77,10 @@ FIELDS = [
|
|||||||
(203, "FloorsWeek", "Floors (Week to Date)", "本周楼层", "WEEK", "周楼层"),
|
(203, "FloorsWeek", "Floors (Week to Date)", "本周楼层", "WEEK", "周楼层"),
|
||||||
(204, "Floors7d", "Floors (Last 7 Days)", "近 7 天楼层", "7D", "7天"),
|
(204, "Floors7d", "Floors (Last 7 Days)", "近 7 天楼层", "7D", "7天"),
|
||||||
(200, "ActiveMin7d", "Active Minutes (7 Days)", "近 7 天活动分钟", "7D", "7天"),
|
(200, "ActiveMin7d", "Active Minutes (7 Days)", "近 7 天活动分钟", "7D", "7天"),
|
||||||
|
(828, "Steps28d", "Steps (28 Days)", "近 28 天步数", "28D", "28天"),
|
||||||
|
(832, "Cal28d", "Calories (28 Days)", "近 28 天卡路里", "28D", "28天"),
|
||||||
|
(836, "Dist28d", "Distance (28 Days)", "近 28 天距离", "28D", "28天"),
|
||||||
|
(840, "Floors28d", "Floors (28 Days)", "近 28 天楼层", "28D", "28天"),
|
||||||
|
|
||||||
# ---- body -----------------------------------------------------------
|
# ---- body -----------------------------------------------------------
|
||||||
(618, "HeartRate", "Heart Rate", "心率", "HR", "心率"),
|
(618, "HeartRate", "Heart Rate", "心率", "HR", "心率"),
|
||||||
@@ -137,6 +145,27 @@ FIELDS = [
|
|||||||
(314, "Visibility", "Visibility", "能见度", "VIS", "能见度"),
|
(314, "Visibility", "Visibility", "能见度", "VIS", "能见度"),
|
||||||
(315, "DewPoint", "Dew Point", "露点", "DEW", "露点"),
|
(315, "DewPoint", "Dew Point", "露点", "DEW", "露点"),
|
||||||
|
|
||||||
|
# ---- 系统 Complication(含第三方 App 发布的)------------------------
|
||||||
|
# 这些走 Toybox.Complications,见 source/Comp.mc。
|
||||||
|
(612, "NextCal", "Next Calendar Event", "下一个日程", "CAL", "日程"),
|
||||||
|
(626, "TrainStatus", "Training Status", "训练状态", "TRN", "训练"),
|
||||||
|
(627, "Race5k", "Race Time (5k)", "5k 完赛预测", "5K", "5K"),
|
||||||
|
(628, "Race10k", "Race Time (10k)", "10k 完赛预测", "10K", "10K"),
|
||||||
|
(629, "RaceHalf", "Race Time (Half)", "半马完赛预测", "HM", "半马"),
|
||||||
|
(630, "RaceFull", "Race Time (Marathon)", "全马完赛预测", "FM", "全马"),
|
||||||
|
(631, "Pace5k", "Pace (5k)", "5k 配速预测", "5K", "5K配速"),
|
||||||
|
(632, "Pace10k", "Pace (10k)", "10k 配速预测", "10K", "10K配速"),
|
||||||
|
(633, "PaceHalf", "Pace (Half)", "半马配速预测", "HM", "半马配速"),
|
||||||
|
(634, "PaceFull", "Pace (Marathon)", "全马配速预测", "FM", "全马配速"),
|
||||||
|
(641, "GolfScore", "Last Golf Round Score", "最近高尔夫得分", "GOLF", "高尔夫"),
|
||||||
|
(619, "RunWeek", "Run Distance (Week)", "本周跑步距离", "RUN", "跑步"),
|
||||||
|
(620, "BikeWeek", "Bike Distance (Week)", "本周骑行距离", "BIKE", "骑行"),
|
||||||
|
(695, "App1", "App 1 (on watch)", "第三方 App 1", "APP1", "App1"),
|
||||||
|
(696, "App2", "App 2 (on watch)", "第三方 App 2", "APP2", "App2"),
|
||||||
|
(697, "App3", "App 3 (on watch)", "第三方 App 3", "APP3", "App3"),
|
||||||
|
(698, "App4", "App 4 (on watch)", "第三方 App 4", "APP4", "App4"),
|
||||||
|
(699, "App5", "App 5 (on watch)", "第三方 App 5", "APP5", "App5"),
|
||||||
|
|
||||||
# ---- OpenWeatherMap -------------------------------------------------
|
# ---- OpenWeatherMap -------------------------------------------------
|
||||||
# Fetched by the background service; see source/Owm.mc.
|
# Fetched by the background service; see source/Owm.mc.
|
||||||
(500, "OwmTemp", "OWM Temperature", "OWM 气温", "TEMP", "气温"),
|
(500, "OwmTemp", "OWM Temperature", "OWM 气温", "TEMP", "气温"),
|
||||||
@@ -162,6 +191,11 @@ FIELDS = [
|
|||||||
(522, "OwmMinMaxTmrw", "OWM Min/Max Tomorrow", "OWM 明日高低温", "TMRW", "明日"),
|
(522, "OwmMinMaxTmrw", "OWM Min/Max Tomorrow", "OWM 明日高低温", "TMRW", "明日"),
|
||||||
(535, "OwmDayAfter", "OWM Temp Day After", "OWM 后日气温", "D+2", "后日"),
|
(535, "OwmDayAfter", "OWM Temp Day After", "OWM 后日气温", "D+2", "后日"),
|
||||||
(523, "OwmMinMaxD2", "OWM Min/Max Day After", "OWM 后日高低温", "D+2", "后日"),
|
(523, "OwmMinMaxD2", "OWM Min/Max Day After", "OWM 后日高低温", "D+2", "后日"),
|
||||||
|
(538, "OwmAqi", "OWM Air Quality (AQI)", "OWM 空气质量", "AQI", "空气"),
|
||||||
|
(539, "OwmAqiText", "OWM AQI Description", "OWM 空气质量描述", "AQI", "空气"),
|
||||||
|
(540, "OwmPm25", "OWM PM2.5", "OWM PM2.5", "PM2.5", "PM2.5"),
|
||||||
|
(541, "OwmPm10", "OWM PM10", "OWM PM10", "PM10", "PM10"),
|
||||||
|
(542, "OwmCo", "OWM CO", "OWM 一氧化碳", "CO", "CO"),
|
||||||
(516, "OwmCity", "OWM City Name", "OWM 城市", "CITY", "城市"),
|
(516, "OwmCity", "OWM City Name", "OWM 城市", "CITY", "城市"),
|
||||||
(517, "OwmUpdated", "OWM Update Time", "OWM 更新时间", "OWM", "更新"),
|
(517, "OwmUpdated", "OWM Update Time", "OWM 更新时间", "OWM", "更新"),
|
||||||
|
|
||||||
@@ -198,6 +232,22 @@ OTHER_SETTINGS = """
|
|||||||
<settingConfig type="numeric" min="-720" max="840"/>
|
<settingConfig type="numeric" min="-720" max="840"/>
|
||||||
</setting>
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.CountdownDate" title="@Strings.SettingCountdownDate">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="10"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.TimeFmt" title="@Strings.SettingTimeFmt">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="16"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.DateFmt1" title="@Strings.SettingDateFmt1">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="16"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.DateFmt2" title="@Strings.SettingDateFmt2">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="16"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
<setting propertyKey="@Properties.OwmKey" title="@Strings.SettingOwmKey">
|
<setting propertyKey="@Properties.OwmKey" title="@Strings.SettingOwmKey">
|
||||||
<settingConfig type="alphaNumeric" maxLength="40"/>
|
<settingConfig type="alphaNumeric" maxLength="40"/>
|
||||||
</setting>
|
</setting>
|
||||||
|
|||||||
@@ -41,8 +41,11 @@ ICONS = {
|
|||||||
"weather": ("weather-icon", 343.55, 89.5, False),
|
"weather": ("weather-icon", 343.55, 89.5, False),
|
||||||
}
|
}
|
||||||
|
|
||||||
# screen width -> whether the panel can blend alpha (MIP cannot)
|
# 屏宽 -> 该尺寸下的屏幕能否做 alpha 混合(MIP 屏不能)。
|
||||||
WIDTHS = {240: False, 260: False, 280: False, 390: True, 416: True, 454: True}
|
# 这张表和启动图标尺寸表都由 tools/gen_devices.py 扫描出的机型集合决定,
|
||||||
|
# 改完设备列表要跟着更新这里 —— 少一个尺寸就会有机型编译不过。
|
||||||
|
WIDTHS = {218: False, 240: False, 260: False, 280: False,
|
||||||
|
360: True, 390: True, 416: True, 454: True}
|
||||||
|
|
||||||
SS = 8 # supersampling factor
|
SS = 8 # supersampling factor
|
||||||
|
|
||||||
@@ -118,8 +121,8 @@ def render(paths, size, cx, cy, blend):
|
|||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
# Launcher icon sizes the target devices ask for.
|
# 各机型要求的启动图标尺寸(Garmin 每款表都有自己的规定值,不能用缩放糊弄)。
|
||||||
LAUNCHER = [40, 56, 60, 65, 70]
|
LAUNCHER = [38, 40, 54, 56, 60, 61, 65, 70]
|
||||||
|
|
||||||
|
|
||||||
def launcher(size):
|
def launcher(size):
|
||||||
@@ -160,6 +163,46 @@ def launcher(size):
|
|||||||
return img.resize((size, size), Image.LANCZOS)
|
return img.resize((size, size), Image.LANCZOS)
|
||||||
|
|
||||||
|
|
||||||
|
def tiny_weather(size):
|
||||||
|
"""给 16px 以下的屏幕专画的天气标记。
|
||||||
|
|
||||||
|
设计稿的天气图标是「太阳被云挡住一半」,靠一道暗缝把两者分开。那道缝在
|
||||||
|
500 画布上只有 1.7px,到 218px 屏(图标 14×14)连一个物理像素都占不到,
|
||||||
|
光栅化出来就是一坨没有结构的橙块。
|
||||||
|
|
||||||
|
所以这个尺寸不再走设计稿路径,改用按目标像素直接摆的简化标记:
|
||||||
|
右上一个太阳圆盘,左下一朵云,中间强制留 1 个像素的空隙。
|
||||||
|
形不如原图,但至少还认得出是「天气」。
|
||||||
|
"""
|
||||||
|
from PIL import ImageDraw
|
||||||
|
ss = 8
|
||||||
|
n = size * ss
|
||||||
|
img = Image.new("L", (n, n), 0)
|
||||||
|
d = ImageDraw.Draw(img)
|
||||||
|
u = n / 14.0 # 以 14 格为基准摆放,任何尺寸都等比
|
||||||
|
|
||||||
|
def disc(cx, cy, r, fill):
|
||||||
|
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=fill)
|
||||||
|
|
||||||
|
# 太阳 + 四道光芒(光芒画成小方块,比线段在低分辨率下更容易留下来)
|
||||||
|
disc(9.2 * u, 4.6 * u, 2.7 * u, 255)
|
||||||
|
for dx, dy in ((0, -4.4), (4.3, 0), (-3.2, -3.2), (3.2, -3.2)):
|
||||||
|
d.rectangle([9.2 * u + dx * u - 0.55 * u, 4.6 * u + dy * u - 0.55 * u,
|
||||||
|
9.2 * u + dx * u + 0.55 * u, 4.6 * u + dy * u + 0.55 * u],
|
||||||
|
fill=255)
|
||||||
|
# 先用黑色把云的轮廓“撑大”一圈,留出与太阳之间的缝,再画回本体
|
||||||
|
for grow, fill in ((1.0 * u, 0), (0.0, 255)):
|
||||||
|
disc(5.0 * u, 8.6 * u, 3.3 * u + grow, fill)
|
||||||
|
disc(8.6 * u, 9.6 * u, 2.4 * u + grow, fill)
|
||||||
|
d.rectangle([1.7 * u - grow, 8.8 * u - grow, 11.0 * u + grow, 12.2 * u],
|
||||||
|
fill=fill)
|
||||||
|
small = img.resize((size, size), Image.LANCZOS)
|
||||||
|
small = small.point(lambda v: 255 if v >= 110 else 0)
|
||||||
|
out = Image.new("RGB", (size, size), (255, 255, 255)).convert("RGBA")
|
||||||
|
out.putalpha(small)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
svg = open(SRC).read()
|
svg = open(SRC).read()
|
||||||
for size in LAUNCHER:
|
for size in LAUNCHER:
|
||||||
@@ -182,7 +225,11 @@ def main():
|
|||||||
if key not in cache:
|
if key not in cache:
|
||||||
p = subpaths(svg, pid)
|
p = subpaths(svg, pid)
|
||||||
cache[key] = flip_chevron(p) if flip else p
|
cache[key] = flip_chevron(p) if flip else p
|
||||||
img = render(cache[key], size, cx, cy, blend)
|
# 天气图标在极小尺寸下用专门的简化画法,日出/日落还撑得住。
|
||||||
|
if name == "weather" and size < 16:
|
||||||
|
img = tiny_weather(size)
|
||||||
|
else:
|
||||||
|
img = render(cache[key], size, cx, cy, blend)
|
||||||
img.save(os.path.join(outdir, "%s.png" % name))
|
img.save(os.path.join(outdir, "%s.png" % name))
|
||||||
names.append(name)
|
names.append(name)
|
||||||
with open(os.path.join(outdir, "drawables.xml"), "w") as f:
|
with open(os.path.join(outdir, "drawables.xml"), "w") as f:
|
||||||
|
|||||||
61
tools/install.sh
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 编译并侧载到通过 USB 接入的 Garmin 手表。
|
||||||
|
#
|
||||||
|
# tools/install.sh # 默认 enduro3
|
||||||
|
# tools/install.sh fenix847mm # 指定机型
|
||||||
|
#
|
||||||
|
# 机型 id 见 manifest.xml 的 <iq:product>。
|
||||||
|
#
|
||||||
|
# 前提:手表用数据线接到电脑,并且在 Finder 里能看到名为 GARMIN 的卷。
|
||||||
|
# 看不到的话见本文件末尾的说明。
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DEV="${1:-enduro3}"
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
SDK="$HOME/Library/Application Support/Garmin/ConnectIQ/Sdks"
|
||||||
|
MONKEYC="$(ls -d "$SDK"/*/bin/monkeyc 2>/dev/null | sort | tail -1)"
|
||||||
|
|
||||||
|
[ -z "$MONKEYC" ] && { echo "找不到 monkeyc,请先装 Connect IQ SDK"; exit 1; }
|
||||||
|
[ -f "$ROOT/developer_key.der" ] || { echo "缺 developer_key.der"; exit 1; }
|
||||||
|
grep -q "\"$DEV\"\|id=\"$DEV\"" "$ROOT/manifest.xml" || {
|
||||||
|
echo "manifest.xml 里没有机型 '$DEV'"; exit 1; }
|
||||||
|
|
||||||
|
PRG="$ROOT/bin/Fenix8V3_${DEV}.prg"
|
||||||
|
echo "==> 编译 $DEV(release)"
|
||||||
|
mkdir -p "$ROOT/bin"
|
||||||
|
"$MONKEYC" -o "$PRG" -f "$ROOT/monkey.jungle" -y "$ROOT/developer_key.der" \
|
||||||
|
-d "$DEV" -r 2>&1 | tail -1
|
||||||
|
|
||||||
|
# Garmin 卷的名字通常就是 GARMIN,个别机型会带型号后缀
|
||||||
|
VOL=""
|
||||||
|
for v in /Volumes/*; do
|
||||||
|
[ -d "$v/GARMIN" ] && VOL="$v" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$VOL" ]; then
|
||||||
|
cat <<'EOS'
|
||||||
|
|
||||||
|
==> 没找到手表。
|
||||||
|
|
||||||
|
在 Finder 里看不到 GARMIN 卷,通常是这两种情况之一:
|
||||||
|
|
||||||
|
1. 表没解锁 —— 部分机型插上后要在表上确认一次
|
||||||
|
2. 走的是 MTP 协议 —— macOS 的 Finder 不支持 MTP。fenix 7 之后的机型
|
||||||
|
多半是这种。装一个 OpenMTP(免费,https://openmtp.ganeshrvel.com)
|
||||||
|
或 Android File Transfer,就能像普通盘一样浏览手表了。
|
||||||
|
|
||||||
|
用 MTP 工具时,把下面这个文件拖到手表的 GARMIN/APPS/ 目录即可:
|
||||||
|
EOS
|
||||||
|
echo " $PRG"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEST="$VOL/GARMIN/APPS"
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
cp "$PRG" "$DEST/"
|
||||||
|
echo "==> 已复制到 $DEST/$(basename "$PRG")"
|
||||||
|
echo
|
||||||
|
echo "接下来:"
|
||||||
|
echo " 1. 在 Finder 里推出(Eject)手表这个卷,再拔线"
|
||||||
|
echo " 2. 表上长按 UP/MENU -> 表盘 -> 找到 Fenix 8 V3"
|
||||||
|
echo " 3. 再长按 MENU 可以进这个表盘自己的设置,改主题和四个数据位"
|
||||||