Written for where the app actually is -- you and friends riding -- rather than jumping straight to a store listing. Stage 0 is today: on Android, messaging friends the APK is a complete answer and may always be. On iOS it is not, because free signing gives 7-day builds and needs your Mac each time, so anyone riding with an iPhone forces the next stage. Stage 1 is the one that fits a riding group: $99/yr for TestFlight turns install into one tap with 90-day builds, and $25 once covers Play internal testing. Both need a privacy policy URL before any tester touches them, since the app records location. Stage 2 covers public listings, and flags that background location is the common rejection on both stores -- pre-empt it with a demo video of a real ride. Stage 3 lays out monetization honestly: paid up front, a one-time unlock for local extras, or a subscription for cloud features. The last one is the only model that justifies recurring money and the only one that brings servers, accounts, GDPR deletion and Apple's in-app account deletion requirement. Recommendation is to stay free now, and reach for a one-time unlock before a subscription. Also noted a positioning question worth settling before any store listing: the pipeline is activity-agnostic, and whether Rippr is a motorcycle app or a general ride recorder decides the category, the screenshots and the audience. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
4.8 KiB
Markdown
105 lines
4.8 KiB
Markdown
# 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 <device-id>
|
||
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=<jdk-21> ./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/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.
|