feat(components): ActionButton stopAlert + typography type fixes
This commit is contained in:
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.
|
||||
Reference in new issue
Block a user