diff --git a/AGENTS.md b/AGENTS.md index 171015e..f94f916 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/package-lock.json b/package-lock.json index 47cf60e..705e570 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "react": "19.2.3", "react-native": "0.86.2", "react-native-css": "^3.0.7", + "react-native-gesture-handler": "~2.32.0", "react-native-mmkv": "^4.3.2", "react-native-reanimated": "4.5.1", "react-native-safe-area-context": "~5.7.0", @@ -1610,6 +1611,18 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@esbuild-kit/core-utils": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", @@ -3774,6 +3787,12 @@ "@types/ms": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -3817,12 +3836,20 @@ "version": "19.2.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" } }, + "node_modules/@types/react-test-renderer": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz", + "integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -4639,7 +4666,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -5992,6 +6018,21 @@ "hermes-estree": "0.35.0" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -7938,6 +7979,22 @@ "@babel/types": "^7.26.0" } }, + "node_modules/react-native-gesture-handler": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.32.0.tgz", + "integrity": "sha512-uYIMOKlKENORq2SABE+jIjbPU+h5I/sQKcq2v16zRq848nwEp1fWRVwML4QWqijc8UcXJC25o54S8GQd4Mf2OA==", + "license": "MIT", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "@types/react-test-renderer": "^19.1.0", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-is-edge-to-edge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz", diff --git a/package.json b/package.json index 5e4ae2f..6fd979c 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "react": "19.2.3", "react-native": "0.86.2", "react-native-css": "^3.0.7", + "react-native-gesture-handler": "~2.32.0", "react-native-mmkv": "^4.3.2", "react-native-reanimated": "4.5.1", "react-native-safe-area-context": "~5.7.0", diff --git a/src/components/SwipeableRow.tsx b/src/components/SwipeableRow.tsx new file mode 100644 index 0000000..051207c --- /dev/null +++ b/src/components/SwipeableRow.tsx @@ -0,0 +1,100 @@ +import { useRef } from "react"; +import { View, Text, Pressable, Animated, StyleSheet } from "react-native"; +import { PanGestureHandler, State } from "react-native-gesture-handler"; + +type SwipeableRowProps = { + children: React.ReactNode; + onDelete: () => void; +}; + +export function SwipeableRow({ children, onDelete }: SwipeableRowProps) { + const translateX = useRef(new Animated.Value(0)).current; + const lastOffset = useRef(0); + + const onGestureEvent = Animated.event([{ nativeEvent: { translationX: translateX } }], { + useNativeDriver: true, + }); + + const onHandlerStateChange = (event: any) => { + if (event.oldState === State.ACTIVE) { + const { translationX } = event.nativeEvent; + const newOffset = lastOffset.current + translationX; + + if (newOffset < -80) { + // Swiped left enough — show delete + Animated.spring(translateX, { + toValue: -80, + useNativeDriver: true, + }).start(); + lastOffset.current = -80; + } else { + // Snap back + Animated.spring(translateX, { + toValue: 0, + useNativeDriver: true, + }).start(); + lastOffset.current = 0; + } + } + }; + + const handleDelete = () => { + Animated.timing(translateX, { + toValue: -400, + duration: 200, + useNativeDriver: true, + }).start(() => { + onDelete(); + }); + }; + + return ( + + + + Delete + + + + + {children} + + + + ); +} + +const styles = StyleSheet.create({ + container: { + marginBottom: 12, + }, + deleteBackground: { + position: "absolute", + right: 0, + top: 0, + bottom: 0, + width: 80, + borderRadius: 16, + backgroundColor: "#ef4444", + justifyContent: "center", + alignItems: "center", + }, + deleteButton: { + flex: 1, + justifyContent: "center", + alignItems: "center", + width: "100%", + }, + deleteText: { + color: "white", + fontWeight: "600", + fontSize: 13, + }, + foreground: { + backgroundColor: "white", + borderRadius: 16, + }, +}); diff --git a/src/features/history/screens/HistoryScreen.tsx b/src/features/history/screens/HistoryScreen.tsx index b681dce..3ee029c 100644 --- a/src/features/history/screens/HistoryScreen.tsx +++ b/src/features/history/screens/HistoryScreen.tsx @@ -1,11 +1,13 @@ -import { useEffect, useState } from "react"; -import { View, Text, Pressable, FlatList } from "react-native"; +import { useCallback, useState } from "react"; +import { View, Text, Pressable, FlatList, Alert } from "react-native"; import { useNavigation } from "@react-navigation/native"; +import { useFocusEffect } from "@react-navigation/native"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; import type { HistoryStackParamList } from "@navigation/types"; -import { getAllWorkouts } from "@db/operations"; +import { getAllWorkouts, deleteWorkout } from "@db/operations"; import { formatTime } from "@utils/formatTime"; import { formatDistance } from "@utils/formatDistance"; +import { SwipeableRow } from "@components/SwipeableRow"; type WorkoutRow = { id: string; @@ -20,16 +22,39 @@ type WorkoutRow = { export function HistoryScreen() { const navigation = useNavigation>(); const [workouts, setWorkouts] = useState([]); - - useEffect(() => { - loadWorkouts(); - }, []); + const [refreshing, setRefreshing] = useState(false); const loadWorkouts = async () => { const data = await getAllWorkouts(); setWorkouts((data as WorkoutRow[]).filter((w) => w.finishedAt)); }; + useFocusEffect( + useCallback(() => { + loadWorkouts(); + }, []) + ); + + const onRefresh = async () => { + setRefreshing(true); + await loadWorkouts(); + setRefreshing(false); + }; + + const onDelete = (id: string) => { + Alert.alert("Delete Workout", "Are you sure?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: async () => { + await deleteWorkout(id); + await loadWorkouts(); + }, + }, + ]); + }; + const grouped = workouts.reduce>((acc, w) => { const date = new Date(w.startedAt).toLocaleDateString(); if (!acc[date]) acc[date] = []; @@ -41,19 +66,12 @@ export function HistoryScreen() { return ( - - navigation.navigate("Stats")} - className="bg-slate-100 rounded-2xl p-3 items-center mb-4" - > - View Statistics - - - date} contentContainerClassName="px-4 pb-8" + refreshing={refreshing} + onRefresh={onRefresh} ListEmptyComponent={ No completed workouts yet @@ -65,30 +83,31 @@ export function HistoryScreen() { {date} {rows.map((w) => ( - navigation.navigate("SessionDetail", { sessionId: w.id })} - className="bg-slate-50 rounded-2xl p-4 mb-2 border border-slate-200" - > - - - - {w.name || "Swim Session"} - - - {formatDistance(w.totalDistance ?? 0)} - {w.totalDuration ? ` · ${formatTime(w.totalDuration)}` : ""} - {w.calories ? ` · ${Math.round(w.calories)} kcal` : ""} + onDelete(w.id)}> + navigation.navigate("SessionDetail", { sessionId: w.id })} + className="p-4" + > + + + + {w.name || "Swim Session"} + + + {formatDistance(w.totalDistance ?? 0)} + {w.totalDuration ? ` · ${formatTime(w.totalDuration)}` : ""} + {w.calories ? ` · ${Math.round(w.calories)} kcal` : ""} + + + + {new Date(w.startedAt).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + })} - - {new Date(w.startedAt).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - })} - - - + + ))} )} diff --git a/src/features/history/screens/SessionDetailScreen.tsx b/src/features/history/screens/SessionDetailScreen.tsx index e798ac1..a66e44b 100644 --- a/src/features/history/screens/SessionDetailScreen.tsx +++ b/src/features/history/screens/SessionDetailScreen.tsx @@ -1,7 +1,8 @@ import { useEffect, useState } from "react"; -import { View, Text, ScrollView } from "react-native"; +import { View, Text, ScrollView, Pressable, Alert } from "react-native"; +import { useNavigation } from "@react-navigation/native"; import type { HistoryStackProps } from "@navigation/types"; -import { getWorkoutWithSets } from "@db/operations"; +import { getWorkoutWithSets, deleteWorkout } from "@db/operations"; import { formatTime } from "@utils/formatTime"; import { formatDistance } from "@utils/formatDistance"; import { STROKE_COLORS } from "@constants/strokes"; @@ -34,6 +35,7 @@ type WorkoutDetail = { }; export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail">) { + const navigation = useNavigation(); const [workout, setWorkout] = useState(null); useEffect(() => { @@ -45,6 +47,20 @@ export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail" setWorkout(data as WorkoutDetail | null); }; + const handleDelete = () => { + Alert.alert("Delete Workout", "Are you sure?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: async () => { + await deleteWorkout(route.params.sessionId); + navigation.goBack(); + }, + }, + ]); + }; + if (!workout) { return ( @@ -62,13 +78,21 @@ export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail" return ( - - - {workout.name || "Swim Session"} - - - {new Date(workout.startedAt).toLocaleDateString()} - + + + + {workout.name || "Swim Session"} + + + {new Date(workout.startedAt).toLocaleDateString()} + + + + 🗑 + diff --git a/src/features/workout-builder/screens/BuilderScreen.tsx b/src/features/workout-builder/screens/BuilderScreen.tsx index e052322..d07b7e4 100644 --- a/src/features/workout-builder/screens/BuilderScreen.tsx +++ b/src/features/workout-builder/screens/BuilderScreen.tsx @@ -11,7 +11,8 @@ const STROKES: StrokeType[] = ["Freestyle", "Backstroke", "Breaststroke", "Butte export function BuilderScreen() { const navigation = useNavigation>(); - const { poolLength } = useSettings(); + const { poolLength: defaultPoolLength } = useSettings(); + const [poolLength, setPoolLength] = useState(defaultPoolLength); const { sets, workoutName, @@ -53,6 +54,29 @@ export function BuilderScreen() { /> + + Pool Length + + {[25, 50].map((length) => ( + setPoolLength(length)} + className={`flex-1 py-2.5 rounded-xl items-center ${ + poolLength === length ? "bg-sky-500" : "bg-slate-100" + }`} + > + + {length}m + + + ))} + + + {sets.map((set, index) => ( diff --git a/src/features/workout-builder/screens/WorkoutListScreen.tsx b/src/features/workout-builder/screens/WorkoutListScreen.tsx index c27d872..b786874 100644 --- a/src/features/workout-builder/screens/WorkoutListScreen.tsx +++ b/src/features/workout-builder/screens/WorkoutListScreen.tsx @@ -1,11 +1,13 @@ -import { useEffect, useState } from "react"; -import { View, Text, Pressable, FlatList } from "react-native"; +import { useCallback, useState } from "react"; +import { View, Text, Pressable, FlatList, Alert } from "react-native"; import { useNavigation } from "@react-navigation/native"; +import { useFocusEffect } from "@react-navigation/native"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; import type { WorkoutStackParamList } from "@navigation/types"; import { getAllWorkouts, deleteWorkout } from "@db/operations"; import { formatTime } from "@utils/formatTime"; import { formatDistance } from "@utils/formatDistance"; +import { SwipeableRow } from "@components/SwipeableRow"; type WorkoutRow = { id: string; @@ -19,37 +21,56 @@ type WorkoutRow = { export function WorkoutListScreen() { const navigation = useNavigation>(); const [workouts, setWorkouts] = useState([]); - - useEffect(() => { - loadWorkouts(); - }, []); + const [refreshing, setRefreshing] = useState(false); const loadWorkouts = async () => { const data = await getAllWorkouts(); setWorkouts(data as WorkoutRow[]); }; + useFocusEffect( + useCallback(() => { + loadWorkouts(); + }, []) + ); + + const onRefresh = async () => { + setRefreshing(true); + await loadWorkouts(); + setRefreshing(false); + }; + + const onDelete = (id: string) => { + Alert.alert("Delete Workout", "Are you sure?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: async () => { + await deleteWorkout(id); + await loadWorkouts(); + }, + }, + ]); + }; + return ( navigation.navigate("Builder")} - className="bg-sky-500 rounded-2xl p-4 items-center mb-3" + className="bg-sky-500 rounded-2xl p-4 items-center mb-4" > + New Workout - navigation.navigate("Templates")} - className="bg-slate-100 rounded-2xl p-3 items-center mb-4" - > - Browse Templates - item.id} contentContainerClassName="px-4 pb-8" + refreshing={refreshing} + onRefresh={onRefresh} ListEmptyComponent={ No workouts yet @@ -57,30 +78,32 @@ export function WorkoutListScreen() { } renderItem={({ item }) => ( - navigation.navigate("PreSwim", { workoutId: item.id })} - className="bg-slate-50 rounded-2xl p-4 mb-3 border border-slate-200" - > - - - - {item.name || "Untitled Workout"} - - - {formatDistance(item.totalDistance ?? 0)} - {item.totalDuration ? ` · ${formatTime(item.totalDuration)}` : ""} - - - {new Date(item.startedAt).toLocaleDateString()} - - - {item.finishedAt && ( - - Done + onDelete(item.id)}> + navigation.navigate("PreSwim", { workoutId: item.id })} + className="p-4" + > + + + + {item.name || "Untitled Workout"} + + + {formatDistance(item.totalDistance ?? 0)} + {item.totalDuration ? ` · ${formatTime(item.totalDuration)}` : ""} + + + {new Date(item.startedAt).toLocaleDateString()} + - )} - - + {item.finishedAt && ( + + Done + + )} + + + )} /> diff --git a/src/navigation/HistoryNavigator.tsx b/src/navigation/HistoryNavigator.tsx index 57d2ab0..ead6ed5 100644 --- a/src/navigation/HistoryNavigator.tsx +++ b/src/navigation/HistoryNavigator.tsx @@ -2,7 +2,6 @@ import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { HistoryStackParamList } from "./types"; import { HistoryScreen } from "../features/history/screens/HistoryScreen"; import { SessionDetailScreen } from "../features/history/screens/SessionDetailScreen"; -import { StatsScreen } from "../features/history/screens/StatsScreen"; const Stack = createNativeStackNavigator(); @@ -25,11 +24,6 @@ export function HistoryNavigator() { component={SessionDetailScreen} options={{ title: "Session Detail" }} /> - ); } diff --git a/src/navigation/RootNavigator.tsx b/src/navigation/RootNavigator.tsx index c647364..9cf8890 100644 --- a/src/navigation/RootNavigator.tsx +++ b/src/navigation/RootNavigator.tsx @@ -5,7 +5,6 @@ import { BuilderScreen } from "../features/workout-builder/screens/BuilderScreen import { PreSwimScreen } from "../features/active-workout/screens/PreSwimScreen"; import { PostSwimScreen } from "../features/active-workout/screens/PostSwimScreen"; import { SessionDetailScreen } from "../features/history/screens/SessionDetailScreen"; -import { StatsScreen } from "../features/history/screens/StatsScreen"; const Stack = createNativeStackNavigator(); @@ -43,11 +42,6 @@ export function RootNavigator() { component={SessionDetailScreen} options={{ title: "Session" }} /> - ); } diff --git a/src/navigation/WorkoutNavigator.tsx b/src/navigation/WorkoutNavigator.tsx index fef766d..56aa1e2 100644 --- a/src/navigation/WorkoutNavigator.tsx +++ b/src/navigation/WorkoutNavigator.tsx @@ -2,7 +2,6 @@ import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { WorkoutStackParamList } from "./types"; import { WorkoutListScreen } from "../features/workout-builder/screens/WorkoutListScreen"; import { BuilderScreen } from "../features/workout-builder/screens/BuilderScreen"; -import { TemplatesScreen } from "../features/templates/screens/TemplatesScreen"; import { PreSwimScreen } from "../features/active-workout/screens/PreSwimScreen"; const Stack = createNativeStackNavigator(); @@ -26,11 +25,6 @@ export function WorkoutNavigator() { component={BuilderScreen} options={{ title: "Build Workout" }} /> - =