# Rippr A GPS ride recorder for motorcycles, on **Android and iOS**. Records telemetry into a local SQLite database, renders the traversed path on a map afterwards, and exports to GPX/GeoJSON. Built for one specific use: **hit start, put the phone in a pocket, ride, hit stop.** Most of the design follows from that. This is a Flutter port of the native Android app in `~/dojo/rippr` (v1 → v2.0.1). The port is feature-complete; see [docs/port/PARITY-AUDIT.md](docs/port/PARITY-AUDIT.md) for exactly what is verified and what is not. ``` Flutter 3.47 · Dart 3.13 Android minSdk 26 · iOS 12+ ~4,600 lines Dart 175 tests (171 unit/widget, 4 integration) ``` ## Status | Feature | Status | |---|---| | GPS recording, foreground on Android / background mode on iOS | Implemented; **not yet ridden** | | Trips with pause / resume / stop / discard | Working, 50 tests | | Live speed + elapsed clock | Working | | Path on OpenStreetMap, per-segment, speed-coloured | Working; colouring unverifiable without a real ride | | Ride statistics + charts | Working; bit-identical to the Kotlin original | | Rename / delete / merge rides | Working | | GPX + GeoJSON export | Working; byte-identical to the Kotlin original | | Upload to a REST endpoint | Implemented, **no UI** (as in the native app) | | Notification actions (Pause/Resume in the shade) | **Absent** — the one capability lost | | Live map while recording · group ride | Deferred to v3 | > **The port has never recorded a real ride.** No simulator produces velocity, so max > speed, moving time, speed colouring, elevation against real GPS error, battery, and iOS > stationary suspension are all unverified. That is > [docs/port/REAL-RIDE-CHECKLIST.md](docs/port/REAL-RIDE-CHECKLIST.md), and it is the next > thing that should happen. ## Quick start Requires **JDK 17–21** for Android (AGP rejects 25) and Xcode for iOS. ```bash flutter pub get dart run build_runner build # Drift codegen flutter analyze && flutter test flutter test integration_test -d flutter run ``` **Watch the disk.** A full dual-platform build cycle costs roughly 10 GB, and the Android emulator needs 7.4 GB free just to boot. Run `flutter clean` before booting it. ## Cross-language parity The strongest guarantee in this repo. It compiles the **real Kotlin sources** from the native app and diffs them against the Dart port across twenty fixtures: ```bash brew install kotlin JAVA_HOME= ./tool/parity/run.sh ``` Everything matches to the last digit — including the elevation accumulator at `38.959594555022136` and byte-identical GPX/GeoJSON. The single expected difference is speed-derived values, where Kotlin's 32-bit `Float` widens with artefacts Dart's uniform `double` does not reproduce. ## Documentation | Document | Contents | |---|---| | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Why it is built this way, and what changed from the native app | | [docs/v3/](docs/v3/) | v3 tickets — one file per feature, ready to pick up | | [docs/BACKLOG.md](docs/BACKLOG.md) | The v3/v4 reasoning behind those tickets | | [docs/LAUNCH.md](docs/LAUNCH.md) | Getting from "on my phone" to the app stores, in stages, with costs | | [docs/port/IOS-VERIFICATION.md](docs/port/IOS-VERIFICATION.md) | What a Mac can prove about the iPhone build, and what cannot | | [docs/port/PLAN.md](docs/port/PLAN.md) | The 28-task migration plan | | [docs/port/PROGRESS.md](docs/port/PROGRESS.md) | What actually happened, including every bug found | | [docs/port/PARITY-AUDIT.md](docs/port/PARITY-AUDIT.md) | Feature-by-feature, with the evidence behind each claim | | [docs/port/REAL-RIDE-CHECKLIST.md](docs/port/REAL-RIDE-CHECKLIST.md) | **The outstanding work** | | [docs/port/RELEASE-IOS.md](docs/port/RELEASE-IOS.md) | App Review readiness | | [docs/PORT_RESEARCH.md](docs/PORT_RESEARCH.md) | The original research this plan was built on | The native repo's `docs/v1/`, `docs/v2/` and `docs/v3/BACKLOG.md` remain the record of how the app got here and where it is going. ## Two bugs this port found in the native app **Crash recovery measured the dead time as distance.** `restoreAfterProcessDeath` says it resumes into a new segment; it actually adopts the segment a crash left open, so the authoritative recomputation measures straight through the gap. Reproduced at **111 km** of phantom distance. Fixed here. **The elevation regression guard passes on seed luck.** On a shared fixture the algorithm yields ~39 m, which would fail the native test's own 35 m bound. It passes only because `kotlin.random.Random(42)` happens to draw a benign sequence. ## Before shipping - Switch the application id from `com.rippr.port` back to `com.rippr`. The suffix exists so the native app can be installed alongside during the port — **keep it until the real ride comparison is done**. - Replace the default Flutter app icon; the native artwork is in `~/dojo/rippr/design/`. - Build and test a release build. Everything so far has been debug.