// Поле данных «Феникс» для профиля «Силовая» (Ф-2, m13). // Экран: текущий пульс (крупно) + плашка зоны цветом с подписью, строка // «ПОДХОД мм:сс - ср N» / «ОТДЫХ мм:сс». Lap — граница подхода и старт // автономного отдыха (errata Ф-2.3/Ф-2.4). Кнопки не перехватываются, // запись .fit ведёт платформа Garmin (Ф-2.5–Ф-2.7). import Toybox.Activity; import Toybox.Graphics; import Toybox.WatchUi; class FenixDataField extends WatchUi.DataField { var _settings; var _zones; var _set; var _rest; var _vibe; var _fmt; var _hr; // кэш пульса (Number или null) var _maxBpm10; // макс. ЧСС ×10 var _zone; // текущая зона (идентификатор ZoneCalc) function initialize() { DataField.initialize(); _settings = new FieldSettings(); _zones = new ZoneCalc(); _set = new SetTracker(); _rest = new RestTimer(); _vibe = new Vibe(); _fmt = new Format(); _hr = null; _maxBpm10 = _zones.maxBpm10For(_settings.hrMaxBpm, _settings.age); _zone = _zones.ZONE_NONE; } function timerNow() { var info = Activity.getActivityInfo(); if (info != null && info.timerTime != null) { return info.timerTime; } return 0; } // Lap (штатно — конец подхода): метрики обнуляются, отдых стартует function onTimerLap() { var t = timerNow(); _set.onLap(t); _rest.start(t, _settings.restSec); } function onTimerStart() { resetState(); } function onTimerReset() { resetState(); } function resetState() { _set.onLap(0); _rest.initialize(); } // Раз в секунду: пульс → метрики, зоны, сигналы вибраций function compute(info) { _hr = info.currentHeartRate; var t = info.timerTime; if (t == null) { return; } _set.onSample(_hr); _zone = _zones.zoneFor(_hr, _maxBpm10); if (_rest.onEnd(t)) { _vibe.vibrateRestEnd(); } _vibe.onZone(_zone == _zones.ZONE_RED, t); } function colorFor(zoneId) { if (zoneId == _zones.ZONE_BLUE) { return Graphics.COLOR_BLUE; } if (zoneId == _zones.ZONE_GREEN) { return Graphics.COLOR_GREEN; } if (zoneId == _zones.ZONE_YELLOW) { return Graphics.COLOR_YELLOW; } if (zoneId == _zones.ZONE_RED) { return Graphics.COLOR_RED; } return Graphics.COLOR_DK_GRAY; } function labelFor(zoneId) { if (zoneId == _zones.ZONE_BLUE) { return WatchUi.loadResource(Rez.Strings.ZoneBlue); } if (zoneId == _zones.ZONE_GREEN) { return WatchUi.loadResource(Rez.Strings.ZoneGreen); } if (zoneId == _zones.ZONE_YELLOW) { return WatchUi.loadResource(Rez.Strings.ZoneYellow); } if (zoneId == _zones.ZONE_RED) { return WatchUi.loadResource(Rez.Strings.ZoneRed); } return WatchUi.loadResource(Rez.Strings.ZoneNone); } // Параметрическая отрисовка по размерам области (dc): ни одной // фиксированной координаты — один код на всё семейство экранов (Ф-2.8) function onUpdate(dc) { var w = dc.getWidth(); var h = dc.getHeight(); dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK); dc.clear(); // 1) плашка зоны сверху: цвет зоны + подпись текстом (цвет — // не единственный носитель, ui-ux) var tiny = Graphics.FONT_TINY; var zoneLabel = labelFor(_zone); var tw = dc.getTextWidthInPixels(zoneLabel, tiny); var th = dc.getFontHeight(tiny); var pad = 4; var plateW = tw + pad * 2; var plateH = th + pad; var plateX = (w - plateW) / 2; var plateY = 2; if (_zone == _zones.ZONE_NONE) { dc.setColor(Graphics.COLOR_DK_GRAY, Graphics.COLOR_DK_GRAY); dc.fillRectangle(plateX, plateY, plateW, plateH); dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_DK_GRAY); } else { var zc = colorFor(_zone); dc.setColor(zc, zc); dc.fillRectangle(plateX, plateY, plateW, plateH); dc.setColor(Graphics.COLOR_BLACK, zc); } dc.drawText(plateX + pad, plateY + 1, tiny, zoneLabel, Graphics.TEXT_JUSTIFY_LEFT); // 2) пульс — крупное число (адаптивный шрифт: THAI_HOT, если не // влезает — MEDIUM; поле может делить страницу с другими полями) var bigFont = Graphics.FONT_NUMBER_THAI_HOT; var bigH = dc.getFontHeight(bigFont); if (bigH > h * 5 / 10) { bigFont = Graphics.FONT_NUMBER_MEDIUM; bigH = dc.getFontHeight(bigFont); } var hrText; if (_hr == null || _hr <= 0) { hrText = "-"; } else { hrText = "" + _hr; } dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK); var hw = dc.getTextWidthInPixels(hrText, bigFont); var hrY = plateY + plateH + (h - plateH - plateY - bigH - th - 8) / 2; if (hrY < plateY + plateH + 2) { hrY = plateY + plateH + 2; } dc.drawText((w - hw) / 2, hrY, bigFont, hrText, Graphics.TEXT_JUSTIFY_LEFT); // 3) подпись единиц под пульсом var unit = WatchUi.loadResource(Rez.Strings.Unit); var uw = dc.getTextWidthInPixels(unit, tiny); dc.drawText((w - uw) / 2, hrY + bigH + 1, tiny, unit, Graphics.TEXT_JUSTIFY_LEFT); // 4) строка состояния: ОТДЫХ (остаток) или ПОДХОД (время с границы // Lap - средний пульс интервала) var line; var t = timerNow(); var small = Graphics.FONT_SMALL; var lh = dc.getFontHeight(small); if (_rest.isResting(t)) { line = WatchUi.loadResource(Rez.Strings.Rest) + " " + _fmt.mmss(_rest.remainingMs(t)); } else { var avg = _set.avgBpm(); var avgText; if (avg == null) { avgText = "-"; } else { avgText = "" + avg; } line = WatchUi.loadResource(Rez.Strings.Set) + " " + _fmt.mmss(_set.elapsedMs(t)) + " - " + WatchUi.loadResource(Rez.Strings.Avg) + " " + avgText; } var lw = dc.getTextWidthInPixels(line, small); dc.drawText((w - lw) / 2, h - lh - 2, small, line, Graphics.TEXT_JUSTIFY_LEFT); } }