# Спецификация 03: экран поля — макет и отрисовка Источники: module_reqs разд. 1 (единица 5) и 2, Ф-2.1/Ф-2.2 (с errata), ui-ux разд. 2 (glanceable, цвет дублируется текстом), tech-req 6 п. 1. Единица 5. ## 1. Макет (resources\layouts\layout.xml) Параметрический макет семейства квадратных MIP-экранов — якоря (`loc="center"` и верх/низ), без единой фиксированной координаты: позиции и шрифты вычисляются от `dc.getWidth()/getHeight()` в `onLayout`. Один макет на всё семейство (fenix6x 280×280, fenix6 260×260, fenix6s 240×240 — добавление таргета без правок, инвариант 5). Элементы (сверху вниз): | Элемент | Содержимое | Шрифт | |---|---|---| | `zoneLbl` | подпись зоны + цветная плашка-ободок | FONT_TINY | | `hrLbl` | текущий пульс или «—» | FONT_NUMBER_LARGE (запасной FONT_NUMBER_MEDIUM — §3) | | `unitLbl` | «уд/мин» | FONT_TINY | | `stateLbl` | «ОТДЫХ mm:ss» / «ПОДХОД mm:сс · ср N» | FONT_SMALL | ## 2. Содержимое и цвета - фон — чёрный (`COLOR_BLACK`), текст — белый; MIP, читаемость на солнце без подсветки; - **зона**: плашка/ободок цвета зоны (`COLOR_BLUE/GREEN/YELLOW/RED` по ZoneCalc) вокруг подписи зоны; подпись текстом — цвет не единственный носитель (ui-ux): «восстановление»/«лёгкая»/ «средняя»/«высокая»; нет пульса → NONE: серая плашка (`COLOR_DK_GRAY`), подпись «—»; - **пульс**: крупное число `currentHeartRate`; null → «—» (unitLbl гасится); - **строка состояния**: - идёт отдых (`RestTimer.isResting`) → «ОТДЫХ 1:48» — обратный отсчёт остатка (mm:сс, при остатке ≥ 10 мин «мм:сс» с ведущими разрядами не рвётся — формат `m:ss`/`mm:ss`); - иначе → «ПОДХОД 2:05 · ср 132»: `SetTracker.elapsedMs` (время с последнего Lap — errata Ф-2.1) и `SetTracker.avgBpm`; среднее null → «ср —»; - форматирование времени — чистая функция `mmss(ms)` (округление вниз до секунд; часы не нужны — интервалы < 10 мин). ## 3. Шрифт пульса (адаптивный) `onLayout`: берётся `FONT_NUMBER_LARGE`; если высота глифов + прочих элементов не влезает в высоту области (мелкие страницы данных, 2+ поля на странице) — шаг вниз до `FONT_NUMBER_MEDIUM`. Пульс — главное число экрана (Ф-2.1: шрифт пульса максимальный из доступных на области). ## 4. Класс `FenixDataField` (связка) - `initialize()`: создание ядра (SetTracker/RestTimer/ZoneCalc/ Vibe/FieldSettings), начальное состояние — «нет данных» (переменные инициализируются здесь, не в `compute` — асинхронность compute/onUpdate по документации); - `compute(info)`: раз в секунду — `currentHeartRate` → `SetTracker.onSample`, кэш пульса; `RestTimer.onEnd(timerTime)` → лёгкая вибрация; `Vibe.onZone(zone, timerTime)` → двойная при красной (гистерезис); - `onTimerLap()`: `SetTracker.onLap` + `RestTimer.start` — Lap = конец подхода = старт отдыха (errata Ф-2.3); - `onTimerStart/Reset`: `SetTracker` к нулю, `RestTimer` не активен; - `onUpdate(dc)`: отрисовка по кэшу (§2); данные могли ещё не прийти — рисуем прочерки; - `onTimerPause/Resume`: без кода — `timerTime` стоит/идёт, таймеры замирают сами (спека 02 §3). ## 5. Симуляторный смок (критерий приёмки 2, шаг 3) 1. Поле добавляется в профиль Strength симулятора, отображается без ошибок отрисовки; 2. симуляция пульса: число обновляется ≈1/с, плашка зоны и подпись соответствуют порогам (прогнать пульс через все 4 зоны); 3. Lap (симулятор): метрики обнуляются, строка «ОТДЫХ …» тикает вниз, по нулю — однократная вибрация (лог симулятора); 4. пульс в красной зоне — двойная вибрация, повтор не чаще 60 с; 5. пауза активности — отсчёты стоят; resume — продолжаются; 6. нулевой/отсутствующий пульс — «—», поле живое (не падает).