feat(components): ActionButton stopAlert + typography type fixes

This commit is contained in:
fightrate-bot committed 2026-06-16 01:50:26 -03:00
1 parent 2c51fd2623
commit bbe2919c2c
89 files changed
+75993 -9961

No files matched your search

@@ -0,0 +1,147 @@
# FightRate Android MVP Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build the first Android-native FightRate app scaffold for live combat-sport heart-rate tracking with Magene H003 BLE support.
**Architecture:** Android-native Kotlin app using Jetpack Compose for UI, pure Kotlin domain/parse logic, Android BLE APIs isolated behind a BLE package, and local persistence planned with Room. The first implementation slice creates a buildable app shell plus a tested Heart Rate Measurement parser.
**Tech Stack:** Kotlin, Gradle Kotlin DSL, Android Gradle Plugin, Jetpack Compose Material 3, Coroutines/Flow, Android BLE APIs, JUnit.
---
## File Structure
```text
settings.gradle.kts Project module declaration
build.gradle.kts Root Gradle plugin versions
gradle.properties Android/Kotlin build flags
app/build.gradle.kts Android app dependencies and config
app/src/main/AndroidManifest.xml Permissions, activity, BLE feature flags
app/src/main/java/com/healthtracking/fightrate/MainActivity.kt
app/src/main/java/com/healthtracking/fightrate/ble/HeartRateParser.kt
app/src/main/java/com/healthtracking/fightrate/domain/model/FightSport.kt
app/src/main/java/com/healthtracking/fightrate/domain/model/HeartRateMeasurement.kt
app/src/main/java/com/healthtracking/fightrate/ui/theme/FightRateTheme.kt
app/src/main/java/com/healthtracking/fightrate/ui/theme/Color.kt
app/src/main/java/com/healthtracking/fightrate/ui/screens/HomeScreen.kt
app/src/test/java/com/healthtracking/fightrate/ble/HeartRateParserTest.kt
```
---
## Task 1: Android project scaffold
**Files:**
- Create: `settings.gradle.kts`
- Create: `build.gradle.kts`
- Create: `gradle.properties`
- Create: `app/build.gradle.kts`
- Create: `app/src/main/AndroidManifest.xml`
- Create: `app/src/main/java/com/healthtracking/fightrate/MainActivity.kt`
- [ ] **Step 1: Create Gradle project files**
Use Android app module `:app`, package `com.healthtracking.fightrate`, min SDK 26, target/compile SDK 35, Kotlin 2.x compatible Compose.
- [ ] **Step 2: Create manifest**
Declare Bluetooth permissions for Android 12+, legacy Bluetooth/location permissions for Android 11 and below, BLE feature, foreground service permissions, and `MainActivity`.
- [ ] **Step 3: Create minimal MainActivity**
Use `ComponentActivity`, `setContent`, `FightRateTheme`, and `HomeScreen`.
- [ ] **Step 4: Verify**
Run: `./gradlew :app:assembleDebug`
Expected: build succeeds when Gradle/Android SDK are installed.
---
## Task 2: Heart Rate Measurement parser, TDD first
**Files:**
- Create: `app/src/main/java/com/healthtracking/fightrate/ble/HeartRateParser.kt`
- Create: `app/src/main/java/com/healthtracking/fightrate/domain/model/HeartRateMeasurement.kt`
- Create: `app/src/test/java/com/healthtracking/fightrate/ble/HeartRateParserTest.kt`
- [ ] **Step 1: Write tests**
Cover these Bluetooth Heart Rate Measurement payloads:
```kotlin
byteArrayOf(0x00, 72) // uint8 BPM
byteArrayOf(0x01, 0x2C, 0x01) // uint16 BPM = 300
byteArrayOf(0x08, 90, 0x34, 0x12) // energy expended = 0x1234
byteArrayOf(0x10, 120, 0x00, 0x04) // RR interval = 1024 -> 1000ms
byteArrayOf() // invalid empty payload
```
- [ ] **Step 2: Implement parser**
Parse flags, BPM format, optional energy expended, optional RR intervals. Throw `IllegalArgumentException` for malformed payloads.
- [ ] **Step 3: Verify**
Run: `./gradlew :app:testDebugUnitTest`
Expected: all parser tests pass.
---
## Task 3: FightRate visual shell
**Files:**
- Create: `app/src/main/java/com/healthtracking/fightrate/ui/theme/Color.kt`
- Create: `app/src/main/java/com/healthtracking/fightrate/ui/theme/FightRateTheme.kt`
- Create: `app/src/main/java/com/healthtracking/fightrate/domain/model/FightSport.kt`
- Create: `app/src/main/java/com/healthtracking/fightrate/ui/screens/HomeScreen.kt`
- [ ] **Step 1: Define design tokens**
Use the FightRate palette from the design system: black canvas, charcoal surfaces, fight orange, redline red, recovery green, muted gray text.
- [ ] **Step 2: Build static HomeScreen**
Include static UI cards for sensor status, current BPM, round timer, zone bar, sport chips, and session metrics.
- [ ] **Step 3: Verify**
Run: `./gradlew :app:assembleDebug`
Expected: app compiles and opens to a dark FightRate dashboard.
---
## Task 4: BLE connection layer placeholder
**Files:**
- Create: `app/src/main/java/com/healthtracking/fightrate/ble/HeartRateBleConstants.kt`
- Create: `app/src/main/java/com/healthtracking/fightrate/ble/HeartRateBleScanner.kt`
- Create: `app/src/main/java/com/healthtracking/fightrate/ble/HeartRateBleClient.kt`
- [ ] **Step 1: Define UUID constants**
```kotlin
HEART_RATE_SERVICE = 0000180d-0000-1000-8000-00805f9b34fb
HEART_RATE_MEASUREMENT = 00002a37-0000-1000-8000-00805f9b34fb
BODY_SENSOR_LOCATION = 00002a38-0000-1000-8000-00805f9b34fb
BATTERY_SERVICE = 0000180f-0000-1000-8000-00805f9b34fb
BATTERY_LEVEL = 00002a19-0000-1000-8000-00805f9b34fb
```
- [ ] **Step 2: Scanner contract**
Expose a Kotlin API that can later be wired to `BluetoothLeScanner` and emits scan results.
- [ ] **Step 3: Client contract**
Expose connect/disconnect and `Flow<HeartRateMeasurement>` API.
---
## Current Execution Choice
The user asked to proceed with app creation, so start with **Inline Execution** for Tasks 1–3, stopping after verification evidence and reporting environment blockers if Android SDK/Gradle is unavailable.
@@ -0,0 +1,52 @@
# Implementation Plan: FightRate P1 Essential Features
## Overview
Implement the next FightRate feature batch as incremental, testable slices: workout sessions, round timer, configurable alerts, more robust BLE state, and sensor metadata (battery/body location). Keep PT-BR visible copy and English code identifiers.
## Architecture Decisions
- Keep local-first SQLite as source of truth.
- Add session data without replacing existing sample storage.
- Prefer pure domain logic for round timer and alerts so behavior is unit-testable.
- Keep BLE foreground service as owner of the device connection.
- Expose sensor metadata through existing service broadcasts to avoid direct BLE ownership in the ViewModel.
## Task List
### Phase 1: Domain foundation
- [ ] Add `WorkoutSession`, `WorkoutSessionStatus`, `RoundTimerConfig`, `RoundTimerSnapshot`, and calculators.
- [ ] Add unit tests for timer transitions and session summary calculations.
### Phase 2: Persistence
- [ ] Add `workout_sessions` table and nullable `session_id` on `heart_rate_samples`.
- [ ] Add safe migration from current database version.
- [ ] Add store methods to start/pause/resume/finish active sessions and insert samples with active session id.
### Phase 3: UI/ViewModel integration
- [ ] Add session and round timer state to `LiveHeartRateUiState`.
- [ ] Add ViewModel actions: start/pause/resume/finish session and update timer config.
- [ ] Add visible Compose controls in Live screen.
### Phase 4: Alerts
- [ ] Add persisted alert settings with safe defaults.
- [ ] Add configurable vibration/sound toggles and zone/limit thresholds.
- [ ] Trigger alerts from service/ViewModel without spamming.
### Phase 5: BLE hardening and metadata
- [ ] Add BLE status events for scan/connect/service/descriptor failures and unexpected disconnects.
- [ ] Add conservative reconnect/backoff in foreground service.
- [ ] Read battery level and body sensor location when available.
- [ ] Show metadata in Sensor screen.
## Review/Verification
- [ ] Run unit tests after domain/persistence slices.
- [ ] Run `./gradlew :app:testDebugUnitTest :app:assembleDebug` before final response.
- [ ] Run code review and front/UI review via fresh-context reviewers.
- [ ] Reconcile findings and fix required issues.
## Risks
| Risk | Impact | Mitigation |
|---|---|---|
| Database migration loses samples | High | Add additive migration, no DROP for current versions |
| BLE changes break screen-off tracking | High | Keep service ownership; add metadata/status only |
| UI becomes cluttered | Medium | Compact cards and PT-BR labels |
| Alert spam | Medium | Throttle alerts and use safe defaults |
@@ -0,0 +1,207 @@
# FightRate — Plano Data-First para histórico, gráficos e UI profissional
## Situação crítica
O feedback atual mostra dois bugs de produto, não apenas problema visual:
1. O gráfico tem espaço vazio demais e exige rolar horizontalmente por muito tempo até encontrar leituras.
2. O histórico não está confiável: dados parecem não salvar ou não ficam acessíveis depois.
Conclusão: dados e persistência devem voltar a ser prioridade absoluta antes de polimento visual.
## Objetivo
Transformar o FightRate em um app data-first:
- Cada batimento recebido deve ser persistido localmente.
- Cada treino deve ter uma janela temporal confiável.
- Histórico deve mostrar dados reais recuperáveis.
- Gráficos devem usar domínio baseado apenas nas amostras reais disponíveis.
- Não pode existir scroll infinito/área vazia até achar leitura.
- Interface deve priorizar dados e navegação histórica, seguindo o design system Combat Lab.
## Skills aplicadas
- `fightrate-quality-loop`: spec → auditoria → implementação em fatias → build.
- `systematic-debugging`: encontrar causa raiz antes de corrigir.
- `test-driven-development`: criar testes de regressão para domínio de gráfico/histórico.
- `design-system` e `ui-ux-pro-max`: estado vazio útil, chart/data UX, consistência visual.
## Hipóteses a investigar
### H1 — Domínio X do Vico está incluindo janela cheia, não amostras reais
Sintoma: usuário rola para a direita “para sempre” até encontrar leituras.
Possível causa:
- O gráfico usa `windowStartMillis`/`windowEndMillis` grandes, mas as amostras estão concentradas em pequeno trecho.
- O X é calculado relativo à janela selecionada, então se a leitura está no fim da janela, sobra um enorme vazio à esquerda/direita.
- O scroll/zoom inicial pode estar mostrando início/fim errado.
Correção esperada:
- Domínio padrão do gráfico deve ser `min(sample.timestamp)` → `max(sample.timestamp)`.
- A janela selecionada filtra dados, mas não força o domínio visual a incluir todo o período vazio.
- Exibir texto com a janela consultada e o intervalo real encontrado.
### H2 — Histórico busca janela errada ou estado não atualiza após persistência
Sintoma: dados históricos não aparecem/salvam.
Possíveis causas:
- `publishLiveSample` salva, mas histórico não recarrega após cada nova amostra.
- `selectedPeriod` usa uma janela curta ou errada.
- Sessão finalizada tem samples com `sessionId` nulo.
- `finishCurrentSession` não associa amostras sem sessão dentro da janela.
- UI exibe `historySamples`, mas dados reais ficam apenas em `visibleSamples`/`dailySamples`.
Correções esperadas:
- Confirmar que `HeartRateSampleStore.save` recebe `sessionId` ativo.
- Confirmar que `samplesForSession` funciona depois do finish.
- Confirmar que `samplesBetween(start, end)` recupera dados persistidos.
- Atualizar estado de histórico depois de salvar amostra e depois de finalizar treino.
### H3 — UI não oferece navegação histórica suficiente
Sintoma: “os dados devem poder ser vistos de qualquer maneira”.
Correção esperada:
- Histórico com modos claros:
- Últimos 10 min
- Última hora
- Hoje
- Sessão atual/finalizada
- Se janela consultada não tem dados, mostrar “nenhum dado nessa janela” e sugerir janela que tem dados.
- Se existe sessão finalizada, exibir relatório e gráfico daquela sessão.
## Arquitetura desejada
### Domínio puro novo/revisado
Criar `ChartDataWindow.kt` se necessário.
Responsabilidades:
- Receber samples + janela consultada.
- Retornar:
- `visibleSamples`: somente amostras reais e úteis.
- `dataStartMillis` / `dataEndMillis`: domínio real do gráfico.
- `queryStartMillis` / `queryEndMillis`: janela consultada.
- `status`: Empty, SinglePoint, Ready.
- resumo: duração real, média, min, max, densidade.
- Não depender de Compose/Vico/Android.
### Persistência
Reutilizar `HeartRateSampleStore`.
Necessário auditar/corrigir:
- insert de samples;
- active session id;
- associação de sample com session id;
- queries por sessão;
- queries por período;
- refresh após write.
Não fazer migração destrutiva.
### ViewModel
- Não mover BLE para ViewModel.
- Garantir que o estado de histórico seja atualizado após persistência.
- Expor chart windows já preparados para UI.
- Evitar que UI calcule regras complexas.
### UI / Gráfico
- `HeartRateLineChart` deve receber domínio real de dados.
- X deve ser relativo a `dataStartMillis`, não ao começo da janela vazia.
- Se `samples.size < 2`, não chamar Vico.
- Mostrar estado vazio editorial.
- Mostrar uma linha de contexto:
- “Consultado: Hoje”
- “Dados encontrados: 14:03–14:17”
- Zoom/scroll habilitado, mas sem espaço fantasma.
- Não usar Canvas custom chart.
## Testes obrigatórios
### `ChartDataWindowTest`
1. Janela grande com dados no fim não gera domínio vazio gigante.
2. Sem dados retorna Empty.
3. Um ponto retorna SinglePoint.
4. Dados fora de ordem são ordenados.
5. Dados filtrados preservam timestamp real.
6. Downsampling mantém primeiro e último ponto.
### Store/persistência
Se for viável em teste local unitário:
1. Salvar sample e recuperar por período.
2. Salvar sample em sessão ativa e recuperar por session id.
3. Finalizar sessão e recuperar samples da sessão.
Se não for viável sem Robolectric/instrumented test, adicionar diagnóstico via código e validar pelo fluxo ViewModel/build.
## Implementação em fatias
### Fase 1 — Auditoria e causa raiz
- Ler `HeartRateSampleStore`.
- Ler `LiveHeartRateViewModel`.
- Ler `LiveHeartRateUiState`.
- Ler `HomeScreen` chart/history.
- Localizar onde sample é salvo.
- Localizar onde `historySamples` é atualizado.
- Localizar como Vico define X/domain.
### Fase 2 — Domínio de janela de gráfico
- Criar `ChartDataWindow.kt`.
- Criar `ChartDataWindowTest.kt`.
- Garantir domínio real `dataStartMillis`→`dataEndMillis`.
- Garantir estado Empty/SinglePoint/Ready.
### Fase 3 — Persistência e refresh
- Corrigir sample/session association se necessário.
- Corrigir reload de histórico depois de salvar/finalizar.
- Adicionar método de query se faltar.
### Fase 4 — Gráfico profissional
- Atualizar `HeartRateLineChart` para usar domínio real.
- Remover scroll fantasma.
- Adicionar zoom/scroll Vico com domínio real.
- Remover render de gráfico vazio.
- Mostrar resumo consultado vs encontrado.
### Fase 5 — Interface data-first
- Histórico como centro de dados, não card secundário.
- Sessões e dados recentes com hierarquia clara.
- Evitar dezenas de cards iguais.
- Estados vazios úteis e específicos.
### Fase 6 — Validação final
- `./gradlew :app:testDebugUnitTest :app:assembleDebug`
- Atualizar Obsidian roadmap.
- Enviar APK Telegram.
## Critérios de sucesso
- Usuário abre Histórico e vê dados reais sem procurar em espaço vazio.
- O gráfico começa/termina no intervalo real dos dados encontrados.
- Sem dados: nenhum gráfico é desenhado.
- Dados salvos são recuperáveis por período e por sessão.
- Testes de domínio passam.
- Build completo passa.
- APK enviado.