108 lines
6.7 KiB
Markdown
108 lines
6.7 KiB
Markdown
# Спецификация 04: UI — «Тренировка», «Допуск», «Настройки» (интеграция m10)
|
||
|
||
Источники: Ф-3.1–3.3, Ф-13.2, ui-ux разд. 2, module_reqs разд. 2,
|
||
решения 3/5/6. Единицы 4–7. Все тексты дублируют цвет (🛡).
|
||
|
||
## 1. Экран «Тренировка» (замена заглушек WorkoutScreen:380/258)
|
||
|
||
### Статусная строка (Ф-3.1)
|
||
|
||
- слева направо: «часы: <статус> (заряд N %)» · «пульс: <bpm|—>» ·
|
||
«сеть: онлайн/офлайн» (сеть — как в m10);
|
||
- тексты и цвета — по таблице спец 03 §1; зелёный/красный/серый
|
||
— только дублирование текстом;
|
||
- заряд опускается (с прочерком) при `battery == null` —
|
||
формулировка «часы: связь есть», без «заряд —» (решение 3).
|
||
|
||
### Блок пульса (Ф-3.2)
|
||
|
||
- текущее значение — крупно (≥40 sp) в существующей зоне статуса,
|
||
окраска по зонам Танака (`ZoneCalculator` m10, `Hr.MaxBpm`;
|
||
границы — те же, что на «Профиле»): зона 1 recovery <50 % синий,
|
||
зона 2 лёгкая 50–70 % зелёный, зона 3 средняя 70–85 % жёлтый,
|
||
зона 4 высокая >85 % красный — рядом с числом название зоны
|
||
словами, как на «Профиле» m10 («recovery»/«лёгкая»/«средняя»/
|
||
«высокая») 🛡;
|
||
- под значением — линия последнего подхода: Canvas по
|
||
`store.series(lastSetAtMs, now)`, обновление ~1 Гц, без сеток;
|
||
пусто/устарело — прочерк «—» вместо линии;
|
||
- `lastSetAtMs` — время последнего подхода текущей тренировки
|
||
(VM из `setsFlow`, при отсутствии подходов — старт тренировки);
|
||
- нет связи/не выбраны/нет разрешения — значение «—», блок
|
||
сворачивается в статус-строку; голос и ручной ввод не зависят
|
||
(Ф-1.1, Ф-3.2).
|
||
|
||
### Финиш
|
||
|
||
- «Завершить» → `finishWorkout(id, avgHr = channel.avgHr(now))`;
|
||
после записи `store.clear()`; при null среднего — как в m09
|
||
(`avg_hr` не отправляется).
|
||
|
||
## 2. Экран «Допуск» (Ф-13.2)
|
||
|
||
- под полем «Пульс сидя» — вторичная кнопка «Взять с часов»:
|
||
активна при `freshHr(now) != null` (свежий сэмпл ≤30 с),
|
||
нажатие подставляет значение в поле (пикером остаётся правка);
|
||
нет сэмпла — кнопка неактивна с подписью причины («часы не
|
||
связаны» / «нет свежего значения»);
|
||
- экран держит канал открытым (`bind(null)`), для допуска
|
||
достаточно пульса покоя — атлет включает трансляцию заранее
|
||
(инструкция в «Настройках»).
|
||
|
||
## 3. Экран «Настройки» — блок «Часы»
|
||
|
||
По образцу блока «Распознавание речи» (m11):
|
||
|
||
- строка статуса канала (текст из спец 03 §1);
|
||
- «Найти часы» — скан 15 с по UUID 0x180D (запрашивает разрешения
|
||
при необходимости), список найденных: имя + адрес, тап — выбор
|
||
(запись `Watch.HrDeviceAddress`, индикатор «выбрано»);
|
||
- «Забыть часы» — сброс адреса (`""` → NOT_SELECTED);
|
||
- инструкция включения трансляции — платформо-нейтральный текст:
|
||
«Включите на часах трансляцию пульса по Bluetooth» +
|
||
расширяемый справочник производителей (v1 — один пункт:
|
||
Garmin: «виджет пульса → удержание MENU → Heart Rate Options →
|
||
Broadcast During Activity = Вкл»); справочник — `map` в коде
|
||
UI (не БД), добавление производителя — правкой одного списка;
|
||
- никаких маркателей конкретных моделей в логике.
|
||
|
||
## 4. Разрешения (runtime)
|
||
|
||
- экраны «Тренировка»/«Допуск»/«Настройки»: при статусе
|
||
NO_PERMISSION — строка «Нет разрешения Bluetooth» + кнопка
|
||
«Разрешить» → системный диалог
|
||
(12+: `BLUETOOTH_SCAN`+`BLUETOOTH_CONNECT` вместе; 8–11:
|
||
`ACCESS_FINE_LOCATION` с пояснением «для поиска часов»);
|
||
«Больше не спрашивать» — кнопка ведёт в системные настройки
|
||
приложения (проверка после возврата);
|
||
- отказ не блокирует экраны (🛡 инвариант 2): статус-строка
|
||
показывает причину, остальное работает.
|
||
|
||
## 5. Манифест
|
||
|
||
```xml
|
||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||
android:usesPermissionFlags="neverForLocation" />
|
||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||
<uses-permission android:name="android.permission.BLUETOOTH"
|
||
android:maxSdkVersion="30" />
|
||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
|
||
android:maxSdkVersion="30" />
|
||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||
```
|
||
|
||
(`BLUETOOTH_CONNECT` — без `neverForLocation`, только SCAN.)
|
||
|
||
## 6. Тесты (pure JVM)
|
||
|
||
- форматирование статус-строки: все WatchStatus × батарея
|
||
есть/нет × пульс есть/устарел (тексты из спец 03 §1, заряд
|
||
опускается при null);
|
||
- зона пульса → слово-зона и цветовой токен (границы 50/70/85 %
|
||
от Hr.MaxBpm по ZoneCalculator);
|
||
- кнопка «Взять с часов»: active/inactive по freshHr, подстановка
|
||
значения;
|
||
- логика «Больше не спрашивать» — на уровне VM-флага (системный
|
||
диалог — ручная приёмка);
|
||
- Compose-иерархия — без unit (ручной смок владельца, критерий 3).
|