Repository navigation
Architecture
Roadstr 1.x is a native Kotlin app — Jetpack Compose for the interface, MapLibre Native for the map. Pure logic (the Nostr protocol, routing and search protocols, navigation) is kept free of Android so it can be tested on a plain JVM; Android-specific I/O sits in thin services. A Flutter engine is still packaged, for one reason only: on the first start of 1.x it reads the previous Flutter app's data once, so an update loses nothing.
android/app/src/main/kotlin/app/roadstr/ # the app (Kotlin, Jetpack Compose)
├── core/ # pure logic with no Android dependency: Nostr protocol (NIP-01/04/19/44/46/47/57/58/78, BIP-340),
│ # the road-report interoperability codec and MeshCore companion protocol (core/protocol/interop,
│ # core/protocol/meshcore), routing and search protocols, navigation phrases, discovery (Explore),
│ # journey and achievements, speech (model contract, end-of-speech detector, address repair)
├── feature/ # one package per screen or flow: home, map, navigation (HUD, host), onboarding, place, profile,
│ # report, route, saved, savedroute, savedtransit, offline, search, settings, transit, performance,
│ # voice, speech, web, wikipedia, activity, history, discovery, journey
├── service/ # I/O: Nostr relays, remote signer (bunker), zaps and NWC, MeshCore radio and bridge, routing, search,
│ # public transport (Transitous, Entur), offline packages, location, hazards, notifications, the
│ # navigation foreground service, the speech models and microphone
├── offlinestt/ # the Kotlin API of the embedded speech engine (vendored)
├── storage/ # preferences, secrets, saved places, history, pending reports
├── migration/ # the reader of the old (Flutter) app's data, transactional and fail-closed
└── startup/ # the launcher activity, the one-time profile import, the guidance runtime holder
android/app/src/test/ # JVM tests of all of the above (about 1,500 cases)
native-android/ # the road-test app (same shell, own stores) and the optional GeckoView variant
lib/ # the Flutter code of 0.5.x, kept for the first-start import and the shared translations
test/ # Dart contract tests that pin generated resources, the build and the docs
tools/kotlin_rewrite/ # generators for Android string resources and test fixtures, release audit
tools/interop/ # independent generator of the road-report test vectors (stdlib-only Python)
third_party/ # vendored code and binaries, each with a record and a rebuild path
docs/ # plans, decisions, risks and release notes
Why GitHub shows roughly half Dart and half Kotlin. The language bar counts the bytes of every tracked file by language; it does not know which code ships. The repository still holds the whole Flutter app (about 1.9 MB), because it reads the previous version's data and shares the translation files, plus about 1.5 MB of generated Dart localisation classes, the Dart test suite and the Dart tools that generate the Android resources. The Kotlin share is the app itself plus its tests.
-
Pure core, thin services.
core/has no Android types. Protocols are validated with golden vectors that are shared between languages and generated by independent implementations. - Fail closed. The first-start import is transactional: it copies before it opens, changes nothing in the old files, and offers Try again or Continue without importing — never a silently empty profile. Downloads are accepted only after size and SHA-256 match; packages are accepted only if they actually open.
- Bounded everything. One HTTP client for all services with a deadline and a response-size limit; parsers cap their input; hostile relays, mirrors, packages and models are expected.
- No hidden background work. Offline packages, voice models and the speech model are downloaded only on an explicit action, resume instead of restarting, and never from a periodic worker.
- Say it in the UI. Anything that sends data to a third party is disclosed where it happens, and the disclosure is pinned by a test.
- Reproducible binaries. Native binaries that are committed rebuild byte for byte from pinned sources, and the Gradle build refuses to start if one differs from its recorded checksum.
The trip, the voice and the GPS live outside the screen, held by a foreground service, so locking the phone, rotating it or swiping the app away does not end a trip. Road guidance is held in memory if Android kills the process; active public-transport guidance has encrypted recovery. The map follows the direction of travel, with the vehicle cursor animated from a continuous follow loop rather than a fixed tween.
| Subsystem | Notes |
|---|---|
| Map | MapLibre Native. Dark themes recolour the standard tiles on the device. Offline maps are local PMTiles sources owned by the package manager, not MapLibre offline packs. See rendering decision |
| Routing | OSRM, GraphHopper and OpenRouteService behind one contract; Valhalla as an opt-in on-device engine. Saved routes are an encrypted, versioned value with their own Keystore alias |
| Search | Nominatim and Overpass, a deterministic multilingual parser for plain-language queries, a discovery layer for Explore |
| Nostr | Relays, signer (Amber via NIP-55, bunkers via NIP-46), zaps and NWC, NIP-44 encrypted sync, a NIP-58 client |
| Voice | Kokoro-82M and Piper on ONNX Runtime with eSpeak NG phonemisation (JNI) |
| Voice search | transcribe.cpp (ggml) through a small JNI facade; Parakeet TDT 0.6B v3; two CPU builds chosen at run time by a feature probe |
| Public transport | Transitous (MOTIS v6 plan API) and Entur (GraphQL), provider-scoped IDs, encrypted saved journeys |
| Radio | MeshCore companion protocol over Bluetooth LE, a bridge with loop prevention and a rate limiter |
The Flutter app stayed in place as the production oracle while the native core grew: forensic baseline, native skeleton,
migration feasibility, pure-core parity, services, UI parity, release parity — each gated on evidence. The documents are in
docs/kotlin-rewrite/; the risks, one by one, are in
RISKS.md and
FEATURE_PARITY.md. The Flutter 0.5.x
line lives on the flutter-maintenance branch.
| Log | Covers |
|---|---|
| docs/offline-engine/DECISIONS.md | Offline packages, saved routes, Valhalla (OFF-001 …) |
| docs/world-discovery/DECISIONS.md | Plain-language search, web results, the in-app browser (D-01 …) |
| docs/rendering-engine-decision.md | Why MapLibre, and what was ruled out |
| docs/kotlin-rewrite/ | The migration, parity and release tooling |
Roadstr's own code is MIT. Third-party components keep their own licences, listed in THIRD_PARTY_NOTICES. Notably:
- eSpeak NG (voice phonemisation) is GPL-3.0-or-later; because the APK binary includes it, a distributed APK has to honour the GPL for that component. Roadstr's source files stay MIT.
- GeckoView (only in the optional in-app browser variant) is MPL-2.0.
- The Parakeet speech model is downloaded on request and licensed CC-BY-4.0; its attribution is shown in Settings.
- GeoNames data used for the offline catalogue is CC BY 4.0.
Roadstr is open source under the MIT licence · Source · Releases · Changelog · Issues
Using Roadstr
- Installation
- Getting started
- Navigation
- Maps and offline use
- Search and Explore
- Road reports
- Public transport
- Voice guidance
- Voice search
- Journey and achievements
- Settings reference
Nostr and money
Trust
Build and extend