Part of epic #983. Android currently has NO tracker (no location permission, no map dependency, no tracking code).
Port the iOS v1 tracker to Kotlin/Compose, file-for-file:
Models + TrackStats exact port, with TrackStatsTest pinning the SAME fixtures as iOS TrackStatsTests.swift (111.195 km / pause fixture / formatDuration strings) — creates the repo's first unit-test source set and finally makes the "keep all three stacks in sync" comment true.
RouteStore (file-per-route JSON + crash snapshot + delete tombstones), FusedLocationClient, ActivityRecorder (exact ingest filter: acc>50 reject, ≥2s AND ≥5m per segment), foreground TrackingService (type=location, while-in-use only — NO background-location permission), RouteSyncService (single-flight upload + tombstone flush, 404 settles).
MapLibre Android with the iOS TrackerStyle.json copied verbatim; Compose Tracker/History/Detail screens + nav entry; Hilt module; prominent-disclosure dialog before permission ask (Play policy).
Ghost race mode on Android is a FOLLOW-UP after this lands (engine ports from iOS GhostComparator).
Part of epic #983. Android currently has NO tracker (no location permission, no map dependency, no tracking code).
Port the iOS v1 tracker to Kotlin/Compose, file-for-file:
- Models + `TrackStats` exact port, with `TrackStatsTest` pinning the SAME fixtures as iOS `TrackStatsTests.swift` (111.195 km / pause fixture / formatDuration strings) — creates the repo's first unit-test source set and finally makes the "keep all three stacks in sync" comment true.
- `RouteStore` (file-per-route JSON + crash snapshot + delete tombstones), `FusedLocationClient`, `ActivityRecorder` (exact ingest filter: acc>50 reject, ≥2s AND ≥5m per segment), foreground `TrackingService` (type=location, while-in-use only — NO background-location permission), `RouteSyncService` (single-flight upload + tombstone flush, 404 settles).
- MapLibre Android with the iOS `TrackerStyle.json` copied verbatim; Compose Tracker/History/Detail screens + nav entry; Hilt module; prominent-disclosure dialog before permission ask (Play policy).
- Deps: maplibre android-sdk, play-services-location, lifecycle-process.
Ghost race mode on Android is a FOLLOW-UP after this lands (engine ports from iOS GhostComparator).
Premise correction: this ticket said "Android has NO tracker" — that was read from a stale local checkout (8 commits behind). spikersoft-android PR #3 (merged 2026-08-05, ref #920) already landed a base tracker: models + TrackStats with the shared fixtures, ingest filter, location foreground service, Room store with tombstones/crash snapshots, WorkManager sync, MapLibre map, and all three Compose screens.
So instead of a re-port, spikersoft-android PR #4 audits the merged code against the iOS behavioral contract and closes the real gaps:
Fixes a real breakage on main: tracker_style.json was loaded from assets/ but that dir is gitignored (Godot sync target) — the map was blank on every fresh clone. Style now committed sha-identical to iOS at assets-tracker/ + assets.srcDir; verified inside the APK.
Tests: 15/15 green incl. new 7-case recorder test; fixtures eye-checked against iOS (111.195 km etc.).
Kept deliberately from PR #3's architecture (not regressed to iOS file-for-file): Room instead of file-per-route JSON, WorkManager instead of foreground-only sync, MapLibre 13.4.1. Behavioral contract items all hold.
Outstanding: emulator GPX end-to-end pass; ghost racing on Android remains the follow-up after this merges.
Premise correction: this ticket said "Android has NO tracker" — that was read from a **stale local checkout** (8 commits behind). spikersoft-android PR #3 (merged 2026-08-05, ref #920) already landed a base tracker: models + TrackStats with the shared fixtures, ingest filter, location foreground service, Room store with tombstones/crash snapshots, WorkManager sync, MapLibre map, and all three Compose screens.
So instead of a re-port, spikersoft-android **PR #4** audits the merged code against the iOS behavioral contract and closes the real gaps:
- **Fixes a real breakage on main**: `tracker_style.json` was loaded from `assets/` but that dir is gitignored (Godot sync target) — the map was blank on every fresh clone. Style now committed sha-identical to iOS at `assets-tracker/` + `assets.srcDir`; verified inside the APK.
- `segment` on the upload wire (epic #983 contract), null-accuracy fix rejection, 1s HIGH_ACCURACY request with recorder-side sampling, pause stops location collection, iOS-shaped crash recovery (Resume/Discard dialog), stop→save-sheet flow, ON_START foreground sync.
- Tests: 15/15 green incl. new 7-case recorder test; fixtures eye-checked against iOS (111.195 km etc.).
Kept deliberately from PR #3's architecture (not regressed to iOS file-for-file): Room instead of file-per-route JSON, WorkManager instead of foreground-only sync, MapLibre 13.4.1. Behavioral contract items all hold.
Outstanding: emulator GPX end-to-end pass; ghost racing on Android remains the follow-up after this merges.
Resolved in spikersoft-android PR #4 (merged to main), on top of the base tracker that PR #3 had already landed (see premise-correction comment above). Gap-closing audit vs the iOS behavioral contract: committed the gitignored map style (blank-map fix), segment on the upload wire, ingest parity (null-accuracy rejection, 1s HIGH_ACCURACY request), pause stops location collection, iOS-shaped crash recovery, stop→save-sheet flow, ON_START sync. 15/15 unit tests green with the shared TrackStats fixtures. Android ghost racing remains the epic #983 follow-up; the record-follow UX fix is #988 (PR #5). Closing.
Resolved in spikersoft-android PR #4 (merged to `main`), on top of the base tracker that PR #3 had already landed (see premise-correction comment above). Gap-closing audit vs the iOS behavioral contract: committed the gitignored map style (blank-map fix), `segment` on the upload wire, ingest parity (null-accuracy rejection, 1s HIGH_ACCURACY request), pause stops location collection, iOS-shaped crash recovery, stop→save-sheet flow, ON_START sync. 15/15 unit tests green with the shared TrackStats fixtures. Android ghost racing remains the epic #983 follow-up; the record-follow UX fix is #988 (PR #5). Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of epic #983. Android currently has NO tracker (no location permission, no map dependency, no tracking code).
Port the iOS v1 tracker to Kotlin/Compose, file-for-file:
TrackStatsexact port, withTrackStatsTestpinning the SAME fixtures as iOSTrackStatsTests.swift(111.195 km / pause fixture / formatDuration strings) — creates the repo's first unit-test source set and finally makes the "keep all three stacks in sync" comment true.RouteStore(file-per-route JSON + crash snapshot + delete tombstones),FusedLocationClient,ActivityRecorder(exact ingest filter: acc>50 reject, ≥2s AND ≥5m per segment), foregroundTrackingService(type=location, while-in-use only — NO background-location permission),RouteSyncService(single-flight upload + tombstone flush, 404 settles).TrackerStyle.jsoncopied verbatim; Compose Tracker/History/Detail screens + nav entry; Hilt module; prominent-disclosure dialog before permission ask (Play policy).Ghost race mode on Android is a FOLLOW-UP after this lands (engine ports from iOS GhostComparator).
Premise correction: this ticket said "Android has NO tracker" — that was read from a stale local checkout (8 commits behind). spikersoft-android PR #3 (merged 2026-08-05, ref #920) already landed a base tracker: models + TrackStats with the shared fixtures, ingest filter, location foreground service, Room store with tombstones/crash snapshots, WorkManager sync, MapLibre map, and all three Compose screens.
So instead of a re-port, spikersoft-android PR #4 audits the merged code against the iOS behavioral contract and closes the real gaps:
tracker_style.jsonwas loaded fromassets/but that dir is gitignored (Godot sync target) — the map was blank on every fresh clone. Style now committed sha-identical to iOS atassets-tracker/+assets.srcDir; verified inside the APK.segmenton the upload wire (epic #983 contract), null-accuracy fix rejection, 1s HIGH_ACCURACY request with recorder-side sampling, pause stops location collection, iOS-shaped crash recovery (Resume/Discard dialog), stop→save-sheet flow, ON_START foreground sync.Kept deliberately from PR #3's architecture (not regressed to iOS file-for-file): Room instead of file-per-route JSON, WorkManager instead of foreground-only sync, MapLibre 13.4.1. Behavioral contract items all hold.
Outstanding: emulator GPX end-to-end pass; ghost racing on Android remains the follow-up after this merges.
Resolved in spikersoft-android PR #4 (merged to
main), on top of the base tracker that PR #3 had already landed (see premise-correction comment above). Gap-closing audit vs the iOS behavioral contract: committed the gitignored map style (blank-map fix),segmenton the upload wire, ingest parity (null-accuracy rejection, 1s HIGH_ACCURACY request), pause stops location collection, iOS-shaped crash recovery, stop→save-sheet flow, ON_START sync. 15/15 unit tests green with the shared TrackStats fixtures. Android ghost racing remains the epic #983 follow-up; the record-follow UX fix is #988 (PR #5). Closing.