Commit Graph

8 Commits

Author SHA1 Message Date
Davide Scaini 946da685e5 feat(mobile): editable activity title for local activities
Adds edits_json column (migration v3) to store user overrides separately
from detail_json so Option A server re-extraction never clobbers them.

- Tap the title in the detail screen to edit (local activities only, shown
  with a ✎ hint). Saves on keyboard dismiss via onEndEditing.
- Cards and search display user_title ?? title.
- Raw upload: user_title sent to server -> sidecar written so web UI shows
  the correct title (server re-extracts from FIT, which has Karoo's title).
- BAS upload: detail.title overridden before sending, no sidecar needed.
2026-04-27 15:20:19 +02:00
Davide Scaini 090d4bd8dc feat(mobile): search/filter tab — sport, date, sort; hidden on Karoo
Adds a fourth tab visible only on Android API 29+ (full phone, not Karoo).
Filters by sport pill, date preset (7d/30d/6mo/year), and sort order
(newest/distance/elevation). Paginated FlatList with the same activity cards.

ActivityCard extracted to mobile/components/ActivityCard.tsx so both the
feed tab and the new search tab share the same component without duplication.
2026-04-27 15:10:35 +02:00
Davide Scaini 7a65ed2078 fix(mobile): clear technical debt — real SHA-256, feed pagination, search
source_hash: BAS JSON import now computes SHA-256 via crypto.subtle.digest
instead of the '${id}-${length}' stub. No extra package — Hermes supports
Web Crypto API natively.

Feed pagination: useActivities(query, limit) accepts a LIMIT parameter.
The feed screen starts at 50, calls loadMore() via FlatList onEndReached
(threshold 0.3) to increment by 50 each time. useActivityCount(query)
drives the hasMore guard so loadMore is a no-op at the end of the list.

Feed search: compact TextInput below the header filters by title via
SQLite json_extract LIKE. Changing the query resets limit to PAGE_SIZE
so stale paginated results don't linger.

Docs: close the three resolved debt items; keep only the accepted
background-polling limitation as a known gap.
2026-04-27 11:53:43 +02:00
Davide Scaini 2f53fbc359 feat(mobile): batch import + Karoo auto-import from watch folder
- Import tab now accepts multiple files at once (DocumentPicker multiple:true),
  processes them sequentially through Pyodide, and shows a summary with per-file
  errors on completion.

- DB migration v2 adds source_path column (original filesystem path before copy)
  and an index on it, enabling O(1) deduplication for watch-folder imports.

- On Android, if auto_import_path is set, the Import tab scans the directory on
  mount and on AppState 'active' (app foreground), then automatically imports any
  FIT files not yet in the DB. Designed for Karoo: finish a ride, open the app,
  new files import without any manual steps.

- insertActivity now accepts optional source_path; both importBasJson and
  importNativeFile pass it through (null for files picked via DocumentPicker,
  real path for watch-folder files).
2026-04-25 21:25:54 +02:00
Davide Scaini 1ac35c84e0 feat: add delete button for local activities (single and bulk)
- Detail screen: Delete button (top-right, red) with confirmation alert;
  deletes SQLite row and original file via expo-file-system
- Feed screen: long-press card to enter select mode; checkbox + blue
  border on selected cards; bottom action bar with bulk Delete N button;
  header switches to show count + Cancel
- db/queries: deleteActivity (returns original_path) and deleteActivities
  (bulk, returns all original paths)
2026-04-25 13:43:12 +02:00
Davide Scaini c1e4ab5af7 feat: add reset synced data button to settings 2026-04-24 15:45:51 +02:00
Davide Scaini 44b2878b14 feat: Phase 0.5 — remote feed sync via Bearer token auth
Server (bincio/serve/server.py):
- Add _require_auth: accepts session cookie OR Authorization: Bearer token
- POST /api/auth/token: same as /api/auth/login but returns token in body
  (password used once, not stored; mobile stores only the session token)
- GET /api/feed: auth-gated; reads _merged/index.json for the user and
  returns the activities array as JSON

Mobile:
- db/sync.ts: syncFeed(db) fetches /api/feed, upserts each summary into
  local SQLite as origin='remote'; skips locally-imported activities
- db/queries.ts: add upsertRemoteActivity (INSERT ... ON CONFLICT DO UPDATE
  WHERE origin='remote' — never overwrites local imports); fix feed sort
  order to started_at DESC instead of insertion order
- settings.tsx: Connect section — password field (not persisted) + Connect
  button calls POST /api/auth/token and stores token; Disconnect clears it
- index.tsx: ↓ Sync button + pull-to-refresh both trigger syncFeed; cloud
  badge on remote activities; empty state updated
2026-04-24 12:07:49 +02:00
Davide Scaini b37df88fe1 feat: Phase 0 mobile app scaffold — Expo 55, SQLite, Feed/Import/Settings screens 2026-04-24 10:39:06 +02:00