Update watch face: modular refactor + OWM/Solar/background + codegen tools
- Split render into FieldTable/Fields/Icons/Layout/Settings modules - Add Fenix8V3Background, Owm (weather), Solar modules - Add tools/ generators (gen_fields/gen_icons/gen_themes/gen_preview_svg) - Tune README/manifest/strings/settings
This commit is contained in:
@@ -1,58 +1,92 @@
|
||||
import Toybox.Lang;
|
||||
import Toybox.Application;
|
||||
|
||||
// Every stored preference in one place, with the defaults that reproduce the
|
||||
// design comps. Keys match resources/settings/properties.xml.
|
||||
// ============================================================================
|
||||
// 设置读取
|
||||
// ----------------------------------------------------------------------------
|
||||
// 所有存储的偏好设置集中在这里,默认值就是设计稿的那套配置。
|
||||
// 键名与 resources/settings/properties.xml 一一对应。
|
||||
//
|
||||
// Properties.getValue returns null for a key the watch has not stored yet --
|
||||
// a fresh install, or a setting added by an update -- so every read falls back
|
||||
// to the default rather than trusting the stored value.
|
||||
// 为什么要包一层而不是各处直接调 Properties.getValue:
|
||||
// Connect IQ 的 Properties.getValue 遇到「手表还没存过的键」时**不是返回 null,
|
||||
// 而是抛 Out of Bounds 异常**。每次版本更新新增设置项,在手表把新的
|
||||
// properties.xml 合并进去之前都会撞上这个坑,轻则某个字段读不到,重则整个表盘
|
||||
// 崩掉。所以全部读取都走本模块,异常一律降级成默认值。
|
||||
// ============================================================================
|
||||
module Settings {
|
||||
|
||||
// Field ids follow https://watchface.io/docs/datafields.
|
||||
const DEF_LEFT_TOP = 712; // sunrise
|
||||
const DEF_RIGHT_TOP = 639; // forecast high / low
|
||||
const DEF_BOTTOM_LEFT = 3; // distance
|
||||
const DEF_BOTTOM_RIGHT = 1; // steps
|
||||
// ---- 四个数据位的默认字段 ----
|
||||
// 字段编号沿用 https://watchface.io/docs/datafields,与 Fields.mc 中一致。
|
||||
// 这四个默认值合起来就是设计稿上的样子。
|
||||
const DEF_LEFT_TOP = 712; // 左上:日出
|
||||
const DEF_RIGHT_TOP = 639; // 右上:预报最高/最低气温
|
||||
const DEF_BOTTOM_LEFT = 3; // 左下:距离
|
||||
const DEF_BOTTOM_RIGHT = 1; // 右下:步数
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 底层读取:吞掉「键不存在」异常,返回 null 交给上层兜底。
|
||||
// 注意 catch 在这里是有效的(Properties 抛的是普通异常);而 Background
|
||||
// 模块的 Out of Bounds 属于 VM 级错误,catch 不住,那边只能用别的办法绕开,
|
||||
// 详见 Fenix8V3App.scheduleWeather()。
|
||||
// ------------------------------------------------------------------------
|
||||
function raw(key as String) {
|
||||
try {
|
||||
return Application.Properties.getValue(key);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 读数字型设置,取不到就用 def。
|
||||
function number(key as String, def as Number) as Number {
|
||||
var v = Application.Properties.getValue(key);
|
||||
var v = raw(key);
|
||||
if (v == null) { return def; }
|
||||
return v as Number;
|
||||
}
|
||||
|
||||
// 读布尔型设置,取不到就用 def。
|
||||
function bool(key as String, def as Boolean) as Boolean {
|
||||
var v = Application.Properties.getValue(key);
|
||||
var v = raw(key);
|
||||
if (v == null) { return def; }
|
||||
return v as Boolean;
|
||||
}
|
||||
|
||||
// 读字符串型设置,取不到返回空串(不是 null,省得每个调用点再判一次)。
|
||||
function text(key as String) as String {
|
||||
var v = Application.Properties.getValue(key);
|
||||
var v = raw(key);
|
||||
if (v == null) { return ""; }
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
// Theme index, clamped into range in case a stored value outlives a
|
||||
// release that removed a theme.
|
||||
// ------------------------------------------------------------------------
|
||||
// 主题序号(0 基)。做范围钳制是为了防止这种情况:用户存的是「主题 7」,
|
||||
// 之后某个版本删掉了一套主题,存量值就会越界 —— 直接崩数组下标。
|
||||
// ------------------------------------------------------------------------
|
||||
function themeIndex(count as Number) as Number {
|
||||
var i = number("Theme", 1) - 1;
|
||||
if (i < 0 || i >= count) { return 0; }
|
||||
return i;
|
||||
}
|
||||
|
||||
// ---- 四个数据位选中的字段编号 ----
|
||||
function leftTop() as Number { return number("LeftTop", DEF_LEFT_TOP); }
|
||||
function rightTop() as Number { return number("RightTop", DEF_RIGHT_TOP); }
|
||||
function bottomLeft() as Number { return number("BottomLeft", DEF_BOTTOM_LEFT); }
|
||||
function bottomRight() as Number { return number("BottomRight", DEF_BOTTOM_RIGHT); }
|
||||
|
||||
function ringTR() as Number { return number("RingTR", 0); }
|
||||
function ringTL() as Number { return number("RingTL", 1); }
|
||||
function ringBR() as Number { return number("RingBR", 4); }
|
||||
function ringBL() as Number { return number("RingBL", 3); }
|
||||
// ---- 进度环四段各自绑定的指标 ----
|
||||
// 注意这里的编号是进度环自己的一张短表(0 步数 / 1 卡路里 / 2 距离 /
|
||||
// 3 楼层 / 4 动动条 / 5 活动分钟 / 6 身体电量),与上面数据位的字段编号
|
||||
// 是两套体系,不要混用。取值逻辑在 Fenix8V3View.ringProgress()。
|
||||
function ringTR() as Number { return number("RingTR", 0); } // 右上:步数
|
||||
function ringTL() as Number { return number("RingTL", 1); } // 左上:卡路里
|
||||
function ringBR() as Number { return number("RingBR", 4); } // 右下:动动条
|
||||
function ringBL() as Number { return number("RingBL", 3); } // 左下:楼层
|
||||
|
||||
// 温度单位:0 跟随手表 / 1 强制摄氏 / 2 强制华氏。
|
||||
function tempUnit() as Number { return number("TempUnit", 0); }
|
||||
|
||||
// ---- 三个显隐开关 ----
|
||||
function showTop() as Boolean { return bool("ShowTop", true); }
|
||||
function showBand() as Boolean { return bool("ShowBand", true); }
|
||||
function showBottom() as Boolean { return bool("ShowBottom", true); }
|
||||
|
||||
Reference in New Issue
Block a user