feat(workouts): add swipe-to-delete with gesture handler and pull-to-refresh
This commit is contained in:
@@ -25,7 +25,7 @@ src/
|
||||
navigation/ # React Navigation (RootNav, TabNav, feature stacks)
|
||||
db/ # Drizzle schema, client, relations, operations
|
||||
providers/ # DatabaseProvider, SettingsProvider (MMKV)
|
||||
utils/ # formatTime, calculateSwolf, etc.
|
||||
utils/ # formatTime, calculateSwolf, uuid, etc.
|
||||
constants/ # strokes, poolLengths, colors
|
||||
types/ # workout.ts, common.ts
|
||||
modules/watch-connectivity/# Expo native module bridging WCSession
|
||||
@@ -45,7 +45,7 @@ targets/watch/ # Apple Watch app (Swift/SwiftUI) — survives preb
|
||||
- Lives in `targets/watch/`, NOT in `ios/` — survives `expo prebuild --clean`
|
||||
- Config: `targets/watch/expo-target.config.js`
|
||||
- Build: open `ios/SwimBuddy.xcworkspace` in Xcode, select Watch scheme
|
||||
- Uses App Groups (`group.nl.guido-it.swimbuddy`) for data sharing via UserDefaults
|
||||
- Uses App Groups (`group.nl.guido-it.swimbuddy`) for data sharing
|
||||
- WCSession for real-time messaging, `transferUserInfo` for reliable offline sync
|
||||
- HealthKit for swim workout tracking (indoor swimming)
|
||||
- Swift types `StoredWorkout`, `StoredSet`, `CompletedLap` in `WorkoutManager.swift`
|
||||
@@ -53,12 +53,40 @@ targets/watch/ # Apple Watch app (Swift/SwiftUI) — survives preb
|
||||
## Database
|
||||
|
||||
- **expo-sqlite** + **Drizzle ORM** (SQLite dialect, expo driver)
|
||||
- Schema: `src/db/schema.ts` — 4 tables: templates, workouts, sets, laps
|
||||
- Schema: `src/db/schema.ts` — tables: templates, workouts, sets, laps
|
||||
- Relations: `src/db/relations.ts`
|
||||
- CRUD: `src/db/operations.ts`
|
||||
- Client: `src/db/client.ts` — exports `db` and `DATABASE_NAME`
|
||||
- Migrations in `drizzle/` — generated by `npx drizzle-kit generate`
|
||||
|
||||
## WCSession Gotchas (Critical)
|
||||
|
||||
These are hard-won bugs that will waste hours if rediscovered:
|
||||
|
||||
- **`NSNull` crashes `sendMessage`**: JS `null` becomes `NSNull` in ObjC, causing `WCErrorCodePayloadUnsupportedTypes` (Code 7010). Always strip `null`/`undefined` values before sending via `cleanForWCSession()`.
|
||||
- **Two delegate methods conflict**: Having both `session(_:didReceiveMessage:)` AND `session(_:didReceiveMessage:replyHandler:)` on the same delegate causes iOS to deliver neither. Watch uses `didReceiveMessage` (no replyHandler); phone uses `didReceiveMessage:replyHandler:` for bidirectional sync.
|
||||
- **`isReachable` is unreliable**: Only `true` when both apps are foreground. Check `session.activationState == .activated` instead for connection readiness.
|
||||
- **Phone `sendMessage` must use `replyHandler: nil`**: Otherwise JS thread blocks waiting for a reply that watch never sends. Use fire-and-forget for workout plans; use `transferUserInfo` for data sync.
|
||||
- **Watch `suiteName` vs `UserDefaults.standard`**: Watch only needs local storage, so use `UserDefaults.standard` — the App Groups suite name is only needed for phone↔watch shared data.
|
||||
- **Phone `pendingEvents` polling**: Phone polls `pollEvents()` every 2s because Expo Modules' `sendEvent` API doesn't work on `AppContext`. Watch data arrives via `didReceiveUserInfo` delegate → stored in `pendingEvents` array.
|
||||
|
||||
## Watch ↔ Phone Data Flow
|
||||
|
||||
- **Phone → Watch**: `sendMessage` with workout plan (fire-and-forget, promise resolves immediately with `["status":"sent"]`)
|
||||
- **Watch → Phone**: `transferUserInfo` with completed workout data (reliable, works in background)
|
||||
- **Phone receives**: `pollEvents()` called from JS every 2s, `saveWatchCompletedWorkout()` writes to SQLite
|
||||
|
||||
## UI Conventions
|
||||
|
||||
- Swipe-to-delete on lists uses `SwipeableRow` component (`react-native-gesture-handler`)
|
||||
- BuilderScreen pool length is a 25m/50m toggle (not read-only from settings)
|
||||
- Templates and Stats features removed from navigation (DB schema preserved but UI deleted)
|
||||
- Ionicons used for bottom tab icons (`@expo/vector-icons`)
|
||||
|
||||
## NativeWind v5 TextInput Crash
|
||||
|
||||
`react-native-css` has a bug where the `textAlign` mapping on `TextInput` causes a crash (`path.split` on boolean `true`). The fix is in `src/utils/` — don't remove the workaround without testing TextInput rendering.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `metro.config.js` has `.sql` in `sourceExts` — required for Drizzle migration imports
|
||||
|
||||
Reference in New Issue
Block a user