6.7 KiB
6.7 KiB
Спецификация 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) в существующей зоне статуса,
окраска по зонам Танака (
ZoneCalculatorm10,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. Манифест
<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).