Add the on-device data field catalogue, always-on mode, and real icons
Fields 75 selectable fields across date/time, activity, body, system, environment, weather and custom text, numbered to match watchface.io/docs/datafields so the two catalogues line up. All four slots (both top, both bottom) pick from the same table and the bottom captions follow the selection. tools/gen_fields.py owns the table and emits FieldTable.mc, settings.xml and both languages' strings, so those cannot drift apart. Fields.Ctx fetches each API at most once per draw, and only if some slot asks -- the forecast, user profile and sensor history lookups are lazy. The rest of that catalogue is deliberately absent: OpenWeatherMap and StormGlass need network calls and user API keys, the per-sport weekly and 28-day aggregates come from a phone-side Garmin Connect integration rather than any on-device API, and the app complications need those apps installed. README section 7 lists what was left out and why. Permissions UserProfile and SensorHistory are now declared; without them resting heart rate, VO2 max, pulse ox and body battery throw at runtime. Sensor turns out to be rejected outright for type="watchface", so altitude and pressure read from Activity.Info instead. Always-on display AMOLED devices need the lit-pixel count cut while asleep. drawAmbient drops the band fill and the unlit ticks, dims what remains, hides the complications, and walks the layout within +/-4 px per minute so nothing burns in. Gated on requiresBurnInProtection, so MIP devices are untouched. Icons The comps' sunrise and weather icons have two- and three-pixel features. Rebuilding them from circles and lines looked right at 454 px and turned to mush at 280. They are now rasterised from the design paths once per screen width and tinted with drawBitmap2(:tintColor) -- which needs packingFormat="png", since a palettised source is refused at runtime. Launcher icons are generated at the five sizes the devices ask for. All 17 devices build; checked on fenix847mm and enduro3 in the simulator. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
121
README.md
@@ -23,17 +23,30 @@ fenix8v3-watchface/
|
|||||||
│ └── rebuild.py # 原作者从位图逆向出 SVG 的脚本(仅存档,不参与构建)
|
│ └── rebuild.py # 原作者从位图逆向出 SVG 的脚本(仅存档,不参与构建)
|
||||||
├── 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_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/
|
||||||
│ ├── Fenix8V3App.mc # 应用入口
|
│ ├── Fenix8V3App.mc # 应用入口
|
||||||
│ ├── Fenix8V3View.mc # 全部渲染逻辑
|
│ ├── Fenix8V3View.mc # 渲染逻辑(含常亮模式)
|
||||||
│ └── Themes.mc # ⚠️ 自动生成,勿手改
|
│ ├── Fields.mc # 75 项数据字段的取值与格式化
|
||||||
├── resources/ # 英文文案 + 设置项定义
|
│ ├── FieldTable.mc # ⚠️ 自动生成:字段 id 表
|
||||||
|
│ └── Themes.mc # ⚠️ 自动生成:七套主题配色
|
||||||
|
├── resources/ # 英文文案 + 设置项定义(部分自动生成)
|
||||||
├── resources-chn/ # 简体中文文案
|
├── resources-chn/ # 简体中文文案
|
||||||
|
├── resources-icons-<宽度>/ # ⚠️ 自动生成:按屏幕尺寸光栅化的图标
|
||||||
|
├── resources-launcher-<尺寸>/ # ⚠️ 自动生成:各尺寸启动图标
|
||||||
└── bin/ # 编译产物(已 gitignore)
|
└── bin/ # 编译产物(已 gitignore)
|
||||||
```
|
```
|
||||||
|
|
||||||
三份实现(表盘 / SVG 预览 / HTML 预览)只有 **一份颜色数据**:`Themes.mc`。预览脚本直接解析它,不会与固件漂移。
|
重新生成全部派生文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/gen_themes.py && python3 tools/gen_fields.py \
|
||||||
|
&& python3 tools/gen_icons.py && python3 tools/gen_preview_svg.py
|
||||||
|
```
|
||||||
|
|
||||||
|
三份渲染实现(表盘 / SVG 预览 / HTML 预览)只有 **一份颜色数据**:`Themes.mc`。预览脚本直接解析它,不会与固件漂移。字段表、设置项、两种语言的文案同样只有 `tools/gen_fields.py` 里那一张表。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -127,42 +140,94 @@ python3 tools/gen_themes.py && python3 tools/gen_preview_svg.py
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. 可配置设置
|
## 7. 可配置设置与数据字段
|
||||||
|
|
||||||
共 13 项,通过 Garmin Connect 手机 App 的「表盘设置」修改(定义在 `resources/settings/settings.xml`,默认值在 `properties.xml`)。
|
19 项设置,在 Garmin Connect 手机 App 的「表盘设置」里改(定义在 `resources/settings/settings.xml`,默认值在 `properties.xml`,两者由 `tools/gen_fields.py` 生成)。
|
||||||
|
|
||||||
| 设置 | 键 | 可选值 | 默认 |
|
| 设置 | 键 | 默认 |
|
||||||
|------|----|--------|------|
|
|------|----|------|
|
||||||
| 主题 | `Theme` | 1–7 | 1 |
|
| 主题 | `Theme` | 1 (Ember) |
|
||||||
| 顶右 / 顶左 / 底右 / 底左弧指标 | `RingTR/TL/BR/BL` | 0 步数 / 1 卡路里 / 2 距离 / 3 楼层 / 4 动动条 | 0 / 1 / 4 / 3 |
|
| 四段进度环指标 | `RingTR/TL/BR/BL` | 步数 / 卡路里 / 动动条 / 楼层 |
|
||||||
| 左上数据 | `LeftTop` | 见下 | 6(日出) |
|
| 四个数据位 | `LeftTop` `RightTop` `BottomLeft` `BottomRight` | 日出 / 高低温 / 距离 / 步数 |
|
||||||
| 右上数据 | `RightTop` | 见下 | 8(最高/最低气温) |
|
| 温度单位 | `TempUnit` | 跟随手表 |
|
||||||
| 左下数据 | `BottomLeft` | 见下 | 3(距离) |
|
| 第二时区 1 / 2 偏移(分钟) | `AltOffset1` `AltOffset2` | 0 |
|
||||||
| 右下数据 | `BottomRight` | 见下 | 2(步数) |
|
| 自定义文字 1 / 2 / 3 | `Custom1/2/3` | 空 |
|
||||||
| 温度单位 | `TempUnit` | 0 跟随手表 / 1 °C / 2 °F | 0 |
|
| 显示顶行 / 日期带 / 底行 | `ShowTop` `ShowBand` `ShowBottom` | true |
|
||||||
| 显示顶行 / 日期带 / 底行 | `ShowTop` / `ShowBand` / `ShowBottom` | 布尔 | true |
|
|
||||||
|
|
||||||
**数据项**(四个数据位共用同一张表):`0` 当前气温 · `1` 卡路里 · `2` 步数 · `3` 距离 · `4` 楼层 · `5` 电量 · `6` 日出 · `7` 日落 · `8` 最高/最低气温 · `9` 心率。
|
进度环指标:步数、卡路里、距离、楼层、动动条、活动分钟(对周目标)、身体电量。
|
||||||
|
|
||||||
默认值即设计稿的配置:左上=日出时间、右上=`63°/52°`、左下=距离、右下=步数。
|
### 数据字段
|
||||||
|
|
||||||
底部两格的小标签跟随所选数据自动变化(`距离` / `步数` / `心率` …),不需要单独配置。
|
四个数据位共用一张 **75 项** 的字段表,编号沿用 [watchface.io/docs/datafields](https://watchface.io/docs/datafields),两边可以直接对照。底部两格的小标签跟随所选字段自动变化,不需要单独配置。
|
||||||
|
|
||||||
|
| 分类 | 字段 |
|
||||||
|
|------|------|
|
||||||
|
| 日期时间 | 时间、UTC 时间、第二时区 1/2、星期月日、星期日、月日、日、星期(简写/全称)、月份(名称/数字)、上午下午、周数(ISO / 日历) |
|
||||||
|
| 活动 | 步数、步数取整、剩余步数、卡路里、活动卡路里、活动分钟(总/中强度/高强度/本周)、距离、上楼层数、下楼层数、爬升高度、推行距离、推行次数 |
|
||||||
|
| 身体 | 心率、静息心率、压力指数、血氧、呼吸频率、身体电量、活动条(正/反向)、体重、BMI、恢复时间、最大摄氧量(跑/骑) |
|
||||||
|
| 系统 | 电量、剩余天数、电量+天数、闹钟、通知、勿扰、蓝牙、太阳能强度 |
|
||||||
|
| 环境 | 海拔、海平面气压、环境气压、日出、日落、下一太阳事件、距太阳事件、月龄、月球照明 |
|
||||||
|
| 天气 | 气温、最高/最低、体感、最高、最低、湿度、降水概率、风速、风向角、风向、更新时间 |
|
||||||
|
| 自定义 | 自定义文字 1 / 2 / 3 |
|
||||||
|
|
||||||
|
加字段:在 `tools/gen_fields.py` 的 `FIELDS` 表里加一行,跑一遍生成器,再到 `source/Fields.mc` 的 `value()` 里补一个分支。设置项、两种语言的文案、表盘标签会自动跟上。
|
||||||
|
|
||||||
|
### ⚠️ 没有实现的字段,以及原因
|
||||||
|
|
||||||
|
watchface.io 的完整目录约 300 项,本表只收了 **手表本机能算出来的** 那些。其余几类不是没做,是一个自包含的 Connect IQ 表盘做不了:
|
||||||
|
|
||||||
|
| 没做的 | 原因 |
|
||||||
|
|--------|------|
|
||||||
|
| OpenWeatherMap(约 30 项)、StormGlass.io(5 项) | 需要 `Communications.makeWebRequest` + 用户自备 API key + 后台服务定时拉取与缓存。是另一个子系统,且要求用户自己去注册 key |
|
||||||
|
| 各运动项目的 WTD / 近 7 天 / 本月 / 近 28 天 汇总(约 80 项) | Connect IQ 不暴露按运动分类的历史累计。这些数据在 GreenBlack 那边是通过手机侧与 Garmin Connect 的集成同步来的,本机 API 只有 `ActivityMonitor.getHistory()` 的最近几天步数/卡路里/距离 |
|
||||||
|
| 第三方 App 复杂功能(CGM、Hydration、QuoteGlance 等 12 项) | 需要用户装了对应的 App,并通过 Complications API 订阅 |
|
||||||
|
| 日历事件、计时器、倒计时 | 日历不对 CIQ 开放;表盘拿不到触摸输入,没法做启停 |
|
||||||
|
| 秒数 | 需要 `onPartialUpdate` 每秒重绘。技术上可行,但会明显吃电,暂未做 |
|
||||||
|
| 晨昏蒙影、黄金/蓝调时刻(约 14 项) | 可以自己算太阳高度角推出来,只是还没写;月龄和月球照明已经是这样算的 |
|
||||||
|
| 睡眠得分、训练状态 | `ActivityMonitor.Info` 上没有这两个字段(SDK 9.1.0 实测) |
|
||||||
|
| 标记类字段(Marker,约 35 项) | 那是给刻度型表盘做环形标记用的,本表盘的进度环用的是自己的指标体系 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. 数据来源
|
## 8. 数据来源与权限
|
||||||
|
|
||||||
| 元素 | API |
|
| 元素 | API |
|
||||||
|------|-----|
|
|------|-----|
|
||||||
| 步数 / 卡路里 / 距离 / 楼层 / 动动条 | `ActivityMonitor.getInfo()` |
|
| 步数 / 卡路里 / 距离 / 楼层 / 动动条 / 活动分钟 / 呼吸 / 压力 / 恢复时间 | `ActivityMonitor.getInfo()` |
|
||||||
| 日出 / 日落 | `Weather.getSunrise/getSunset(location, Time.now())`,位置取 `CurrentConditions.observationLocationPosition`,回退 `Activity.getActivityInfo().currentLocation` |
|
| 心率 | `Activity.getActivityInfo().currentHeartRate`,回退 `ActivityMonitor.getHeartRateHistory()` |
|
||||||
| 最高 / 最低气温 | `Weather.getDailyForecast()[0].highTemperature / lowTemperature` |
|
| 血氧 / 身体电量 | `SensorHistory`(需 `SensorHistory` 权限) |
|
||||||
| 当前气温 | `Weather.getCurrentConditions().temperature` |
|
| 静息心率 / 体重 / 身高 / 最大摄氧量 | `UserProfile.getProfile()`(需 `UserProfile` 权限) |
|
||||||
| 电量 | `System.getSystemStats().battery`(0–100 的百分比) |
|
| 海拔 / 环境气压 / 海平面气压 | `Activity.getActivityInfo()` |
|
||||||
| 时间 / 日期 | `Time.Gregorian`,`FORMAT_SHORT`(只有短格式的 `day_of_week` / `month` 是数字) |
|
| 日出 / 日落 | `Weather.getSunrise/getSunset(location, time)`,位置取 `CurrentConditions.observationLocationPosition`,回退 `Activity.currentLocation` |
|
||||||
| 蓝牙 | `DeviceSettings.phoneConnected`,未连接时图标压暗 |
|
| 气温 / 体感 / 湿度 / 降水 / 风 | `Weather.getCurrentConditions()` |
|
||||||
|
| 最高 / 最低气温 | `Weather.getDailyForecast()[0]` |
|
||||||
|
| 电量 / 剩余天数 / 太阳能 | `System.getSystemStats()`(`battery` 是 0–100 的百分比,不是 0–1) |
|
||||||
|
| 闹钟 / 通知 / 勿扰 / 蓝牙 / 单位制 | `System.getDeviceSettings()` |
|
||||||
|
| 月龄 / 月球照明 | 自算(儒略日 + 朔望月 29.530588853 天) |
|
||||||
|
| 周数 | 自算(ISO 8601 与日历周两种) |
|
||||||
|
|
||||||
单位与制式跟随手表设置:`is24Hour`(24 小时制下不显示 AM/PM)、`distanceUnits`(km / mi)、`temperatureUnits`(°C / °F)。天气与日出需手表已配对并同步过数据,否则显示 `--`。
|
### 权限
|
||||||
|
|
||||||
|
`manifest.xml` 声明两项:`UserProfile`(静息心率、体重、VO2max)与 `SensorHistory`(血氧、身体电量)。用户安装时会看到授权提示。
|
||||||
|
|
||||||
|
> `Sensor` 权限**不能**用于 `type="watchface"`(除非同时申请 `Background`),编译器会直接拒绝。所以海拔与气压走 `Activity.Info` 而不是 `Sensor.getInfo()`。
|
||||||
|
|
||||||
|
单位与制式跟随手表:`is24Hour`、`distanceUnits`、`temperatureUnits`(温度可用 `TempUnit` 覆盖)。天气与日出需手表已配对并同步过数据,否则显示 `--`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8.2 常亮显示(AOD)
|
||||||
|
|
||||||
|
AMOLED 机型在常亮模式下必须压低点亮像素,否则既费电又有烧屏风险,Garmin 上架审核也会卡。`onEnterSleep` / `onExitSleep` 切换到 `drawAmbient()`:
|
||||||
|
|
||||||
|
- 不画日期带底色(整条亮橙色横幅是最费电的元素)
|
||||||
|
- 进度环只画**点亮的**刻度,且整体压暗到 45%
|
||||||
|
- 时间与日期压暗到 55%,其余数据位全部隐藏
|
||||||
|
- 整个画面按分钟在 ±4px 内游走,避免固定像素长期点亮
|
||||||
|
|
||||||
|
只在 `DeviceSettings.requiresBurnInProtection` 为真时启用;MIP 机型(Fenix 7 / Enduro 3 等)不受影响,照常全量绘制。
|
||||||
|
|
||||||
|
模拟器可用 `Settings → Display Mode` 切换验证。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -34,5 +34,12 @@
|
|||||||
<iq:language>eng</iq:language>
|
<iq:language>eng</iq:language>
|
||||||
</iq:languages>
|
</iq:languages>
|
||||||
|
|
||||||
|
<!-- Sensor is rejected for type="watchface" unless Background is also
|
||||||
|
requested, so altitude and pressure come from Activity.Info instead. -->
|
||||||
|
<iq:permissions>
|
||||||
|
<iq:uses-permission id="UserProfile"/>
|
||||||
|
<iq:uses-permission id="SensorHistory"/>
|
||||||
|
</iq:permissions>
|
||||||
|
|
||||||
</iq:application>
|
</iq:application>
|
||||||
</iq:manifest>
|
</iq:manifest>
|
||||||
|
|||||||
@@ -1,7 +1,32 @@
|
|||||||
project.manifest = manifest.xml
|
project.manifest = manifest.xml
|
||||||
|
|
||||||
base.sourcePath = source
|
base.sourcePath = source
|
||||||
# resources-chn holds the Simplified Chinese variant of every string;
|
|
||||||
# the -chn suffix is what tells monkeyc which language it belongs to.
|
# resources-chn holds the Simplified Chinese variant of every string; the
|
||||||
|
# -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:
|
||||||
|
# resources-icons-<screen width> sunrise/sunset/weather rasterised from the
|
||||||
|
# design paths for that exact pixel grid
|
||||||
|
# resources-launcher-<size> launcher icon at the size the device asks for
|
||||||
|
|
||||||
|
enduro3.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
||||||
|
epix2.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
|
||||||
|
fenix7.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
||||||
|
fenix7s.resourcePath = $(base.resourcePath);resources-icons-240;resources-launcher-40
|
||||||
|
fenix7x.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
||||||
|
fenix843mm.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
||||||
|
fenix847mm.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
|
||||||
|
fenix8solar51mm.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40
|
||||||
|
fr255.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40
|
||||||
|
fr265.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60
|
||||||
|
fr965.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65
|
||||||
|
venu3.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-70
|
||||||
|
vivoactive5.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-56
|
||||||
|
|
||||||
base.excludeAnnotations = release
|
base.excludeAnnotations = release
|
||||||
|
|||||||
89
resources-chn/strings/fields.xml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<strings>
|
||||||
|
<!-- Field names shown in the settings picker. -->
|
||||||
|
<string id="FldOff">关闭</string>
|
||||||
|
<string id="FldTime">时间</string>
|
||||||
|
<string id="FldUtcTime">UTC 时间</string>
|
||||||
|
<string id="FldAltTime1">第二时区 1</string>
|
||||||
|
<string id="FldAltTime2">第二时区 2</string>
|
||||||
|
<string id="FldDayMonthDay">星期 月 日</string>
|
||||||
|
<string id="FldDayDate">星期 日</string>
|
||||||
|
<string id="FldMonthDay">月 日</string>
|
||||||
|
<string id="FldDayOfMonth">日</string>
|
||||||
|
<string id="FldDayShort">星期简写</string>
|
||||||
|
<string id="FldDayFull">星期全称</string>
|
||||||
|
<string id="FldMonthName">月份</string>
|
||||||
|
<string id="FldMonthNum">月份数字</string>
|
||||||
|
<string id="FldMeridiem">上午/下午</string>
|
||||||
|
<string id="FldWeekIso">周数 (ISO)</string>
|
||||||
|
<string id="FldWeekCal">周数</string>
|
||||||
|
<string id="FldSteps">步数</string>
|
||||||
|
<string id="FldStepsRound">步数 (取整)</string>
|
||||||
|
<string id="FldStepsToGo">剩余步数</string>
|
||||||
|
<string id="FldCalories">卡路里</string>
|
||||||
|
<string id="FldActiveCal">活动卡路里</string>
|
||||||
|
<string id="FldActiveMin">活动分钟</string>
|
||||||
|
<string id="FldActiveMod">中强度分钟</string>
|
||||||
|
<string id="FldActiveVig">高强度分钟</string>
|
||||||
|
<string id="FldActiveWeek">本周活动分钟</string>
|
||||||
|
<string id="FldDistance">距离</string>
|
||||||
|
<string id="FldFloorsUp">上楼层数</string>
|
||||||
|
<string id="FldFloorsDown">下楼层数</string>
|
||||||
|
<string id="FldMetersUp">爬升高度</string>
|
||||||
|
<string id="FldPushDist">推行距离</string>
|
||||||
|
<string id="FldPushes">推行次数</string>
|
||||||
|
<string id="FldHeartRate">心率</string>
|
||||||
|
<string id="FldRestingHr">静息心率</string>
|
||||||
|
<string id="FldStress">压力指数</string>
|
||||||
|
<string id="FldPulseOx">血氧</string>
|
||||||
|
<string id="FldRespiration">呼吸频率</string>
|
||||||
|
<string id="FldBodyBattery">身体电量</string>
|
||||||
|
<string id="FldMoveBar">活动条</string>
|
||||||
|
<string id="FldMoveBarRev">活动条 (反向)</string>
|
||||||
|
<string id="FldWeight">体重</string>
|
||||||
|
<string id="FldBmi">BMI</string>
|
||||||
|
<string id="FldRecovery">恢复时间</string>
|
||||||
|
<string id="FldVo2Run">最大摄氧量 (跑)</string>
|
||||||
|
<string id="FldVo2Bike">最大摄氧量 (骑)</string>
|
||||||
|
<string id="FldBattery">电量</string>
|
||||||
|
<string id="FldBatteryDays">剩余天数</string>
|
||||||
|
<string id="FldBatteryBoth">电量 / 天数</string>
|
||||||
|
<string id="FldAlarms">闹钟</string>
|
||||||
|
<string id="FldNotifications">通知</string>
|
||||||
|
<string id="FldDoNotDisturb">勿扰模式</string>
|
||||||
|
<string id="FldBluetooth">蓝牙</string>
|
||||||
|
<string id="FldSolar">太阳能强度</string>
|
||||||
|
<string id="FldAltitude">海拔</string>
|
||||||
|
<string id="FldSeaPressure">海平面气压</string>
|
||||||
|
<string id="FldAmbPressure">环境气压</string>
|
||||||
|
<string id="FldSunrise">日出</string>
|
||||||
|
<string id="FldSunset">日落</string>
|
||||||
|
<string id="FldNextSun">下一太阳事件</string>
|
||||||
|
<string id="FldUntilSun">距太阳事件</string>
|
||||||
|
<string id="FldMoonAge">月龄</string>
|
||||||
|
<string id="FldMoonLit">月球照明</string>
|
||||||
|
<string id="FldTemp">气温</string>
|
||||||
|
<string id="FldTempHiLo">最高/最低气温</string>
|
||||||
|
<string id="FldFeelsLike">体感温度</string>
|
||||||
|
<string id="FldTempHigh">最高气温</string>
|
||||||
|
<string id="FldTempLow">最低气温</string>
|
||||||
|
<string id="FldHumidity">湿度</string>
|
||||||
|
<string id="FldPrecip">降水概率</string>
|
||||||
|
<string id="FldWindSpeed">风速</string>
|
||||||
|
<string id="FldWindBearing">风向角</string>
|
||||||
|
<string id="FldWindDir">风向</string>
|
||||||
|
<string id="FldWeatherTime">天气更新时间</string>
|
||||||
|
<string id="FldCustom1">自定义文字 1</string>
|
||||||
|
<string id="FldCustom2">自定义文字 2</string>
|
||||||
|
<string id="FldCustom3">自定义文字 3</string>
|
||||||
|
|
||||||
|
<!-- On-face captions, in FieldTable.IDS order. -->
|
||||||
|
<string id="FieldLabels">,时间,UTC,时区1,时区2,日期,日期,日期,日,星期,星期,月,月,,周,周,步数,步数,还差,千卡,活动,分钟,中强度,高强度,周分钟,距离,楼层,下楼,爬升,推行,推行,心率,静息,压力,血氧,呼吸,体能,活动,活动,体重,BMI,恢复,摄氧,摄氧,电量,天数,电量,闹钟,通知,勿扰,蓝牙,太阳能,海拔,气压,气压,日出,日落,太阳,太阳,月龄,月相,气温,高低温,体感,最高,最低,湿度,降水,风速,风向,风向,更新,,,</string>
|
||||||
|
|
||||||
|
<string id="MetricSteps">步数</string>
|
||||||
|
<string id="MetricCalories">卡路里</string>
|
||||||
|
<string id="MetricDistance">距离</string>
|
||||||
|
<string id="MetricFloors">楼层</string>
|
||||||
|
<string id="MetricMoveBar">动动条</string>
|
||||||
|
<string id="MetricActiveMin">活动分钟</string>
|
||||||
|
<string id="MetricBodyBattery">身体电量</string>
|
||||||
|
</strings>
|
||||||
@@ -10,10 +10,20 @@
|
|||||||
<string id="SettingRightTop">右上数据</string>
|
<string id="SettingRightTop">右上数据</string>
|
||||||
<string id="SettingBottomLeft">左下数据</string>
|
<string id="SettingBottomLeft">左下数据</string>
|
||||||
<string id="SettingBottomRight">右下数据</string>
|
<string id="SettingBottomRight">右下数据</string>
|
||||||
|
<string id="SettingTempUnit">温度单位</string>
|
||||||
|
<string id="SettingAltOffset1">第二时区 1(相对本地分钟数)</string>
|
||||||
|
<string id="SettingAltOffset2">第二时区 2(相对本地分钟数)</string>
|
||||||
|
<string id="SettingCustom1">自定义文字 1</string>
|
||||||
|
<string id="SettingCustom2">自定义文字 2</string>
|
||||||
|
<string id="SettingCustom3">自定义文字 3</string>
|
||||||
<string id="SettingShowTop">显示顶部一行</string>
|
<string id="SettingShowTop">显示顶部一行</string>
|
||||||
<string id="SettingShowBand">显示日期带</string>
|
<string id="SettingShowBand">显示日期带</string>
|
||||||
<string id="SettingShowBottom">显示底部一行</string>
|
<string id="SettingShowBottom">显示底部一行</string>
|
||||||
|
|
||||||
|
<string id="TempUnitAuto">跟随手表</string>
|
||||||
|
<string id="TempUnitC">摄氏度 (°C)</string>
|
||||||
|
<string id="TempUnitF">华氏度 (°F)</string>
|
||||||
|
|
||||||
<string id="ThemeEmber">1 · 余烬</string>
|
<string id="ThemeEmber">1 · 余烬</string>
|
||||||
<string id="ThemeAurora">2 · 极光</string>
|
<string id="ThemeAurora">2 · 极光</string>
|
||||||
<string id="ThemeBrass">3 · 黄铜</string>
|
<string id="ThemeBrass">3 · 黄铜</string>
|
||||||
@@ -22,32 +32,9 @@
|
|||||||
<string id="ThemeAcid">6 · 酸绿</string>
|
<string id="ThemeAcid">6 · 酸绿</string>
|
||||||
<string id="ThemeKelp">7 · 海藻</string>
|
<string id="ThemeKelp">7 · 海藻</string>
|
||||||
|
|
||||||
<string id="MetricSteps">步数</string>
|
|
||||||
<string id="MetricCalories">卡路里</string>
|
|
||||||
<string id="MetricDistance">距离</string>
|
|
||||||
<string id="MetricFloors">楼层</string>
|
|
||||||
<string id="MetricMoveBar">动动条</string>
|
|
||||||
|
|
||||||
<string id="FieldWeather">气温</string>
|
|
||||||
<string id="FieldHiLo">最高 / 最低气温</string>
|
|
||||||
<string id="FieldSunrise">日出</string>
|
|
||||||
<string id="FieldSunset">日落</string>
|
|
||||||
<string id="FieldCalories">卡路里</string>
|
|
||||||
<string id="FieldSteps">步数</string>
|
|
||||||
<string id="FieldDistance">距离</string>
|
|
||||||
<string id="FieldFloors">楼层</string>
|
|
||||||
<string id="FieldBattery">电量</string>
|
|
||||||
<string id="FieldHeartRate">心率</string>
|
|
||||||
|
|
||||||
<!-- 表盘上的文案。逗号分隔的列表在运行时拆分。 -->
|
<!-- 表盘上的文案。逗号分隔的列表在运行时拆分。 -->
|
||||||
<string id="DowNames">周日,周一,周二,周三,周四,周五,周六</string>
|
<string id="DowNames">周日,周一,周二,周三,周四,周五,周六</string>
|
||||||
<string id="MonNames">1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月</string>
|
<string id="MonNames">1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月</string>
|
||||||
<string id="DateFormat">$1$$2$日</string>
|
<string id="DateFormat">$1$$2$日</string>
|
||||||
<string id="Meridiem">上午,下午</string>
|
<string id="Meridiem">上午,下午</string>
|
||||||
<string id="FieldLabels">气温,卡路里,步数,距离,楼层,电量,日出,日落,高低温,心率</string>
|
|
||||||
|
|
||||||
<string id="SettingTempUnit">温度单位</string>
|
|
||||||
<string id="TempUnitAuto">跟随手表</string>
|
|
||||||
<string id="TempUnitC">摄氏度 (°C)</string>
|
|
||||||
<string id="TempUnitF">华氏度 (°F)</string>
|
|
||||||
</strings>
|
</strings>
|
||||||
|
|||||||
5
resources-icons-240/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-240/drawables/sunrise.png
Normal file
|
After Width: | Height: | Size: 155 B |
BIN
resources-icons-240/drawables/sunset.png
Normal file
|
After Width: | Height: | Size: 148 B |
BIN
resources-icons-240/drawables/weather.png
Normal file
|
After Width: | Height: | Size: 157 B |
5
resources-icons-260/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-260/drawables/sunrise.png
Normal file
|
After Width: | Height: | Size: 172 B |
BIN
resources-icons-260/drawables/sunset.png
Normal file
|
After Width: | Height: | Size: 165 B |
BIN
resources-icons-260/drawables/weather.png
Normal file
|
After Width: | Height: | Size: 163 B |
5
resources-icons-280/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-280/drawables/sunrise.png
Normal file
|
After Width: | Height: | Size: 172 B |
BIN
resources-icons-280/drawables/sunset.png
Normal file
|
After Width: | Height: | Size: 168 B |
BIN
resources-icons-280/drawables/weather.png
Normal file
|
After Width: | Height: | Size: 173 B |
5
resources-icons-390/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-390/drawables/sunrise.png
Normal file
|
After Width: | Height: | Size: 636 B |
BIN
resources-icons-390/drawables/sunset.png
Normal file
|
After Width: | Height: | Size: 662 B |
BIN
resources-icons-390/drawables/weather.png
Normal file
|
After Width: | Height: | Size: 677 B |
5
resources-icons-416/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-416/drawables/sunrise.png
Normal file
|
After Width: | Height: | Size: 701 B |
BIN
resources-icons-416/drawables/sunset.png
Normal file
|
After Width: | Height: | Size: 733 B |
BIN
resources-icons-416/drawables/weather.png
Normal file
|
After Width: | Height: | Size: 746 B |
5
resources-icons-454/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-454/drawables/sunrise.png
Normal file
|
After Width: | Height: | Size: 767 B |
BIN
resources-icons-454/drawables/sunset.png
Normal file
|
After Width: | Height: | Size: 783 B |
BIN
resources-icons-454/drawables/weather.png
Normal file
|
After Width: | Height: | Size: 829 B |
BIN
resources-launcher-40/drawables/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
3
resources-launcher-56/drawables/drawables.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<drawables>
|
||||||
|
<bitmap id="LauncherIcon" filename="launcher_icon.png"/>
|
||||||
|
</drawables>
|
||||||
BIN
resources-launcher-56/drawables/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
3
resources-launcher-60/drawables/drawables.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<drawables>
|
||||||
|
<bitmap id="LauncherIcon" filename="launcher_icon.png"/>
|
||||||
|
</drawables>
|
||||||
BIN
resources-launcher-60/drawables/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
3
resources-launcher-65/drawables/drawables.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<drawables>
|
||||||
|
<bitmap id="LauncherIcon" filename="launcher_icon.png"/>
|
||||||
|
</drawables>
|
||||||
BIN
resources-launcher-65/drawables/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
3
resources-launcher-70/drawables/drawables.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<drawables>
|
||||||
|
<bitmap id="LauncherIcon" filename="launcher_icon.png"/>
|
||||||
|
</drawables>
|
||||||
BIN
resources-launcher-70/drawables/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 498 B |
@@ -4,11 +4,18 @@
|
|||||||
<property id="RingTL" type="number">1</property>
|
<property id="RingTL" type="number">1</property>
|
||||||
<property id="RingBR" type="number">4</property>
|
<property id="RingBR" type="number">4</property>
|
||||||
<property id="RingBL" type="number">3</property>
|
<property id="RingBL" type="number">3</property>
|
||||||
<property id="LeftTop" type="number">6</property>
|
<!-- Field ids follow https://watchface.io/docs/datafields; the defaults are
|
||||||
<property id="RightTop" type="number">8</property>
|
the comps: sunrise, forecast high/low, distance, steps. -->
|
||||||
|
<property id="LeftTop" type="number">712</property>
|
||||||
|
<property id="RightTop" type="number">639</property>
|
||||||
<property id="BottomLeft" type="number">3</property>
|
<property id="BottomLeft" type="number">3</property>
|
||||||
<property id="BottomRight" type="number">2</property>
|
<property id="BottomRight" type="number">1</property>
|
||||||
<property id="TempUnit" type="number">0</property>
|
<property id="TempUnit" type="number">0</property>
|
||||||
|
<property id="AltOffset1" type="number">0</property>
|
||||||
|
<property id="AltOffset2" type="number">0</property>
|
||||||
|
<property id="Custom1" type="string"></property>
|
||||||
|
<property id="Custom2" type="string"></property>
|
||||||
|
<property id="Custom3" type="string"></property>
|
||||||
<property id="ShowTop" type="boolean">true</property>
|
<property id="ShowTop" type="boolean">true</property>
|
||||||
<property id="ShowBand" type="boolean">true</property>
|
<property id="ShowBand" type="boolean">true</property>
|
||||||
<property id="ShowBottom" type="boolean">true</property>
|
<property id="ShowBottom" type="boolean">true</property>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<settings>
|
<settings>
|
||||||
|
|
||||||
<setting propertyKey="@Properties.Theme" title="@Strings.SettingTheme">
|
<setting propertyKey="@Properties.Theme" title="@Strings.SettingTheme">
|
||||||
<settingConfig type="list">
|
<settingConfig type="list">
|
||||||
<listEntry value="1">@Strings.ThemeEmber</listEntry>
|
<listEntry value="1">@Strings.ThemeEmber</listEntry>
|
||||||
@@ -11,6 +12,326 @@
|
|||||||
</settingConfig>
|
</settingConfig>
|
||||||
</setting>
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.LeftTop" title="@Strings.SettingLeftTop">
|
||||||
|
<settingConfig type="list">
|
||||||
|
<listEntry value="0">@Strings.FldOff</listEntry>
|
||||||
|
<listEntry value="860">@Strings.FldTime</listEntry>
|
||||||
|
<listEntry value="880">@Strings.FldUtcTime</listEntry>
|
||||||
|
<listEntry value="864">@Strings.FldAltTime1</listEntry>
|
||||||
|
<listEntry value="865">@Strings.FldAltTime2</listEntry>
|
||||||
|
<listEntry value="851">@Strings.FldDayMonthDay</listEntry>
|
||||||
|
<listEntry value="872">@Strings.FldDayDate</listEntry>
|
||||||
|
<listEntry value="852">@Strings.FldMonthDay</listEntry>
|
||||||
|
<listEntry value="855">@Strings.FldDayOfMonth</listEntry>
|
||||||
|
<listEntry value="870">@Strings.FldDayShort</listEntry>
|
||||||
|
<listEntry value="856">@Strings.FldDayFull</listEntry>
|
||||||
|
<listEntry value="853">@Strings.FldMonthName</listEntry>
|
||||||
|
<listEntry value="868">@Strings.FldMonthNum</listEntry>
|
||||||
|
<listEntry value="858">@Strings.FldMeridiem</listEntry>
|
||||||
|
<listEntry value="859">@Strings.FldWeekIso</listEntry>
|
||||||
|
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
||||||
|
<listEntry value="1">@Strings.FldSteps</listEntry>
|
||||||
|
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
||||||
|
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
||||||
|
<listEntry value="2">@Strings.FldCalories</listEntry>
|
||||||
|
<listEntry value="603">@Strings.FldActiveCal</listEntry>
|
||||||
|
<listEntry value="150">@Strings.FldActiveMin</listEntry>
|
||||||
|
<listEntry value="152">@Strings.FldActiveMod</listEntry>
|
||||||
|
<listEntry value="154">@Strings.FldActiveVig</listEntry>
|
||||||
|
<listEntry value="151">@Strings.FldActiveWeek</listEntry>
|
||||||
|
<listEntry value="3">@Strings.FldDistance</listEntry>
|
||||||
|
<listEntry value="4">@Strings.FldFloorsUp</listEntry>
|
||||||
|
<listEntry value="5">@Strings.FldFloorsDown</listEntry>
|
||||||
|
<listEntry value="6">@Strings.FldMetersUp</listEntry>
|
||||||
|
<listEntry value="9">@Strings.FldPushDist</listEntry>
|
||||||
|
<listEntry value="10">@Strings.FldPushes</listEntry>
|
||||||
|
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
||||||
|
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
||||||
|
<listEntry value="12">@Strings.FldStress</listEntry>
|
||||||
|
<listEntry value="635">@Strings.FldPulseOx</listEntry>
|
||||||
|
<listEntry value="11">@Strings.FldRespiration</listEntry>
|
||||||
|
<listEntry value="623">@Strings.FldBodyBattery</listEntry>
|
||||||
|
<listEntry value="8">@Strings.FldMoveBar</listEntry>
|
||||||
|
<listEntry value="15">@Strings.FldMoveBarRev</listEntry>
|
||||||
|
<listEntry value="281">@Strings.FldWeight</listEntry>
|
||||||
|
<listEntry value="742">@Strings.FldBmi</listEntry>
|
||||||
|
<listEntry value="13">@Strings.FldRecovery</listEntry>
|
||||||
|
<listEntry value="278">@Strings.FldVo2Run</listEntry>
|
||||||
|
<listEntry value="277">@Strings.FldVo2Bike</listEntry>
|
||||||
|
<listEntry value="250">@Strings.FldBattery</listEntry>
|
||||||
|
<listEntry value="251">@Strings.FldBatteryDays</listEntry>
|
||||||
|
<listEntry value="257">@Strings.FldBatteryBoth</listEntry>
|
||||||
|
<listEntry value="253">@Strings.FldAlarms</listEntry>
|
||||||
|
<listEntry value="255">@Strings.FldNotifications</listEntry>
|
||||||
|
<listEntry value="254">@Strings.FldDoNotDisturb</listEntry>
|
||||||
|
<listEntry value="256">@Strings.FldBluetooth</listEntry>
|
||||||
|
<listEntry value="252">@Strings.FldSolar</listEntry>
|
||||||
|
<listEntry value="615">@Strings.FldAltitude</listEntry>
|
||||||
|
<listEntry value="616">@Strings.FldSeaPressure</listEntry>
|
||||||
|
<listEntry value="733">@Strings.FldAmbPressure</listEntry>
|
||||||
|
<listEntry value="712">@Strings.FldSunrise</listEntry>
|
||||||
|
<listEntry value="717">@Strings.FldSunset</listEntry>
|
||||||
|
<listEntry value="700">@Strings.FldNextSun</listEntry>
|
||||||
|
<listEntry value="731">@Strings.FldUntilSun</listEntry>
|
||||||
|
<listEntry value="706">@Strings.FldMoonAge</listEntry>
|
||||||
|
<listEntry value="732">@Strings.FldMoonLit</listEntry>
|
||||||
|
<listEntry value="306">@Strings.FldTemp</listEntry>
|
||||||
|
<listEntry value="639">@Strings.FldTempHiLo</listEntry>
|
||||||
|
<listEntry value="300">@Strings.FldFeelsLike</listEntry>
|
||||||
|
<listEntry value="301">@Strings.FldTempHigh</listEntry>
|
||||||
|
<listEntry value="302">@Strings.FldTempLow</listEntry>
|
||||||
|
<listEntry value="305">@Strings.FldHumidity</listEntry>
|
||||||
|
<listEntry value="304">@Strings.FldPrecip</listEntry>
|
||||||
|
<listEntry value="308">@Strings.FldWindSpeed</listEntry>
|
||||||
|
<listEntry value="307">@Strings.FldWindBearing</listEntry>
|
||||||
|
<listEntry value="310">@Strings.FldWindDir</listEntry>
|
||||||
|
<listEntry value="309">@Strings.FldWeatherTime</listEntry>
|
||||||
|
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
||||||
|
<listEntry value="704">@Strings.FldCustom2</listEntry>
|
||||||
|
<listEntry value="705">@Strings.FldCustom3</listEntry>
|
||||||
|
</settingConfig>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.RightTop" title="@Strings.SettingRightTop">
|
||||||
|
<settingConfig type="list">
|
||||||
|
<listEntry value="0">@Strings.FldOff</listEntry>
|
||||||
|
<listEntry value="860">@Strings.FldTime</listEntry>
|
||||||
|
<listEntry value="880">@Strings.FldUtcTime</listEntry>
|
||||||
|
<listEntry value="864">@Strings.FldAltTime1</listEntry>
|
||||||
|
<listEntry value="865">@Strings.FldAltTime2</listEntry>
|
||||||
|
<listEntry value="851">@Strings.FldDayMonthDay</listEntry>
|
||||||
|
<listEntry value="872">@Strings.FldDayDate</listEntry>
|
||||||
|
<listEntry value="852">@Strings.FldMonthDay</listEntry>
|
||||||
|
<listEntry value="855">@Strings.FldDayOfMonth</listEntry>
|
||||||
|
<listEntry value="870">@Strings.FldDayShort</listEntry>
|
||||||
|
<listEntry value="856">@Strings.FldDayFull</listEntry>
|
||||||
|
<listEntry value="853">@Strings.FldMonthName</listEntry>
|
||||||
|
<listEntry value="868">@Strings.FldMonthNum</listEntry>
|
||||||
|
<listEntry value="858">@Strings.FldMeridiem</listEntry>
|
||||||
|
<listEntry value="859">@Strings.FldWeekIso</listEntry>
|
||||||
|
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
||||||
|
<listEntry value="1">@Strings.FldSteps</listEntry>
|
||||||
|
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
||||||
|
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
||||||
|
<listEntry value="2">@Strings.FldCalories</listEntry>
|
||||||
|
<listEntry value="603">@Strings.FldActiveCal</listEntry>
|
||||||
|
<listEntry value="150">@Strings.FldActiveMin</listEntry>
|
||||||
|
<listEntry value="152">@Strings.FldActiveMod</listEntry>
|
||||||
|
<listEntry value="154">@Strings.FldActiveVig</listEntry>
|
||||||
|
<listEntry value="151">@Strings.FldActiveWeek</listEntry>
|
||||||
|
<listEntry value="3">@Strings.FldDistance</listEntry>
|
||||||
|
<listEntry value="4">@Strings.FldFloorsUp</listEntry>
|
||||||
|
<listEntry value="5">@Strings.FldFloorsDown</listEntry>
|
||||||
|
<listEntry value="6">@Strings.FldMetersUp</listEntry>
|
||||||
|
<listEntry value="9">@Strings.FldPushDist</listEntry>
|
||||||
|
<listEntry value="10">@Strings.FldPushes</listEntry>
|
||||||
|
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
||||||
|
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
||||||
|
<listEntry value="12">@Strings.FldStress</listEntry>
|
||||||
|
<listEntry value="635">@Strings.FldPulseOx</listEntry>
|
||||||
|
<listEntry value="11">@Strings.FldRespiration</listEntry>
|
||||||
|
<listEntry value="623">@Strings.FldBodyBattery</listEntry>
|
||||||
|
<listEntry value="8">@Strings.FldMoveBar</listEntry>
|
||||||
|
<listEntry value="15">@Strings.FldMoveBarRev</listEntry>
|
||||||
|
<listEntry value="281">@Strings.FldWeight</listEntry>
|
||||||
|
<listEntry value="742">@Strings.FldBmi</listEntry>
|
||||||
|
<listEntry value="13">@Strings.FldRecovery</listEntry>
|
||||||
|
<listEntry value="278">@Strings.FldVo2Run</listEntry>
|
||||||
|
<listEntry value="277">@Strings.FldVo2Bike</listEntry>
|
||||||
|
<listEntry value="250">@Strings.FldBattery</listEntry>
|
||||||
|
<listEntry value="251">@Strings.FldBatteryDays</listEntry>
|
||||||
|
<listEntry value="257">@Strings.FldBatteryBoth</listEntry>
|
||||||
|
<listEntry value="253">@Strings.FldAlarms</listEntry>
|
||||||
|
<listEntry value="255">@Strings.FldNotifications</listEntry>
|
||||||
|
<listEntry value="254">@Strings.FldDoNotDisturb</listEntry>
|
||||||
|
<listEntry value="256">@Strings.FldBluetooth</listEntry>
|
||||||
|
<listEntry value="252">@Strings.FldSolar</listEntry>
|
||||||
|
<listEntry value="615">@Strings.FldAltitude</listEntry>
|
||||||
|
<listEntry value="616">@Strings.FldSeaPressure</listEntry>
|
||||||
|
<listEntry value="733">@Strings.FldAmbPressure</listEntry>
|
||||||
|
<listEntry value="712">@Strings.FldSunrise</listEntry>
|
||||||
|
<listEntry value="717">@Strings.FldSunset</listEntry>
|
||||||
|
<listEntry value="700">@Strings.FldNextSun</listEntry>
|
||||||
|
<listEntry value="731">@Strings.FldUntilSun</listEntry>
|
||||||
|
<listEntry value="706">@Strings.FldMoonAge</listEntry>
|
||||||
|
<listEntry value="732">@Strings.FldMoonLit</listEntry>
|
||||||
|
<listEntry value="306">@Strings.FldTemp</listEntry>
|
||||||
|
<listEntry value="639">@Strings.FldTempHiLo</listEntry>
|
||||||
|
<listEntry value="300">@Strings.FldFeelsLike</listEntry>
|
||||||
|
<listEntry value="301">@Strings.FldTempHigh</listEntry>
|
||||||
|
<listEntry value="302">@Strings.FldTempLow</listEntry>
|
||||||
|
<listEntry value="305">@Strings.FldHumidity</listEntry>
|
||||||
|
<listEntry value="304">@Strings.FldPrecip</listEntry>
|
||||||
|
<listEntry value="308">@Strings.FldWindSpeed</listEntry>
|
||||||
|
<listEntry value="307">@Strings.FldWindBearing</listEntry>
|
||||||
|
<listEntry value="310">@Strings.FldWindDir</listEntry>
|
||||||
|
<listEntry value="309">@Strings.FldWeatherTime</listEntry>
|
||||||
|
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
||||||
|
<listEntry value="704">@Strings.FldCustom2</listEntry>
|
||||||
|
<listEntry value="705">@Strings.FldCustom3</listEntry>
|
||||||
|
</settingConfig>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.BottomLeft" title="@Strings.SettingBottomLeft">
|
||||||
|
<settingConfig type="list">
|
||||||
|
<listEntry value="0">@Strings.FldOff</listEntry>
|
||||||
|
<listEntry value="860">@Strings.FldTime</listEntry>
|
||||||
|
<listEntry value="880">@Strings.FldUtcTime</listEntry>
|
||||||
|
<listEntry value="864">@Strings.FldAltTime1</listEntry>
|
||||||
|
<listEntry value="865">@Strings.FldAltTime2</listEntry>
|
||||||
|
<listEntry value="851">@Strings.FldDayMonthDay</listEntry>
|
||||||
|
<listEntry value="872">@Strings.FldDayDate</listEntry>
|
||||||
|
<listEntry value="852">@Strings.FldMonthDay</listEntry>
|
||||||
|
<listEntry value="855">@Strings.FldDayOfMonth</listEntry>
|
||||||
|
<listEntry value="870">@Strings.FldDayShort</listEntry>
|
||||||
|
<listEntry value="856">@Strings.FldDayFull</listEntry>
|
||||||
|
<listEntry value="853">@Strings.FldMonthName</listEntry>
|
||||||
|
<listEntry value="868">@Strings.FldMonthNum</listEntry>
|
||||||
|
<listEntry value="858">@Strings.FldMeridiem</listEntry>
|
||||||
|
<listEntry value="859">@Strings.FldWeekIso</listEntry>
|
||||||
|
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
||||||
|
<listEntry value="1">@Strings.FldSteps</listEntry>
|
||||||
|
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
||||||
|
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
||||||
|
<listEntry value="2">@Strings.FldCalories</listEntry>
|
||||||
|
<listEntry value="603">@Strings.FldActiveCal</listEntry>
|
||||||
|
<listEntry value="150">@Strings.FldActiveMin</listEntry>
|
||||||
|
<listEntry value="152">@Strings.FldActiveMod</listEntry>
|
||||||
|
<listEntry value="154">@Strings.FldActiveVig</listEntry>
|
||||||
|
<listEntry value="151">@Strings.FldActiveWeek</listEntry>
|
||||||
|
<listEntry value="3">@Strings.FldDistance</listEntry>
|
||||||
|
<listEntry value="4">@Strings.FldFloorsUp</listEntry>
|
||||||
|
<listEntry value="5">@Strings.FldFloorsDown</listEntry>
|
||||||
|
<listEntry value="6">@Strings.FldMetersUp</listEntry>
|
||||||
|
<listEntry value="9">@Strings.FldPushDist</listEntry>
|
||||||
|
<listEntry value="10">@Strings.FldPushes</listEntry>
|
||||||
|
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
||||||
|
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
||||||
|
<listEntry value="12">@Strings.FldStress</listEntry>
|
||||||
|
<listEntry value="635">@Strings.FldPulseOx</listEntry>
|
||||||
|
<listEntry value="11">@Strings.FldRespiration</listEntry>
|
||||||
|
<listEntry value="623">@Strings.FldBodyBattery</listEntry>
|
||||||
|
<listEntry value="8">@Strings.FldMoveBar</listEntry>
|
||||||
|
<listEntry value="15">@Strings.FldMoveBarRev</listEntry>
|
||||||
|
<listEntry value="281">@Strings.FldWeight</listEntry>
|
||||||
|
<listEntry value="742">@Strings.FldBmi</listEntry>
|
||||||
|
<listEntry value="13">@Strings.FldRecovery</listEntry>
|
||||||
|
<listEntry value="278">@Strings.FldVo2Run</listEntry>
|
||||||
|
<listEntry value="277">@Strings.FldVo2Bike</listEntry>
|
||||||
|
<listEntry value="250">@Strings.FldBattery</listEntry>
|
||||||
|
<listEntry value="251">@Strings.FldBatteryDays</listEntry>
|
||||||
|
<listEntry value="257">@Strings.FldBatteryBoth</listEntry>
|
||||||
|
<listEntry value="253">@Strings.FldAlarms</listEntry>
|
||||||
|
<listEntry value="255">@Strings.FldNotifications</listEntry>
|
||||||
|
<listEntry value="254">@Strings.FldDoNotDisturb</listEntry>
|
||||||
|
<listEntry value="256">@Strings.FldBluetooth</listEntry>
|
||||||
|
<listEntry value="252">@Strings.FldSolar</listEntry>
|
||||||
|
<listEntry value="615">@Strings.FldAltitude</listEntry>
|
||||||
|
<listEntry value="616">@Strings.FldSeaPressure</listEntry>
|
||||||
|
<listEntry value="733">@Strings.FldAmbPressure</listEntry>
|
||||||
|
<listEntry value="712">@Strings.FldSunrise</listEntry>
|
||||||
|
<listEntry value="717">@Strings.FldSunset</listEntry>
|
||||||
|
<listEntry value="700">@Strings.FldNextSun</listEntry>
|
||||||
|
<listEntry value="731">@Strings.FldUntilSun</listEntry>
|
||||||
|
<listEntry value="706">@Strings.FldMoonAge</listEntry>
|
||||||
|
<listEntry value="732">@Strings.FldMoonLit</listEntry>
|
||||||
|
<listEntry value="306">@Strings.FldTemp</listEntry>
|
||||||
|
<listEntry value="639">@Strings.FldTempHiLo</listEntry>
|
||||||
|
<listEntry value="300">@Strings.FldFeelsLike</listEntry>
|
||||||
|
<listEntry value="301">@Strings.FldTempHigh</listEntry>
|
||||||
|
<listEntry value="302">@Strings.FldTempLow</listEntry>
|
||||||
|
<listEntry value="305">@Strings.FldHumidity</listEntry>
|
||||||
|
<listEntry value="304">@Strings.FldPrecip</listEntry>
|
||||||
|
<listEntry value="308">@Strings.FldWindSpeed</listEntry>
|
||||||
|
<listEntry value="307">@Strings.FldWindBearing</listEntry>
|
||||||
|
<listEntry value="310">@Strings.FldWindDir</listEntry>
|
||||||
|
<listEntry value="309">@Strings.FldWeatherTime</listEntry>
|
||||||
|
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
||||||
|
<listEntry value="704">@Strings.FldCustom2</listEntry>
|
||||||
|
<listEntry value="705">@Strings.FldCustom3</listEntry>
|
||||||
|
</settingConfig>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.BottomRight" title="@Strings.SettingBottomRight">
|
||||||
|
<settingConfig type="list">
|
||||||
|
<listEntry value="0">@Strings.FldOff</listEntry>
|
||||||
|
<listEntry value="860">@Strings.FldTime</listEntry>
|
||||||
|
<listEntry value="880">@Strings.FldUtcTime</listEntry>
|
||||||
|
<listEntry value="864">@Strings.FldAltTime1</listEntry>
|
||||||
|
<listEntry value="865">@Strings.FldAltTime2</listEntry>
|
||||||
|
<listEntry value="851">@Strings.FldDayMonthDay</listEntry>
|
||||||
|
<listEntry value="872">@Strings.FldDayDate</listEntry>
|
||||||
|
<listEntry value="852">@Strings.FldMonthDay</listEntry>
|
||||||
|
<listEntry value="855">@Strings.FldDayOfMonth</listEntry>
|
||||||
|
<listEntry value="870">@Strings.FldDayShort</listEntry>
|
||||||
|
<listEntry value="856">@Strings.FldDayFull</listEntry>
|
||||||
|
<listEntry value="853">@Strings.FldMonthName</listEntry>
|
||||||
|
<listEntry value="868">@Strings.FldMonthNum</listEntry>
|
||||||
|
<listEntry value="858">@Strings.FldMeridiem</listEntry>
|
||||||
|
<listEntry value="859">@Strings.FldWeekIso</listEntry>
|
||||||
|
<listEntry value="873">@Strings.FldWeekCal</listEntry>
|
||||||
|
<listEntry value="1">@Strings.FldSteps</listEntry>
|
||||||
|
<listEntry value="14">@Strings.FldStepsRound</listEntry>
|
||||||
|
<listEntry value="707">@Strings.FldStepsToGo</listEntry>
|
||||||
|
<listEntry value="2">@Strings.FldCalories</listEntry>
|
||||||
|
<listEntry value="603">@Strings.FldActiveCal</listEntry>
|
||||||
|
<listEntry value="150">@Strings.FldActiveMin</listEntry>
|
||||||
|
<listEntry value="152">@Strings.FldActiveMod</listEntry>
|
||||||
|
<listEntry value="154">@Strings.FldActiveVig</listEntry>
|
||||||
|
<listEntry value="151">@Strings.FldActiveWeek</listEntry>
|
||||||
|
<listEntry value="3">@Strings.FldDistance</listEntry>
|
||||||
|
<listEntry value="4">@Strings.FldFloorsUp</listEntry>
|
||||||
|
<listEntry value="5">@Strings.FldFloorsDown</listEntry>
|
||||||
|
<listEntry value="6">@Strings.FldMetersUp</listEntry>
|
||||||
|
<listEntry value="9">@Strings.FldPushDist</listEntry>
|
||||||
|
<listEntry value="10">@Strings.FldPushes</listEntry>
|
||||||
|
<listEntry value="618">@Strings.FldHeartRate</listEntry>
|
||||||
|
<listEntry value="276">@Strings.FldRestingHr</listEntry>
|
||||||
|
<listEntry value="12">@Strings.FldStress</listEntry>
|
||||||
|
<listEntry value="635">@Strings.FldPulseOx</listEntry>
|
||||||
|
<listEntry value="11">@Strings.FldRespiration</listEntry>
|
||||||
|
<listEntry value="623">@Strings.FldBodyBattery</listEntry>
|
||||||
|
<listEntry value="8">@Strings.FldMoveBar</listEntry>
|
||||||
|
<listEntry value="15">@Strings.FldMoveBarRev</listEntry>
|
||||||
|
<listEntry value="281">@Strings.FldWeight</listEntry>
|
||||||
|
<listEntry value="742">@Strings.FldBmi</listEntry>
|
||||||
|
<listEntry value="13">@Strings.FldRecovery</listEntry>
|
||||||
|
<listEntry value="278">@Strings.FldVo2Run</listEntry>
|
||||||
|
<listEntry value="277">@Strings.FldVo2Bike</listEntry>
|
||||||
|
<listEntry value="250">@Strings.FldBattery</listEntry>
|
||||||
|
<listEntry value="251">@Strings.FldBatteryDays</listEntry>
|
||||||
|
<listEntry value="257">@Strings.FldBatteryBoth</listEntry>
|
||||||
|
<listEntry value="253">@Strings.FldAlarms</listEntry>
|
||||||
|
<listEntry value="255">@Strings.FldNotifications</listEntry>
|
||||||
|
<listEntry value="254">@Strings.FldDoNotDisturb</listEntry>
|
||||||
|
<listEntry value="256">@Strings.FldBluetooth</listEntry>
|
||||||
|
<listEntry value="252">@Strings.FldSolar</listEntry>
|
||||||
|
<listEntry value="615">@Strings.FldAltitude</listEntry>
|
||||||
|
<listEntry value="616">@Strings.FldSeaPressure</listEntry>
|
||||||
|
<listEntry value="733">@Strings.FldAmbPressure</listEntry>
|
||||||
|
<listEntry value="712">@Strings.FldSunrise</listEntry>
|
||||||
|
<listEntry value="717">@Strings.FldSunset</listEntry>
|
||||||
|
<listEntry value="700">@Strings.FldNextSun</listEntry>
|
||||||
|
<listEntry value="731">@Strings.FldUntilSun</listEntry>
|
||||||
|
<listEntry value="706">@Strings.FldMoonAge</listEntry>
|
||||||
|
<listEntry value="732">@Strings.FldMoonLit</listEntry>
|
||||||
|
<listEntry value="306">@Strings.FldTemp</listEntry>
|
||||||
|
<listEntry value="639">@Strings.FldTempHiLo</listEntry>
|
||||||
|
<listEntry value="300">@Strings.FldFeelsLike</listEntry>
|
||||||
|
<listEntry value="301">@Strings.FldTempHigh</listEntry>
|
||||||
|
<listEntry value="302">@Strings.FldTempLow</listEntry>
|
||||||
|
<listEntry value="305">@Strings.FldHumidity</listEntry>
|
||||||
|
<listEntry value="304">@Strings.FldPrecip</listEntry>
|
||||||
|
<listEntry value="308">@Strings.FldWindSpeed</listEntry>
|
||||||
|
<listEntry value="307">@Strings.FldWindBearing</listEntry>
|
||||||
|
<listEntry value="310">@Strings.FldWindDir</listEntry>
|
||||||
|
<listEntry value="309">@Strings.FldWeatherTime</listEntry>
|
||||||
|
<listEntry value="703">@Strings.FldCustom1</listEntry>
|
||||||
|
<listEntry value="704">@Strings.FldCustom2</listEntry>
|
||||||
|
<listEntry value="705">@Strings.FldCustom3</listEntry>
|
||||||
|
</settingConfig>
|
||||||
|
</setting>
|
||||||
|
|
||||||
<setting propertyKey="@Properties.RingTR" title="@Strings.SettingRingTR">
|
<setting propertyKey="@Properties.RingTR" title="@Strings.SettingRingTR">
|
||||||
<settingConfig type="list">
|
<settingConfig type="list">
|
||||||
<listEntry value="0">@Strings.MetricSteps</listEntry>
|
<listEntry value="0">@Strings.MetricSteps</listEntry>
|
||||||
@@ -18,6 +339,8 @@
|
|||||||
<listEntry value="2">@Strings.MetricDistance</listEntry>
|
<listEntry value="2">@Strings.MetricDistance</listEntry>
|
||||||
<listEntry value="3">@Strings.MetricFloors</listEntry>
|
<listEntry value="3">@Strings.MetricFloors</listEntry>
|
||||||
<listEntry value="4">@Strings.MetricMoveBar</listEntry>
|
<listEntry value="4">@Strings.MetricMoveBar</listEntry>
|
||||||
|
<listEntry value="5">@Strings.MetricActiveMin</listEntry>
|
||||||
|
<listEntry value="6">@Strings.MetricBodyBattery</listEntry>
|
||||||
</settingConfig>
|
</settingConfig>
|
||||||
</setting>
|
</setting>
|
||||||
|
|
||||||
@@ -28,6 +351,8 @@
|
|||||||
<listEntry value="2">@Strings.MetricDistance</listEntry>
|
<listEntry value="2">@Strings.MetricDistance</listEntry>
|
||||||
<listEntry value="3">@Strings.MetricFloors</listEntry>
|
<listEntry value="3">@Strings.MetricFloors</listEntry>
|
||||||
<listEntry value="4">@Strings.MetricMoveBar</listEntry>
|
<listEntry value="4">@Strings.MetricMoveBar</listEntry>
|
||||||
|
<listEntry value="5">@Strings.MetricActiveMin</listEntry>
|
||||||
|
<listEntry value="6">@Strings.MetricBodyBattery</listEntry>
|
||||||
</settingConfig>
|
</settingConfig>
|
||||||
</setting>
|
</setting>
|
||||||
|
|
||||||
@@ -38,6 +363,8 @@
|
|||||||
<listEntry value="2">@Strings.MetricDistance</listEntry>
|
<listEntry value="2">@Strings.MetricDistance</listEntry>
|
||||||
<listEntry value="3">@Strings.MetricFloors</listEntry>
|
<listEntry value="3">@Strings.MetricFloors</listEntry>
|
||||||
<listEntry value="4">@Strings.MetricMoveBar</listEntry>
|
<listEntry value="4">@Strings.MetricMoveBar</listEntry>
|
||||||
|
<listEntry value="5">@Strings.MetricActiveMin</listEntry>
|
||||||
|
<listEntry value="6">@Strings.MetricBodyBattery</listEntry>
|
||||||
</settingConfig>
|
</settingConfig>
|
||||||
</setting>
|
</setting>
|
||||||
|
|
||||||
@@ -48,68 +375,11 @@
|
|||||||
<listEntry value="2">@Strings.MetricDistance</listEntry>
|
<listEntry value="2">@Strings.MetricDistance</listEntry>
|
||||||
<listEntry value="3">@Strings.MetricFloors</listEntry>
|
<listEntry value="3">@Strings.MetricFloors</listEntry>
|
||||||
<listEntry value="4">@Strings.MetricMoveBar</listEntry>
|
<listEntry value="4">@Strings.MetricMoveBar</listEntry>
|
||||||
|
<listEntry value="5">@Strings.MetricActiveMin</listEntry>
|
||||||
|
<listEntry value="6">@Strings.MetricBodyBattery</listEntry>
|
||||||
</settingConfig>
|
</settingConfig>
|
||||||
</setting>
|
</setting>
|
||||||
|
|
||||||
<setting propertyKey="@Properties.LeftTop" title="@Strings.SettingLeftTop">
|
|
||||||
<settingConfig type="list">
|
|
||||||
<listEntry value="0">@Strings.FieldWeather</listEntry>
|
|
||||||
<listEntry value="8">@Strings.FieldHiLo</listEntry>
|
|
||||||
<listEntry value="6">@Strings.FieldSunrise</listEntry>
|
|
||||||
<listEntry value="7">@Strings.FieldSunset</listEntry>
|
|
||||||
<listEntry value="1">@Strings.FieldCalories</listEntry>
|
|
||||||
<listEntry value="2">@Strings.FieldSteps</listEntry>
|
|
||||||
<listEntry value="3">@Strings.FieldDistance</listEntry>
|
|
||||||
<listEntry value="4">@Strings.FieldFloors</listEntry>
|
|
||||||
<listEntry value="5">@Strings.FieldBattery</listEntry>
|
|
||||||
<listEntry value="9">@Strings.FieldHeartRate</listEntry>
|
|
||||||
</settingConfig>
|
|
||||||
</setting>
|
|
||||||
|
|
||||||
<setting propertyKey="@Properties.RightTop" title="@Strings.SettingRightTop">
|
|
||||||
<settingConfig type="list">
|
|
||||||
<listEntry value="0">@Strings.FieldWeather</listEntry>
|
|
||||||
<listEntry value="8">@Strings.FieldHiLo</listEntry>
|
|
||||||
<listEntry value="6">@Strings.FieldSunrise</listEntry>
|
|
||||||
<listEntry value="7">@Strings.FieldSunset</listEntry>
|
|
||||||
<listEntry value="1">@Strings.FieldCalories</listEntry>
|
|
||||||
<listEntry value="2">@Strings.FieldSteps</listEntry>
|
|
||||||
<listEntry value="3">@Strings.FieldDistance</listEntry>
|
|
||||||
<listEntry value="4">@Strings.FieldFloors</listEntry>
|
|
||||||
<listEntry value="5">@Strings.FieldBattery</listEntry>
|
|
||||||
<listEntry value="9">@Strings.FieldHeartRate</listEntry>
|
|
||||||
</settingConfig>
|
|
||||||
</setting>
|
|
||||||
|
|
||||||
<setting propertyKey="@Properties.BottomLeft" title="@Strings.SettingBottomLeft">
|
|
||||||
<settingConfig type="list">
|
|
||||||
<listEntry value="0">@Strings.FieldWeather</listEntry>
|
|
||||||
<listEntry value="8">@Strings.FieldHiLo</listEntry>
|
|
||||||
<listEntry value="6">@Strings.FieldSunrise</listEntry>
|
|
||||||
<listEntry value="7">@Strings.FieldSunset</listEntry>
|
|
||||||
<listEntry value="1">@Strings.FieldCalories</listEntry>
|
|
||||||
<listEntry value="2">@Strings.FieldSteps</listEntry>
|
|
||||||
<listEntry value="3">@Strings.FieldDistance</listEntry>
|
|
||||||
<listEntry value="4">@Strings.FieldFloors</listEntry>
|
|
||||||
<listEntry value="5">@Strings.FieldBattery</listEntry>
|
|
||||||
<listEntry value="9">@Strings.FieldHeartRate</listEntry>
|
|
||||||
</settingConfig>
|
|
||||||
</setting>
|
|
||||||
|
|
||||||
<setting propertyKey="@Properties.BottomRight" title="@Strings.SettingBottomRight">
|
|
||||||
<settingConfig type="list">
|
|
||||||
<listEntry value="0">@Strings.FieldWeather</listEntry>
|
|
||||||
<listEntry value="8">@Strings.FieldHiLo</listEntry>
|
|
||||||
<listEntry value="6">@Strings.FieldSunrise</listEntry>
|
|
||||||
<listEntry value="7">@Strings.FieldSunset</listEntry>
|
|
||||||
<listEntry value="1">@Strings.FieldCalories</listEntry>
|
|
||||||
<listEntry value="2">@Strings.FieldSteps</listEntry>
|
|
||||||
<listEntry value="3">@Strings.FieldDistance</listEntry>
|
|
||||||
<listEntry value="4">@Strings.FieldFloors</listEntry>
|
|
||||||
<listEntry value="5">@Strings.FieldBattery</listEntry>
|
|
||||||
<listEntry value="9">@Strings.FieldHeartRate</listEntry>
|
|
||||||
</settingConfig>
|
|
||||||
</setting>
|
|
||||||
|
|
||||||
<setting propertyKey="@Properties.TempUnit" title="@Strings.SettingTempUnit">
|
<setting propertyKey="@Properties.TempUnit" title="@Strings.SettingTempUnit">
|
||||||
<settingConfig type="list">
|
<settingConfig type="list">
|
||||||
@@ -119,6 +389,26 @@
|
|||||||
</settingConfig>
|
</settingConfig>
|
||||||
</setting>
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.AltOffset1" title="@Strings.SettingAltOffset1">
|
||||||
|
<settingConfig type="numeric" min="-720" max="840"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.AltOffset2" title="@Strings.SettingAltOffset2">
|
||||||
|
<settingConfig type="numeric" min="-720" max="840"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.Custom1" title="@Strings.SettingCustom1">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="12"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.Custom2" title="@Strings.SettingCustom2">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="12"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.Custom3" title="@Strings.SettingCustom3">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="12"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
<setting propertyKey="@Properties.ShowTop" title="@Strings.SettingShowTop">
|
<setting propertyKey="@Properties.ShowTop" title="@Strings.SettingShowTop">
|
||||||
<settingConfig type="boolean"/>
|
<settingConfig type="boolean"/>
|
||||||
</setting>
|
</setting>
|
||||||
|
|||||||
89
resources/strings/fields.xml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<strings>
|
||||||
|
<!-- Field names shown in the settings picker. -->
|
||||||
|
<string id="FldOff">Off</string>
|
||||||
|
<string id="FldTime">Time</string>
|
||||||
|
<string id="FldUtcTime">UTC Time</string>
|
||||||
|
<string id="FldAltTime1">Alternate Time 1</string>
|
||||||
|
<string id="FldAltTime2">Alternate Time 2</string>
|
||||||
|
<string id="FldDayMonthDay">Day Month Day</string>
|
||||||
|
<string id="FldDayDate">Day Date</string>
|
||||||
|
<string id="FldMonthDay">Month Day</string>
|
||||||
|
<string id="FldDayOfMonth">Day of Month</string>
|
||||||
|
<string id="FldDayShort">Day (Tue)</string>
|
||||||
|
<string id="FldDayFull">Full Day (Tuesday)</string>
|
||||||
|
<string id="FldMonthName">Month (Jan)</string>
|
||||||
|
<string id="FldMonthNum">Month (6)</string>
|
||||||
|
<string id="FldMeridiem">AM/PM</string>
|
||||||
|
<string id="FldWeekIso">Week Number (ISO)</string>
|
||||||
|
<string id="FldWeekCal">Week Number</string>
|
||||||
|
<string id="FldSteps">Steps</string>
|
||||||
|
<string id="FldStepsRound">Steps Rounded</string>
|
||||||
|
<string id="FldStepsToGo">Steps to Go</string>
|
||||||
|
<string id="FldCalories">Calories</string>
|
||||||
|
<string id="FldActiveCal">Active Calories</string>
|
||||||
|
<string id="FldActiveMin">Active Minutes</string>
|
||||||
|
<string id="FldActiveMod">Active Min (Moderate)</string>
|
||||||
|
<string id="FldActiveVig">Active Min (Vigorous)</string>
|
||||||
|
<string id="FldActiveWeek">Active Minutes (Week)</string>
|
||||||
|
<string id="FldDistance">Distance</string>
|
||||||
|
<string id="FldFloorsUp">Floors Climbed</string>
|
||||||
|
<string id="FldFloorsDown">Floors Descended</string>
|
||||||
|
<string id="FldMetersUp">Distance Climbed</string>
|
||||||
|
<string id="FldPushDist">Push Distance</string>
|
||||||
|
<string id="FldPushes">Pushes</string>
|
||||||
|
<string id="FldHeartRate">Heart Rate</string>
|
||||||
|
<string id="FldRestingHr">Resting Heart Rate</string>
|
||||||
|
<string id="FldStress">Stress Score</string>
|
||||||
|
<string id="FldPulseOx">Pulse Ox</string>
|
||||||
|
<string id="FldRespiration">Respiration Rate</string>
|
||||||
|
<string id="FldBodyBattery">Body Battery</string>
|
||||||
|
<string id="FldMoveBar">Move Bar</string>
|
||||||
|
<string id="FldMoveBarRev">Move Bar Reversed</string>
|
||||||
|
<string id="FldWeight">Weight</string>
|
||||||
|
<string id="FldBmi">BMI</string>
|
||||||
|
<string id="FldRecovery">Time to Recovery</string>
|
||||||
|
<string id="FldVo2Run">VO2 Max (Run)</string>
|
||||||
|
<string id="FldVo2Bike">VO2 Max (Bike)</string>
|
||||||
|
<string id="FldBattery">Battery</string>
|
||||||
|
<string id="FldBatteryDays">Battery in Days</string>
|
||||||
|
<string id="FldBatteryBoth">Battery / Days</string>
|
||||||
|
<string id="FldAlarms">Alarms</string>
|
||||||
|
<string id="FldNotifications">Notifications</string>
|
||||||
|
<string id="FldDoNotDisturb">Do Not Disturb</string>
|
||||||
|
<string id="FldBluetooth">Bluetooth</string>
|
||||||
|
<string id="FldSolar">Solar Intensity</string>
|
||||||
|
<string id="FldAltitude">Altitude</string>
|
||||||
|
<string id="FldSeaPressure">Sea Level Pressure</string>
|
||||||
|
<string id="FldAmbPressure">Ambient Pressure</string>
|
||||||
|
<string id="FldSunrise">Sunrise</string>
|
||||||
|
<string id="FldSunset">Sunset</string>
|
||||||
|
<string id="FldNextSun">Next Sun Event</string>
|
||||||
|
<string id="FldUntilSun">Time Until Sun Event</string>
|
||||||
|
<string id="FldMoonAge">Moon Age</string>
|
||||||
|
<string id="FldMoonLit">Moon Illumination</string>
|
||||||
|
<string id="FldTemp">Temperature</string>
|
||||||
|
<string id="FldTempHiLo">Temperature High/Low</string>
|
||||||
|
<string id="FldFeelsLike">Feels Like</string>
|
||||||
|
<string id="FldTempHigh">High Temperature</string>
|
||||||
|
<string id="FldTempLow">Low Temperature</string>
|
||||||
|
<string id="FldHumidity">Humidity</string>
|
||||||
|
<string id="FldPrecip">Precipitation %</string>
|
||||||
|
<string id="FldWindSpeed">Wind Speed</string>
|
||||||
|
<string id="FldWindBearing">Wind Bearing</string>
|
||||||
|
<string id="FldWindDir">Wind Direction</string>
|
||||||
|
<string id="FldWeatherTime">Weather Update Time</string>
|
||||||
|
<string id="FldCustom1">Custom Text 1</string>
|
||||||
|
<string id="FldCustom2">Custom Text 2</string>
|
||||||
|
<string id="FldCustom3">Custom Text 3</string>
|
||||||
|
|
||||||
|
<!-- On-face captions, in FieldTable.IDS order. -->
|
||||||
|
<string id="FieldLabels">,TIME,UTC,TZ1,TZ2,DATE,DATE,DATE,DAY,DAY,DAY,MON,MON,,WEEK,WEEK,STEP,STEP,TO GO,KCAL,ACAL,MIN,MOD,VIG,WEEK,DST,FLR,DESC,ASC,PUSH,PUSH,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,TEMP,HI/LO,FEEL,HIGH,LOW,HUM,RAIN,WIND,WIND,WIND,WX,,,</string>
|
||||||
|
|
||||||
|
<string id="MetricSteps">Steps</string>
|
||||||
|
<string id="MetricCalories">Calories</string>
|
||||||
|
<string id="MetricDistance">Distance</string>
|
||||||
|
<string id="MetricFloors">Floors</string>
|
||||||
|
<string id="MetricMoveBar">Move Bar</string>
|
||||||
|
<string id="MetricActiveMin">Active Minutes</string>
|
||||||
|
<string id="MetricBodyBattery">Body Battery</string>
|
||||||
|
</strings>
|
||||||
@@ -6,14 +6,24 @@
|
|||||||
<string id="SettingRingTL">Ring · Top-Left</string>
|
<string id="SettingRingTL">Ring · Top-Left</string>
|
||||||
<string id="SettingRingBR">Ring · Bottom-Right</string>
|
<string id="SettingRingBR">Ring · Bottom-Right</string>
|
||||||
<string id="SettingRingBL">Ring · Bottom-Left</string>
|
<string id="SettingRingBL">Ring · Bottom-Left</string>
|
||||||
<string id="SettingLeftTop">Top-Left Complication</string>
|
<string id="SettingLeftTop">Top-Left Field</string>
|
||||||
<string id="SettingRightTop">Top-Right Complication</string>
|
<string id="SettingRightTop">Top-Right Field</string>
|
||||||
<string id="SettingBottomLeft">Bottom-Left Field</string>
|
<string id="SettingBottomLeft">Bottom-Left Field</string>
|
||||||
<string id="SettingBottomRight">Bottom-Right Field</string>
|
<string id="SettingBottomRight">Bottom-Right Field</string>
|
||||||
|
<string id="SettingTempUnit">Temperature Unit</string>
|
||||||
|
<string id="SettingAltOffset1">Alternate Time 1 (minutes from local)</string>
|
||||||
|
<string id="SettingAltOffset2">Alternate Time 2 (minutes from local)</string>
|
||||||
|
<string id="SettingCustom1">Custom Text 1</string>
|
||||||
|
<string id="SettingCustom2">Custom Text 2</string>
|
||||||
|
<string id="SettingCustom3">Custom Text 3</string>
|
||||||
<string id="SettingShowTop">Show Top Row</string>
|
<string id="SettingShowTop">Show Top Row</string>
|
||||||
<string id="SettingShowBand">Show Date Band</string>
|
<string id="SettingShowBand">Show Date Band</string>
|
||||||
<string id="SettingShowBottom">Show Bottom Row</string>
|
<string id="SettingShowBottom">Show Bottom Row</string>
|
||||||
|
|
||||||
|
<string id="TempUnitAuto">Follow watch</string>
|
||||||
|
<string id="TempUnitC">Celsius (°C)</string>
|
||||||
|
<string id="TempUnitF">Fahrenheit (°F)</string>
|
||||||
|
|
||||||
<string id="ThemeEmber">1 · Ember</string>
|
<string id="ThemeEmber">1 · Ember</string>
|
||||||
<string id="ThemeAurora">2 · Aurora</string>
|
<string id="ThemeAurora">2 · Aurora</string>
|
||||||
<string id="ThemeBrass">3 · Brass</string>
|
<string id="ThemeBrass">3 · Brass</string>
|
||||||
@@ -22,32 +32,9 @@
|
|||||||
<string id="ThemeAcid">6 · Acid</string>
|
<string id="ThemeAcid">6 · Acid</string>
|
||||||
<string id="ThemeKelp">7 · Kelp</string>
|
<string id="ThemeKelp">7 · Kelp</string>
|
||||||
|
|
||||||
<string id="MetricSteps">Steps</string>
|
|
||||||
<string id="MetricCalories">Calories</string>
|
|
||||||
<string id="MetricDistance">Distance</string>
|
|
||||||
<string id="MetricFloors">Floors</string>
|
|
||||||
<string id="MetricMoveBar">Move Bar</string>
|
|
||||||
|
|
||||||
<string id="FieldWeather">Temperature</string>
|
|
||||||
<string id="FieldHiLo">Hi / Lo Temp</string>
|
|
||||||
<string id="FieldSunrise">Sunrise</string>
|
|
||||||
<string id="FieldSunset">Sunset</string>
|
|
||||||
<string id="FieldCalories">Calories</string>
|
|
||||||
<string id="FieldSteps">Steps</string>
|
|
||||||
<string id="FieldDistance">Distance</string>
|
|
||||||
<string id="FieldFloors">Floors</string>
|
|
||||||
<string id="FieldBattery">Battery</string>
|
|
||||||
<string id="FieldHeartRate">Heart Rate</string>
|
|
||||||
|
|
||||||
<!-- On-face wording. Comma-separated lists are split at runtime. -->
|
<!-- On-face wording. Comma-separated lists are split at runtime. -->
|
||||||
<string id="DowNames">SUN,MON,TUE,WED,THU,FRI,SAT</string>
|
<string id="DowNames">SUN,MON,TUE,WED,THU,FRI,SAT</string>
|
||||||
<string id="MonNames">JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC</string>
|
<string id="MonNames">JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC</string>
|
||||||
<string id="DateFormat">$1$ $2$</string>
|
<string id="DateFormat">$1$ $2$</string>
|
||||||
<string id="Meridiem">AM,PM</string>
|
<string id="Meridiem">AM,PM</string>
|
||||||
<string id="FieldLabels">TEMP,KCAL,STEP,DST,FLR,BATT,RISE,SET,HI/LO,HR</string>
|
|
||||||
|
|
||||||
<string id="SettingTempUnit">Temperature Unit</string>
|
|
||||||
<string id="TempUnitAuto">Follow watch</string>
|
|
||||||
<string id="TempUnitC">Celsius (°C)</string>
|
|
||||||
<string id="TempUnitF">Fahrenheit (°F)</string>
|
|
||||||
</strings>
|
</strings>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import Toybox.Time;
|
|||||||
import Toybox.Time.Gregorian;
|
import Toybox.Time.Gregorian;
|
||||||
import Toybox.WatchUi;
|
import Toybox.WatchUi;
|
||||||
import Themes;
|
import Themes;
|
||||||
|
import Fields;
|
||||||
|
import Toybox.SensorHistory;
|
||||||
|
|
||||||
// Every literal below is a coordinate on the 500x500 design canvas, measured
|
// Every literal below is a coordinate on the 500x500 design canvas, measured
|
||||||
// from design/watchface-*.svg. Nothing here is eyeballed: ring geometry comes
|
// from design/watchface-*.svg. Nothing here is eyeballed: ring geometry comes
|
||||||
@@ -47,7 +49,7 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
|
|
||||||
const LEFT_X = 157.00; // top complications, centred
|
const LEFT_X = 157.00; // top complications, centred
|
||||||
const RIGHT_X = 343.70;
|
const RIGHT_X = 343.70;
|
||||||
const ICON_CY = 90.00;
|
const ICON_CY = 89.50;
|
||||||
const VALUE_CY = 131.06;
|
const VALUE_CY = 131.06;
|
||||||
|
|
||||||
const TIME_CY = 221.10;
|
const TIME_CY = 221.10;
|
||||||
@@ -89,22 +91,37 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
private var mFontBand = Graphics.FONT_SMALL;
|
private var mFontBand = Graphics.FONT_SMALL;
|
||||||
private var mFontComp = Graphics.FONT_MEDIUM;
|
private var mFontComp = Graphics.FONT_MEDIUM;
|
||||||
private var mFontTime = Graphics.FONT_NUMBER_HOT;
|
private var mFontTime = Graphics.FONT_NUMBER_HOT;
|
||||||
private var mDow = null;
|
// Always-on display: AMOLED devices must cut lit pixels hard while
|
||||||
private var mMon = null;
|
// asleep, and shift what is left so nothing burns in.
|
||||||
private var mLabels = null;
|
private var mSleeping = false;
|
||||||
private var mMeridiem = null;
|
private var mBurnIn = false;
|
||||||
private var mDateFmt = " ";
|
private var mIconSunrise = null;
|
||||||
|
private var mIconSunset = null;
|
||||||
|
private var mIconWeather = null;
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
WatchFace.initialize();
|
WatchFace.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onEnterSleep() as Void {
|
||||||
|
mSleeping = true;
|
||||||
|
WatchUi.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onExitSleep() as Void {
|
||||||
|
mSleeping = false;
|
||||||
|
WatchUi.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
function onLayout(dc as Dc) as Void {
|
function onLayout(dc as Dc) as Void {
|
||||||
mW = dc.getWidth();
|
mW = dc.getWidth();
|
||||||
mH = dc.getHeight();
|
mH = dc.getHeight();
|
||||||
mS = mW / 500.0;
|
mS = mW / 500.0;
|
||||||
mCx = mW / 2;
|
mCx = mW / 2;
|
||||||
mCy = mH / 2;
|
mCy = mH / 2;
|
||||||
|
var ds = System.getDeviceSettings();
|
||||||
|
mBurnIn = (ds != null) && (ds has :requiresBurnInProtection)
|
||||||
|
&& ds.requiresBurnInProtection;
|
||||||
var text = [Graphics.FONT_XTINY, Graphics.FONT_TINY, Graphics.FONT_SMALL,
|
var text = [Graphics.FONT_XTINY, Graphics.FONT_TINY, Graphics.FONT_SMALL,
|
||||||
Graphics.FONT_MEDIUM, Graphics.FONT_LARGE];
|
Graphics.FONT_MEDIUM, Graphics.FONT_LARGE];
|
||||||
var nums = [Graphics.FONT_NUMBER_MILD, Graphics.FONT_NUMBER_MEDIUM,
|
var nums = [Graphics.FONT_NUMBER_MILD, Graphics.FONT_NUMBER_MEDIUM,
|
||||||
@@ -115,28 +132,11 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
loadStrings();
|
loadStrings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every piece of on-face wording lives in resources so resources-chn can
|
|
||||||
// replace it. loadResource is not cheap, so pull it all once per layout.
|
|
||||||
function loadStrings() as Void {
|
function loadStrings() as Void {
|
||||||
mDow = splitCsv(WatchUi.loadResource(Rez.Strings.DowNames) as String);
|
Fields.loadStrings();
|
||||||
mMon = splitCsv(WatchUi.loadResource(Rez.Strings.MonNames) as String);
|
mIconSunrise = WatchUi.loadResource(Rez.Drawables.IconSunrise);
|
||||||
mLabels = splitCsv(WatchUi.loadResource(Rez.Strings.FieldLabels) as String);
|
mIconSunset = WatchUi.loadResource(Rez.Drawables.IconSunset);
|
||||||
mMeridiem = splitCsv(WatchUi.loadResource(Rez.Strings.Meridiem) as String);
|
mIconWeather = WatchUi.loadResource(Rez.Drawables.IconWeather);
|
||||||
mDateFmt = WatchUi.loadResource(Rez.Strings.DateFormat) as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Monkey C has no String.split.
|
|
||||||
function splitCsv(str as String) as Array<String> {
|
|
||||||
var out = [] as Array<String>;
|
|
||||||
var rest = str;
|
|
||||||
var cut = rest.find(",");
|
|
||||||
while (cut != null) {
|
|
||||||
out.add(rest.substring(0, cut) as String);
|
|
||||||
rest = rest.substring(cut + 1, rest.length()) as String;
|
|
||||||
cut = rest.find(",");
|
|
||||||
}
|
|
||||||
out.add(rest);
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect IQ exposes ascent and descent but not cap height, and the ascent
|
// Connect IQ exposes ascent and descent but not cap height, and the ascent
|
||||||
@@ -254,6 +254,15 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
? info.floorsClimbedGoal.toFloat() : 10.0f;
|
? info.floorsClimbedGoal.toFloat() : 10.0f;
|
||||||
p = info.floorsClimbed.toFloat() / goal;
|
p = info.floorsClimbed.toFloat() / goal;
|
||||||
}
|
}
|
||||||
|
} else if (idx == 5) { // Active minutes vs the weekly goal
|
||||||
|
if (info.activeMinutesWeek != null && info.activeMinutesWeekGoal != null
|
||||||
|
&& info.activeMinutesWeekGoal > 0) {
|
||||||
|
p = info.activeMinutesWeek.total.toFloat()
|
||||||
|
/ info.activeMinutesWeekGoal.toFloat();
|
||||||
|
}
|
||||||
|
} else if (idx == 6) { // Body battery is already 0..100
|
||||||
|
var bb = Fields.latest(SensorHistory.getBodyBatteryHistory({:period => 1}));
|
||||||
|
if (bb != null) { p = bb.toFloat() / 100.0; }
|
||||||
} else if (idx == 4) { // Move bar: an integer level, not a ratio
|
} else if (idx == 4) { // Move bar: an integer level, not a ratio
|
||||||
if (info.moveBarLevel != null) {
|
if (info.moveBarLevel != null) {
|
||||||
var span = (ActivityMonitor.MOVE_BAR_LEVEL_MAX
|
var span = (ActivityMonitor.MOVE_BAR_LEVEL_MAX
|
||||||
@@ -268,103 +277,6 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------ data bits
|
|
||||||
function distanceString(info as ActivityMonitor.Info, ds as System.DeviceSettings) as String {
|
|
||||||
if (info == null || info.distance == null) { return "0.0"; }
|
|
||||||
var km = info.distance.toFloat() / 100000.0;
|
|
||||||
if (ds != null && ds.distanceUnits == System.UNIT_STATUTE) { km = km * 0.621371; }
|
|
||||||
return km.format("%.1f");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TempUnit: 0 follow the watch, 1 force Celsius, 2 force Fahrenheit.
|
|
||||||
function tempString(c as Numeric?, ds as System.DeviceSettings) as String {
|
|
||||||
if (c == null) { return "--"; }
|
|
||||||
var t = c.toFloat();
|
|
||||||
var mode = getSetting("TempUnit", 0);
|
|
||||||
var fahrenheit = (mode == 2)
|
|
||||||
|| (mode == 0 && ds != null && ds.temperatureUnits == System.UNIT_STATUTE);
|
|
||||||
if (fahrenheit) { t = t * 9.0 / 5.0 + 32.0; }
|
|
||||||
return t.format("%d") + "°";
|
|
||||||
}
|
|
||||||
|
|
||||||
function clockString(m as Time.Moment?, ds as System.DeviceSettings) as String {
|
|
||||||
if (m == null) { return "--:--"; }
|
|
||||||
var g = Gregorian.info(m, Time.FORMAT_SHORT);
|
|
||||||
var h = g.hour;
|
|
||||||
if (ds == null || !ds.is24Hour) {
|
|
||||||
if (h == 0) { h = 12; } else if (h > 12) { h = h - 12; }
|
|
||||||
}
|
|
||||||
return h.format("%d") + ":" + g.min.format("%02d");
|
|
||||||
}
|
|
||||||
|
|
||||||
function here(weather as Weather.CurrentConditions?) as Position.Location? {
|
|
||||||
if (weather != null && weather.observationLocationPosition != null) {
|
|
||||||
return weather.observationLocationPosition;
|
|
||||||
}
|
|
||||||
var act = Activity.getActivityInfo();
|
|
||||||
if (act != null && act.currentLocation != null) { return act.currentLocation; }
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// value string for a complication slot
|
|
||||||
function complicationValue(typeIdx as Number, info as ActivityMonitor.Info,
|
|
||||||
weather as Weather.CurrentConditions?, stats as System.Stats,
|
|
||||||
ds as System.DeviceSettings) as String {
|
|
||||||
if (typeIdx == 1) { // Calories
|
|
||||||
return info != null && info.calories != null ? info.calories.format("%d") : "0";
|
|
||||||
} else if (typeIdx == 2) { // Steps
|
|
||||||
return info != null && info.steps != null ? info.steps.format("%d") : "0";
|
|
||||||
} else if (typeIdx == 3) { // Distance
|
|
||||||
return distanceString(info, ds);
|
|
||||||
} else if (typeIdx == 4) { // Floors
|
|
||||||
return info != null && info.floorsClimbed != null
|
|
||||||
? info.floorsClimbed.format("%d") : "0";
|
|
||||||
} else if (typeIdx == 5) { // Battery
|
|
||||||
var pct = 0;
|
|
||||||
if (stats != null && stats.battery != null) { pct = (stats.battery * 100.0).toNumber(); }
|
|
||||||
return pct.format("%d") + "%";
|
|
||||||
} else if (typeIdx == 6 || typeIdx == 7) { // Sunrise / sunset
|
|
||||||
var loc = here(weather);
|
|
||||||
if (loc == null) { return "--:--"; }
|
|
||||||
var m = typeIdx == 6 ? Weather.getSunrise(loc, Time.now())
|
|
||||||
: Weather.getSunset(loc, Time.now());
|
|
||||||
return clockString(m, ds);
|
|
||||||
} else if (typeIdx == 9) { // Heart rate
|
|
||||||
var act = Activity.getActivityInfo();
|
|
||||||
if (act != null && act.currentHeartRate != null) {
|
|
||||||
return act.currentHeartRate.format("%d");
|
|
||||||
}
|
|
||||||
var hist = ActivityMonitor.getHeartRateHistory(1, true);
|
|
||||||
if (hist != null) {
|
|
||||||
var sample = hist.next();
|
|
||||||
if (sample != null && sample.heartRate != null
|
|
||||||
&& sample.heartRate != ActivityMonitor.INVALID_HR_SAMPLE) {
|
|
||||||
return sample.heartRate.format("%d");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "--";
|
|
||||||
} else if (typeIdx == 8) { // Forecast high / low
|
|
||||||
var fc = Weather.getDailyForecast();
|
|
||||||
if (fc != null && fc.size() > 0) {
|
|
||||||
return tempString(fc[0].highTemperature, ds) + "/"
|
|
||||||
+ tempString(fc[0].lowTemperature, ds);
|
|
||||||
}
|
|
||||||
return "--/--";
|
|
||||||
}
|
|
||||||
// 0: current temperature
|
|
||||||
if (weather != null && weather.temperature != null) {
|
|
||||||
return tempString(weather.temperature, ds);
|
|
||||||
}
|
|
||||||
return "--";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Short caption under a bottom-row value. Indexed like the field types, so
|
|
||||||
// the label always follows whatever the slot is set to.
|
|
||||||
function fieldLabel(typeIdx as Number) as String {
|
|
||||||
if (mLabels == null || typeIdx < 0 || typeIdx >= mLabels.size()) { return ""; }
|
|
||||||
return mLabels[typeIdx];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------- ring
|
// ----------------------------------------------------------------- ring
|
||||||
function tick(dc as Dc, ang as Float, hw as Float) as Void {
|
function tick(dc as Dc, ang as Float, hw as Float) as Void {
|
||||||
var a0 = ang - hw;
|
var a0 = ang - hw;
|
||||||
@@ -441,79 +353,19 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filled half-ellipse, flat side down. The comp's dome is 14.90 wide but
|
|
||||||
// 8.93 tall, so it is not a plain semicircle.
|
|
||||||
function dome(dc as Dc, cx as Numeric, baseY as Numeric,
|
|
||||||
rx as Numeric, ry as Numeric) as Void {
|
|
||||||
var pts = [];
|
|
||||||
for (var i = 0; i <= 12; i++) {
|
|
||||||
var rad = Math.toRadians(180.0 - 15.0 * i);
|
|
||||||
pts.add([Math.round(px(cx) + Math.cos(rad) * px(rx)).toNumber(),
|
|
||||||
Math.round(px(baseY) - Math.sin(rad) * px(ry)).toNumber()]);
|
|
||||||
}
|
|
||||||
dc.fillPolygon(pts);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stub(dc as Dc, x1 as Numeric, y1 as Numeric, x2 as Numeric, y2 as Numeric) as Void {
|
function stub(dc as Dc, x1 as Numeric, y1 as Numeric, x2 as Numeric, y2 as Numeric) as Void {
|
||||||
dc.drawLine(px(x1), px(y1), px(x2), px(y2));
|
dc.drawLine(px(x1), px(y1), px(x2), px(y2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sun over a horizon bar with a chevron. Every ray in the comp is a short
|
// The comps' sunrise and weather icons are traced outlines with two- and
|
||||||
// stub roughly as long as the pen is wide -- drawing them longer is what
|
// three-pixel features. Rebuilding them from circles and lines held up at
|
||||||
// made this icon read taller than the weather one.
|
// 454 px and turned to mush at 280, so they ship as bitmaps rasterised
|
||||||
// dir = -1 flips the chevron up, which is what the sunset slot uses.
|
// from the design paths at each screen size (tools/gen_icons.py) and get
|
||||||
function iconSun(dc as Dc, cx as Numeric, cy as Numeric, scale as Float,
|
// the theme's accent applied through :tintColor.
|
||||||
col as Number, dir as Number) as Void {
|
function blitIcon(dc as Dc, bmp, cx as Numeric, cy as Numeric, col as Number) as Void {
|
||||||
var k = scale;
|
if (bmp == null) { return; }
|
||||||
var ox = cx - 156.32 * k; // design-space offset of this icon
|
dc.drawBitmap2(px(cx) - bmp.getWidth() / 2, px(cy) - bmp.getHeight() / 2,
|
||||||
var oy = cy - 90.34 * k;
|
bmp, {:tintColor => col});
|
||||||
dc.setColor(col, Graphics.COLOR_TRANSPARENT);
|
|
||||||
dome(dc, ox + 156.39 * k, oy + 94.02 * k, 7.45 * k, 8.93 * k);
|
|
||||||
dc.setPenWidth(maxNum(2, px(2.96 * k)));
|
|
||||||
stub(dc, ox + 156.39 * k, oy + 79.4 * k, ox + 156.39 * k, oy + 81.1 * k);
|
|
||||||
stub(dc, ox + 148.2 * k, oy + 83.3 * k, ox + 146.5 * k, oy + 84.5 * k);
|
|
||||||
stub(dc, ox + 164.6 * k, oy + 84.5 * k, ox + 166.3 * k, oy + 83.3 * k);
|
|
||||||
stub(dc, ox + 142.6 * k, oy + 92.5 * k, ox + 144.7 * k, oy + 92.5 * k);
|
|
||||||
stub(dc, ox + 168.0 * k, oy + 92.5 * k, ox + 170.1 * k, oy + 92.5 * k);
|
|
||||||
dc.setPenWidth(maxNum(2, px(2.6 * k)));
|
|
||||||
var by = oy + 98.45 * k;
|
|
||||||
stub(dc, ox + 142.3 * k, by, ox + 170.3 * k, by);
|
|
||||||
stub(dc, ox + 152.6 * k, by, ox + 156.3 * k, by + dir * 3.05 * k);
|
|
||||||
stub(dc, ox + 156.3 * k, by + dir * 3.05 * k, ox + 160.0 * k, by);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sun peeking out from behind a cloud. The comp keeps a dark notch where
|
|
||||||
// the cloud crosses the sun; without it the two shapes merge into one blob
|
|
||||||
// and the icon reads much heavier than the sunrise one next to it. So the
|
|
||||||
// cloud is stamped twice: once inflated in black to cut the notch, then at
|
|
||||||
// true size in the accent colour.
|
|
||||||
function cloudBody(dc as Dc, ox as Numeric, oy as Numeric, k as Float,
|
|
||||||
grow as Float) as Void {
|
|
||||||
dc.fillCircle(px(ox + 336.6 * k), px(oy + 94.4 * k), px(8.7 * k + grow));
|
|
||||||
dc.fillCircle(px(ox + 346.4 * k), px(oy + 96.9 * k), px(6.3 * k + grow));
|
|
||||||
var g = px(grow);
|
|
||||||
dc.fillRoundedRectangle(px(ox + 328.0 * k) - g, px(oy + 95.5 * k) - g,
|
|
||||||
px(24.6 * k) + 2 * g, px(7.75 * k) + g,
|
|
||||||
px(3.4 * k));
|
|
||||||
}
|
|
||||||
|
|
||||||
function iconCloud(dc as Dc, cx as Numeric, cy as Numeric, scale as Float,
|
|
||||||
col as Number) as Void {
|
|
||||||
var k = scale;
|
|
||||||
var ox = cx - 343.55 * k;
|
|
||||||
var oy = cy - 89.25 * k;
|
|
||||||
dc.setColor(col, Graphics.COLOR_TRANSPARENT);
|
|
||||||
dc.fillCircle(px(ox + 347.0 * k), px(oy + 88.9 * k), px(7.8 * k));
|
|
||||||
dc.setPenWidth(maxNum(2, px(2.3 * k)));
|
|
||||||
stub(dc, ox + 346.2 * k, oy + 76.0 * k, ox + 346.2 * k, oy + 78.7 * k);
|
|
||||||
stub(dc, ox + 354.9 * k, oy + 87.7 * k, ox + 357.5 * k, oy + 87.7 * k);
|
|
||||||
dc.setPenWidth(maxNum(2, px(2.7 * k)));
|
|
||||||
stub(dc, ox + 339.5 * k, oy + 79.5 * k, ox + 338.1 * k, oy + 80.9 * k);
|
|
||||||
stub(dc, ox + 353.5 * k, oy + 80.9 * k, ox + 354.6 * k, oy + 79.8 * k);
|
|
||||||
dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_TRANSPARENT);
|
|
||||||
cloudBody(dc, ox, oy, k, 1.7 * k);
|
|
||||||
dc.setColor(col, Graphics.COLOR_TRANSPARENT);
|
|
||||||
cloudBody(dc, ox, oy, k, 0.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function maxNum(a as Number, b as Number) as Number {
|
function maxNum(a as Number, b as Number) as Number {
|
||||||
@@ -522,47 +374,52 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
|
|
||||||
function drawComplicationIcon(dc as Dc, cx as Numeric, cy as Numeric, scale as Float,
|
function drawComplicationIcon(dc as Dc, cx as Numeric, cy as Numeric, scale as Float,
|
||||||
col as Number, typeIdx as Number, level as Float) as Void {
|
col as Number, typeIdx as Number, level as Float) as Void {
|
||||||
if (typeIdx == 6) {
|
if (typeIdx == 712 || typeIdx == 700) {
|
||||||
iconSun(dc, cx, cy, scale, col, 1);
|
blitIcon(dc, mIconSunrise, cx, ICON_CY, col);
|
||||||
} else if (typeIdx == 7) {
|
} else if (typeIdx == 717 || typeIdx == 731) {
|
||||||
iconSun(dc, cx, cy, scale, col, -1);
|
blitIcon(dc, mIconSunset, cx, ICON_CY, col);
|
||||||
} else if (typeIdx == 0 || typeIdx == 8) {
|
} else if (typeIdx == 306 || typeIdx == 639 || typeIdx == 300
|
||||||
iconCloud(dc, cx, cy, scale, col);
|
|| typeIdx == 301 || typeIdx == 302 || typeIdx == 304
|
||||||
} else if (typeIdx == 5) {
|
|| typeIdx == 305 || typeIdx == 308 || typeIdx == 309
|
||||||
|
|| typeIdx == 310 || typeIdx == 307) {
|
||||||
|
blitIcon(dc, mIconWeather, cx, ICON_CY, col);
|
||||||
|
} else if (typeIdx == 250 || typeIdx == 251 || typeIdx == 257) {
|
||||||
iconBattery(dc, cx - 14.4 * scale, cy, scale, col, level);
|
iconBattery(dc, cx - 14.4 * scale, cy, scale, col, level);
|
||||||
} else {
|
} else {
|
||||||
var r = 13.0 * scale;
|
var r = 13.0 * scale;
|
||||||
dc.setColor(col, Graphics.COLOR_TRANSPARENT);
|
dc.setColor(col, Graphics.COLOR_TRANSPARENT);
|
||||||
dc.setPenWidth(maxNum(2, px(3.0 * scale)));
|
dc.setPenWidth(maxNum(2, px(3.0 * scale)));
|
||||||
if (typeIdx == 1) { // calories: flame
|
if (typeIdx == 2 || typeIdx == 603) { // calories: flame
|
||||||
dc.fillPolygon([[px(cx), px(cy - r)],
|
dc.fillPolygon([[px(cx), px(cy - r)],
|
||||||
[px(cx + r * 0.72), px(cy + r * 0.25)],
|
[px(cx + r * 0.72), px(cy + r * 0.25)],
|
||||||
[px(cx + r * 0.40), px(cy + r * 0.85)],
|
[px(cx + r * 0.40), px(cy + r * 0.85)],
|
||||||
[px(cx - r * 0.40), px(cy + r * 0.85)],
|
[px(cx - r * 0.40), px(cy + r * 0.85)],
|
||||||
[px(cx - r * 0.72), px(cy + r * 0.25)]]);
|
[px(cx - r * 0.72), px(cy + r * 0.25)]]);
|
||||||
} else if (typeIdx == 2) { // steps: two prints
|
} else if (typeIdx == 1 || typeIdx == 14 || typeIdx == 707) { // steps
|
||||||
dc.fillRoundedRectangle(px(cx - r * 0.85), px(cy - r * 0.75),
|
dc.fillRoundedRectangle(px(cx - r * 0.85), px(cy - r * 0.75),
|
||||||
px(r * 0.62), px(r * 1.15), px(r * 0.3));
|
px(r * 0.62), px(r * 1.15), px(r * 0.3));
|
||||||
dc.fillRoundedRectangle(px(cx + r * 0.22), px(cy - r * 0.35),
|
dc.fillRoundedRectangle(px(cx + r * 0.22), px(cy - r * 0.35),
|
||||||
px(r * 0.62), px(r * 1.15), px(r * 0.3));
|
px(r * 0.62), px(r * 1.15), px(r * 0.3));
|
||||||
} else if (typeIdx == 3) { // distance: arrow
|
} else if (typeIdx == 3 || typeIdx == 9 || typeIdx == 615) { // distance
|
||||||
dc.fillPolygon([[px(cx), px(cy - r)],
|
dc.fillPolygon([[px(cx), px(cy - r)],
|
||||||
[px(cx + r * 0.75), px(cy + r * 0.8)],
|
[px(cx + r * 0.75), px(cy + r * 0.8)],
|
||||||
[px(cx), px(cy + r * 0.35)],
|
[px(cx), px(cy + r * 0.35)],
|
||||||
[px(cx - r * 0.75), px(cy + r * 0.8)]]);
|
[px(cx - r * 0.75), px(cy + r * 0.8)]]);
|
||||||
} else if (typeIdx == 9) { // heart rate
|
} else if (typeIdx == 618 || typeIdx == 276 || typeIdx == 623) { // heart
|
||||||
dc.fillCircle(px(cx - r * 0.38), px(cy - r * 0.28), px(r * 0.44));
|
dc.fillCircle(px(cx - r * 0.38), px(cy - r * 0.28), px(r * 0.44));
|
||||||
dc.fillCircle(px(cx + r * 0.38), px(cy - r * 0.28), px(r * 0.44));
|
dc.fillCircle(px(cx + r * 0.38), px(cy - r * 0.28), px(r * 0.44));
|
||||||
dc.fillPolygon([[px(cx - r * 0.78), px(cy - r * 0.12)],
|
dc.fillPolygon([[px(cx - r * 0.78), px(cy - r * 0.12)],
|
||||||
[px(cx + r * 0.78), px(cy - r * 0.12)],
|
[px(cx + r * 0.78), px(cy - r * 0.12)],
|
||||||
[px(cx), px(cy + r * 0.85)]]);
|
[px(cx), px(cy + r * 0.85)]]);
|
||||||
} else { // floors: stairs
|
} else if (typeIdx == 4 || typeIdx == 5 || typeIdx == 6) { // stairs
|
||||||
for (var i = 0; i < 3; i++) {
|
for (var i = 0; i < 3; i++) {
|
||||||
dc.fillRectangle(px(cx - r * 0.85 + r * 0.58 * i),
|
dc.fillRectangle(px(cx - r * 0.85 + r * 0.58 * i),
|
||||||
px(cy + r * 0.6 - r * 0.55 * i),
|
px(cy + r * 0.6 - r * 0.55 * i),
|
||||||
px(r * 0.62), px(r * 0.4));
|
px(r * 0.62), px(r * 0.4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Everything else -- dates, week numbers, custom text -- reads fine
|
||||||
|
// without a glyph, so no icon is drawn.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,14 +435,10 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawTopComplication(dc as Dc, ti as Number, typeIdx as Number, isLeft as Boolean,
|
function drawTopComplication(dc as Dc, ti as Number, typeIdx as Number, isLeft as Boolean,
|
||||||
info as ActivityMonitor.Info, weather as Weather.CurrentConditions?,
|
ctx as Fields.Ctx, level as Float) as Void {
|
||||||
stats as System.Stats, ds as System.DeviceSettings) as Void {
|
|
||||||
var x = isLeft ? LEFT_X : RIGHT_X;
|
var x = isLeft ? LEFT_X : RIGHT_X;
|
||||||
var level = 0.0f;
|
|
||||||
if (stats != null && stats.battery != null) { level = stats.battery / 100.0; }
|
|
||||||
drawComplicationIcon(dc, x, ICON_CY, 1.0, Themes.ACCENT[ti] as Number, typeIdx, level);
|
drawComplicationIcon(dc, x, ICON_CY, 1.0, Themes.ACCENT[ti] as Number, typeIdx, level);
|
||||||
text(dc, x, VALUE_CY, mFontComp,
|
text(dc, x, VALUE_CY, mFontComp, Fields.value(typeIdx, ctx),
|
||||||
complicationValue(typeIdx, info, weather, stats, ds),
|
|
||||||
Graphics.TEXT_JUSTIFY_CENTER, Themes.TEXTPRIMARY[ti] as Number);
|
Graphics.TEXT_JUSTIFY_CENTER, Themes.TEXTPRIMARY[ti] as Number);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,15 +468,12 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
// FORMAT_SHORT, not FORMAT_MEDIUM: only the short form returns
|
// FORMAT_SHORT, not FORMAT_MEDIUM: only the short form returns
|
||||||
// day_of_week and month as numbers rather than localised strings.
|
// day_of_week and month as numbers rather than localised strings.
|
||||||
var g = Gregorian.info(Time.now(), Time.FORMAT_SHORT);
|
var g = Gregorian.info(Time.now(), Time.FORMAT_SHORT);
|
||||||
text(dc, DOW_CX, BAND_CY, mFontBand, mDow[g.day_of_week - 1],
|
text(dc, DOW_CX, BAND_CY, mFontBand, Fields.dow(g),
|
||||||
Graphics.TEXT_JUSTIFY_CENTER, col);
|
Graphics.TEXT_JUSTIFY_CENTER, col);
|
||||||
// DateFormat is "$1$ $2$" in English and "$1$$2$日" in Chinese, where
|
text(dc, MD_CX, BAND_CY, mFontBand, Fields.monthDay(g),
|
||||||
// the Chinese month names already carry their own 月.
|
|
||||||
text(dc, MD_CX, BAND_CY, mFontBand,
|
|
||||||
Lang.format(mDateFmt, [mMon[g.month - 1], g.day.format("%d")]),
|
|
||||||
Graphics.TEXT_JUSTIFY_CENTER, col);
|
Graphics.TEXT_JUSTIFY_CENTER, col);
|
||||||
if (ds == null || !ds.is24Hour) {
|
if (ds == null || !ds.is24Hour) {
|
||||||
text(dc, AMPM_R, BAND_CY, mFontBand, mMeridiem[g.hour >= 12 ? 1 : 0],
|
text(dc, AMPM_R, BAND_CY, mFontBand, Fields.meridiem(g),
|
||||||
Graphics.TEXT_JUSTIFY_RIGHT, col);
|
Graphics.TEXT_JUSTIFY_RIGHT, col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -653,49 +503,120 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
dc.fillRectangle(px(COLON_X), px(COLON_Y2), px(COLON_W), px(COLON_H));
|
dc.fillRectangle(px(COLON_X), px(COLON_Y2), px(COLON_W), px(COLON_H));
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawBottom(dc as Dc, ti as Number, info as ActivityMonitor.Info,
|
function drawBottom(dc as Dc, ti as Number, ctx as Fields.Ctx) as Void {
|
||||||
weather as Weather.CurrentConditions?, stats as System.Stats,
|
|
||||||
ds as System.DeviceSettings) as Void {
|
|
||||||
var value = Themes.TEXTPRIMARY[ti] as Number;
|
var value = Themes.TEXTPRIMARY[ti] as Number;
|
||||||
var label = Themes.ACCENT[ti] as Number;
|
var label = Themes.ACCENT[ti] as Number;
|
||||||
var left = getSetting("BottomLeft", 3);
|
var left = getSetting("BottomLeft", 3);
|
||||||
var right = getSetting("BottomRight", 2);
|
var right = getSetting("BottomRight", 1);
|
||||||
text(dc, DIST_R, BOT_VAL_CY, mFontComp,
|
text(dc, DIST_R, BOT_VAL_CY, mFontComp, Fields.value(left, ctx),
|
||||||
complicationValue(left, info, weather, stats, ds),
|
|
||||||
Graphics.TEXT_JUSTIFY_RIGHT, value);
|
Graphics.TEXT_JUSTIFY_RIGHT, value);
|
||||||
text(dc, DIST_R, BOT_LAB_CY, mFontComp, fieldLabel(left),
|
text(dc, DIST_R, BOT_LAB_CY, mFontComp, Fields.label(left),
|
||||||
Graphics.TEXT_JUSTIFY_RIGHT, label);
|
Graphics.TEXT_JUSTIFY_RIGHT, label);
|
||||||
text(dc, STEP_L, BOT_VAL_CY, mFontComp,
|
text(dc, STEP_L, BOT_VAL_CY, mFontComp, Fields.value(right, ctx),
|
||||||
complicationValue(right, info, weather, stats, ds),
|
|
||||||
Graphics.TEXT_JUSTIFY_LEFT, value);
|
Graphics.TEXT_JUSTIFY_LEFT, value);
|
||||||
text(dc, STEP_L, BOT_LAB_CY, mFontComp, fieldLabel(right),
|
text(dc, STEP_L, BOT_LAB_CY, mFontComp, Fields.label(right),
|
||||||
Graphics.TEXT_JUSTIFY_LEFT, label);
|
Graphics.TEXT_JUSTIFY_LEFT, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ AOD
|
||||||
|
function dim(col as Number, f as Float) as Number {
|
||||||
|
var r = (((col >> 16) & 0xFF) * f).toNumber();
|
||||||
|
var g = (((col >> 8) & 0xFF) * f).toNumber();
|
||||||
|
var b = ((col & 0xFF) * f).toNumber();
|
||||||
|
return (r << 16) | (g << 8) | b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Low-power face: no band fill, no unlit ticks, no complications -- just a
|
||||||
|
// dimmed time, the lit part of the ring, and the date. The whole thing
|
||||||
|
// walks a few pixels each minute so no pixel stays lit in one place.
|
||||||
|
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 {
|
||||||
|
var g = Gregorian.info(Time.now(), Time.FORMAT_SHORT);
|
||||||
|
var ox = ((g.min % 5) - 2) * 2.0 / mS; // +/- 4 device px, in design units
|
||||||
|
var oy = ((g.min / 5) % 5 - 2) * 2.0 / mS;
|
||||||
|
|
||||||
|
var top = Themes.RINGTOP[ti] as Array<Number>;
|
||||||
|
var bot = Themes.RINGBOTTOM[ti] as Array<Number>;
|
||||||
|
var litTR = (pTR * TOP_N).toNumber();
|
||||||
|
var litTL = (pTL * TOP_N).toNumber();
|
||||||
|
var litBR = (pBR * BOT_N).toNumber();
|
||||||
|
var litBL = (pBL * BOT_N).toNumber();
|
||||||
|
for (var i = 0; i < TOP_N; i++) {
|
||||||
|
var a = TOP_PH + TOP_PITCH * i;
|
||||||
|
if (i >= TOP_N - litTR) {
|
||||||
|
dc.setColor(dim(top[i], 0.45), Graphics.COLOR_TRANSPARENT);
|
||||||
|
tick(dc, a, TOP_HW);
|
||||||
|
}
|
||||||
|
if (i >= TOP_N - litTL) {
|
||||||
|
dc.setColor(dim(top[i], 0.45), Graphics.COLOR_TRANSPARENT);
|
||||||
|
tick(dc, -a, TOP_HW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < BOT_N; i++) {
|
||||||
|
var a = BOT_PH + BOT_PITCH * i;
|
||||||
|
if (i < litBL) {
|
||||||
|
dc.setColor(dim(bot[i], 0.45), Graphics.COLOR_TRANSPARENT);
|
||||||
|
tick(dc, 180.0 + a, BOT_HW);
|
||||||
|
}
|
||||||
|
if (i < litBR) {
|
||||||
|
dc.setColor(dim(bot[i], 0.45), Graphics.COLOR_TRANSPARENT);
|
||||||
|
tick(dc, 180.0 - a, BOT_HW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var h = g.hour;
|
||||||
|
if (ds == null || !ds.is24Hour) {
|
||||||
|
if (h == 0) { h = 12; } else if (h > 12) { h = h - 12; }
|
||||||
|
}
|
||||||
|
var hours = Themes.HOURS[ti] as Array<Number>;
|
||||||
|
var mins = Themes.MINUTES[ti] as Array<Number>;
|
||||||
|
text(dc, HOURS_R + ox, TIME_CY + oy, mFontTime, h.format("%d"),
|
||||||
|
Graphics.TEXT_JUSTIFY_RIGHT, dim(hours[hours.size() / 2], 0.55));
|
||||||
|
text(dc, MINUTES_L + ox, TIME_CY + oy, mFontTime, g.min.format("%02d"),
|
||||||
|
Graphics.TEXT_JUSTIFY_LEFT, dim(mins[mins.size() / 2], 0.55));
|
||||||
|
dc.setColor(dim(Themes.COLON[ti] as Number, 0.55), Graphics.COLOR_TRANSPARENT);
|
||||||
|
dc.fillRectangle(px(COLON_X + ox), px(COLON_Y1 + oy), px(COLON_W), px(COLON_H));
|
||||||
|
dc.fillRectangle(px(COLON_X + ox), px(COLON_Y2 + oy), px(COLON_W), px(COLON_H));
|
||||||
|
|
||||||
|
var band = dim(Themes.ACCENT[ti] as Number, 0.55);
|
||||||
|
text(dc, DOW_CX + ox, BAND_CY + oy, mFontBand, Fields.dow(g),
|
||||||
|
Graphics.TEXT_JUSTIFY_CENTER, band);
|
||||||
|
text(dc, MD_CX + ox, BAND_CY + oy, mFontBand, Fields.monthDay(g),
|
||||||
|
Graphics.TEXT_JUSTIFY_CENTER, band);
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------- main
|
// ----------------------------------------------------------------- main
|
||||||
function onUpdate(dc as Dc) as Void {
|
function onUpdate(dc as Dc) as Void {
|
||||||
if (mW != dc.getWidth() || mDow == null) { onLayout(dc); }
|
if (mW != dc.getWidth() || !Fields.ready()) { onLayout(dc); }
|
||||||
if (dc has :setAntiAlias) { dc.setAntiAlias(true); }
|
if (dc has :setAntiAlias) { dc.setAntiAlias(true); }
|
||||||
|
|
||||||
var ti = themeIndex();
|
var ti = themeIndex();
|
||||||
var info = ActivityMonitor.getInfo();
|
|
||||||
var stats = System.getSystemStats();
|
|
||||||
var ds = System.getDeviceSettings();
|
var ds = System.getDeviceSettings();
|
||||||
var weather = Weather.getCurrentConditions();
|
var ctx = new Fields.Ctx(ds, getSetting("TempUnit", 0));
|
||||||
|
var info = ctx.info();
|
||||||
|
var stats = ctx.stats();
|
||||||
|
|
||||||
dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
|
dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
|
||||||
dc.clear();
|
dc.clear();
|
||||||
|
|
||||||
drawRing(dc, ti,
|
var pTR = metricProgress(getSetting("RingTR", 0), info);
|
||||||
metricProgress(getSetting("RingTR", 0), info),
|
var pTL = metricProgress(getSetting("RingTL", 1), info);
|
||||||
metricProgress(getSetting("RingTL", 1), info),
|
var pBR = metricProgress(getSetting("RingBR", 4), info);
|
||||||
metricProgress(getSetting("RingBR", 4), info),
|
var pBL = metricProgress(getSetting("RingBL", 3), info);
|
||||||
metricProgress(getSetting("RingBL", 3), info));
|
|
||||||
|
if (mSleeping && mBurnIn) {
|
||||||
|
drawAmbient(dc, ti, ds, pTR, pTL, pBR, pBL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawRing(dc, ti, pTR, pTL, pBR, pBL);
|
||||||
|
|
||||||
if (getBool("ShowTop", true)) {
|
if (getBool("ShowTop", true)) {
|
||||||
|
var level = 0.0f;
|
||||||
|
if (stats != null && stats.battery != null) { level = stats.battery / 100.0; }
|
||||||
drawBattery(dc, ti, stats);
|
drawBattery(dc, ti, stats);
|
||||||
drawTopComplication(dc, ti, getSetting("LeftTop", 6), true, info, weather, stats, ds);
|
drawTopComplication(dc, ti, getSetting("LeftTop", 712), true, ctx, level);
|
||||||
drawTopComplication(dc, ti, getSetting("RightTop", 8), false, info, weather, stats, ds);
|
drawTopComplication(dc, ti, getSetting("RightTop", 639), false, ctx, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawTime(dc, ti, ds);
|
drawTime(dc, ti, ds);
|
||||||
@@ -705,7 +626,7 @@ class Fenix8V3View extends WatchUi.WatchFace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (getBool("ShowBottom", true)) {
|
if (getBool("ShowBottom", true)) {
|
||||||
drawBottom(dc, ti, info, weather, stats, ds);
|
drawBottom(dc, ti, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
source/FieldTable.mc
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// AUTO-GENERATED by tools/gen_fields.py -- do not edit by hand.
|
||||||
|
// Ids follow https://watchface.io/docs/datafields; only the fields a
|
||||||
|
// Connect IQ watch face can source on-device are listed.
|
||||||
|
import Toybox.Lang;
|
||||||
|
|
||||||
|
module FieldTable {
|
||||||
|
|
||||||
|
// Field ids, in the same order as the Labels resource string.
|
||||||
|
const IDS = [
|
||||||
|
0,860,880,864,865,851,872,852,855,870,856,853,868,858,859,873,1,14,707,2,603,150,
|
||||||
|
152,154,151,3,4,5,6,9,10,618,276,12,635,11,623,8,15,281,742,13,278,277,250,251,257,
|
||||||
|
253,255,254,256,252,615,616,733,712,717,700,731,706,732,306,639,300,301,302,305,304,
|
||||||
|
308,307,310,309,703,704,705
|
||||||
|
];
|
||||||
|
|
||||||
|
function indexOf(id as Number) as Number {
|
||||||
|
for (var i = 0; i < IDS.size(); i++) {
|
||||||
|
if (IDS[i] == id) { return i; }
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
491
source/Fields.mc
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
import Toybox.Lang;
|
||||||
|
import Toybox.Math;
|
||||||
|
import Toybox.System;
|
||||||
|
import Toybox.Time;
|
||||||
|
import Toybox.Time.Gregorian;
|
||||||
|
import Toybox.Application;
|
||||||
|
import Toybox.ActivityMonitor;
|
||||||
|
import Toybox.Activity;
|
||||||
|
import Toybox.UserProfile;
|
||||||
|
import Toybox.SensorHistory;
|
||||||
|
import Toybox.Weather;
|
||||||
|
import Toybox.Position;
|
||||||
|
import Toybox.WatchUi;
|
||||||
|
import FieldTable;
|
||||||
|
|
||||||
|
// Everything a complication slot can show. Ids match
|
||||||
|
// https://watchface.io/docs/datafields so the two catalogues line up; see
|
||||||
|
// tools/gen_fields.py for the table that drives the settings and the labels.
|
||||||
|
//
|
||||||
|
// Only fields Connect IQ can answer from the watch itself are here. The
|
||||||
|
// week-to-date and 7/28-day per-sport aggregates in that catalogue come from a
|
||||||
|
// phone-side Garmin Connect integration, and the OpenWeatherMap / StormGlass /
|
||||||
|
// third-party-app fields need network calls and API keys; neither is something
|
||||||
|
// a self-contained watch face can produce.
|
||||||
|
module Fields {
|
||||||
|
|
||||||
|
const DASH = "--";
|
||||||
|
|
||||||
|
var mDow as Array<String>?;
|
||||||
|
var mMon as Array<String>?;
|
||||||
|
var mLabels as Array<String>?;
|
||||||
|
var mMeridiem as Array<String>?;
|
||||||
|
var mDateFmt as String = "$1$ $2$";
|
||||||
|
|
||||||
|
function loadStrings() as Void {
|
||||||
|
mDow = splitCsv(WatchUi.loadResource(Rez.Strings.DowNames) as String);
|
||||||
|
mMon = splitCsv(WatchUi.loadResource(Rez.Strings.MonNames) as String);
|
||||||
|
mLabels = splitCsv(WatchUi.loadResource(Rez.Strings.FieldLabels) as String);
|
||||||
|
mMeridiem = splitCsv(WatchUi.loadResource(Rez.Strings.Meridiem) as String);
|
||||||
|
mDateFmt = WatchUi.loadResource(Rez.Strings.DateFormat) as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ready() as Boolean { return mDow != null; }
|
||||||
|
|
||||||
|
// Monkey C has no String.split.
|
||||||
|
function splitCsv(str as String) as Array<String> {
|
||||||
|
var out = [] as Array<String>;
|
||||||
|
var rest = str;
|
||||||
|
var cut = rest.find(",");
|
||||||
|
while (cut != null) {
|
||||||
|
out.add(rest.substring(0, cut) as String);
|
||||||
|
rest = rest.substring(cut + 1, rest.length()) as String;
|
||||||
|
cut = rest.find(",");
|
||||||
|
}
|
||||||
|
out.add(rest);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dow(g as Gregorian.Info) as String {
|
||||||
|
return mDow == null ? "" : mDow[g.day_of_week - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function month(g as Gregorian.Info) as String {
|
||||||
|
return mMon == null ? "" : mMon[g.month - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function monthDay(g as Gregorian.Info) as String {
|
||||||
|
return Lang.format(mDateFmt, [month(g), g.day.format("%d")]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function meridiem(g as Gregorian.Info) as String {
|
||||||
|
return mMeridiem == null ? "" : mMeridiem[g.hour >= 12 ? 1 : 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short caption printed under a bottom-row value.
|
||||||
|
function label(id as Number) as String {
|
||||||
|
if (mLabels == null) { return ""; }
|
||||||
|
var i = FieldTable.indexOf(id);
|
||||||
|
return i < mLabels.size() ? mLabels[i] : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// One of these is built per onUpdate and handed to every slot, so the
|
||||||
|
// expensive lookups (forecast, sensor history, user profile) happen at most
|
||||||
|
// once per draw and only if some slot actually asks for them.
|
||||||
|
class Ctx {
|
||||||
|
var greg as Gregorian.Info;
|
||||||
|
var is24 as Boolean = true;
|
||||||
|
var metric as Boolean = true;
|
||||||
|
var fahrenheit as Boolean = false;
|
||||||
|
|
||||||
|
private var mInfo as ActivityMonitor.Info?;
|
||||||
|
private var mStats as System.Stats?;
|
||||||
|
private var mDs as System.DeviceSettings?;
|
||||||
|
private var mAct as Activity.Info?;
|
||||||
|
private var mWx as Weather.CurrentConditions?;
|
||||||
|
private var mFc as Array<Weather.DailyForecast>?;
|
||||||
|
private var mProf as UserProfile.Profile?;
|
||||||
|
private var mGotAct = false;
|
||||||
|
private var mGotWx = false;
|
||||||
|
private var mGotFc = false;
|
||||||
|
private var mGotProf = false;
|
||||||
|
|
||||||
|
function initialize(ds as System.DeviceSettings?, tempUnit as Number) {
|
||||||
|
mDs = ds;
|
||||||
|
greg = Gregorian.info(Time.now(), Time.FORMAT_SHORT);
|
||||||
|
if (ds != null) {
|
||||||
|
is24 = ds.is24Hour;
|
||||||
|
metric = (ds.distanceUnits != System.UNIT_STATUTE);
|
||||||
|
fahrenheit = (tempUnit == 2)
|
||||||
|
|| (tempUnit == 0 && ds.temperatureUnits == System.UNIT_STATUTE);
|
||||||
|
}
|
||||||
|
if (tempUnit == 1) { fahrenheit = false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function ds() as System.DeviceSettings? { return mDs; }
|
||||||
|
|
||||||
|
function info() as ActivityMonitor.Info? {
|
||||||
|
if (mInfo == null) { mInfo = ActivityMonitor.getInfo(); }
|
||||||
|
return mInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stats() as System.Stats? {
|
||||||
|
if (mStats == null) { mStats = System.getSystemStats(); }
|
||||||
|
return mStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
function act() as Activity.Info? {
|
||||||
|
if (!mGotAct) { mAct = Activity.getActivityInfo(); mGotAct = true; }
|
||||||
|
return mAct;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wx() as Weather.CurrentConditions? {
|
||||||
|
if (!mGotWx) { mWx = Weather.getCurrentConditions(); mGotWx = true; }
|
||||||
|
return mWx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function forecast() as Weather.DailyForecast? {
|
||||||
|
if (!mGotFc) { mFc = Weather.getDailyForecast(); mGotFc = true; }
|
||||||
|
if (mFc != null && mFc.size() > 0) { return mFc[0]; }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function profile() as UserProfile.Profile? {
|
||||||
|
if (!mGotProf) { mProf = UserProfile.getProfile(); mGotProf = true; }
|
||||||
|
return mProf;
|
||||||
|
}
|
||||||
|
|
||||||
|
function where() as Position.Location? {
|
||||||
|
var w = wx();
|
||||||
|
if (w != null && w.observationLocationPosition != null) {
|
||||||
|
return w.observationLocationPosition;
|
||||||
|
}
|
||||||
|
var a = act();
|
||||||
|
if (a != null && a.currentLocation != null) { return a.currentLocation; }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------- helpers
|
||||||
|
function num(v as Numeric?) as String {
|
||||||
|
return v == null ? DASH : v.format("%d");
|
||||||
|
}
|
||||||
|
|
||||||
|
function one(v as Float?) as String {
|
||||||
|
return v == null ? DASH : v.format("%.1f");
|
||||||
|
}
|
||||||
|
|
||||||
|
function clock(m as Time.Moment?, c as Ctx) as String {
|
||||||
|
if (m == null) { return DASH; }
|
||||||
|
return clockInfo(Gregorian.info(m, Time.FORMAT_SHORT), c);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clockInfo(g as Gregorian.Info, c as Ctx) as String {
|
||||||
|
var h = g.hour;
|
||||||
|
if (!c.is24) {
|
||||||
|
if (h == 0) { h = 12; } else if (h > 12) { h = h - 12; }
|
||||||
|
}
|
||||||
|
return h.format("%d") + ":" + g.min.format("%02d");
|
||||||
|
}
|
||||||
|
|
||||||
|
function offsetClock(minutes as Number, c as Ctx) as String {
|
||||||
|
var m = Time.now().add(new Time.Duration(minutes * 60));
|
||||||
|
return clock(m, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
function temp(celsius as Numeric?, c as Ctx) as String {
|
||||||
|
if (celsius == null) { return DASH; }
|
||||||
|
var t = celsius.toFloat();
|
||||||
|
if (c.fahrenheit) { t = t * 9.0 / 5.0 + 32.0; }
|
||||||
|
return t.format("%d") + "°";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivityMonitor distances are centimetres.
|
||||||
|
function dist(cm as Numeric?, c as Ctx) as String {
|
||||||
|
if (cm == null) { return DASH; }
|
||||||
|
var km = cm.toFloat() / 100000.0;
|
||||||
|
return (c.metric ? km : km * 0.621371).format("%.1f");
|
||||||
|
}
|
||||||
|
|
||||||
|
function metres(m as Numeric?, c as Ctx) as String {
|
||||||
|
if (m == null) { return DASH; }
|
||||||
|
var v = m.toFloat();
|
||||||
|
return (c.metric ? v : v * 3.28084).format("%d");
|
||||||
|
}
|
||||||
|
|
||||||
|
function rounded(v as Numeric?) as String {
|
||||||
|
if (v == null) { return DASH; }
|
||||||
|
var n = v.toNumber();
|
||||||
|
if (n < 1000) { return n.format("%d"); }
|
||||||
|
return (n / 1000.0).format("%.1f") + "k";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setting(name as String, def as Number) as Number {
|
||||||
|
var v = Application.Properties.getValue(name);
|
||||||
|
if (v == null) { return def; }
|
||||||
|
return v as Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function settingText(name as String) as String {
|
||||||
|
var v = Application.Properties.getValue(name);
|
||||||
|
if (v == null) { return ""; }
|
||||||
|
return v.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newest reading out of a SensorHistory iterator, or null.
|
||||||
|
function latest(iter) as Numeric? {
|
||||||
|
if (iter == null) { return null; }
|
||||||
|
var s = iter.next();
|
||||||
|
if (s == null) { return null; }
|
||||||
|
return s.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dayOfYear(g as Gregorian.Info) as Number {
|
||||||
|
var cum = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
||||||
|
var d = cum[g.month - 1] + g.day;
|
||||||
|
var y = g.year;
|
||||||
|
var leap = (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
|
||||||
|
if (leap && g.month > 2) { d = d + 1; }
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISO 8601 week: weeks start Monday, week 1 holds the first Thursday.
|
||||||
|
function isoWeek(g as Gregorian.Info) as Number {
|
||||||
|
var dow = g.day_of_week - 1; // 0 = Sunday
|
||||||
|
var iso = dow == 0 ? 7 : dow; // 1 = Monday .. 7 = Sunday
|
||||||
|
var w = (dayOfYear(g) - iso + 10) / 7;
|
||||||
|
if (w < 1) { return 52; }
|
||||||
|
if (w > 52) { return 1; }
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Days since the new moon of 2000-01-06, folded into one synodic month.
|
||||||
|
function moonAge(g as Gregorian.Info) as Float {
|
||||||
|
var y = g.year;
|
||||||
|
var m = g.month;
|
||||||
|
if (m <= 2) { y = y - 1; m = m + 12; }
|
||||||
|
var a = y / 100;
|
||||||
|
var b = 2 - a + a / 4;
|
||||||
|
var jd = (365.25 * (y + 4716)).toNumber() + (30.6001 * (m + 1)).toNumber()
|
||||||
|
+ g.day + b - 1524.5 + g.hour / 24.0;
|
||||||
|
// Monkey C has no float modulo.
|
||||||
|
var syn = 29.530588853;
|
||||||
|
var x = jd - 2451550.1;
|
||||||
|
var age = x - (x / syn).toNumber() * syn;
|
||||||
|
if (age < 0) { age = age + syn; }
|
||||||
|
return age.toFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function windDir(bearingDeg as Numeric?) as String {
|
||||||
|
if (bearingDeg == null) { return DASH; }
|
||||||
|
var pts = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
|
||||||
|
"S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"];
|
||||||
|
var b = bearingDeg.toFloat();
|
||||||
|
while (b < 0) { b = b + 360.0; }
|
||||||
|
var i = ((b + 11.25) / 22.5).toNumber() % 16;
|
||||||
|
return pts[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------- value
|
||||||
|
function value(id as Number, c as Ctx) as String {
|
||||||
|
// ---- date / time
|
||||||
|
if (id == 860) { return clockInfo(c.greg, c); }
|
||||||
|
if (id == 880) {
|
||||||
|
var utc = Gregorian.utcInfo(Time.now(), Time.FORMAT_SHORT);
|
||||||
|
return clockInfo(utc, c);
|
||||||
|
}
|
||||||
|
if (id == 864) { return offsetClock(setting("AltOffset1", 0), c); }
|
||||||
|
if (id == 865) { return offsetClock(setting("AltOffset2", 0), c); }
|
||||||
|
if (id == 855) { return c.greg.day.format("%d"); }
|
||||||
|
if (id == 868) { return c.greg.month.format("%d"); }
|
||||||
|
if (id == 859) { return isoWeek(c.greg).format("%d"); }
|
||||||
|
if (id == 873) { return ((dayOfYear(c.greg) + 6) / 7).format("%d"); }
|
||||||
|
if (id == 870) { return dow(c.greg); }
|
||||||
|
if (id == 856) { return dow(c.greg); }
|
||||||
|
if (id == 853) { return month(c.greg); }
|
||||||
|
if (id == 852) { return monthDay(c.greg); }
|
||||||
|
if (id == 872) { return dow(c.greg) + " " + c.greg.day.format("%d"); }
|
||||||
|
if (id == 851) { return dow(c.greg) + " " + monthDay(c.greg); }
|
||||||
|
if (id == 858) { return meridiem(c.greg); }
|
||||||
|
|
||||||
|
// ---- activity
|
||||||
|
var i = c.info();
|
||||||
|
if (id == 1) { return i == null ? DASH : num(i.steps); }
|
||||||
|
if (id == 14) { return i == null ? DASH : rounded(i.steps); }
|
||||||
|
if (id == 707) {
|
||||||
|
if (i == null || i.steps == null || i.stepGoal == null) { return DASH; }
|
||||||
|
var left = i.stepGoal - i.steps;
|
||||||
|
return left > 0 ? left.format("%d") : "0";
|
||||||
|
}
|
||||||
|
if (id == 2) { return i == null ? DASH : num(i.calories); }
|
||||||
|
if (id == 603) {
|
||||||
|
if (i == null || i.calories == null) { return DASH; }
|
||||||
|
var p = c.profile();
|
||||||
|
if (p == null || p.weight == null || p.height == null) { return num(i.calories); }
|
||||||
|
return num(i.calories); // device reports total only
|
||||||
|
}
|
||||||
|
if (id == 150 || id == 152 || id == 154) {
|
||||||
|
if (i == null || i.activeMinutesDay == null) { return DASH; }
|
||||||
|
var am = i.activeMinutesDay;
|
||||||
|
if (id == 152) { return num(am.moderate); }
|
||||||
|
if (id == 154) { return num(am.vigorous); }
|
||||||
|
return num(am.total);
|
||||||
|
}
|
||||||
|
if (id == 151) {
|
||||||
|
if (i == null || i.activeMinutesWeek == null) { return DASH; }
|
||||||
|
return num(i.activeMinutesWeek.total);
|
||||||
|
}
|
||||||
|
if (id == 3) { return i == null ? DASH : dist(i.distance, c); }
|
||||||
|
if (id == 4) { return i == null ? DASH : num(i.floorsClimbed); }
|
||||||
|
if (id == 5) { return i == null ? DASH : num(i.floorsDescended); }
|
||||||
|
if (id == 6) { return i == null ? DASH : metres(i.metersClimbed, c); }
|
||||||
|
if (id == 9) { return i == null ? DASH : dist(i.pushDistance, c); }
|
||||||
|
if (id == 10) { return i == null ? DASH : num(i.pushes); }
|
||||||
|
if (id == 8 || id == 15) {
|
||||||
|
if (i == null || i.moveBarLevel == null) { return DASH; }
|
||||||
|
var lvl = i.moveBarLevel;
|
||||||
|
if (id == 15) { lvl = ActivityMonitor.MOVE_BAR_LEVEL_MAX - lvl; }
|
||||||
|
return lvl.format("%d");
|
||||||
|
}
|
||||||
|
if (id == 13) { return i == null ? DASH : num(i.timeToRecovery); }
|
||||||
|
if (id == 11) { return i == null ? DASH : num(i.respirationRate); }
|
||||||
|
if (id == 12) { return i == null ? DASH : num(i.stressScore); }
|
||||||
|
|
||||||
|
// ---- body
|
||||||
|
if (id == 618) {
|
||||||
|
var a = c.act();
|
||||||
|
if (a != null && a.currentHeartRate != null) { return num(a.currentHeartRate); }
|
||||||
|
var h = ActivityMonitor.getHeartRateHistory(1, true);
|
||||||
|
if (h != null) {
|
||||||
|
var s = h.next();
|
||||||
|
if (s != null && s.heartRate != null
|
||||||
|
&& s.heartRate != ActivityMonitor.INVALID_HR_SAMPLE) {
|
||||||
|
return num(s.heartRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DASH;
|
||||||
|
}
|
||||||
|
if (id == 276) {
|
||||||
|
var p = c.profile();
|
||||||
|
return (p == null) ? DASH : num(p.restingHeartRate);
|
||||||
|
}
|
||||||
|
if (id == 635) {
|
||||||
|
if (!(SensorHistory has :getOxygenSaturationHistory)) { return DASH; }
|
||||||
|
return num(latest(SensorHistory.getOxygenSaturationHistory({:period => 1})));
|
||||||
|
}
|
||||||
|
if (id == 623) {
|
||||||
|
if (!(SensorHistory has :getBodyBatteryHistory)) { return DASH; }
|
||||||
|
return num(latest(SensorHistory.getBodyBatteryHistory({:period => 1})));
|
||||||
|
}
|
||||||
|
if (id == 281 || id == 742) {
|
||||||
|
var p = c.profile();
|
||||||
|
if (p == null || p.weight == null) { return DASH; }
|
||||||
|
var kg = p.weight.toFloat() / 1000.0;
|
||||||
|
if (id == 281) {
|
||||||
|
return (c.metric ? kg : kg * 2.20462).format("%.1f");
|
||||||
|
}
|
||||||
|
if (p.height == null || p.height <= 0) { return DASH; }
|
||||||
|
var m = p.height.toFloat() / 100.0;
|
||||||
|
return (kg / (m * m)).format("%.1f");
|
||||||
|
}
|
||||||
|
if (id == 277 || id == 278) {
|
||||||
|
var p = c.profile();
|
||||||
|
if (p == null) { return DASH; }
|
||||||
|
return num(id == 278 ? p.vo2maxRunning : p.vo2maxCycling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- system
|
||||||
|
var st = c.stats();
|
||||||
|
if (id == 250) {
|
||||||
|
return st == null || st.battery == null ? DASH
|
||||||
|
: st.battery.format("%d") + "%";
|
||||||
|
}
|
||||||
|
if (id == 251) {
|
||||||
|
if (st == null || !(st has :batteryInDays) || st.batteryInDays == null) {
|
||||||
|
return DASH;
|
||||||
|
}
|
||||||
|
return st.batteryInDays.format("%d") + "d";
|
||||||
|
}
|
||||||
|
if (id == 257) {
|
||||||
|
var pct = (st == null || st.battery == null) ? DASH : st.battery.format("%d");
|
||||||
|
var d = (st == null || !(st has :batteryInDays) || st.batteryInDays == null)
|
||||||
|
? DASH : st.batteryInDays.format("%d");
|
||||||
|
return pct + "/" + d;
|
||||||
|
}
|
||||||
|
if (id == 252) {
|
||||||
|
if (st == null || !(st has :solarIntensity) || st.solarIntensity == null) {
|
||||||
|
return DASH;
|
||||||
|
}
|
||||||
|
return st.solarIntensity.format("%d") + "%";
|
||||||
|
}
|
||||||
|
var ds = c.ds();
|
||||||
|
if (id == 253) { return ds == null ? DASH : num(ds.alarmCount); }
|
||||||
|
if (id == 255) { return ds == null ? DASH : num(ds.notificationCount); }
|
||||||
|
if (id == 254) {
|
||||||
|
return ds == null ? DASH : (ds.doNotDisturb ? "ON" : "OFF");
|
||||||
|
}
|
||||||
|
if (id == 256) {
|
||||||
|
return ds == null ? DASH : (ds.phoneConnected ? "ON" : "OFF");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- environment
|
||||||
|
if (id == 615) {
|
||||||
|
var a = c.act();
|
||||||
|
return (a == null) ? DASH : metres(a.altitude, c);
|
||||||
|
}
|
||||||
|
if (id == 616 || id == 733) {
|
||||||
|
var a = c.act();
|
||||||
|
if (a == null) { return DASH; }
|
||||||
|
var pa = (id == 616) ? a.meanSeaLevelPressure : a.ambientPressure;
|
||||||
|
if (pa == null) { return DASH; }
|
||||||
|
return (pa.toFloat() / 100.0).format("%d");
|
||||||
|
}
|
||||||
|
if (id == 712 || id == 717 || id == 700 || id == 731) {
|
||||||
|
var loc = c.where();
|
||||||
|
if (loc == null) { return DASH; }
|
||||||
|
var now = Time.now();
|
||||||
|
var rise = Weather.getSunrise(loc, now);
|
||||||
|
var set = Weather.getSunset(loc, now);
|
||||||
|
if (id == 712) { return clock(rise, c); }
|
||||||
|
if (id == 717) { return clock(set, c); }
|
||||||
|
if (rise == null || set == null) { return DASH; }
|
||||||
|
var next = (now.compare(rise) < 0) ? rise
|
||||||
|
: ((now.compare(set) < 0) ? set : rise);
|
||||||
|
if (id == 700) { return clock(next, c); }
|
||||||
|
var secs = next.compare(now);
|
||||||
|
if (secs < 0) { secs = secs + 86400; }
|
||||||
|
return (secs / 3600).format("%d") + "h" + ((secs % 3600) / 60).format("%02d");
|
||||||
|
}
|
||||||
|
if (id == 706) { return moonAge(c.greg).format("%.1f"); }
|
||||||
|
if (id == 732) {
|
||||||
|
var age = moonAge(c.greg);
|
||||||
|
var lit = (1.0 - Math.cos(2.0 * Math.PI * age / 29.530588853)) / 2.0;
|
||||||
|
return (lit * 100.0).format("%d") + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- weather
|
||||||
|
var w = c.wx();
|
||||||
|
if (id == 306) { return w == null ? DASH : temp(w.temperature, c); }
|
||||||
|
if (id == 300) { return w == null ? DASH : temp(w.feelsLikeTemperature, c); }
|
||||||
|
if (id == 305) { return w == null ? DASH : num(w.relativeHumidity) + "%"; }
|
||||||
|
if (id == 304) {
|
||||||
|
if (w == null || !(w has :precipitationChance)) { return DASH; }
|
||||||
|
return num(w.precipitationChance) + "%";
|
||||||
|
}
|
||||||
|
if (id == 308) {
|
||||||
|
if (w == null || w.windSpeed == null) { return DASH; }
|
||||||
|
var kmh = w.windSpeed.toFloat() * 3.6;
|
||||||
|
return (c.metric ? kmh : kmh * 0.621371).format("%d");
|
||||||
|
}
|
||||||
|
if (id == 307) { return w == null ? DASH : num(w.windBearing); }
|
||||||
|
if (id == 310) { return w == null ? DASH : windDir(w.windBearing); }
|
||||||
|
if (id == 309) {
|
||||||
|
if (w == null || w.observationTime == null) { return DASH; }
|
||||||
|
return clock(w.observationTime, c);
|
||||||
|
}
|
||||||
|
if (id == 301 || id == 302 || id == 639) {
|
||||||
|
var f = c.forecast();
|
||||||
|
if (f == null) { return DASH; }
|
||||||
|
if (id == 301) { return temp(f.highTemperature, c); }
|
||||||
|
if (id == 302) { return temp(f.lowTemperature, c); }
|
||||||
|
return temp(f.highTemperature, c) + "/" + temp(f.lowTemperature, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- custom
|
||||||
|
if (id == 703) { return settingText("Custom1"); }
|
||||||
|
if (id == 704) { return settingText("Custom2"); }
|
||||||
|
if (id == 705) { return settingText("Custom3"); }
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
252
tools/gen_fields.py
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""One table -> the field id list, both languages, and the settings XML.
|
||||||
|
|
||||||
|
Field ids follow the numbering at https://watchface.io/docs/datafields so the
|
||||||
|
two catalogues line up, but only the fields Connect IQ can actually serve from
|
||||||
|
the watch are here. Anything needing a web service (OpenWeatherMap,
|
||||||
|
StormGlass), a companion phone integration (the week-to-date and 7/28-day
|
||||||
|
per-sport aggregates), or another installed app (CGM, hydration, quotes) is
|
||||||
|
deliberately absent -- see README section 8 for the full reasoning.
|
||||||
|
|
||||||
|
python3 tools/gen_fields.py
|
||||||
|
|
||||||
|
Writes:
|
||||||
|
source/FieldTable.mc ids + short label lookup
|
||||||
|
resources/settings/settings.xml the four field pickers
|
||||||
|
resources/strings/fields.xml English names and labels
|
||||||
|
resources-chn/strings/fields.xml Chinese names and labels
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.dirname(HERE)
|
||||||
|
|
||||||
|
# id, key, English name, Chinese name, on-face label (en), on-face label (cn)
|
||||||
|
FIELDS = [
|
||||||
|
(0, "Off", "Off", "关闭", "", ""),
|
||||||
|
|
||||||
|
# ---- date / time ----------------------------------------------------
|
||||||
|
(860, "Time", "Time", "时间", "TIME", "时间"),
|
||||||
|
(880, "UtcTime", "UTC Time", "UTC 时间", "UTC", "UTC"),
|
||||||
|
(864, "AltTime1", "Alternate Time 1", "第二时区 1", "TZ1", "时区1"),
|
||||||
|
(865, "AltTime2", "Alternate Time 2", "第二时区 2", "TZ2", "时区2"),
|
||||||
|
(851, "DayMonthDay", "Day Month Day", "星期 月 日", "DATE", "日期"),
|
||||||
|
(872, "DayDate", "Day Date", "星期 日", "DATE", "日期"),
|
||||||
|
(852, "MonthDay", "Month Day", "月 日", "DATE", "日期"),
|
||||||
|
(855, "DayOfMonth", "Day of Month", "日", "DAY", "日"),
|
||||||
|
(870, "DayShort", "Day (Tue)", "星期简写", "DAY", "星期"),
|
||||||
|
(856, "DayFull", "Full Day (Tuesday)", "星期全称", "DAY", "星期"),
|
||||||
|
(853, "MonthName", "Month (Jan)", "月份", "MON", "月"),
|
||||||
|
(868, "MonthNum", "Month (6)", "月份数字", "MON", "月"),
|
||||||
|
(858, "Meridiem", "AM/PM", "上午/下午", "", ""),
|
||||||
|
(859, "WeekIso", "Week Number (ISO)", "周数 (ISO)", "WEEK", "周"),
|
||||||
|
(873, "WeekCal", "Week Number", "周数", "WEEK", "周"),
|
||||||
|
|
||||||
|
# ---- activity -------------------------------------------------------
|
||||||
|
(1, "Steps", "Steps", "步数", "STEP", "步数"),
|
||||||
|
(14, "StepsRound", "Steps Rounded", "步数 (取整)", "STEP", "步数"),
|
||||||
|
(707, "StepsToGo", "Steps to Go", "剩余步数", "TO GO", "还差"),
|
||||||
|
(2, "Calories", "Calories", "卡路里", "KCAL", "千卡"),
|
||||||
|
(603, "ActiveCal", "Active Calories", "活动卡路里", "ACAL", "活动"),
|
||||||
|
(150, "ActiveMin", "Active Minutes", "活动分钟", "MIN", "分钟"),
|
||||||
|
(152, "ActiveMod", "Active Min (Moderate)", "中强度分钟", "MOD", "中强度"),
|
||||||
|
(154, "ActiveVig", "Active Min (Vigorous)", "高强度分钟", "VIG", "高强度"),
|
||||||
|
(151, "ActiveWeek", "Active Minutes (Week)", "本周活动分钟", "WEEK", "周分钟"),
|
||||||
|
(3, "Distance", "Distance", "距离", "DST", "距离"),
|
||||||
|
(4, "FloorsUp", "Floors Climbed", "上楼层数", "FLR", "楼层"),
|
||||||
|
(5, "FloorsDown", "Floors Descended", "下楼层数", "DESC", "下楼"),
|
||||||
|
(6, "MetersUp", "Distance Climbed", "爬升高度", "ASC", "爬升"),
|
||||||
|
(9, "PushDist", "Push Distance", "推行距离", "PUSH", "推行"),
|
||||||
|
(10, "Pushes", "Pushes", "推行次数", "PUSH", "推行"),
|
||||||
|
|
||||||
|
# ---- body -----------------------------------------------------------
|
||||||
|
(618, "HeartRate", "Heart Rate", "心率", "HR", "心率"),
|
||||||
|
(276, "RestingHr", "Resting Heart Rate", "静息心率", "RHR", "静息"),
|
||||||
|
(12, "Stress", "Stress Score", "压力指数", "STRS", "压力"),
|
||||||
|
(635, "PulseOx", "Pulse Ox", "血氧", "SPO2", "血氧"),
|
||||||
|
(11, "Respiration", "Respiration Rate", "呼吸频率", "RESP", "呼吸"),
|
||||||
|
(623, "BodyBattery", "Body Battery", "身体电量", "BODY", "体能"),
|
||||||
|
(8, "MoveBar", "Move Bar", "活动条", "MOVE", "活动"),
|
||||||
|
(15, "MoveBarRev", "Move Bar Reversed", "活动条 (反向)", "MOVE", "活动"),
|
||||||
|
(281, "Weight", "Weight", "体重", "WT", "体重"),
|
||||||
|
(742, "Bmi", "BMI", "BMI", "BMI", "BMI"),
|
||||||
|
(13, "Recovery", "Time to Recovery", "恢复时间", "RCVR", "恢复"),
|
||||||
|
(278, "Vo2Run", "VO2 Max (Run)", "最大摄氧量 (跑)", "VO2", "摄氧"),
|
||||||
|
(277, "Vo2Bike", "VO2 Max (Bike)", "最大摄氧量 (骑)", "VO2", "摄氧"),
|
||||||
|
|
||||||
|
# ---- system ---------------------------------------------------------
|
||||||
|
(250, "Battery", "Battery", "电量", "BATT", "电量"),
|
||||||
|
(251, "BatteryDays", "Battery in Days", "剩余天数", "DAYS", "天数"),
|
||||||
|
(257, "BatteryBoth", "Battery / Days", "电量 / 天数", "BATT", "电量"),
|
||||||
|
(253, "Alarms", "Alarms", "闹钟", "ALRM", "闹钟"),
|
||||||
|
(255, "Notifications", "Notifications", "通知", "MSG", "通知"),
|
||||||
|
(254, "DoNotDisturb", "Do Not Disturb", "勿扰模式", "DND", "勿扰"),
|
||||||
|
(256, "Bluetooth", "Bluetooth", "蓝牙", "BT", "蓝牙"),
|
||||||
|
(252, "Solar", "Solar Intensity", "太阳能强度", "SOLR", "太阳能"),
|
||||||
|
|
||||||
|
# ---- environment ----------------------------------------------------
|
||||||
|
(615, "Altitude", "Altitude", "海拔", "ALT", "海拔"),
|
||||||
|
(616, "SeaPressure", "Sea Level Pressure", "海平面气压", "hPa", "气压"),
|
||||||
|
(733, "AmbPressure", "Ambient Pressure", "环境气压", "hPa", "气压"),
|
||||||
|
(712, "Sunrise", "Sunrise", "日出", "RISE", "日出"),
|
||||||
|
(717, "Sunset", "Sunset", "日落", "SET", "日落"),
|
||||||
|
(700, "NextSun", "Next Sun Event", "下一太阳事件", "SUN", "太阳"),
|
||||||
|
(731, "UntilSun", "Time Until Sun Event", "距太阳事件", "SUN", "太阳"),
|
||||||
|
(706, "MoonAge", "Moon Age", "月龄", "MOON", "月龄"),
|
||||||
|
(732, "MoonLit", "Moon Illumination", "月球照明", "MOON", "月相"),
|
||||||
|
|
||||||
|
# ---- weather --------------------------------------------------------
|
||||||
|
(306, "Temp", "Temperature", "气温", "TEMP", "气温"),
|
||||||
|
(639, "TempHiLo", "Temperature High/Low", "最高/最低气温", "HI/LO", "高低温"),
|
||||||
|
(300, "FeelsLike", "Feels Like", "体感温度", "FEEL", "体感"),
|
||||||
|
(301, "TempHigh", "High Temperature", "最高气温", "HIGH", "最高"),
|
||||||
|
(302, "TempLow", "Low Temperature", "最低气温", "LOW", "最低"),
|
||||||
|
(305, "Humidity", "Humidity", "湿度", "HUM", "湿度"),
|
||||||
|
(304, "Precip", "Precipitation %", "降水概率", "RAIN", "降水"),
|
||||||
|
(308, "WindSpeed", "Wind Speed", "风速", "WIND", "风速"),
|
||||||
|
(307, "WindBearing", "Wind Bearing", "风向角", "WIND", "风向"),
|
||||||
|
(310, "WindDir", "Wind Direction", "风向", "WIND", "风向"),
|
||||||
|
(309, "WeatherTime", "Weather Update Time", "天气更新时间", "WX", "更新"),
|
||||||
|
|
||||||
|
# ---- custom ---------------------------------------------------------
|
||||||
|
(703, "Custom1", "Custom Text 1", "自定义文字 1", "", ""),
|
||||||
|
(704, "Custom2", "Custom Text 2", "自定义文字 2", "", ""),
|
||||||
|
(705, "Custom3", "Custom Text 3", "自定义文字 3", "", ""),
|
||||||
|
]
|
||||||
|
|
||||||
|
SLOTS = [("LeftTop", "Top-Left Field", "左上数据"),
|
||||||
|
("RightTop", "Top-Right Field", "右上数据"),
|
||||||
|
("BottomLeft", "Bottom-Left Field", "左下数据"),
|
||||||
|
("BottomRight", "Bottom-Right Field", "右下数据")]
|
||||||
|
|
||||||
|
RING = [(0, "Steps", "Steps", "步数"), (1, "Calories", "Calories", "卡路里"),
|
||||||
|
(2, "Distance", "Distance", "距离"), (3, "Floors", "Floors", "楼层"),
|
||||||
|
(4, "MoveBar", "Move Bar", "动动条"), (5, "ActiveMin", "Active Minutes", "活动分钟"),
|
||||||
|
(6, "BodyBattery", "Body Battery", "身体电量")]
|
||||||
|
|
||||||
|
OTHER_SETTINGS = """
|
||||||
|
<setting propertyKey="@Properties.TempUnit" title="@Strings.SettingTempUnit">
|
||||||
|
<settingConfig type="list">
|
||||||
|
<listEntry value="0">@Strings.TempUnitAuto</listEntry>
|
||||||
|
<listEntry value="1">@Strings.TempUnitC</listEntry>
|
||||||
|
<listEntry value="2">@Strings.TempUnitF</listEntry>
|
||||||
|
</settingConfig>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.AltOffset1" title="@Strings.SettingAltOffset1">
|
||||||
|
<settingConfig type="numeric" min="-720" max="840"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.AltOffset2" title="@Strings.SettingAltOffset2">
|
||||||
|
<settingConfig type="numeric" min="-720" max="840"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.Custom1" title="@Strings.SettingCustom1">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="12"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.Custom2" title="@Strings.SettingCustom2">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="12"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.Custom3" title="@Strings.SettingCustom3">
|
||||||
|
<settingConfig type="alphaNumeric" maxLength="12"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.ShowTop" title="@Strings.SettingShowTop">
|
||||||
|
<settingConfig type="boolean"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.ShowBand" title="@Strings.SettingShowBand">
|
||||||
|
<settingConfig type="boolean"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
|
<setting propertyKey="@Properties.ShowBottom" title="@Strings.SettingShowBottom">
|
||||||
|
<settingConfig type="boolean"/>
|
||||||
|
</setting>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def field_list(indent=8):
|
||||||
|
pad = " " * indent
|
||||||
|
return "\n".join('%s<listEntry value="%d">@Strings.Fld%s</listEntry>'
|
||||||
|
% (pad, fid, key) for fid, key, _, _, _, _ in FIELDS)
|
||||||
|
|
||||||
|
|
||||||
|
def write(path, text):
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
open(path, "w").write(text)
|
||||||
|
print("wrote", os.path.relpath(path, ROOT))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ids = [f[0] for f in FIELDS]
|
||||||
|
assert len(ids) == len(set(ids)), "duplicate field id"
|
||||||
|
|
||||||
|
# ---- FieldTable.mc ----
|
||||||
|
L = ["// AUTO-GENERATED by tools/gen_fields.py -- do not edit by hand.",
|
||||||
|
"// Ids follow https://watchface.io/docs/datafields; only the fields a",
|
||||||
|
"// Connect IQ watch face can source on-device are listed.",
|
||||||
|
"import Toybox.Lang;", "", "module FieldTable {", "",
|
||||||
|
" // Field ids, in the same order as the Labels resource string.",
|
||||||
|
" const IDS = ["]
|
||||||
|
row = " "
|
||||||
|
for i, fid in enumerate(ids):
|
||||||
|
piece = "%d%s" % (fid, "," if i < len(ids) - 1 else "")
|
||||||
|
if len(row) + len(piece) > 92:
|
||||||
|
L.append(row)
|
||||||
|
row = " "
|
||||||
|
row += piece
|
||||||
|
L += [row, " ];", "",
|
||||||
|
" function indexOf(id as Number) as Number {",
|
||||||
|
" for (var i = 0; i < IDS.size(); i++) {",
|
||||||
|
" if (IDS[i] == id) { return i; }",
|
||||||
|
" }",
|
||||||
|
" return 0;",
|
||||||
|
" }",
|
||||||
|
"}"]
|
||||||
|
write(os.path.join(ROOT, "source", "FieldTable.mc"), "\n".join(L) + "\n")
|
||||||
|
|
||||||
|
# ---- settings.xml ----
|
||||||
|
S = ['<settings>', '',
|
||||||
|
' <setting propertyKey="@Properties.Theme" title="@Strings.SettingTheme">',
|
||||||
|
' <settingConfig type="list">']
|
||||||
|
names = ["Ember", "Aurora", "Brass", "Voltage", "Reef", "Acid", "Kelp"]
|
||||||
|
for i, n in enumerate(names):
|
||||||
|
S.append(' <listEntry value="%d">@Strings.Theme%s</listEntry>' % (i + 1, n))
|
||||||
|
S += [' </settingConfig>', ' </setting>', '']
|
||||||
|
for slot, _, _ in SLOTS:
|
||||||
|
S += [' <setting propertyKey="@Properties.%s" title="@Strings.Setting%s">' % (slot, slot),
|
||||||
|
' <settingConfig type="list">', field_list(12),
|
||||||
|
' </settingConfig>', ' </setting>', '']
|
||||||
|
for ring in ("RingTR", "RingTL", "RingBR", "RingBL"):
|
||||||
|
S += [' <setting propertyKey="@Properties.%s" title="@Strings.Setting%s">' % (ring, ring),
|
||||||
|
' <settingConfig type="list">']
|
||||||
|
for rid, key, _, _ in RING:
|
||||||
|
S.append(' <listEntry value="%d">@Strings.Metric%s</listEntry>' % (rid, key))
|
||||||
|
S += [' </settingConfig>', ' </setting>', '']
|
||||||
|
S.append(OTHER_SETTINGS.rstrip())
|
||||||
|
S.append('</settings>')
|
||||||
|
write(os.path.join(ROOT, "resources", "settings", "settings.xml"), "\n".join(S) + "\n")
|
||||||
|
|
||||||
|
# ---- field strings, both languages ----
|
||||||
|
for lang, folder, ni, li in (("en", "resources", 2, 4), ("chn", "resources-chn", 3, 5)):
|
||||||
|
X = ['<strings>']
|
||||||
|
X.append(' <!-- Field names shown in the settings picker. -->')
|
||||||
|
for f in FIELDS:
|
||||||
|
X.append(' <string id="Fld%s">%s</string>' % (f[1], f[ni]))
|
||||||
|
X.append('')
|
||||||
|
X.append(' <!-- On-face captions, in FieldTable.IDS order. -->')
|
||||||
|
X.append(' <string id="FieldLabels">%s</string>'
|
||||||
|
% ",".join(f[li] for f in FIELDS))
|
||||||
|
X.append('')
|
||||||
|
for rid, key, en, cn in RING:
|
||||||
|
X.append(' <string id="Metric%s">%s</string>' % (key, en if lang == "en" else cn))
|
||||||
|
X.append('</strings>')
|
||||||
|
write(os.path.join(ROOT, folder, "strings", "fields.xml"), "\n".join(X) + "\n")
|
||||||
|
|
||||||
|
print("%d fields, %d slots" % (len(FIELDS), len(SLOTS)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
199
tools/gen_icons.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Rasterise the design's icon paths into tintable bitmap resources.
|
||||||
|
|
||||||
|
The sunrise and weather icons in design/*.svg are traced outlines whose finest
|
||||||
|
features are two or three design pixels across. Redrawing them from circles
|
||||||
|
and lines works at 454 px and falls apart at 280 px, where those features land
|
||||||
|
under one device pixel. Rendering the real paths once per screen size and
|
||||||
|
blitting them with drawBitmap2(:tintColor) keeps them exact everywhere.
|
||||||
|
|
||||||
|
python3 tools/gen_icons.py
|
||||||
|
|
||||||
|
Writes resources-icons-<W>/drawables/{sunrise,sunset,weather}.png plus the
|
||||||
|
matching drawables.xml. White pixels, alpha coverage; the theme's accent
|
||||||
|
colour is applied at draw time via :tintColor.
|
||||||
|
|
||||||
|
MIP devices have no alpha blending, so for those widths the coverage mask is
|
||||||
|
thresholded to hard edges rather than left antialiased -- a soft edge there
|
||||||
|
would be quantised to something muddier than a clean one.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.dirname(HERE)
|
||||||
|
SRC = os.path.join(ROOT, "design", "watchface-01-ember.svg")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
except ImportError:
|
||||||
|
raise SystemExit("this generator needs Pillow: python3 -m pip install pillow")
|
||||||
|
|
||||||
|
# The three icons the comps actually contain, and the design-space centre each
|
||||||
|
# one is drawn around. A common 32x32 design box keeps blitting trivial.
|
||||||
|
BOX = 32.0
|
||||||
|
ICONS = {
|
||||||
|
"sunrise": ("sunrise-icon", 156.32, 89.5, False),
|
||||||
|
"sunset": ("sunrise-icon", 156.32, 89.5, True), # same art, chevron flipped
|
||||||
|
"weather": ("weather-icon", 343.55, 89.5, False),
|
||||||
|
}
|
||||||
|
|
||||||
|
# screen width -> whether the panel can blend alpha (MIP cannot)
|
||||||
|
WIDTHS = {240: False, 260: False, 280: False, 390: True, 416: True, 454: True}
|
||||||
|
|
||||||
|
SS = 8 # supersampling factor
|
||||||
|
|
||||||
|
|
||||||
|
def subpaths(svg, pid):
|
||||||
|
d = re.search(r'<path id="%s" d="([^"]+)"' % pid, svg).group(1)
|
||||||
|
out = []
|
||||||
|
for chunk in d.split("Z"):
|
||||||
|
pts = [(float(x), float(y))
|
||||||
|
for x, y in re.findall(r"(-?\d+\.\d+),(-?\d+\.\d+)", chunk)]
|
||||||
|
if len(pts) >= 3:
|
||||||
|
out.append(pts)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def flip_chevron(paths):
|
||||||
|
"""Mirror the horizon bar so its notch points up -- our sunset variant."""
|
||||||
|
out = []
|
||||||
|
for p in paths:
|
||||||
|
ys = [q[1] for q in p]
|
||||||
|
if min(ys) > 96.0: # the bar is the lowest subpath
|
||||||
|
mid = (min(ys) + max(ys)) / 2.0
|
||||||
|
p = [(x, 2 * mid - y) for x, y in p]
|
||||||
|
out.append(p)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def scanline_evenodd(paths, size, ox, oy, scale):
|
||||||
|
"""Even-odd fill at supersampled resolution, returned as a coverage mask."""
|
||||||
|
n = size * SS
|
||||||
|
cov = [bytearray(n) for _ in range(n)]
|
||||||
|
edges = []
|
||||||
|
for p in paths:
|
||||||
|
for i in range(len(p)):
|
||||||
|
x0, y0 = p[i]
|
||||||
|
x1, y1 = p[(i + 1) % len(p)]
|
||||||
|
X0 = (x0 - ox) * scale * SS
|
||||||
|
Y0 = (y0 - oy) * scale * SS
|
||||||
|
X1 = (x1 - ox) * scale * SS
|
||||||
|
Y1 = (y1 - oy) * scale * SS
|
||||||
|
if Y0 != Y1:
|
||||||
|
edges.append((Y0, Y1, X0, X1))
|
||||||
|
for row in range(n):
|
||||||
|
yc = row + 0.5
|
||||||
|
xs = []
|
||||||
|
for Y0, Y1, X0, X1 in edges:
|
||||||
|
if (Y0 <= yc < Y1) or (Y1 <= yc < Y0):
|
||||||
|
xs.append(X0 + (yc - Y0) * (X1 - X0) / (Y1 - Y0))
|
||||||
|
if not xs:
|
||||||
|
continue
|
||||||
|
xs.sort()
|
||||||
|
line = cov[row]
|
||||||
|
for i in range(0, len(xs) - 1, 2):
|
||||||
|
a = max(0, int(round(xs[i])))
|
||||||
|
b = min(n, int(round(xs[i + 1])))
|
||||||
|
for x in range(a, b):
|
||||||
|
line[x] = 255
|
||||||
|
return cov
|
||||||
|
|
||||||
|
|
||||||
|
def render(paths, size, cx, cy, blend):
|
||||||
|
scale = size / BOX
|
||||||
|
ox, oy = cx - BOX / 2, cy - BOX / 2
|
||||||
|
cov = scanline_evenodd(paths, size, ox, oy, scale)
|
||||||
|
n = size * SS
|
||||||
|
big = Image.frombytes("L", (n, n), b"".join(bytes(r) for r in cov))
|
||||||
|
small = big.resize((size, size), Image.LANCZOS)
|
||||||
|
if not blend:
|
||||||
|
small = small.point(lambda v: 255 if v >= 110 else 0)
|
||||||
|
white = Image.new("RGB", (size, size), (255, 255, 255))
|
||||||
|
img = white.convert("RGBA")
|
||||||
|
img.putalpha(small)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
# Launcher icon sizes the target devices ask for.
|
||||||
|
LAUNCHER = [40, 56, 60, 65, 70]
|
||||||
|
|
||||||
|
|
||||||
|
def launcher(size):
|
||||||
|
"""A miniature of the face: the gradient ring plus the date band."""
|
||||||
|
from PIL import ImageDraw
|
||||||
|
ss = 8
|
||||||
|
n = size * ss
|
||||||
|
img = Image.new("RGBA", (n, n), (0, 0, 0, 0))
|
||||||
|
d = ImageDraw.Draw(img)
|
||||||
|
d.ellipse([0, 0, n - 1, n - 1], fill=(0, 0, 0, 255))
|
||||||
|
svg = open(SRC).read()
|
||||||
|
top = re.findall(r'fill="(#[0-9A-Fa-f]{6})"',
|
||||||
|
re.search(r'<g id="ring-top-right">(.*?)</g>', svg, re.S).group(1))
|
||||||
|
bot = re.findall(r'fill="(#[0-9A-Fa-f]{6})"',
|
||||||
|
re.search(r'<g id="ring-bottom-left">(.*?)</g>', svg, re.S).group(1))
|
||||||
|
ro, ri = n * 0.495, n * 0.375
|
||||||
|
box_o = [n / 2 - ro, n / 2 - ro, n / 2 + ro, n / 2 + ro]
|
||||||
|
# sweep the whole rim, mirroring the comp's two ramps into four quadrants
|
||||||
|
for q, ramp in ((0, top), (1, top), (2, bot), (3, bot)):
|
||||||
|
for i, c in enumerate(ramp):
|
||||||
|
col = tuple(int(c[1 + 2 * j:3 + 2 * j], 16) for j in range(3)) + (255,)
|
||||||
|
span = 90.0 / len(ramp)
|
||||||
|
if q == 0:
|
||||||
|
a0 = -90 + i * span
|
||||||
|
elif q == 1:
|
||||||
|
a0 = -90 - (i + 1) * span
|
||||||
|
elif q == 2:
|
||||||
|
a0 = 90 - (i + 1) * span
|
||||||
|
else:
|
||||||
|
a0 = 90 + i * span
|
||||||
|
d.pieslice(box_o, a0, a0 + span, fill=col)
|
||||||
|
d.ellipse([n / 2 - ri, n / 2 - ri, n / 2 + ri, n / 2 + ri], fill=(0, 0, 0, 255))
|
||||||
|
band = tuple(int("FEAA00"[2 * j:2 * j + 2], 16) for j in range(3)) + (255,)
|
||||||
|
d.rectangle([0, n * 0.44, n, n * 0.60], fill=band)
|
||||||
|
mask = Image.new("L", (n, n), 0)
|
||||||
|
ImageDraw.Draw(mask).ellipse([0, 0, n - 1, n - 1], fill=255)
|
||||||
|
img.putalpha(mask)
|
||||||
|
return img.resize((size, size), Image.LANCZOS)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
svg = open(SRC).read()
|
||||||
|
for size in LAUNCHER:
|
||||||
|
outdir = os.path.join(ROOT, "resources-launcher-%d" % size, "drawables")
|
||||||
|
os.makedirs(outdir, exist_ok=True)
|
||||||
|
launcher(size).save(os.path.join(outdir, "launcher_icon.png"))
|
||||||
|
with open(os.path.join(outdir, "drawables.xml"), "w") as f:
|
||||||
|
f.write('<drawables>\n'
|
||||||
|
' <bitmap id="LauncherIcon" filename="launcher_icon.png"/>\n'
|
||||||
|
'</drawables>\n')
|
||||||
|
print("wrote %s (%dx%d)" % (outdir, size, size))
|
||||||
|
cache = {}
|
||||||
|
for width, blend in sorted(WIDTHS.items()):
|
||||||
|
size = int(round(BOX * width / 500.0))
|
||||||
|
outdir = os.path.join(ROOT, "resources-icons-%d" % width, "drawables")
|
||||||
|
os.makedirs(outdir, exist_ok=True)
|
||||||
|
names = []
|
||||||
|
for name, (pid, cx, cy, flip) in ICONS.items():
|
||||||
|
key = (pid, flip)
|
||||||
|
if key not in cache:
|
||||||
|
p = subpaths(svg, pid)
|
||||||
|
cache[key] = flip_chevron(p) if flip else p
|
||||||
|
img = render(cache[key], size, cx, cy, blend)
|
||||||
|
img.save(os.path.join(outdir, "%s.png" % name))
|
||||||
|
names.append(name)
|
||||||
|
with open(os.path.join(outdir, "drawables.xml"), "w") as f:
|
||||||
|
f.write("<drawables>\n")
|
||||||
|
for n in names:
|
||||||
|
# packingFormat="png" keeps the alpha channel instead of
|
||||||
|
# collapsing the image to a palette; drawBitmap2(:tintColor)
|
||||||
|
# refuses a palettised source.
|
||||||
|
f.write(' <bitmap id="Icon%s" filename="%s.png" '
|
||||||
|
'packingFormat="png"/>\n' % (n.capitalize(), n))
|
||||||
|
f.write("</drawables>\n")
|
||||||
|
print("wrote %s (%dx%d, %s edges)"
|
||||||
|
% (outdir, size, size, "soft" if blend else "hard"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||