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,691 @@
|
||||
# Spec: FightRate P0 — Perfil Avançado e Insights do Lutador
|
||||
|
||||
## Objective
|
||||
|
||||
Criar a primeira versão do Perfil Avançado do Lutador para coletar dados pessoais, cardiovasculares, subjetivos e de recuperação suficientes para gerar insights úteis de treino sem deixar o app inchado.
|
||||
|
||||
O objetivo não é diagnóstico médico. O objetivo é apoio esportivo: entender carga, recuperação, hidratação, intensidade e evolução do atleta usando os batimentos reais do sensor BLE somados a dados simples informados pelo usuário.
|
||||
|
||||
Usuário principal: lutador ou praticante de treino de combate que usa o FightRate para acompanhar batimentos durante treinos, rounds e sessões.
|
||||
|
||||
## Scope P0
|
||||
|
||||
Implementar somente o núcleo inicial:
|
||||
|
||||
1. Perfil do Lutador.
|
||||
2. Check-in pré-treino.
|
||||
3. Registro pós-treino.
|
||||
4. Insights básicos do treino e recuperação.
|
||||
|
||||
Não implementar neste P0:
|
||||
|
||||
- exames médicos avançados;
|
||||
- integração com wearable externo além do sensor BLE atual;
|
||||
- Health Connect;
|
||||
- prescrição médica;
|
||||
- VO2max avançado;
|
||||
- IA generativa para recomendação clínica;
|
||||
- dashboard muito complexo.
|
||||
|
||||
## Assumptions
|
||||
|
||||
1. O app continua Android nativo Kotlin + Jetpack Compose.
|
||||
2. Textos visíveis continuam em PT-BR.
|
||||
3. Identificadores de código continuam em inglês.
|
||||
4. SQLite local-first continua como fonte de verdade.
|
||||
5. O sensor BLE continua sendo fonte dos batimentos em tempo real.
|
||||
6. Cálculos de saúde serão apresentados como estimativas esportivas, não diagnósticos.
|
||||
7. O P0 deve priorizar campos que geram cálculos imediatos.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Kotlin
|
||||
- Jetpack Compose Material3
|
||||
- SQLite via `SQLiteOpenHelper` existente em `HeartRateSampleStore`
|
||||
- Gradle Kotlin DSL
|
||||
- JUnit 4 para testes unitários
|
||||
- Vico para gráficos existentes
|
||||
- Lucide para ícones existentes
|
||||
|
||||
## Commands
|
||||
|
||||
Build completo:
|
||||
|
||||
```bash
|
||||
./gradlew :app:testDebugUnitTest :app:assembleDebug
|
||||
```
|
||||
|
||||
Teste unitário focado:
|
||||
|
||||
```bash
|
||||
./gradlew :app:testDebugUnitTest --tests "*AthleteInsights*"
|
||||
```
|
||||
|
||||
Compilação rápida:
|
||||
|
||||
```bash
|
||||
./gradlew :app:compileDebugKotlin
|
||||
```
|
||||
|
||||
Instalação debug:
|
||||
|
||||
```bash
|
||||
./gradlew :app:installDebug
|
||||
```
|
||||
|
||||
## Existing Code to Reuse
|
||||
|
||||
Reaproveitar antes de criar novo:
|
||||
|
||||
- `AthleteProfile.kt`
|
||||
- `HeartRateZones.kt`
|
||||
- `WorkoutSession.kt`
|
||||
- `HeartRateStats.kt`
|
||||
- `HeartRateSampleStore.kt`
|
||||
- `LiveHeartRateViewModel.kt`
|
||||
- `LiveHeartRateUiState.kt`
|
||||
- `HomeScreen.kt`
|
||||
- `FightCard`
|
||||
- `MetricTile`
|
||||
- `LiveMetricPill`
|
||||
- cards de Perfil existentes
|
||||
- SQLite `fightrate.db`
|
||||
|
||||
## Data Model — P0 Fields
|
||||
|
||||
### 1. Perfil básico
|
||||
|
||||
Campos:
|
||||
|
||||
- `age`
|
||||
- `biologicalSex`
|
||||
- `heightCm`
|
||||
- `weightKg`
|
||||
- `fightSport`
|
||||
- `weightClassKg`
|
||||
- `trainingGoal`
|
||||
|
||||
Uso:
|
||||
|
||||
- personalizar zonas;
|
||||
- calcular IMC;
|
||||
- acompanhar categoria de peso;
|
||||
- contextualizar insights.
|
||||
|
||||
### 2. Perfil cardiovascular
|
||||
|
||||
Campos:
|
||||
|
||||
- `restingHeartRate`
|
||||
- `measuredMaxHeartRate`
|
||||
|
||||
Uso:
|
||||
|
||||
- limite cardíaco personalizado;
|
||||
- zonas por percentual;
|
||||
- reserva cardíaca;
|
||||
- alertas de esforço.
|
||||
|
||||
### 3. Check-in pré-treino
|
||||
|
||||
Campos por sessão:
|
||||
|
||||
- `preWorkoutEnergy` escala 1–5
|
||||
- `preWorkoutFatigue` escala 1–5
|
||||
- `muscleSoreness` escala 1–5
|
||||
- `sleepHours`
|
||||
- `sleepQuality` escala 1–5
|
||||
- `preWorkoutWeightKg`
|
||||
- `waterIntakeMl`
|
||||
- `symptomsNote`
|
||||
|
||||
Uso:
|
||||
|
||||
- prontidão;
|
||||
- risco de fadiga;
|
||||
- contexto para batimentos altos;
|
||||
- hidratação estimada.
|
||||
|
||||
### 4. Pós-treino
|
||||
|
||||
Campos por sessão:
|
||||
|
||||
- `postWorkoutRpe` escala 1–10
|
||||
- `postWorkoutFatigue` escala 1–5
|
||||
- `postWorkoutSoreness` escala 1–5
|
||||
- `postWorkoutWeightKg`
|
||||
- `postWorkoutNote`
|
||||
|
||||
Uso:
|
||||
|
||||
- carga subjetiva;
|
||||
- perda hídrica;
|
||||
- recuperação;
|
||||
- comparação entre percepção e batimentos.
|
||||
|
||||
## Calculations — P0
|
||||
|
||||
### 1. IMC
|
||||
|
||||
Entrada:
|
||||
|
||||
- peso
|
||||
- altura
|
||||
|
||||
Resultado:
|
||||
|
||||
- `bmi`
|
||||
- faixa textual simples
|
||||
|
||||
### 2. Reserva cardíaca
|
||||
|
||||
Entrada:
|
||||
|
||||
- limite cardíaco real ou estimado
|
||||
- FC repouso
|
||||
|
||||
Resultado:
|
||||
|
||||
- `heartRateReserve`
|
||||
|
||||
### 3. Zonas por Karvonen
|
||||
|
||||
Entrada:
|
||||
|
||||
- limite cardíaco
|
||||
- FC repouso
|
||||
|
||||
Resultado:
|
||||
|
||||
- zonas mais personalizadas para o atleta.
|
||||
|
||||
### 4. Score de treino
|
||||
|
||||
Entrada:
|
||||
|
||||
- duração da sessão
|
||||
- BPM médio
|
||||
- BPM pico
|
||||
- tempo em zonas
|
||||
- RPE pós-treino
|
||||
|
||||
Resultado:
|
||||
|
||||
- score 0–100
|
||||
- rótulo: leve, moderado, intenso, extremo
|
||||
|
||||
### 5. Carga subjetiva
|
||||
|
||||
Entrada:
|
||||
|
||||
- RPE pós-treino
|
||||
- duração em minutos
|
||||
|
||||
Resultado:
|
||||
|
||||
- `sessionLoad = RPE × durationMinutes`
|
||||
|
||||
### 6. Score de recuperação
|
||||
|
||||
Entrada:
|
||||
|
||||
- sono
|
||||
- qualidade do sono
|
||||
- energia antes
|
||||
- fadiga antes
|
||||
- dor muscular
|
||||
- FC repouso opcional
|
||||
|
||||
Resultado:
|
||||
|
||||
- score 0–100
|
||||
- rótulo: baixa, ok, boa, excelente
|
||||
|
||||
### 7. Hidratação estimada
|
||||
|
||||
Entrada:
|
||||
|
||||
- peso antes
|
||||
- peso depois
|
||||
- água ingerida
|
||||
|
||||
Resultado:
|
||||
|
||||
- perda de peso no treino
|
||||
- perda hídrica estimada
|
||||
- alerta se perda relativa for alta
|
||||
|
||||
### 8. Tempo em zona de luta
|
||||
|
||||
Entrada:
|
||||
|
||||
- samples de BPM da sessão
|
||||
- zonas cardíacas
|
||||
|
||||
Resultado:
|
||||
|
||||
- tempo em zona alta/intensa
|
||||
- percentual do treino em zona de luta
|
||||
|
||||
### 9. Alertas de segurança esportiva
|
||||
|
||||
Entrada:
|
||||
|
||||
- sintomas
|
||||
- fadiga alta
|
||||
- sono ruim
|
||||
- dor alta
|
||||
- esforço cardíaco alto
|
||||
|
||||
Resultado:
|
||||
|
||||
- mensagens de atenção no app.
|
||||
|
||||
Exemplos:
|
||||
|
||||
- “Sono baixo + fadiga alta: considere treino técnico ou leve.”
|
||||
- “Perda de peso alta no treino: reponha líquidos.”
|
||||
- “Esforço muito alto para uma recuperação baixa.”
|
||||
|
||||
## UI/UX
|
||||
|
||||
### Tela Perfil
|
||||
|
||||
Adicionar seções recolhíveis ou cards:
|
||||
|
||||
1. Básico
|
||||
2. Cardiovascular
|
||||
3. Luta
|
||||
|
||||
Evitar formulário gigante em uma tela só.
|
||||
|
||||
### Tela Check-in
|
||||
|
||||
Fluxo simples antes de iniciar sessão:
|
||||
|
||||
- energia
|
||||
- fadiga
|
||||
- dor
|
||||
- sono
|
||||
- peso
|
||||
- água
|
||||
- sintomas
|
||||
|
||||
Pode ser card ou bottom sheet antes de iniciar treino.
|
||||
|
||||
### Tela Pós-treino
|
||||
|
||||
Após encerrar sessão:
|
||||
|
||||
- RPE
|
||||
- fadiga
|
||||
- dor
|
||||
- peso pós
|
||||
- observação
|
||||
|
||||
### Tela Insights do Lutador
|
||||
|
||||
Primeira versão com cards:
|
||||
|
||||
- Score do treino
|
||||
- Recuperação
|
||||
- Hidratação
|
||||
- Tempo em zona de luta
|
||||
- Carga subjetiva
|
||||
- Alertas
|
||||
|
||||
## Project Structure
|
||||
|
||||
Novos arquivos prováveis:
|
||||
|
||||
```text
|
||||
app/src/main/java/com/healthtracking/fightrate/domain/AthleteInsights.kt
|
||||
app/src/main/java/com/healthtracking/fightrate/domain/ReadinessScore.kt
|
||||
app/src/main/java/com/healthtracking/fightrate/domain/HydrationEstimate.kt
|
||||
app/src/test/java/com/healthtracking/fightrate/domain/AthleteInsightsTest.kt
|
||||
```
|
||||
|
||||
Arquivos existentes a alterar:
|
||||
|
||||
```text
|
||||
AthleteProfile.kt
|
||||
WorkoutSession.kt
|
||||
HeartRateSampleStore.kt
|
||||
LiveHeartRateUiState.kt
|
||||
LiveHeartRateViewModel.kt
|
||||
HomeScreen.kt
|
||||
```
|
||||
|
||||
A criação de novos arquivos deve ser mínima e justificada por responsabilidade clara.
|
||||
|
||||
## Code Style
|
||||
|
||||
Exemplo esperado:
|
||||
|
||||
```kotlin
|
||||
data class ReadinessInput(
|
||||
val sleepHours: Double?,
|
||||
val sleepQuality: Int?,
|
||||
val energy: Int?,
|
||||
val fatigue: Int?,
|
||||
val soreness: Int?,
|
||||
)
|
||||
|
||||
object ReadinessScoreCalculator {
|
||||
fun calculate(input: ReadinessInput): ReadinessScore {
|
||||
val score = 100
|
||||
.minus(input.fatigue.orDefaultPenalty(multiplier = 8))
|
||||
.minus(input.soreness.orDefaultPenalty(multiplier = 6))
|
||||
.plus(input.energy.orDefaultBonus(multiplier = 5))
|
||||
.coerceIn(0, 100)
|
||||
|
||||
return ReadinessScore(value = score)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Convenções:
|
||||
|
||||
- código em inglês;
|
||||
- UI em PT-BR;
|
||||
- cálculo puro no domínio;
|
||||
- Compose apenas renderiza estado;
|
||||
- evitar mágica sem teste.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Testar com JUnit:
|
||||
|
||||
- IMC
|
||||
- reserva cardíaca
|
||||
- Karvonen
|
||||
- score de recuperação
|
||||
- carga subjetiva
|
||||
- hidratação estimada
|
||||
- tempo em zona de luta
|
||||
- alertas esportivos
|
||||
|
||||
Comandos:
|
||||
|
||||
```bash
|
||||
./gradlew :app:testDebugUnitTest --tests "*AthleteInsights*"
|
||||
./gradlew :app:testDebugUnitTest :app:assembleDebug
|
||||
```
|
||||
|
||||
## Boundaries
|
||||
|
||||
### Always
|
||||
|
||||
- Criar cálculo no domínio antes da UI.
|
||||
- Usar testes para cálculo.
|
||||
- Manter campos opcionais quando possível.
|
||||
- Explicar que cálculos são estimativas esportivas.
|
||||
- Reaproveitar `AthleteProfile`, `WorkoutSession`, `HeartRateSampleStore`.
|
||||
- Salvar dados localmente.
|
||||
|
||||
### Ask first
|
||||
|
||||
- Adicionar dependência nova.
|
||||
- Adicionar Health Connect.
|
||||
- Criar tela nova separada se puder ser card inicialmente.
|
||||
- Fazer alteração destrutiva no banco.
|
||||
- Criar cálculo médico sensível.
|
||||
- Coletar dados muito pessoais sem justificativa.
|
||||
|
||||
### Never
|
||||
|
||||
- Apresentar diagnóstico médico.
|
||||
- Bloquear treino como decisão médica.
|
||||
- Criar formulário gigante difícil de usar.
|
||||
- Adicionar campos que não alimentam nenhum cálculo.
|
||||
- Criar código morto para métricas futuras.
|
||||
- Misturar todos os cálculos dentro da UI.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Usuário consegue preencher dados P0 sem fricção excessiva.
|
||||
- App calcula pelo menos:
|
||||
- IMC;
|
||||
- reserva cardíaca;
|
||||
- score de treino;
|
||||
- carga subjetiva;
|
||||
- score de recuperação;
|
||||
- hidratação estimada;
|
||||
- tempo em zona de luta.
|
||||
- Dados ficam persistidos localmente.
|
||||
- Insights aparecem em PT-BR.
|
||||
- Cálculos puros têm testes.
|
||||
- Build final passa.
|
||||
- Nenhuma dependência nova é adicionada sem aprovação.
|
||||
|
||||
## Decisions Approved
|
||||
|
||||
1. Check-in pré-treino será obrigatório antes de iniciar uma sessão.
|
||||
2. Insights será uma nova aba no menu inferior.
|
||||
3. Sexo biológico será coletado como `Masculino` ou `Feminino`.
|
||||
4. Energia, fadiga, dor e sono usarão escala 0–10.
|
||||
5. Campos avançados devem poder ficar ocultos/expandidos para manter a interface limpa.
|
||||
|
||||
## BLE Reliability and Battery Plan
|
||||
|
||||
### Objective
|
||||
|
||||
A leitura do sensor deve continuar enquanto o app estiver em treino, inclusive com tela bloqueada, salvo quando:
|
||||
|
||||
1. o usuário desconectar manualmente;
|
||||
2. o usuário encerrar o treino;
|
||||
3. o Android remover o processo por condição extrema fora do controle do app.
|
||||
|
||||
Quando houver queda por distância/sinal, o serviço deve tentar reconectar automaticamente quando o usuário se aproximar novamente. Se o usuário desconectar manualmente, o app não deve reconectar sozinho.
|
||||
|
||||
### BLE Rules
|
||||
|
||||
- O `HeartRateForegroundService` continua sendo dono da conexão BLE.
|
||||
- O ViewModel não deve abrir conexão direta com o sensor.
|
||||
- Toda queda inesperada deve ser registrada como estado de conexão e mensagem visível.
|
||||
- Desconexão manual deve salvar uma flag `manualDisconnect = true`.
|
||||
- Queda inesperada deve salvar `manualDisconnect = false` e iniciar retry/backoff.
|
||||
- O último sensor selecionado deve ser preservado para reconexão automática.
|
||||
|
||||
### Reconnect Strategy
|
||||
|
||||
Estados sugeridos:
|
||||
|
||||
```text
|
||||
Idle
|
||||
Connecting
|
||||
Connected
|
||||
LostSignal
|
||||
RetryWaiting
|
||||
ManualDisconnected
|
||||
Failed
|
||||
```
|
||||
|
||||
Backoff conservador:
|
||||
|
||||
```text
|
||||
1ª tentativa: 2s
|
||||
2ª tentativa: 5s
|
||||
3ª tentativa: 10s
|
||||
4ª tentativa em diante: 30s
|
||||
```
|
||||
|
||||
Regras:
|
||||
|
||||
- Não tentar reconectar se `manualDisconnect = true`.
|
||||
- Não tentar reconectar se Bluetooth estiver desligado; aguardar próximo ciclo.
|
||||
- Não tentar reconectar sem permissão Bluetooth.
|
||||
- Não fazer scan agressivo contínuo.
|
||||
- Para sensor já conhecido, tentar reconectar por `deviceId` direto antes de scan.
|
||||
- Scan só deve ser usado quando não houver deviceId válido.
|
||||
|
||||
### Battery Strategy
|
||||
|
||||
Para menor consumo:
|
||||
|
||||
- Foreground service único durante sessão.
|
||||
- Sem polling pesado no ViewModel a cada batimento.
|
||||
- SQLite insert apenas por amostra recebida.
|
||||
- Notificação com throttle.
|
||||
- Reconexão com backoff, nunca loop apertado.
|
||||
- Metadata do sensor lida na conexão e depois em intervalo longo, não a cada amostra.
|
||||
- Gráficos atualizam pelo estado recebido, sem reconsultar o banco inteiro.
|
||||
- Evitar wake locks permanentes. Usar foreground service e orientação de ignorar otimização de bateria.
|
||||
|
||||
### Acceptance BLE
|
||||
|
||||
- Tela bloqueada não interrompe leitura durante treino.
|
||||
- Distância/sinal perdido mostra status de sensor perdido.
|
||||
- Ao aproximar novamente, o app tenta reconectar sozinho.
|
||||
- Se usuário tocar em Desconectar, reconexão automática fica desativada.
|
||||
- Se usuário escolher o sensor/iniciar novamente, reconexão automática volta a valer.
|
||||
- Build passa.
|
||||
|
||||
## Recommended First Implementation Plan
|
||||
|
||||
Após aprovação desta spec:
|
||||
|
||||
1. Criar domínio puro de insights e testes.
|
||||
2. Expandir modelo de perfil e sessão com campos P0.
|
||||
3. Adicionar migração SQLite aditiva.
|
||||
4. Criar UI de Perfil Avançado com seções expansíveis.
|
||||
5. Criar check-in pré-treino obrigatório antes de iniciar sessão.
|
||||
6. Criar pós-treino ao encerrar sessão.
|
||||
7. Criar nova aba Insights com cards básicos.
|
||||
8. Melhorar BLE com desconexão manual vs queda inesperada, reconexão automática e backoff.
|
||||
|
||||
|
||||
9. Rodar build/test completo.
|
||||
|
||||
---
|
||||
|
||||
# Spec Expansion: Gráficos, Janela de Treino e Relatório Científico
|
||||
|
||||
## Objective
|
||||
|
||||
Transformar a sessão do FightRate em uma janela de coleta confiável com relatório pós-treino detalhado, gráficos Vico mais legíveis/interativos e métricas esportivas fundamentadas em frequência cardíaca real do sensor BLE.
|
||||
|
||||
O objetivo é interpretação esportiva para lutas, não diagnóstico médico. Toda métrica estimada deve ser rotulada como estimativa.
|
||||
|
||||
## Research Summary
|
||||
|
||||
Fontes consultadas antes da implementação:
|
||||
|
||||
1. Cole et al., NEJM 1999, PubMed: `https://pubmed.ncbi.nlm.nih.gov/10536127/`
|
||||
- HR Recovery é queda da FC do pico para 1 minuto pós-exercício.
|
||||
- Queda <= 12 bpm em 1 min foi considerada anormal no estudo clínico.
|
||||
2. Brubaker & Kitzman, Circulation review, PMC: `https://pmc.ncbi.nlm.nih.gov/articles/PMC3065291/`
|
||||
- Resposta cronotrópica é capacidade de aumentar FC com demanda.
|
||||
- Critérios clínicos exigem teste progressivo e contexto; no app usar só interpretação esportiva.
|
||||
- HRR 1 min < 12 bpm, 2 min < 42 bpm aparecem como thresholds de risco em contexto clínico, mas o app não diagnostica.
|
||||
3. Uth et al., Eur J Appl Physiol 2004, PubMed: `https://pubmed.ncbi.nlm.nih.gov/14624296/`
|
||||
- VO2max estimado pode usar `15.3 * (HRmax / HRrest)` em população treinada.
|
||||
- Aplicabilidade fora desse perfil deve ser tratada como aproximação.
|
||||
4. Boxing HR response, PMC: `https://pmc.ncbi.nlm.nih.gov/articles/PMC3871409/`
|
||||
- Boxe simulado passou grande parte acima de VT2; HR aumenta por round.
|
||||
- Reforça foco em tempo em zonas altas, recuperação entre rounds e drift.
|
||||
5. Muay Thai physiological response, Ingenta: `https://www.ingentaconnect.com/content/uwic/ujpa/2012/00000012/00000003/art00004`
|
||||
- Muay Thai competitivo teve FC média próxima do máximo e alto estresse cardiovascular.
|
||||
6. Edwards/TRIMP summaries: `https://www.trainingimpulse.com/edwards-trimp` e `https://journals.lww.com/nsca-scj/fulltext/2018/04000/quantification_of_training_load_in_endurance.12.aspx`
|
||||
- Edwards TRIMP é prático por zonas, mas coeficientes são arbitrários.
|
||||
- Banister usa reserva cardíaca e ponderação exponencial; ambos devem ser apresentados como carga interna estimada.
|
||||
7. HRV em Muay Thai, PMC: `https://pmc.ncbi.nlm.nih.gov/articles/PMC12143277/`
|
||||
- RMSSD/SDNN exigem RR/NN intervals confiáveis. O app hoje coleta BPM, portanto HRV fica fora do escopo até o parser/persistência salvarem RR intervals.
|
||||
|
||||
## Implementable Metrics From Current Data
|
||||
|
||||
Viáveis agora:
|
||||
|
||||
- duração total da sessão;
|
||||
- duração ativa aproximada e pausada;
|
||||
- BPM inicial/final/mínimo/médio/máximo;
|
||||
- desvio padrão;
|
||||
- qualidade dos dados por densidade, gaps e duração;
|
||||
- tempo e percentual por zona;
|
||||
- tempo em zona de luta, RedLine e Limite;
|
||||
- maior sequência contínua em zona intensa;
|
||||
- picos e quedas relevantes;
|
||||
- HR Recovery 1min/2min quando houver amostras pós-pico;
|
||||
- drift primeira metade vs segunda metade;
|
||||
- slope de BPM por minuto;
|
||||
- resposta cronotrópica inicial aproximada;
|
||||
- tempo até zona vigorosa/luta;
|
||||
- Edwards TRIMP;
|
||||
- Banister TRIMP quando houver FC repouso e limite cardíaco;
|
||||
- carga subjetiva RPE x duração;
|
||||
- VO2max estimado quando houver FC repouso e limite cardíaco;
|
||||
- hidratação estimada com peso pré/pós e água;
|
||||
- interpretação cruzada com energy/fatigue/soreness/sleep/symptoms/RPE.
|
||||
|
||||
Não viável agora sem novos dados:
|
||||
|
||||
- HRV RMSSD/SDNN/pNN50 real;
|
||||
- diagnóstico de arritmia;
|
||||
- limiar ventilatório real;
|
||||
- VO2 real;
|
||||
- economia cardíaca precisa entre sessões sem normalizar tipo/duração/intensidade.
|
||||
|
||||
## Data Collection / Time Window
|
||||
|
||||
Reaproveitar `workout_sessions`:
|
||||
|
||||
- `startedAtMillis` define início do range.
|
||||
- `endedAtMillis` define fim do range.
|
||||
- `session_id` em `heart_rate_samples` associa amostra à sessão ativa.
|
||||
- Ao finalizar, buscar amostras por `session_id`; fallback por intervalo se necessário.
|
||||
- Foreground service continua dono do BLE.
|
||||
- ViewModel não assume BLE.
|
||||
- Migração SQLite só aditiva se relatório persistido exigir cache.
|
||||
|
||||
## Domain Design
|
||||
|
||||
Criar ou expandir domínio puro para relatório:
|
||||
|
||||
- `TrainingReportMetrics`
|
||||
- `SessionReportBuilder`
|
||||
- `DataQualityAnalyzer`
|
||||
- `ZoneDistributionCalculator`
|
||||
- `HeartRateRecoveryCalculator`
|
||||
- `CardiacDriftCalculator`
|
||||
- `ChronotropicResponseCalculator`
|
||||
- `TrainingLoadCalculator`
|
||||
- `HydrationTrainingInterpreter`
|
||||
|
||||
Business rules fora do Compose.
|
||||
|
||||
## UI / Charts
|
||||
|
||||
Reaproveitar `HeartRateLineChart`, `FightCard`, `MetricTile`.
|
||||
|
||||
Adicionar:
|
||||
|
||||
- chart summary pills de mínimo/médio/máximo;
|
||||
- legenda de zonas;
|
||||
- destaque textual para tempo em zona de luta/limite;
|
||||
- estados profissionais para poucos dados;
|
||||
- leitura de eixo X por tempo real;
|
||||
- relatório em seções expansíveis:
|
||||
1. Resumo do treino;
|
||||
2. Gráfico principal;
|
||||
3. Zonas cardíacas;
|
||||
4. Recuperação;
|
||||
5. Carga de treino;
|
||||
6. Drift e fadiga;
|
||||
7. Hidratação;
|
||||
8. Comparação com histórico;
|
||||
9. Alertas e interpretação.
|
||||
|
||||
Evitar Canvas customizado e dependências novas.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Spec atualizada antes do código.
|
||||
- Código existente auditado e reaproveitado.
|
||||
- Testes JUnit cobrem todos os cálculos de domínio novos.
|
||||
- Sessão finalizada gera relatório com amostras do range correto.
|
||||
- Histórico permite abrir relatório de sessão.
|
||||
- UI deixa claro estimativa esportiva vs dado medido.
|
||||
- BLE continua no foreground service.
|
||||
- Sem migração destrutiva.
|
||||
- `./gradlew :app:testDebugUnitTest :app:assembleDebug` termina com `BUILD SUCCESSFUL`.
|
||||
- APK gerado e tentativa de `telegram_attach(localPath)` realizada.
|
||||
@@ -0,0 +1,27 @@
|
||||
# FightRate RN — Background Capture + Corner Instrument Redesign
|
||||
|
||||
## Objective
|
||||
Make the bare React Native Android app capture heart-rate data continuously until the athlete manually stops the workout, including when the screen is locked or the app is backgrounded. Replace foreground-JS BLE ownership and generic card UI with native Android capture ownership, local SQLite persistence, and Corner Instrument screens.
|
||||
|
||||
## Success Criteria
|
||||
- Capture is started/stopped from React Native, but BLE scan/connect/monitor runs in an Android foreground service.
|
||||
- Android manifest declares Bluetooth, notification, foreground service, and battery optimization permissions needed for continuous capture.
|
||||
- The foreground service repeats scan/reconnect attempts unless stopped manually.
|
||||
- Heart Rate Service `180D` and Heart Rate Measurement `2A37` are used for real BLE BPM data.
|
||||
- Each BPM sample is timestamped and persisted locally in SQLite.
|
||||
- History and insights use persisted real sessions/samples; no mock BPM/history/insight data is displayed.
|
||||
- UI follows `FIGHTRATE_DESIGN_SYSTEM.md` / Corner Instrument: dark physical instrument surfaces, dominant BPM, technical strips, no nested generic card dashboard.
|
||||
- Release APK builds as an installable arm64 artifact and is sent to Telegram.
|
||||
|
||||
## Boundaries
|
||||
- React Native bare CLI only; no Expo.
|
||||
- Android-first implementation; iOS background BLE is out of scope.
|
||||
- Physical Magene H003 validation remains required on device.
|
||||
- Manual destructive deletion of non-design project files is avoided.
|
||||
|
||||
## Verification
|
||||
- `npm run typecheck`
|
||||
- `npm run lint`
|
||||
- `npm test -- --runInBand`
|
||||
- `npm audit --audit-level=moderate`
|
||||
- `cd FightRateRN/android && ./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a`
|
||||
@@ -0,0 +1,86 @@
|
||||
# FightRate — Redesign de gráficos e sistema visual
|
||||
|
||||
## Objetivo
|
||||
|
||||
Corrigir a direção visual do FightRate: gráficos profissionais, úteis e interativos; interface menos genérica/card-heavy; design system original e obrigatório para próximas telas.
|
||||
|
||||
## Problemas atuais
|
||||
|
||||
- Gráficos aparecem mesmo sem dados úteis.
|
||||
- Estados vazios parecem gráficos quebrados/amadores.
|
||||
- Vico está subutilizado: pouca interação, pouco contexto visual, sem zoom/scroll claro.
|
||||
- UI tem excesso de cards iguais, pouca hierarquia e pouco padrão visual.
|
||||
- O aplicativo parece montado por IA, não por um produto esportivo premium.
|
||||
|
||||
## Decisões
|
||||
|
||||
- Manter Vico. Pesquisa em 2026-06 mostra Vico como biblioteca Compose/Kotlin madura com suporte a `scrollState` e `zoomState`.
|
||||
- Não trocar dependências nesta fase.
|
||||
- Não desenhar chart customizado em Canvas.
|
||||
- Não renderizar gráfico se houver menos de 2 pontos reais.
|
||||
- Se houver poucos dados, mostrar insight/empty state, não eixos vazios.
|
||||
- O eixo temporal deve continuar baseado em timestamps reais.
|
||||
- O design system passa a ser a fonte visual do app.
|
||||
|
||||
## Design System: FightRate Combat Lab
|
||||
|
||||
### Conceito
|
||||
|
||||
Interface de laboratório de performance para lutas: escura, precisa, atlética, com sensação de equipamento esportivo premium. Menos cartões genéricos, mais painéis editoriais, faixas de dados, chips compactos, e superfícies com função clara.
|
||||
|
||||
### Tokens principais
|
||||
|
||||
- Fundo: `#090A0B` — preto quente de OLED.
|
||||
- Surface: `#111315` — bloco primário.
|
||||
- Surface Elevated: `#181B1E` — módulo de leitura.
|
||||
- Ink: `#F6F0E7` — texto principal quente.
|
||||
- Muted: `#A79C8F` — texto secundário.
|
||||
- Dim: `#6E665D` — metadados.
|
||||
- Accent: `#FF6A1A` — FightRate Orange.
|
||||
- Amber: `#F6B44B` — atenção.
|
||||
- Redline: `#FF3B30` — limite/risco.
|
||||
- Cyan: `#48C7D8` — recuperação/ciência.
|
||||
- Line: `#2A2E32` — divisores sutis.
|
||||
|
||||
### Forma
|
||||
|
||||
- Telas: blocos editoriais com seções claras, não grade de cards repetidos.
|
||||
- Painéis principais: radius 28–32 dp, borda hairline, padding 20–24 dp.
|
||||
- Métricas pequenas: strips/pills, não cards grandes.
|
||||
- Gráfico: painel amplo, prioridade visual máxima, legenda compacta.
|
||||
- Empty state: peça editorial com próxima ação, nunca gráfico vazio.
|
||||
|
||||
### Tipografia
|
||||
|
||||
- Números grandes, tabulares quando possível.
|
||||
- Títulos curtos, fortes, em PT-BR.
|
||||
- Corpo pequeno, mas legível. Nada abaixo de 10sp para informação essencial.
|
||||
- Evitar muitos pesos/tamanhos diferentes.
|
||||
|
||||
### Gráficos
|
||||
|
||||
- Mostrar gráfico somente se `samples.size >= 2` e duração > 0.
|
||||
- Para 1 ponto: mostrar leitura única e instrução de continuar medindo.
|
||||
- Para 0 pontos: mostrar estado vazio contextual.
|
||||
- Linha com espessura alta, cor FightRate Orange, área/ambiente escuro.
|
||||
- Scroll/zoom habilitado via Vico quando possível.
|
||||
- Controles explícitos de janela/zoom fora do gráfico.
|
||||
- Resumo sempre visível: média, pico, mínima, duração, densidade de amostras.
|
||||
- Não usar índices como tempo; usar segundos/minutos reais desde o início da janela.
|
||||
|
||||
## Fatias de implementação
|
||||
|
||||
1. Consolidar tokens em tema/design system.
|
||||
2. Criar componentes visuais base: `FightSection`, `FightMetricStrip`, `FightEmptyState`, `FightChartPanel`.
|
||||
3. Atualizar `HeartRateLineChart` para não renderizar vazio e usar zoom/scroll Vico.
|
||||
4. Reorganizar Home/Histórico para priorizar gráfico e dados úteis.
|
||||
5. Atualizar documentação e roadmap.
|
||||
6. Rodar testes/build e gerar APK.
|
||||
|
||||
## Critérios de sucesso
|
||||
|
||||
- Nenhum gráfico vazio é renderizado.
|
||||
- Gráfico com dados tem zoom/scroll ou controle equivalente claro.
|
||||
- Histórico sem dados mostra estado vazio premium.
|
||||
- UI usa tokens e componentes do design system.
|
||||
- Build final passa com `./gradlew :app:testDebugUnitTest :app:assembleDebug`.
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# FightRate — Correção de produto: captura contínua, gráfico e sensor
|
||||
|
||||
## Contexto
|
||||
|
||||
Feedback do usuário: o app serve para monitorar batimento cardíaco enquanto o atleta treina. Durante luta/treino, o usuário não vai pegar o celular para iniciar/parar rounds manualmente.
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Tornar captura contínua de treino inteiro o fluxo principal.
|
||||
2. Manter modo round como secundário/opcional, sem dominar a tela principal.
|
||||
3. Corrigir gráfico para abrir com a janela selecionada completa, sem zoom inicial estranho.
|
||||
4. Tornar eixo temporal adaptativo e menos poluído.
|
||||
5. Melhorar tela de sensor para exibir dados/serviços disponíveis do dispositivo.
|
||||
6. Melhorar insights com informação densa, útil e explicável sem textão.
|
||||
|
||||
## Regras de produto
|
||||
|
||||
- Fluxo principal: iniciar monitoramento antes do treino, largar celular, parar ao final, analisar sessão inteira.
|
||||
- Round é alternativa opcional. Preferir segmentação automática pré-configurada em vez de controles manuais durante treino.
|
||||
- Histórico precisa ser rico e organizado: sessões, janela, zonas, carga, recuperação, pico, média, densidade de amostras.
|
||||
- Gráfico deve priorizar legibilidade e fidelidade temporal.
|
||||
|
||||
## Critérios de aceitação desta fatia
|
||||
|
||||
- Tela Live comunica “captura contínua” como ação principal.
|
||||
- Botões de sessão usam linguagem de treino inteiro: iniciar/parar captura/treino.
|
||||
- Round continua disponível/visível como informação secundária, mas não como conceito central.
|
||||
- Gráfico inicia mostrando a janela de dados selecionada, sem pre-zoom e com `initialZoom = null` quando possível.
|
||||
- Labels do eixo X usam granularidade por duração:
|
||||
- até 10 min: HH:mm
|
||||
- até 1 h: HH:mm
|
||||
- até 24 h: HH'h'
|
||||
- zoom/duração muito curta: HH:mm:ss apenas quando necessário.
|
||||
- Tela Sensor exibe painel de capacidades do sensor: BPM, local do sensor, bateria, metadados, RSSI e serviços BLE conhecidos/disponíveis.
|
||||
- Build final passa com `./gradlew :app:testDebugUnitTest :app:assembleDebug`.
|
||||
|
||||
## Fora desta fatia
|
||||
|
||||
- Implementar segmentação automática completa por round.
|
||||
- Persistir todos os valores de serviços BLE desconhecidos.
|
||||
- Reescrever toda a arquitetura visual do app.
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
# FightRate — monitoramento contínuo, histórico customizado e indicadores BPM
|
||||
|
||||
## Objetivo
|
||||
Corrigir o fluxo para que o FightRate se comporte como um monitor contínuo de sensor cardíaco, e não como um app dependente da janela de treino. A janela de treino deve apenas destacar um intervalo dentro do fluxo contínuo. Também adicionar consulta histórica customizada por data/hora/minuto, estabilizar a UI do histórico e documentar indicadores úteis derivados de BPM.
|
||||
|
||||
## Problemas relatados
|
||||
1. BPM em tempo real só aparece depois de abrir e fechar uma janela de treino.
|
||||
2. Histórico precisa consultar intervalo preciso por dia, hora e minuto, inclusive múltiplos dias.
|
||||
3. Histórico tem divisores redundantes entre controles, janelas salvas e registros.
|
||||
4. Em “Uso Total”, o item expandido colapsa quando entra novo dado em tempo real.
|
||||
5. É necessário mapear indicadores técnicos/científicos extraíveis de BPM para evolução futura.
|
||||
|
||||
## Critérios de sucesso
|
||||
- Ao abrir o app, se o monitor estava ativo ou há sensor salvo, o app retoma o monitoramento contínuo automaticamente.
|
||||
- Após conectar ao sensor peitoral, os dados começam a atualizar o app inteiro sem depender de janela de treino.
|
||||
- Abrir/fechar janela de treino não interrompe nem inicia artificialmente o stream contínuo; só marca um intervalo para histórico.
|
||||
- Histórico permite modo “Faixa” com início/fim definidos por dia, hora e minuto.
|
||||
- Faixa customizada funciona no mesmo dia e entre dias diferentes.
|
||||
- Uso Total mantém o item expandido mesmo quando novas amostras chegam em segundo plano.
|
||||
- Divisores do histórico ficam lineares: uma linha sutil por seção/registro, sem duplicação visual.
|
||||
- Pesquisa de indicadores BPM gera relatório com fórmulas, dados necessários, limites e recomendações.
|
||||
- Sem dados fake; gráficos e métricas continuam derivados de amostras reais.
|
||||
|
||||
## Arquitetura proposta
|
||||
### Monitoramento contínuo
|
||||
- Manter BLE e background capture no serviço nativo Android.
|
||||
- No hook RN, diferenciar claramente:
|
||||
- `isMonitoring`: stream contínuo ativo.
|
||||
- `isCapturing`: janela de treino ativa.
|
||||
- `samples`: amostras contínuas em memória recente.
|
||||
- Na inicialização do hook, depois de `getCaptureSnapshot`, se o snapshot indicar monitor ativo/sensor salvo, retomar/confirmar `startMonitoring` sem abrir janela.
|
||||
|
||||
### Histórico customizado
|
||||
- Criar lógica pura para filtrar amostras por intervalo `[start, end]`.
|
||||
- Usar a mesma fonte de verdade (`continuous_heart_rate_samples`) para “Uso Total” e faixa customizada.
|
||||
- Representar a seleção com componentes simples sem dependência nova: campos de data e hora/minuto via `TextInput`, com validação e presets.
|
||||
|
||||
### Estabilidade do Uso Total
|
||||
- Não usar ID derivado de `startedAt-endedAt-count` como única identidade quando blocos vivos mudam com novas amostras.
|
||||
- Manter expansão por `startedAt` do bloco e re-resolver o bloco depois de cada atualização.
|
||||
|
||||
### Indicadores BPM
|
||||
- Produzir documento separado com métricas implementáveis depois:
|
||||
- FC média/mínima/máxima;
|
||||
- zonas de intensidade;
|
||||
- tempo em zona;
|
||||
- carga interna/TRIMP;
|
||||
- recuperação pós-esforço;
|
||||
- deriva cardiovascular;
|
||||
- variabilidade de FC quando houver RR intervals;
|
||||
- alertas de limiar e anomalias.
|
||||
|
||||
## Fora de escopo
|
||||
- Trocar BLE stack.
|
||||
- Adicionar dependências nativas de calendário/data.
|
||||
- Alterar design aprovado fora dos pontos pedidos.
|
||||
- Criar diagnóstico médico automatizado sem validação clínica.
|
||||
|
||||
## Testes esperados
|
||||
- Testes unitários para agrupamento de uso contínuo estável.
|
||||
- Testes unitários para filtro de intervalo customizado.
|
||||
- Testes existentes continuam passando.
|
||||
|
||||
## Verificação
|
||||
- `npm run typecheck`
|
||||
- `npm run lint`
|
||||
- `npm test -- --runInBand`
|
||||
- `npm audit --audit-level=moderate`
|
||||
- `./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a`
|
||||
@@ -0,0 +1,25 @@
|
||||
# FightRate RN — Continuous Monitoring + Training Windows
|
||||
|
||||
## Objective
|
||||
Change FightRate so BLE heart-rate monitoring is continuous and locally persisted, while a workout/training session is only a marked time window inside that continuous stream. Starting a workout begins a highlighted window; stopping it closes that window and keeps monitoring alive.
|
||||
|
||||
## Success Criteria
|
||||
- The native Android foreground service can start monitoring without creating a workout window.
|
||||
- Samples are saved continuously even when no workout window is active.
|
||||
- Start/stop workout only opens/closes a `workout_sessions` window; it does not stop BLE monitoring.
|
||||
- History lists saved windows and each item can be opened to inspect details from samples inside that window.
|
||||
- Insight and history screens use only real timestamped samples.
|
||||
- Capture/History/Insights UI removes generic card-heavy containers and uses Corner Instrument rails, strips, dividers, and instrument surfaces.
|
||||
- Existing bare React Native CLI constraint remains; no Expo.
|
||||
|
||||
## Boundaries
|
||||
- No new dependencies unless unavoidable.
|
||||
- Do not delete existing tables destructively; migrate by adding a continuous sample table and deriving session summaries from timestamps.
|
||||
- Android physical BLE validation remains required after APK install.
|
||||
|
||||
## Verification
|
||||
- `npm run typecheck`
|
||||
- `npm run lint`
|
||||
- `npm test -- --runInBand`
|
||||
- `npm audit --audit-level=moderate`
|
||||
- `cd FightRateRN/android && ./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a`
|
||||
@@ -0,0 +1,81 @@
|
||||
# FightRate Insights Científicos — Mini-spec
|
||||
|
||||
## Objetivo
|
||||
|
||||
Implementar uma nova visualização de cálculos e insights na aba `Insights`, usando métricas derivadas do relatório científico `docs/research/bpm-indicators-fightrate-2026-06-10.md`, sem inventar dados e sem abandonar o design Corner Instrument.
|
||||
|
||||
## Escopo
|
||||
|
||||
### Incluir agora
|
||||
|
||||
- Grade/tabela compacta de métricas com melhor aproveitamento vertical.
|
||||
- Cálculos com dados reais já disponíveis:
|
||||
- FC atual, média, mínima e máxima;
|
||||
- duração observada e contagem de amostras;
|
||||
- amplitude de BPM;
|
||||
- desvio padrão de BPM;
|
||||
- coeficiente de variação de BPM;
|
||||
- classificação operacional de estabilidade/oscilação;
|
||||
- tempo em zonas já calculadas;
|
||||
- carga observada existente;
|
||||
- tendência de deriva de FC quando a janela for longa o suficiente;
|
||||
- recuperação pós-janela quando houver amostras contínuas após o fim da sessão;
|
||||
- status de HRV/VFC baseado na presença de `rrIntervalsMs`.
|
||||
- Estados honestos para métricas que exigem perfil ou RR intervals.
|
||||
|
||||
### Não incluir agora
|
||||
|
||||
- Cadastro de perfil do atleta.
|
||||
- Diagnóstico médico.
|
||||
- LF/HF, frequência espectral, SampEn ou DFA reais sem pipeline de RR validado.
|
||||
- Mudanças amplas de design fora de `Insights`.
|
||||
- Novas dependências.
|
||||
|
||||
## Arquitetura
|
||||
|
||||
- Reutilizar `src/training/trainingMetrics.ts` para cálculos puros.
|
||||
- Estender `src/training/trainingTypes.ts` com tipos de resumo de insights.
|
||||
- Atualizar `src/screens/InsightsScreen.tsx` para renderizar uma tabela/grade de instrumentos, não lista simples.
|
||||
- Atualizar `src/app/FightRateApp.tsx` para passar `getContinuousSamples` ao Insights, permitindo cálculo de recuperação pós-janela com dados persistidos.
|
||||
- Testar cálculos puros em `__tests__/trainingMetrics.test.ts`.
|
||||
|
||||
## Critérios de sucesso
|
||||
|
||||
- Insights usa somente amostras reais ou mostra estado indisponível.
|
||||
- Visual evita cards genéricos; usa trilhos, linhas tabulares, separadores e linguagem Corner Instrument.
|
||||
- Métricas BPM-only aparecem com valor/unidade/status/leitura curta.
|
||||
- HRV/VFC aparece como disponível somente quando existirem RR intervals; caso contrário, informa que requer RR.
|
||||
- Recuperação pós-janela só aparece quando há amostras depois de `endedAt`.
|
||||
- Typecheck, lint e testes passam.
|
||||
- Build release Android é gerado e enviado ao Telegram ao final.
|
||||
|
||||
## Reuso vs criação
|
||||
|
||||
### Reutilizar
|
||||
|
||||
- `calculateLiveMetrics`, `calculateZones`, `formatDuration` em `trainingMetrics.ts`.
|
||||
- `HeartRateSample`, `CompletedTrainingSession` em `trainingTypes.ts`.
|
||||
- Tokens existentes em `src/theme`.
|
||||
- Fluxo nativo `getContinuousSamples` já exposto pelo hook.
|
||||
|
||||
### Criar/alterar
|
||||
|
||||
- Criar funções puras em `trainingMetrics.ts`:
|
||||
- `buildInsightDashboard()`;
|
||||
- `calculateBpmVariability()`;
|
||||
- `calculateHeartRateDrift()`;
|
||||
- `calculatePostSessionRecovery()`;
|
||||
- `summarizeRrAvailability()`.
|
||||
- Criar tipos em `trainingTypes.ts`:
|
||||
- `InsightMetricStatus`;
|
||||
- `InsightMetricRow`;
|
||||
- `InsightDashboard`.
|
||||
- Reescrever o layout interno de `InsightsScreen.tsx` para matriz tabular.
|
||||
|
||||
## Plano de execução
|
||||
|
||||
1. Escrever testes para cálculos puros.
|
||||
2. Implementar funções puras em `trainingMetrics.ts`.
|
||||
3. Passar `getContinuousSamples` para `InsightsScreen` e carregar amostras persistidas de forma segura.
|
||||
4. Recriar UI da aba Insights com tabela compacta e blocos de instrumentos.
|
||||
5. Rodar validação completa.
|
||||
@@ -0,0 +1,108 @@
|
||||
# FightRate React Native Bare Rebuild — Mini-spec
|
||||
|
||||
Data: 2026-06-10
|
||||
Status: implementação autorizada
|
||||
Stack: **React Native Community CLI, sem Expo**
|
||||
|
||||
## Objetivo
|
||||
|
||||
Criar a nova base do FightRate em React Native bare, sem Expo, usando a direção visual oficial **Corner Instrument**.
|
||||
|
||||
A implementação inicial deve provar:
|
||||
|
||||
- identidade visual escolhida;
|
||||
- fluxo principal de captura contínua;
|
||||
- estrutura de código React Native escalável;
|
||||
- telas base para Captura, Histórico, Sensor e Insights;
|
||||
- dados mockados apenas como estado de protótipo local, não como gráfico ou amostra real persistida.
|
||||
|
||||
## Fora de escopo nesta primeira fatia
|
||||
|
||||
- BLE real Magene H003.
|
||||
- SQLite real.
|
||||
- Build de APK final assinado.
|
||||
- Migração de todos os recursos do Android legado.
|
||||
- Expo ou qualquer dependência Expo.
|
||||
|
||||
## Decisões
|
||||
|
||||
- Criar novo app em `FightRateRN/` para não destruir o Android legado antes da base RN compilar.
|
||||
- Usar React Native Community CLI.
|
||||
- Usar TypeScript.
|
||||
- Começar com UI e estado local; BLE/persistência entram em fatias posteriores.
|
||||
- Fonte visual obrigatória: `FIGHTRATE_DESIGN_SYSTEM.md`.
|
||||
|
||||
## Estrutura desejada
|
||||
|
||||
```text
|
||||
FightRateRN/
|
||||
src/
|
||||
app/
|
||||
FightRateApp.tsx
|
||||
components/
|
||||
ActionButton.tsx
|
||||
MetricTile.tsx
|
||||
SectionPanel.tsx
|
||||
StatusTag.tsx
|
||||
TrainingChart.tsx
|
||||
screens/
|
||||
CaptureScreen.tsx
|
||||
HistoryScreen.tsx
|
||||
SensorScreen.tsx
|
||||
InsightsScreen.tsx
|
||||
theme/
|
||||
colors.ts
|
||||
radius.ts
|
||||
spacing.ts
|
||||
typography.ts
|
||||
index.ts
|
||||
data/
|
||||
demoTraining.ts
|
||||
```
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
1. Projeto React Native bare criado sem Expo.
|
||||
2. `package.json` não contém dependências Expo.
|
||||
3. App inicial mostra a direção Corner Instrument.
|
||||
4. Captura contínua é o fluxo principal.
|
||||
5. Round manual não domina a interface.
|
||||
6. Design tokens estão centralizados em `src/theme`.
|
||||
7. Componentes principais reutilizáveis existem.
|
||||
8. Telas visíveis:
|
||||
- Captura;
|
||||
- Histórico;
|
||||
- Sensor;
|
||||
- Insights.
|
||||
9. Verificação mínima:
|
||||
|
||||
```bash
|
||||
cd FightRateRN
|
||||
npm install
|
||||
npm run typecheck
|
||||
npm run lint
|
||||
npm test -- --runInBand
|
||||
```
|
||||
|
||||
Build Android debug validado neste ambiente:
|
||||
|
||||
```bash
|
||||
cd FightRateRN/android
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
Observação local: o PATH atual tem JRE, mas não `javac`. Para o build Gradle RN,
|
||||
use o JBR do Android Studio como `JAVA_HOME`:
|
||||
|
||||
```bash
|
||||
export JAVA_HOME="/c/Users/Evandro/AppData/Local/Programs/Android Studio/jbr"
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
cd FightRateRN/android
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
## Riscos
|
||||
|
||||
- React Native CLI pode mudar template conforme versão atual.
|
||||
- Ambiente local pode não ter Android SDK/NDK adequado para build RN.
|
||||
- BLE real exigirá dependência nativa futura e teste físico.
|
||||
@@ -0,0 +1,93 @@
|
||||
# FightRate RN — Real BLE / No Mocks Corrective Slice
|
||||
|
||||
Data: 2026-06-10
|
||||
Status: implementado / APK enviado ao Telegram
|
||||
Stack: React Native Community CLI bare, sem Expo
|
||||
|
||||
## Problema
|
||||
|
||||
A primeira versão React Native enviada era instalável, mas não funcional como app de treino real:
|
||||
|
||||
- usava dados de demonstração;
|
||||
- não escaneava BLE;
|
||||
- indicava estados como conectado/pronto sem sensor real;
|
||||
- gráficos não eram alimentados por amostras reais;
|
||||
- histórico e insights pareciam conteúdo estático;
|
||||
- navegação inferior estava visualmente fraca;
|
||||
- logo/ícone do projeto não aparecia na UI.
|
||||
|
||||
## Objetivo desta fatia
|
||||
|
||||
Transformar a versão RN em uma base real de captura:
|
||||
|
||||
1. Remover dados mockados da UI.
|
||||
2. Adicionar BLE real com `react-native-ble-plx`.
|
||||
3. Pedir permissões Android Bluetooth/localização.
|
||||
4. Escanear sensores com Heart Rate Service `180D`.
|
||||
5. Conectar ao sensor encontrado.
|
||||
6. Monitorar Heart Rate Measurement `2A37`.
|
||||
7. Parsear BPM real a partir do payload BLE.
|
||||
8. Atualizar BPM, média, pico e amostras em tempo real.
|
||||
9. Renderizar gráfico somente com amostras reais recebidas.
|
||||
10. Exibir histórico/insights como estados vazios honestos quando não houver sessão real.
|
||||
11. Melhorar bottom nav: ícones, barra flutuante, sem faixa preta atrás.
|
||||
12. Remover indicador `READY` do topo.
|
||||
13. Usar logo existente do projeto na UI.
|
||||
14. Gerar APK release e enviar ao Telegram.
|
||||
|
||||
## Dependências aprovadas pela necessidade do pedido
|
||||
|
||||
- `react-native-ble-plx`: BLE real no React Native bare.
|
||||
- `react-native-svg`: gráfico real leve e ícones SVG.
|
||||
- `lucide-react-native`: ícones para navegação/estado.
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- App não mostra BPM mockado.
|
||||
- Tela de captura mostra estado de permissão/scan/conexão real.
|
||||
- Botão de captura dispara permissões e scanning BLE.
|
||||
- Se nenhum sensor for encontrado, a UI diz isso claramente.
|
||||
- Se sensor conectar, BPM vem do characteristic `2A37`.
|
||||
- Gráfico só aparece depois de amostras reais.
|
||||
- Histórico não finge sessões inexistentes.
|
||||
- Insights não fingem análise sem dados.
|
||||
- APK release abre sem Metro.
|
||||
|
||||
## Validação
|
||||
|
||||
```bash
|
||||
cd FightRateRN
|
||||
npm run typecheck
|
||||
npm run lint
|
||||
npm test -- --runInBand
|
||||
npm audit --audit-level=moderate
|
||||
```
|
||||
|
||||
Resultado:
|
||||
|
||||
```text
|
||||
tsc OK
|
||||
ESLint: No issues found
|
||||
Tests: 2 suites / 4 tests passed
|
||||
found 0 vulnerabilities
|
||||
```
|
||||
|
||||
Build Android release arm64:
|
||||
|
||||
```bash
|
||||
cd FightRateRN/android
|
||||
./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a
|
||||
```
|
||||
|
||||
Resultado:
|
||||
|
||||
```text
|
||||
BUILD SUCCESSFUL
|
||||
APK: FightRateRN/android/app/build/outputs/apk/release/app-release.apk
|
||||
Tamanho: 29.7 MB
|
||||
Telegram: enviado
|
||||
```
|
||||
|
||||
## Limitação ainda aberta
|
||||
|
||||
A conexão BLE real precisa ser validada no aparelho físico com a cinta Magene H003. O app agora escaneia/conecta/monitora BLE real, mas só o teste com o sensor confirma comportamento final de campo.
|
||||
@@ -0,0 +1,54 @@
|
||||
# FightRate — Sensor contínuo e histórico geral
|
||||
|
||||
## Objetivo
|
||||
Separar claramente três conceitos:
|
||||
|
||||
1. **Sensor**: descobrir, mostrar disponibilidade e permitir conexão ao sensor BLE.
|
||||
2. **Monitoramento contínuo**: depois de conectado, receber, mostrar e salvar BPM real o tempo todo no app.
|
||||
3. **Janela de treino**: ação disponível somente em Captura; marca um intervalo dentro do fluxo contínuo para destaque no histórico.
|
||||
|
||||
## Critérios de sucesso
|
||||
- A tela Sensor não mostra botão de abrir/iniciar janela de treino.
|
||||
- A tela Sensor mostra se o sensor está disponível/conectado e permite conectar/desconectar/monitorar pelo fluxo de sensor.
|
||||
- Conectar o sensor inicia o recebimento contínuo via serviço nativo e os dados alimentam Captura, Sensor, Histórico e Insights.
|
||||
- A janela de treino continua existindo apenas na tela Captura e não interrompe o monitoramento ao fechar.
|
||||
- Histórico mantém a lista de janelas de treino e adiciona uma visão de **uso total** baseada em `continuous_heart_rate_samples`.
|
||||
- O histórico geral não usa dados falsos: se não houver amostras contínuas, mostra estado vazio honesto.
|
||||
- BLE services/characteristics ficam mais legíveis, com hierarquia compacta e sem espaçamento excessivo.
|
||||
- Código visível ao usuário em PT-BR; identificadores em inglês.
|
||||
|
||||
## Arquitetura recomendada
|
||||
- Reusar `FightRateCaptureStore` como fonte local-first.
|
||||
- Reusar `continuous_heart_rate_samples` como verdade para histórico geral.
|
||||
- Criar uma pequena camada de domínio TS para agrupar amostras contínuas em blocos de uso por lacunas de tempo. Essa lógica é pura e testável.
|
||||
- Expor do módulo nativo um método de leitura paginada/resumida das amostras contínuas, sem mudar o contrato BLE.
|
||||
- Manter `workout_sessions` apenas para janelas destacadas.
|
||||
|
||||
## Responsabilidades
|
||||
### Reusar
|
||||
- `useHeartRateMonitor`: estado app-wide recebido do módulo nativo.
|
||||
- `TrainingChart`: gráfico real e interativo com amostras timestampadas.
|
||||
- `FightRateCaptureStore`: persistência SQLite local.
|
||||
- `HistoryScreen`: entrada natural para alternar entre janelas e uso total.
|
||||
|
||||
### Criar/alterar
|
||||
- `src/training/continuousUsage.ts`: agrupar amostras contínuas em blocos de uso e sumarizar BPM/duração.
|
||||
- Teste Jest para `continuousUsage`.
|
||||
- API nativa `getContinuousSamples(limit?)`.
|
||||
- Wrapper TS para `getContinuousSamples`.
|
||||
- UI de Histórico com seção/alternância para “Janelas” e “Uso total”.
|
||||
- UI de Sensor sem ação de treino e com diagnóstico BLE mais claro.
|
||||
|
||||
## Fora de escopo
|
||||
- Não trocar biblioteca BLE.
|
||||
- Não adicionar dependências.
|
||||
- Não mudar o design aprovado fora dos pontos pedidos.
|
||||
- Não criar dados demo/fake.
|
||||
- Não mexer no app Android legado.
|
||||
|
||||
## Verificação
|
||||
- `npm run typecheck`
|
||||
- `npm run lint`
|
||||
- `npm test -- --runInBand`
|
||||
- `npm audit --audit-level=moderate`
|
||||
- `./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a`
|
||||
@@ -0,0 +1,190 @@
|
||||
# FightRate RN — Gráfico dinâmico: zoom, pan, pin e auto-follow
|
||||
|
||||
## Objetivo
|
||||
|
||||
Substituir o `TrainingChart` atual (estático, 120 últimos samples, sem zoom) por um gráfico dinâmico com viewport temporal, zoom por pinch, pan, granularidade adaptativa do eixo X, e seleção pinada que **sobrevive a novas amostras em tempo real**. O comportamento de auto-follow deve retomar automaticamente quando o usuário arrasta o viewport até a borda direita (último ponto conhecido).
|
||||
|
||||
Aplica-se aos três call sites atuais: `CaptureScreen` (tempo real), `HistoryScreen` (janela salva) e `HistoryScreen` (faixa customizada).
|
||||
|
||||
## Problema atual
|
||||
|
||||
`src/components/TrainingChart.tsx` (versão vigente):
|
||||
|
||||
```ts
|
||||
const chart = useMemo(() => buildChart(samples.slice(-120)), [samples]);
|
||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
||||
```
|
||||
|
||||
- `samples.slice(-120)` corta o array por contagem, não por janela temporal. Janelas longas (1h+) ficam visualmente compactadas a 120 pontos; janelas curtas (10s) ficam visualizadas com 120 pontos (impossível na prática, mas o problema é simétrico).
|
||||
- `selectedIndex` é a **posição no array de pontos**. Quando novas amostras chegam, `buildChart` re-cria o array `points`, e o índice anteriormente válido passa a apontar para uma amostra diferente — o ponto pinado "pula" e, no limite, some do viewport. Em tempo real isso é especialmente grave: o atleta clica num pico, e o gráfico "come" o pin a cada BLE notify.
|
||||
- Sem zoom (não há pinch). O gráfico é uma janela fixa do início ao fim do dataset.
|
||||
- Eixo X fixo, em segundos totais. Granularidade dos labels nunca se adapta.
|
||||
- Hit-testing em `onResponderMove` usa `event.nativeEvent.locationX` dividido por `layoutWidth` para mapear ao viewBox 320-wide — perde precisão fora do viewBox e não tolera gestos de 2 dedos.
|
||||
|
||||
## Decisões
|
||||
|
||||
### Stack
|
||||
|
||||
- Manter `react-native-svg` como renderer.
|
||||
- Adicionar `react-native-gesture-handler` (~v2) e `react-native-reanimated` (~v3) para pinch/pan nativos. Setup padrão de RN bare: import `react-native-gesture-handler` no topo de `index.js`, envolver `App` com `GestureHandlerRootView`. Habilitar reanimated via Babel plugin (`react-native-reanimated/plugin` em `babel.config.js`).
|
||||
- Sem troca de chart lib. `react-native-svg` continua sendo o motor de render.
|
||||
|
||||
### Modelo conceitual
|
||||
|
||||
Substituir "render dos últimos N samples" por **viewport temporal**:
|
||||
|
||||
```ts
|
||||
type Viewport = {
|
||||
readonly startMs: number; // primeira timestamp visível
|
||||
readonly endMs: number; // última timestamp visível (inclusive)
|
||||
readonly liveTail: boolean; // true quando endMs === lastSampleTimestamp
|
||||
};
|
||||
```
|
||||
|
||||
```ts
|
||||
type ChartPin = {
|
||||
readonly timestampMs: number;
|
||||
readonly sample: HeartRateSample;
|
||||
} | null;
|
||||
```
|
||||
|
||||
Estado interno do `TrainingChart` (hooks, não Redux):
|
||||
|
||||
```ts
|
||||
const [viewport, setViewport] = useState<Viewport>(initialViewport);
|
||||
const [pin, setPin] = useState<ChartPin>(null);
|
||||
```
|
||||
|
||||
`viewport` é o único "contrato" entre zoom/pan e o render. O SVG é derivado do `viewport` + samples. `pin` é independente de posição no array — é por `timestampMs`. Amostras novas entram, o `viewport` é recalculado somente se `liveTail` estiver `true`; o `pin` continua válido.
|
||||
|
||||
### Granularidade adaptativa do eixo X
|
||||
|
||||
A duração visível do viewport (`endMs - startMs`) decide a granularidade dos ticks:
|
||||
|
||||
| Duração visível | Granularidade dos ticks principais | Label |
|
||||
|---|---|---|
|
||||
| `>= 5 min` | 1 min | `HH:mm` |
|
||||
| `>= 1 min` | 30 s | `HH:mm:ss` |
|
||||
| `>= 30 s` | 15 s | `HH:mm:ss` |
|
||||
| `>= 10 s` | 10 s | `HH:mm:ss` |
|
||||
| `>= 2 s` | 1 s | `HH:mm:ss` |
|
||||
| `< 2 s` | 0.5 s | `HH:mm:ss.S` |
|
||||
|
||||
Regras:
|
||||
- Mostrar no máximo 4–5 ticks para não poluir.
|
||||
- Tick 0 (início do viewport) sempre renderizado.
|
||||
- Não usar ticks decorativos. Se o dataset não tem amostra no tick, o tick vira referência de eixo apenas.
|
||||
|
||||
### Zoom
|
||||
|
||||
- **Pinch** (dois dedos): escala o viewport em torno do ponto focado (ponto médio entre os dois dedos em coordenadas de tempo).
|
||||
- **Botões `+` / `−`** no canto superior direito do painel gráfico: zoom in/out de 2x mantendo o centro do viewport.
|
||||
- **Double-tap**: zoom in de 2x no ponto clicado; três taps em 1.2 s volta ao fit da janela selecionada (de acordo com o `windowSamples` que o pai passou).
|
||||
- Limites: mínimo 1 s visível; máximo = janela total selecionada (fit).
|
||||
- Os gestos de pinch e pan **não devem disparar tap**; gesto horizontal no gráfico move viewport; gesto vertical isolado é ignorado (a altura do gráfico não muda).
|
||||
|
||||
### Pan
|
||||
|
||||
- 1 dedo arrastando = pan. `pan` ajusta `startMs` e `endMs` proporcionalmente ao movimento em pixels (relação `pixelPorMs = layoutWidth / (endMs - startMs)`).
|
||||
- Quando o usuário inicia pan, `liveTail` vai para `false` imediatamente.
|
||||
- `liveTail` retorna a `true` **somente** quando, ao final do gesto, o `endMs` do viewport estiver a menos de `5%` da duração visível da última amostra conhecida (snap zone).
|
||||
- Quando `liveTail` está `true` e novas amostras chegam, o viewport estende o `endMs` automaticamente (a "esteira" do tempo real).
|
||||
- Quando `liveTail` está `false`, novas amostras **não** movem o viewport (mas o `lastPoint` continua sendo o último da coleção, para render do "tail off-screen indicator").
|
||||
|
||||
### Pin (clique em ponto)
|
||||
|
||||
- Tap em um ponto = `setPin({ timestampMs, sample })`.
|
||||
- Pin sobrevive a:
|
||||
- novas amostras chegarem (o `timestampMs` continua existindo no array de samples, a menos que o ponto esteja fora do novo viewport);
|
||||
- zoom in/out (o pin continua naquele `timestampMs`);
|
||||
- pan (idem).
|
||||
- Pin some se:
|
||||
- o usuário tocar fora do gráfico;
|
||||
- o usuário tocar de novo no mesmo ponto pinado (toggle);
|
||||
- o `timestampMs` do pin ficar **fora do viewport** e o usuário não voltar para ele (auto-clear opcional, mas default é manter — fica "off-screen" e um pequeno indicador pode avisar).
|
||||
- Render do pin: círculo âmbar + linha tracejada vertical até o eixo X (visual atual mantido, refinado).
|
||||
- O `readout` (painel inferior) mostra o sample pinado. Sem pin, mostra "TOQUE/ARRASTE PARA INSPECIONAR" e mantém o BPM atual.
|
||||
|
||||
### Hit-testing
|
||||
|
||||
- O gesto de tap é resolvido por `Gesture.Tap().onEnd((e) => pickPointAtX(e.x, viewport, samples))`.
|
||||
- `pickPointAtX` mapeia `e.x` (em pixels no SVG) para `timestampMs = startMs + (e.x - PAD_X) / (CHART_WIDTH - 2*PAD_X) * (endMs - startMs)`, e então busca a amostra de `samples` mais próxima no tempo (busca binária por timestamp, fallback linear para datasets pequenos).
|
||||
- Tolerância de snap: se a distância para a amostra mais próxima for maior que `10%` da duração visível, **não** seta pin (considera tap em área vazia).
|
||||
|
||||
## Estrutura de código
|
||||
|
||||
### Novos arquivos
|
||||
|
||||
- `src/training/chartViewport.ts` — funções puras (testáveis) para:
|
||||
- `granularityForSpan(spanMs: number): Granularity`
|
||||
- `formatTickLabel(timestampMs: number, granularity: Granularity): string`
|
||||
- `buildChartViewport(samples, options)` — calcula o viewport inicial
|
||||
- `applyZoomIn(viewport, focusMs)` / `applyZoomOut(viewport, focusMs)`
|
||||
- `applyPan(viewport, dxPixels, pixelsPerMs)`
|
||||
- `isLiveTailSnap(viewport, lastSampleTimestampMs)` — regra do snap zone
|
||||
- `pickSampleAtTimestamp(samples, timestampMs, toleranceMs)`
|
||||
- tipos: `Viewport`, `ChartPin`, `Granularity`
|
||||
- `src/training/__tests__/chartViewport.test.ts` — testes TDD das funções puras.
|
||||
|
||||
### Modificados
|
||||
|
||||
- `src/components/TrainingChart.tsx` — reescrito. Mantém:
|
||||
- viewBox 320×148 (compatibilidade com tokens);
|
||||
- cor de linha `colors.action`, fundo `colors.surfaceHot`, raio `radius.md`;
|
||||
- painel `readout` (label + valor) abaixo do gráfico;
|
||||
- pin: círculo âmbar + linha tracejada.
|
||||
Acrescenta:
|
||||
- pinch + pan via `react-native-gesture-handler`;
|
||||
- tap handler para pin;
|
||||
- eixo X com ticks adaptativos (função pura);
|
||||
- barra/cluster de botões `+` / `−` no canto;
|
||||
- indicador "off-screen latest" quando `!liveTail` (uma seta/marca à direita mostrando que há dados mais novos).
|
||||
- `src/components/ActionButton.tsx` — pode receber uso novo (botão pequeno icon-only para +/−). Avaliar reuso antes de criar componente novo.
|
||||
- `FightRateRN/index.js` — adicionar `import 'react-native-gesture-handler'` no topo.
|
||||
- `FightRateRN/babel.config.js` — adicionar `react-native-reanimated/plugin` (deve ser o último da lista).
|
||||
- `FightRateRN/App.tsx` — envolver conteúdo em `GestureHandlerRootView` (verificar se já está).
|
||||
- `package.json` — adicionar `react-native-gesture-handler` e `react-native-reanimated`.
|
||||
- `android/app/src/main/java/com/healthtracking/fightrate/MainActivity.kt` — verificar se herda de `ReactActivity` (gesture-handler detecta automaticamente; nada a fazer em geral).
|
||||
- `docs/FIGHTRATE_DEVELOPMENT_WORKFLOW.md` — atualizar a nota de que a skill `fightrate-quality-loop` recomenda aprovação antes de adicionar dependências (já cobre).
|
||||
|
||||
### Não modificados
|
||||
|
||||
- `useHeartRateMonitor.ts` — o gráfico consome `samples` como prop. O comportamento de auto-follow é responsabilidade do gráfico, não do hook.
|
||||
- `CaptureScreen.tsx`, `HistoryScreen.tsx` — call sites do `TrainingChart`. A interface do componente (`samples`) permanece compatível; nenhum `prop` novo obrigatório é exposto ao pai nesta fatia (zoom/pan/pin são internos).
|
||||
|
||||
## Critérios de sucesso
|
||||
|
||||
1. **Auto-follow funcional**: ao entrar no `CaptureScreen` e a captura estar rodando, o gráfico se mantém mostrando os últimos N segundos; cada novo BPM real reposiciona o viewport para incluir a nova amostra à direita.
|
||||
2. **Pin sobrevive a novas amostras**: tocar num ponto, deixar o sensor entregar 5 novas leituras → o pin continua visível na mesma posição, com o mesmo BPM e timestamp. O gráfico pode crescer à direita.
|
||||
3. **Pan sai do live**: arrastar para a esquerda → `liveTail = false`; novas amostras param de mover o viewport. O indicador "off-screen latest" aparece à direita.
|
||||
4. **Retomada do live**: arrastar de volta até a snap zone (últimos 5% da duração visível) → `liveTail = true` e o gráfico volta a rolar.
|
||||
5. **Pinch**: 2 dedos abrindo → zoom in em torno do ponto médio; fechar → zoom out. Não dispara tap acidental.
|
||||
6. **Botões `+` / `−`**: zoom in/out de 2x no centro.
|
||||
7. **Granularidade adaptativa**: 10 min de janela visível → labels em `HH:mm`; 1 min → `HH:mm:ss` 30s; 30 s → 15 s; 10 s → 10 s; 2 s → 1 s; < 2 s → 0.5 s. Trocas suaves, sem flicker.
|
||||
8. **Hit-test estável**: tap a 30% da largura do gráfico fixa o pin na amostra mais próxima no tempo, mesmo que o layout tenha width real diferente de `CHART_WIDTH`.
|
||||
9. **Hit-test fora da área**: tap longe de qualquer amostra (gap > 10% da duração) não seta pin; o readout continua mostrando "TOQUE/ARRASTE PARA INSPECIONAR".
|
||||
10. **Sem regressão visual**: tokens do Corner Instrument mantidos (cores, raio, borda, tipografia label, fundo `colors.surfaceHot`).
|
||||
11. **Sem gráfico fake**: se `samples.length < 2`, mostra o estado vazio atual (sem render de linha). Nenhuma mudança nesse fluxo.
|
||||
12. **Testes passam**: `npm test -- --runInBand` cobre `chartViewport.ts` (granularidade, zoom, pan, snap, pick). `App.test.tsx` continua passando.
|
||||
13. **Build OK**: `npm run typecheck` e `npm run lint` exit 0. Build Android (`./gradlew :app:assembleDebug` em `FightRateRN/android`) só é rodado se JDK/JBR estiver disponível no ambiente.
|
||||
|
||||
## Riscos e mitigações
|
||||
|
||||
- **Risco**: gesture-handler requer setup de imports no Android (já é o caso em RN 0.86, mas o `MainActivity` pode precisar de `createReactActivityDelegate` atualizado).
|
||||
**Mitigação**: seguir o passo-a-passo da documentação oficial; se o build falhar, voltar à abordagem `PanResponder + botões` e reusar o resto do spec.
|
||||
- **Risco**: reanimated v3 + RN 0.86 — compatibilidade.
|
||||
**Mitigação**: travar versões compatíveis (`react-native-reanimated@~3.16.0` recomendado para RN 0.86). Documentar a versão exata no `package.json` após smoke test.
|
||||
- **Risco**: conflito entre gesto de pan do `ScrollView` pai e pan do gráfico.
|
||||
**Mitigação**: usar `simultaneousWithExternalGesture` no `GestureDetector` do gráfico para desativar o pan do `ScrollView` apenas dentro da área do gráfico; testar com `CaptureScreen` (que está dentro de `ScrollView`).
|
||||
- **Risco**: a busca binária de `pickSampleAtTimestamp` precisa de `samples` ordenados. Garantir invariante no hook e/ou ordenar defensivamente.
|
||||
**Mitigação**: função pura de ordenação dedicada; teste cobre o caso de samples fora de ordem.
|
||||
|
||||
## Não-objetivos (fora do escopo)
|
||||
|
||||
- Crosshair, brush selection, comparação de duas janelas no mesmo gráfico.
|
||||
- Exportar gráfico como imagem.
|
||||
- Reorganizar layout da `CaptureScreen` ou da `HistoryScreen` (apenas a parte do `TrainingChart`).
|
||||
- Trocar a paleta de cores.
|
||||
- Adicionar leitura de RR no eixo Y (permanece em BPM).
|
||||
- Sincronizar dois gráficos lado a lado.
|
||||
- Persistir o último zoom/pan entre sessões (state efêmero).
|
||||
Reference in new issue
Block a user