Previous script used NorthWest gravity with fixed pixel offsets, placing
the BR pair in the upper-left quadrant. When Android clips to a circle
the letters appeared off-center.
New approach: -gravity Center with ±HALF offsets so each letter is
symmetrically placed around the canvas centre mathematically. No
subshells, no pixel guesswork. Regenerated all four icon assets.
New src/services/offline.ts:
- downloadRegion(): createPack with bounds, zoom 6-16, progress callback
- listRegions() / deleteRegion(): pack management
- expandBounds(): adds 5km buffer around the visible area
- formatBytes(): human-readable size string
RecordingScreen:
- MapRef attached to Map component to read getBounds()
- '↓ Offline' overlay button (Liberty style + idle state only)
- Modal: name input → download → progress bar with % and MB counter
- Raster styles show no download button (not supported by OfflineManager)
Settings → App → Offline maps:
- Lists all downloaded regions with size and tile count
- Delete with confirm alert
- Placeholder text when no regions exist
New src/mapStyles.ts defines five styles from bincio_planner's sources:
- Liberty (OpenFreeMap vector, default)
- CyclOSM (cycling infrastructure raster)
- Topo (OpenTopoMap elevation raster)
- Satellite (Esri World Imagery raster)
- OSM (standard raster fallback)
Raster sources are wrapped in a StyleSpecification so MapLibre handles
them natively. Setting persisted via ThemeContext (AsyncStorage key
mapTileStyle). RecordingScreen and ActivityDetailScreen both read from
context so the style updates everywhere simultaneously.
MAP_STRATEGY.md added (untracked) with full map roadmap.
- scripts/generate_icons.sh: ImageMagick script — dark #09090b bg,
white B and red R (#ef4444) at the same 480pt size, NotoSans-Bold.
Generates icon, adaptive foreground, monochrome, and splash variants.
- app.json: name changed to 'Bincio Rec'; adaptive icon backgroundColor
updated to #09090b; removed redundant backgroundImage
Removed iOS-only presentationStyle='pageSheet'. Modal now applies
useSafeAreaInsets() padding so it doesn't overlap the status bar
or gesture navigation bar on Android.
Tap any saved recording to open ActivityDetailScreen:
- Full-screen MapLibre map with track fitted to bounds (LngLatBounds padding)
- Stats panel: duration, distance, avg speed, elevation gain (computed
from track points), avg HR/power/cadence, point count
- 'Edit' button in header opens a pageSheet modal with title TextInput,
sport grid, and subtype pills — same controls as PostRecordingScreen
- updateRecording() added to db.ts; edits update header title and sport
summary without navigating away
SavedRecordingsScreen: tapping a card in normal mode navigates to detail;
tapping in selection mode still toggles the checkbox.
Select button in header enters selection mode. Cards show a checkbox and
become tappable. Header updates to show N selected + Cancel.
Bulk actions (bottom bar):
- Export: sequential Sharing.shareAsync for each selected file
- Upload: sequential upload with N/total progress text
- Delete: confirm then delete all selected at once
- Merge: parses each GPX file, combines track points sorted by time,
prompts for title via Modal, saves as new recording
parseGpxFile() added to gpx.ts: reads file via expo-file-system,
extracts trkpt elements including hr/power/cad extensions via regex.
New src/sports.ts mirrors bincio_activity definitions exactly:
7 sports (cycling/running/hiking/walking/swimming/skiing/other),
10 subtypes grouped by sport, icons and labels matching format.ts.
PostRecordingScreen: sport grid with emoji pills, conditional subtype
row (only for sports that have subtypes), subtype deselectable by
tapping again. Defaults to cycling.
DB: sport + sub_sport columns added; ALTER TABLE migration for existing
installs (try/catch — column already exists is silently ignored).
SavedRecordingsScreen: sport emoji + label + subtype shown in each card.
Three modes selectable from Settings > App > Map orientation:
- North up: map always points north (MapLibre 'default')
- Compass: rotates with device compass heading ('heading')
- Course up: rotates to direction of travel ('course')
Default is North up. Setting persisted in AsyncStorage via ThemeContext.
- Install expo-auth-session + expo-web-browser
- Add 'bincio-rec' URL scheme to app.json for deep-link redirect
- auth.ts: generate PKCE verifier/challenge, open bincio.org/oauth2/authorize
in browser, exchange auth code for RS256 id_token, store in AsyncStorage
- SettingsScreen: remove handle/password fields, single 'Sign in with bincio'
button that opens the browser flow
Replace default MapLibre puck with a custom CircleLayer child:
accent-coloured fill, 2.5px white stroke, map-pitch-aligned.
Dot colour updates live when the palette changes in Settings.
Request foreground location permission on RecordingScreen mount so the
map can find the user immediately. Camera now always has trackUserLocation
set — 'default' when idle/paused (follows position, no rotation) and
'course' when recording (follows direction of travel).
- ThemeContext: dynamic palette (Default/Giro/Tour/Vuelta), font size
(small/medium/large), bold labels — all persisted to AsyncStorage
- Settings: three top tabs; Interface tab has palette picker + font
size pills + bold labels toggle; App tab has km notifications;
Sync tab has bincio instance login + autarchive placeholder
- RecordingScreen: stat labels now use theme accent colour and scale
with fontSize; font weight follows boldLabels setting
- All accent/accentDim usages migrated from static colors to useTheme()
New auth.ts service: login() POSTs to /api/auth/token with handle +
password, stores instanceUrl/handle/apiToken in AsyncStorage, password
never persisted. logout() clears all credentials. loadAuthState()
returns stored credentials or null.
Settings screen now shows a login form (URL + handle + password) when
not connected, and a connected state card with Disconnect button when
logged in. km notifications toggle auto-saves without a separate Save
button.
Replace SVG TrackView with a real MapLibre map:
- OpenFreeMap liberty tiles (no API key)
- Camera follows user in course mode while recording
- GeoJSONSource + LineLayer renders track polyline updated live
- UserLocation dot shows current GPS position
- Sensors button overlaid with semi-transparent background
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent is silently dropped without
the matching manifest permission. Added it to app.json permissions.
Also replaced .catch() chain (which only triggers on thrown errors) with
try/catch blocks so the fallback to IGNORE_BATTERY_OPTIMIZATION_SETTINGS
actually fires. Added resetBatteryOptPrompt() helper to re-trigger the
prompt during testing.
One-time prompt on first launch (Android only) directing the user to
exclude bincio-rec from battery optimization. Uses the direct
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS system dialog with a fallback to
the general settings page for OEMs that block the direct intent.
Dismissal persisted in AsyncStorage so prompt never repeats.
- 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
- Keep-awake now conditional: activates only when recording + toggle on
- Sensor button overlaid on map area navigates to SensorPairing modal
- Pause/resume now start/stop the GPS background task, not just store state
- TrackView renders lat/lon polyline via react-native-svg (no tile server)
- react-native-svg added as dependency
prebuild --clean overwrote CLAUDE.md with an Expo-generated stub.
Restored the original project plan and added ignorePaths in app.json
so Expo skips CLAUDE.md on future prebuild runs.
Sets up the full bincio-rec source tree: Zustand recording store with
haversine stats, background GPS via expo-task-manager, BLE scan/subscribe
for HR and power, GPX writer with Garmin extensions, SQLite recordings
list, multipart upload to bincio-activity, React Navigation stack with
bottom tabs, and build instructions in README.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>