- Foreground notification handler in App.tsx (iOS shows banners while active) - requestNotificationPermissions() called on app mount - GPS task tracks running distance per recording session (module-level state) - Fires immediate notification at each km crossed, gated on kmNotifications setting
6.3 KiB
bincio-rec
GPS activity recorder for the bincio ecosystem. Companion to bincio-autarchive (archive/sync app): rec makes the data, autarchive stores and syncs it.
Concept
A focused, minimal recording app for cycling, running, hiking, and walking. Records GPS track + BLE sensor data, saves to GPX, optionally uploads directly to a bincio-activity server. Future: live navigation / turn-by-turn instructions.
The app does one thing well: record. No browsing, no analysis — that's autarchive's job.
Ecosystem
bincio-rec → writes GPX files to shared storage
bincio-autarchive → imports GPX, stores locally, syncs to server
bincio-activity → server: parses GPX, stores activities, serves API
bincio-auth → auth service: issues JWTs for all bincio apps
bincio-rec can also upload directly to bincio-activity via /api/upload/raw (same endpoint autarchive uses), with instance URL + API token configured in settings.
Tech Stack
Expo Prebuild (not managed Expo, not bare RN).
- Managed Expo ruled out: BLE requires native modules
- Bare RN ruled out: Expo library ecosystem is valuable (location, notifications, file system)
- Prebuild gives full native access while keeping Expo tooling and EAS Build
Key libraries:
expo-location+expo-task-manager— background GPS recordingreact-native-ble-plx— BLE sensors (HR, power, cadence)@maplibre/maplibre-react-native— live track map, future navigationexpo-notifications— km milestone alerts, future navigation cuesexpo-keep-awake— keep screen on during recording (user-toggleable)expo-file-system— write GPX/JSON to shared storageexpo-sqlite— local DB for past recordings list
Target: Android first, iOS second.
v1 Screens
- Sensor pairing — scan BLE, pair HR monitor / power meter / cadence sensor; persisted
- Recording — start / pause / resume / stop
- Live stats: elapsed time, distance, current+avg speed, HR, power, cadence, elevation gain
- Map with track drawn in real time
- Keep-awake toggle
- Post-recording summary — stats overview, title field, save / discard
- Saved recordings — list of past sessions; tap to export GPX or upload to server
- Settings — bincio instance URL + API token; km notification toggle; upload format
Output Format
GPX with Garmin trackpoint extensions — one <trkpt> per second with:
- lat/lon/elevation from GPS
<gpxtpx:hr>— heart rate<gpxtpx:power>— power (watts)<gpxtpx:cad>— cadence (rpm)
bincio-activity already parses this format via bincio.extract.parsers.
Files saved to a user-accessible location (iOS Files app / Android shared storage) so bincio-autarchive can import them manually.
Known Platform Concerns
- Android battery optimization: aggressive OEMs (Xiaomi, Samsung, Huawei) kill background tasks. Prompt user to whitelist bincio-rec in battery settings on first launch. Every recording app has this problem — no framework-level fix.
- iOS background location: requires
locationbackground mode inInfo.plistand "Always" location permission. Fine for sideloading/TestFlight; App Store submission requires clear justification. - iOS foreground notifications: notification sounds are suppressed when app is in foreground — use
expo-avaudio cue instead for km alerts while screen is on.
Out of Scope (v1)
- Navigation / turn-by-turn routing
- Auto-pause (stop lights, etc.)
- Structured workouts / intervals
- Offline maps
- Live tracking / sharing
v1 Completion Plan
Scaffold is done (all screens, navigation, store, services, GPX, DB, upload). Items below are what remains before v1 is shippable.
1 — Quick fixes ✅
- Keep-awake toggle — wired to
keepAwakestore state viaactivateKeepAwakeAsync/deactivateKeepAwake; toggle button visible in controls bar - Sensor button on Recording screen — "⚡ Sensors" button overlaid on map area navigates to SensorPairing modal
- GPS pause/resume —
handlePausecallsstopGpsRecording(),handleResumecallsstartGpsRecording() - Track view —
TrackViewcomponent renders recorded lat/lon points as a scaled SVG polyline on a dark background (no tile server needed); replaces the grey placeholder
2 — BLE ✅
- Android runtime permissions —
requestBlePermissions()requestsBLUETOOTH_SCAN+BLUETOOTH_CONNECTon Android 12+ (API 31+) before scanning - Cadence CSC parsing — stateful parsing in
parseCscMeasurement(): tracks previous crank revolution count + event time (uint16, 1/1024 s), computes RPM from delta with uint16 rollover handling - BLE persistence —
savePairedDevice/loadPairedDevices/removePairedDeviceinble.tsvia AsyncStorage; SensorPairingScreen shows saved sensors at top, auto-attempts reconnect on mount, Forget button removes a device
3 — Km notifications ✅
- Permission request —
requestNotificationPermissions()called inApp.tsxon mount; foreground notification handler set at module level so iOS shows banners while app is active - Milestone tracker — module-level
runningDistanceMeters/lastNotifiedKm/prevPointingps.ts, reset on eachstartGpsRecording(); each incoming location adds haversine distance and fires a notification when a new km is crossed, gated on thekmNotificationsAsyncStorage setting
4 — Android battery optimization prompt (1–2 hours)
- On first launch, detect if the app is affected by battery optimization (
expo-intent-launcher) and show a one-time prompt directing the user to whitelist bincio-rec; persist dismissal in AsyncStorage so it only shows once
5 — Map (optional upgrade)
The track view from section 1 already shows the GPX polyline scaled to fit the screen. MapLibre can be added later for a real basemap (streets/terrain), but is not required for v1.
- MapLibre basemap — replace
TrackViewSVG with<MapLibreGL.MapView>+ a tile source (e.g. OpenFreeMap); callMapLibreGL.setAccessToken(null)for raster-free usage - Camera follow — add
<MapLibreGL.Camera>that followstrackPoints[last]during recording - Line layer — replace SVG polyline with
<MapLibreGL.ShapeSource>+<MapLibreGL.LineLayer>