166 lines
6.3 KiB
TypeScript
166 lines
6.3 KiB
TypeScript
import { useState } from "react";
|
|
import { View, Text, TextInput, Pressable, ScrollView, Alert } from "react-native";
|
|
import { useNavigation } from "@react-navigation/native";
|
|
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
|
import type { RootStackParamList } from "@navigation/types";
|
|
import { useWorkoutBuilder } from "../hooks/useWorkoutBuilder";
|
|
import { useSettings } from "../../../providers/WatchProvider";
|
|
import type { StrokeType } from "@types/workout";
|
|
|
|
const STROKES: StrokeType[] = ["Freestyle", "Backstroke", "Breaststroke", "Butterfly", "Mixed"];
|
|
|
|
export function BuilderScreen() {
|
|
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
|
const { poolLength: defaultPoolLength } = useSettings();
|
|
const [poolLength, setPoolLength] = useState(defaultPoolLength);
|
|
const {
|
|
sets,
|
|
workoutName,
|
|
setWorkoutName,
|
|
totalDistance,
|
|
isSaving,
|
|
addSet,
|
|
updateSet,
|
|
removeSet,
|
|
save,
|
|
} = useWorkoutBuilder();
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
const workoutId = await save(poolLength);
|
|
navigation.navigate("PreSwim", { workoutId });
|
|
} catch (e) {
|
|
console.error("Failed to save workout:", e);
|
|
Alert.alert("Error", `Failed to save workout: ${e}`);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ScrollView className="flex-1 bg-white">
|
|
<View className="p-4">
|
|
<Text className="text-2xl font-bold text-slate-900 mb-1">Build Workout</Text>
|
|
<Text className="text-slate-500 mb-4">
|
|
{totalDistance}m total · {poolLength}m pool
|
|
</Text>
|
|
|
|
<View className="mb-4">
|
|
<Text className="text-sm font-medium text-slate-500 mb-1">Workout Name</Text>
|
|
<TextInput
|
|
value={workoutName}
|
|
onChangeText={setWorkoutName}
|
|
placeholder="Morning Swim"
|
|
placeholderTextColor="#94a3b8"
|
|
className="bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 text-slate-900"
|
|
/>
|
|
</View>
|
|
|
|
<View className="mb-4">
|
|
<Text className="text-sm font-medium text-slate-500 mb-1">Pool Length</Text>
|
|
<View className="flex-row gap-2">
|
|
{[25, 50].map((length) => (
|
|
<Pressable
|
|
key={length}
|
|
onPress={() => setPoolLength(length)}
|
|
className={`flex-1 py-2.5 rounded-xl items-center ${
|
|
poolLength === length ? "bg-sky-500" : "bg-slate-100"
|
|
}`}
|
|
>
|
|
<Text
|
|
className={`font-semibold ${
|
|
poolLength === length ? "text-white" : "text-slate-600"
|
|
}`}
|
|
>
|
|
{length}m
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{sets.map((set, index) => (
|
|
<View key={index} className="bg-slate-50 rounded-2xl p-4 mb-3 border border-slate-200">
|
|
<View className="flex-row justify-between items-center mb-3">
|
|
<Text className="font-semibold text-slate-700">Set {index + 1}</Text>
|
|
{sets.length > 1 && (
|
|
<Pressable onPress={() => removeSet(index)}>
|
|
<Text className="text-red-500 text-sm font-medium">Remove</Text>
|
|
</Pressable>
|
|
)}
|
|
</View>
|
|
|
|
<Text className="text-xs font-medium text-slate-500 mb-1.5 uppercase tracking-wide">
|
|
Stroke
|
|
</Text>
|
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} className="mb-3">
|
|
{STROKES.map((stroke) => (
|
|
<Pressable
|
|
key={stroke}
|
|
onPress={() => updateSet(index, { stroke })}
|
|
className={`mr-2 px-3 py-1.5 rounded-full ${
|
|
set.stroke === stroke ? "bg-sky-500" : "bg-slate-200"
|
|
}`}
|
|
>
|
|
<Text
|
|
className={`text-xs font-medium ${
|
|
set.stroke === stroke ? "text-white" : "text-slate-600"
|
|
}`}
|
|
>
|
|
{stroke}
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</ScrollView>
|
|
|
|
<View className="flex-row gap-2">
|
|
<View className="flex-1">
|
|
<Text className="text-xs font-medium text-slate-500 mb-1">Distance (m)</Text>
|
|
<TextInput
|
|
keyboardType="numeric"
|
|
value={String(set.distancePerRep)}
|
|
onChangeText={(t) => updateSet(index, { distancePerRep: Number(t) || 0 })}
|
|
className="bg-white border border-slate-300 rounded-lg px-3 py-2 text-slate-900"
|
|
/>
|
|
</View>
|
|
<View className="flex-1">
|
|
<Text className="text-xs font-medium text-slate-500 mb-1">Reps</Text>
|
|
<TextInput
|
|
keyboardType="numeric"
|
|
value={String(set.repCount)}
|
|
onChangeText={(t) => updateSet(index, { repCount: Number(t) || 1 })}
|
|
className="bg-white border border-slate-300 rounded-lg px-3 py-2 text-slate-900"
|
|
/>
|
|
</View>
|
|
<View className="flex-1">
|
|
<Text className="text-xs font-medium text-slate-500 mb-1">Rest (s)</Text>
|
|
<TextInput
|
|
keyboardType="numeric"
|
|
value={String(set.restDuration)}
|
|
onChangeText={(t) => updateSet(index, { restDuration: Number(t) || 0 })}
|
|
className="bg-white border border-slate-300 rounded-lg px-3 py-2 text-slate-900"
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
))}
|
|
|
|
<Pressable
|
|
onPress={addSet}
|
|
className="bg-slate-100 rounded-2xl py-3 items-center mb-6 border border-slate-200"
|
|
>
|
|
<Text className="text-sky-600 font-semibold">+ Add Set</Text>
|
|
</Pressable>
|
|
|
|
<Pressable
|
|
onPress={handleSave}
|
|
disabled={isSaving}
|
|
className={`rounded-2xl py-4 items-center mb-8 ${isSaving ? "bg-sky-300" : "bg-sky-500"}`}
|
|
>
|
|
<Text className="text-white font-bold text-lg">
|
|
{isSaving ? "Saving..." : "Start Swim"}
|
|
</Text>
|
|
</Pressable>
|
|
</View>
|
|
</ScrollView>
|
|
);
|
|
}
|