mirror of
https://github.com/DocNR/POWR.git
synced 2025-06-02 07:12:06 +00:00
268 lines
8.4 KiB
TypeScript
268 lines
8.4 KiB
TypeScript
// app/(workout)/template/[id]/_layout.tsx
|
|
import React, { useState, useEffect } from 'react';
|
|
import { View, ActivityIndicator } from 'react-native';
|
|
import { useLocalSearchParams, router } from 'expo-router';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
import { useTheme } from '@react-navigation/native';
|
|
import { useSQLiteContext } from 'expo-sqlite';
|
|
import { useWorkoutStore } from '@/stores/workoutStore';
|
|
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
|
import {
|
|
ChevronLeft,
|
|
Play,
|
|
Heart
|
|
} from 'lucide-react-native';
|
|
|
|
// UI Components
|
|
import { Text } from '@/components/ui/text';
|
|
import { Button } from '@/components/ui/button';
|
|
import { TabScreen } from '@/components/layout/TabScreen';
|
|
|
|
// Import tab screens
|
|
import OverviewTab from './index';
|
|
import SocialTab from './social';
|
|
import HistoryTab from './history';
|
|
|
|
// Import the shared context
|
|
import { TemplateContext } from './_templateContext';
|
|
|
|
// Types
|
|
import { WorkoutTemplate } from '@/types/templates';
|
|
import type { CustomTheme } from '@/lib/theme';
|
|
|
|
// Create the tab navigator
|
|
const Tab = createMaterialTopTabNavigator();
|
|
|
|
export default function TemplateDetailsLayout() {
|
|
const params = useLocalSearchParams();
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [workoutTemplate, setWorkoutTemplate] = useState<WorkoutTemplate | null>(null);
|
|
const [isFavorite, setIsFavorite] = useState(false);
|
|
|
|
const templateId = typeof params.id === 'string' ? params.id : '';
|
|
const db = useSQLiteContext();
|
|
const insets = useSafeAreaInsets();
|
|
const theme = useTheme() as CustomTheme;
|
|
|
|
// Use the workoutStore
|
|
const { startWorkoutFromTemplate, checkFavoriteStatus, addFavorite, removeFavorite } = useWorkoutStore();
|
|
|
|
useEffect(() => {
|
|
async function loadTemplate() {
|
|
try {
|
|
setIsLoading(true);
|
|
|
|
if (!templateId) {
|
|
router.replace('/');
|
|
return;
|
|
}
|
|
|
|
// Check if it's a favorite
|
|
const isFav = checkFavoriteStatus(templateId);
|
|
setIsFavorite(isFav);
|
|
|
|
// If it's a favorite, get it from favorites
|
|
if (isFav) {
|
|
const favorites = await useWorkoutStore.getState().getFavorites();
|
|
const favoriteTemplate = favorites.find(f => f.id === templateId);
|
|
|
|
if (favoriteTemplate) {
|
|
setWorkoutTemplate(favoriteTemplate.content);
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If not found in favorites or not a favorite, fetch from database
|
|
// TODO: Implement fetching from database if needed
|
|
|
|
// For now, create a mock template if we couldn't find it
|
|
if (!workoutTemplate) {
|
|
// This is a fallback mock. In production, you'd show an error
|
|
const mockTemplate: WorkoutTemplate = {
|
|
id: templateId,
|
|
title: "Sample Workout",
|
|
type: "strength",
|
|
category: "Full Body",
|
|
exercises: [{
|
|
exercise: {
|
|
id: "ex1",
|
|
title: "Barbell Squat",
|
|
type: "strength",
|
|
category: "Legs",
|
|
tags: ["compound", "legs"],
|
|
availability: { source: ["local"] },
|
|
created_at: Date.now()
|
|
},
|
|
targetSets: 3,
|
|
targetReps: 8
|
|
}],
|
|
isPublic: false,
|
|
tags: ["strength", "beginner"],
|
|
version: 1,
|
|
created_at: Date.now(),
|
|
availability: { source: ["local"] }
|
|
};
|
|
|
|
setWorkoutTemplate(mockTemplate);
|
|
}
|
|
|
|
setIsLoading(false);
|
|
} catch (error) {
|
|
console.error("Error loading template:", error);
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
|
|
loadTemplate();
|
|
}, [templateId, db]);
|
|
|
|
const handleStartWorkout = async () => {
|
|
if (!workoutTemplate) return;
|
|
|
|
try {
|
|
// Use the workoutStore action to start a workout from template
|
|
await startWorkoutFromTemplate(workoutTemplate.id);
|
|
|
|
// Navigate to the active workout screen
|
|
router.push('/(workout)/create');
|
|
} catch (error) {
|
|
console.error("Error starting workout:", error);
|
|
}
|
|
};
|
|
|
|
const handleGoBack = () => {
|
|
router.back();
|
|
};
|
|
|
|
const handleToggleFavorite = async () => {
|
|
if (!workoutTemplate) return;
|
|
|
|
try {
|
|
if (isFavorite) {
|
|
await removeFavorite(workoutTemplate.id);
|
|
} else {
|
|
await addFavorite(workoutTemplate);
|
|
}
|
|
|
|
setIsFavorite(!isFavorite);
|
|
} catch (error) {
|
|
console.error("Error toggling favorite:", error);
|
|
}
|
|
};
|
|
|
|
if (isLoading || !workoutTemplate) {
|
|
return (
|
|
<TabScreen>
|
|
<View style={{ flex: 1, paddingTop: insets.top }}>
|
|
<View className="px-4 py-3 flex-row items-center border-b border-border">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onPress={handleGoBack}
|
|
>
|
|
<ChevronLeft className="text-foreground" />
|
|
</Button>
|
|
<Text className="text-xl font-semibold ml-2">
|
|
Template Details
|
|
</Text>
|
|
</View>
|
|
<View className="flex-1 items-center justify-center">
|
|
<ActivityIndicator size="large" className="mb-4" />
|
|
<Text className="text-muted-foreground">Loading template...</Text>
|
|
</View>
|
|
</View>
|
|
</TabScreen>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<TabScreen>
|
|
<View style={{ flex: 1, paddingTop: insets.top }}>
|
|
{/* Header with back button, title and Start button */}
|
|
<View className="px-4 py-3 flex-row items-center justify-between border-b border-border">
|
|
<View className="flex-row items-center flex-1 mr-2">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onPress={handleGoBack}
|
|
>
|
|
<ChevronLeft className="text-foreground" />
|
|
</Button>
|
|
<Text className="text-xl font-semibold ml-2" numberOfLines={1}>
|
|
{workoutTemplate.title}
|
|
</Text>
|
|
</View>
|
|
|
|
<View className="flex-row items-center">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onPress={handleToggleFavorite}
|
|
className="mr-2"
|
|
>
|
|
<Heart
|
|
className={isFavorite ? "text-primary" : "text-muted-foreground"}
|
|
fill={isFavorite ? "currentColor" : "none"}
|
|
size={22}
|
|
/>
|
|
</Button>
|
|
|
|
{/* Updated to match Add Exercises button */}
|
|
<Button
|
|
variant="purple"
|
|
className="flex-row items-center justify-center"
|
|
onPress={handleStartWorkout}
|
|
>
|
|
<Text className="text-white font-medium">Start Workout</Text>
|
|
</Button>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Context provider to pass template to the tabs */}
|
|
<TemplateContext.Provider value={{ template: workoutTemplate }}>
|
|
<Tab.Navigator
|
|
screenOptions={{
|
|
// Match exact library tab styling
|
|
tabBarActiveTintColor: theme.colors.tabIndicator,
|
|
tabBarInactiveTintColor: theme.colors.tabInactive,
|
|
tabBarLabelStyle: {
|
|
fontSize: 14,
|
|
textTransform: 'capitalize',
|
|
fontWeight: 'bold',
|
|
},
|
|
tabBarIndicatorStyle: {
|
|
backgroundColor: theme.colors.tabIndicator,
|
|
height: 2,
|
|
},
|
|
tabBarStyle: {
|
|
backgroundColor: theme.colors.background,
|
|
elevation: 0,
|
|
shadowOpacity: 0,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: theme.colors.border,
|
|
},
|
|
tabBarPressColor: theme.colors.primary,
|
|
}}
|
|
>
|
|
<Tab.Screen
|
|
name="index"
|
|
component={OverviewTab}
|
|
options={{ title: 'Overview' }}
|
|
/>
|
|
<Tab.Screen
|
|
name="social"
|
|
component={SocialTab}
|
|
options={{ title: 'Social' }}
|
|
/>
|
|
<Tab.Screen
|
|
name="history"
|
|
component={HistoryTab}
|
|
options={{ title: 'History' }}
|
|
/>
|
|
</Tab.Navigator>
|
|
</TemplateContext.Provider>
|
|
</View>
|
|
</TabScreen>
|
|
);
|
|
} |