diff --git a/README.md b/README.md index a82e455..c0e6c2d 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,147 @@ -# FightRateRN +
-React Native Community CLI rebuild of FightRate. This app is **bare React Native only** and does not use Expo. +FightRate logo -## Visual source of truth +# FightRate (HealthTracking) -The app follows the selected FightRate direction: +**Continuous heart-rate capture for combat-sport training from a BLE chest strap — a bare React Native +app (no Expo) with a native Android foreground service, local SQLite storage and metrics computed only from +real timestamped BPM.** -- `../FIGHTRATE_DESIGN_SYSTEM.md` -- Direction: `01 — Corner Instrument` +![React Native](https://img.shields.io/badge/React_Native-0.86.0-informational) +![Android](https://img.shields.io/badge/Android-minSdk_24-informational) +![Node](https://img.shields.io/badge/Node-%E2%89%A5_22.11-informational) -Do not reuse the deprecated Combat Lab/card-heavy direction. +[Features](#features) · [How it works](#how-it-works) · [Run locally](#run-locally) · [Known limitations](#known-limitations) -## Current slice +
-Implemented in this first slice: +> [!NOTE] +> **Project status:** work in progress (`package.json` is `0.0.1`, Android `versionName` `1.0`). The +> `applicationId` is `com.healthtracking.fightrate`. The latest commits (2026-06-16) ship the +> "Instrumento Forte" UI refinement (v2): tabular JetBrains Mono numerics, animated live indicators and +> a zoomable chart. The record in [`.impeccable.md`](.impeccable.md) lists `npm run typecheck` with 0 +> errors and `npm test -- --runInBand` with 74/74 passing for that release. -- Corner Instrument app shell. -- Continuous capture-first home screen. -- History, Sensor, and Insights screens. -- Centralized design tokens in `src/theme`. -- Reusable UI components in `src/components`. -- No Expo dependencies. -- Android `applicationId` aligned to `com.healthtracking.fightrate`. +> [!IMPORTANT] +> **Real data only, Android only.** Every number on screen is computed from BPM samples received from a +> real sensor; with no samples the app shows an empty state instead of inventing a peak, zone, load or +> HRV. The BLE capture module is written in Kotlin (`android/…/capture/`); the `ios/` project has no +> native capture module. -Not implemented yet: +## Features -- Real BLE Magene H003 integration. -- SQLite/local persistence. -- Real timestamped workout chart data. -- Navigation library. +| Tab | What it does | +|---|---| +| **Capture** | Live BPM, average / peak / sample-count tiles, BLE, battery and background status, and the training chart. Start/stop marks a **training window** inside the continuous stream — monitoring keeps storing samples either way | +| **History** | Saved training windows and continuous-usage blocks (split by gaps), each with average / peak / min BPM; open one to inspect the timestamped data; custom time range | +| **Sensor** | BLE scan, device list, connect, connection phase, signal (RSSI), battery level, BLE services/characteristics diagnostics and a shortcut to Android battery-optimization settings | +| **Insights** | Calculation matrix: current/average BPM, range, variability, time in zones, load, heart-rate drift, post-session recovery, HRmax/HRR (marked as needing a profile) and HRV (only when the strap sends RR intervals) | -## Commands +- **Zoomable chart** (`TrainingChart`): pinch zoom, pan and a tap-to-pin marker, with tick granularity that + follows the visible span (half-second up to one-minute steps). +- **Zones** by fixed BPM thresholds: `BASE <150`, `PRESSÃO 150–169`, `REDLINE 170+`. +- Samples outside 30–240 bpm, or reported without skin contact, are discarded from metrics; a sample is + considered fresh for 12 s. +- UI copy is PT-BR; code identifiers are English. + +> [!WARNING] +> **Grant the permissions and exempt the app from battery optimization.** The manifest requests Bluetooth +> scan/connect, fine location, notifications and a foreground service of type `connectedDevice`. Capture +> runs in that service; the **Bateria** button on the Sensor tab opens Android's battery-optimization +> settings (`REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` is declared) so the background capture isn't +> throttled. + +## How it works + +```mermaid +flowchart LR + A["Chest strap (BLE)
Heart Rate 0x180D · 2A37"] --> B["FightRateCaptureService
Kotlin foreground service"] + B --> C["FightRateCaptureStore
SQLite"] + B --> D["FightRateCaptureModule
native module · events"] + D --> E["useHeartRateMonitor
React hook"] + C -. "samples / sessions" .-> D + E --> F["trainingMetrics · continuousUsage
chartViewport"] + F --> G["Capture · History · Sensor · Insights"] +``` + +
+Technical details + +| Piece | Where | Notes | +|---|---|---| +| BLE client | `FightRateCaptureService.kt` | Scans, connects by known device id, enables notifications on Heart Rate Measurement `2A37` (CCCD `2902`), reads Battery Level `2A19` from service `180F`, reads RSSI | +| Reconnect | same file | `START_STICKY` service; automatic reconnect with backoff 2 s → 5 s → 10 s → 30 s unless stopped manually; restores the last device after a restart; after 12 s without a valid sample it reports the stale state and waits for new real data | +| Parser | `HeartRateMeasurementParser.kt`, `src/ble/heartRateParser.ts` | Flags: UInt8/UInt16 BPM, contact detection, RR intervals (converted from 1/1024 s to ms) | +| Storage | `FightRateCaptureStore.kt` | `fightrate_rn_capture.db` (version 2): `workout_sessions` (training windows with device id/name), `continuous_heart_rate_samples` (timestamp, BPM, contact, RR intervals, device) and the legacy `heart_rate_samples`, migrated on upgrade | +| Bridge | `FightRateCaptureModule.kt`, `src/native/FightRateCaptureNative.ts` | Start/stop monitoring and capture window, connect, snapshot, session and continuous samples, battery-optimization settings; service broadcasts become JS events | +| Domain (pure TS) | `src/training/` | Live metrics, variability, drift, recovery, zones, insight dashboard, continuous-usage blocks (gap of 5 min starts a new block), chart viewport math | +| UI | `src/screens/`, `src/components/`, `src/theme/` | Tab shell in `src/app/FightRateApp.tsx` (state-driven; no navigation library), Reanimated 4 + Worklets animations, design tokens | + +Design references: [`FIGHTRATE_DESIGN_SYSTEM.md`](FIGHTRATE_DESIGN_SYSTEM.md) (direction **01 — Corner +Instrument**, the official one) and, for history only, +[`FIGHTRATE_COMBAT_LAB_DESIGN_SYSTEM.md`](FIGHTRATE_COMBAT_LAB_DESIGN_SYSTEM.md) (deprecated direction — do +not reuse). Specs and plans live in [`docs/superpowers/`](docs/superpowers). +
+ +## Known limitations + +- **Android only:** capture exists only in the Kotlin native module; there is no equivalent on iOS. +- **No athlete profile yet:** zones use the fixed BPM thresholds above; `%HRmax` zones, Karvonen and full TRIMP + are shown as requiring a profile — the app doesn't invent HRmax or resting heart rate. +- **HRV needs RR intervals:** without them the dashboard says so instead of estimating. +- **No navigation library:** tabs are driven by component state. +- **Release signing:** the `release` build type is configured with the **debug keystore** + (`android/app/build.gradle`); generate your own keystore before distributing. +- **Lint:** at the last record in [`.impeccable.md`](.impeccable.md), `npm run lint` fails on a pre-existing + `jest/globals` configuration error (not blocking typecheck or tests). + +## Run locally + +Requirements: Node ≥ 22.11 (`engines` in `package.json`), the Android SDK and a JDK. The project doesn't use Expo. ```bash +git clone https://git.evandro.dev.br/evandro/HealthTracking.git +cd HealthTracking npm install npm run typecheck -npm run lint npm test -- --runInBand +npm run lint ``` -Run Metro: - ```bash -npm run start +npm run start # Metro +npm run android # run on a device/emulator ``` -Run Android on a device/emulator: - -```bash -npm run android -``` - -Build Android debug APK: +Debug APK: ```bash cd android ./gradlew assembleDebug +# output: android/app/build/outputs/apk/debug/app-debug.apk ``` -If Gradle reports `No Java compiler found`, point `JAVA_HOME` to Android Studio JBR: +If Gradle reports `No Java compiler found`, point `JAVA_HOME` to the JBR bundled with Android Studio: ```bash -export JAVA_HOME="/c/Users/Evandro/AppData/Local/Programs/Android Studio/jbr" +export JAVA_HOME="/jbr" export PATH="$JAVA_HOME/bin:$PATH" -cd android -./gradlew assembleDebug ``` -If React Native autolinking keeps a stale package after renaming Android package paths, -clean generated Gradle caches before rebuilding: +If React Native autolinking keeps a stale package after renaming Android package paths, clean the generated +Gradle caches before rebuilding: ```bash -cd .. rm -rf android/.gradle android/app/build android/build .gradle -cd android -./gradlew assembleDebug +cd android && ./gradlew assembleDebug ``` -Debug APK output: +## Notices -```text -android/app/build/outputs/apk/debug/app-debug.apk -``` +- The literature notes in [`docs/data/pesquisa.md`](docs/data/pesquisa.md) state that population tables and + formula-based estimates are not a substitute for measured testing. +- Built on [React Native](https://reactnative.dev/), [Reanimated](https://docs.swmansion.com/react-native-reanimated/), + [Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/), + [react-native-svg](https://github.com/software-mansion/react-native-svg), + [Lucide](https://lucide.dev/) icons and [JetBrains Mono](https://www.jetbrains.com/lp/mono/).