2025-02-24 22:27:01 -05:00
|
|
|
// app/(tabs)/index.tsx
|
2025-02-26 23:30:00 -05:00
|
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
2025-02-24 22:27:01 -05:00
|
|
|
import { ScrollView, View } from 'react-native'
|
2025-02-26 23:30:00 -05:00
|
|
|
import { useFocusEffect } from '@react-navigation/native';
|
2025-02-24 22:27:01 -05:00
|
|
|
import { router } from 'expo-router'
|
|
|
|
import {
|
|
|
|
AlertDialog,
|
|
|
|
AlertDialogAction,
|
|
|
|
AlertDialogCancel,
|
|
|
|
AlertDialogContent,
|
|
|
|
AlertDialogDescription,
|
|
|
|
AlertDialogHeader,
|
|
|
|
AlertDialogTitle
|
|
|
|
} from '@/components/ui/alert-dialog'
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
|
|
import { TabScreen } from '@/components/layout/TabScreen'
|
|
|
|
import Header from '@/components/Header'
|
|
|
|
import HomeWorkout from '@/components/workout/HomeWorkout'
|
|
|
|
import FavoriteTemplate from '@/components/workout/FavoriteTemplate'
|
|
|
|
import { useWorkoutStore } from '@/stores/workoutStore'
|
|
|
|
import { Text } from '@/components/ui/text'
|
|
|
|
import { getRandomWorkoutTitle } from '@/utils/workoutTitles'
|
2025-02-27 20:24:04 -05:00
|
|
|
import { Bell } from 'lucide-react-native';
|
|
|
|
import { Button } from '@/components/ui/button';
|
2025-02-24 22:27:01 -05:00
|
|
|
|
|
|
|
interface FavoriteTemplateData {
|
|
|
|
id: string;
|
|
|
|
title: string;
|
|
|
|
description: string;
|
|
|
|
exercises: Array<{
|
|
|
|
title: string;
|
|
|
|
sets: number;
|
|
|
|
reps: number;
|
|
|
|
}>;
|
|
|
|
exerciseCount: number;
|
|
|
|
duration?: number;
|
|
|
|
isFavorited: boolean;
|
|
|
|
lastUsed?: number;
|
|
|
|
source: 'local' | 'powr' | 'nostr';
|
|
|
|
}
|
2025-02-09 20:38:38 -05:00
|
|
|
|
2025-02-25 15:03:45 -05:00
|
|
|
// Type for tracking pending workout actions
|
|
|
|
type PendingWorkoutAction =
|
|
|
|
| { type: 'quick-start' }
|
|
|
|
| { type: 'template', templateId: string }
|
|
|
|
| { type: 'template-select' };
|
|
|
|
|
2025-02-15 14:03:42 -05:00
|
|
|
export default function WorkoutScreen() {
|
2025-02-24 22:27:01 -05:00
|
|
|
const { startWorkout } = useWorkoutStore.getState();
|
2025-02-25 15:03:45 -05:00
|
|
|
const [showActiveWorkoutModal, setShowActiveWorkoutModal] = useState(false);
|
|
|
|
const [pendingAction, setPendingAction] = useState<PendingWorkoutAction | null>(null);
|
|
|
|
const [favoriteWorkouts, setFavoriteWorkouts] = useState<FavoriteTemplateData[]>([]);
|
|
|
|
const [isLoadingFavorites, setIsLoadingFavorites] = useState(true);
|
2025-02-24 22:27:01 -05:00
|
|
|
|
|
|
|
const {
|
|
|
|
getFavorites,
|
|
|
|
startWorkoutFromTemplate,
|
|
|
|
removeFavorite,
|
|
|
|
checkFavoriteStatus,
|
|
|
|
isActive,
|
|
|
|
endWorkout
|
2025-02-25 15:03:45 -05:00
|
|
|
} = useWorkoutStore();
|
2025-02-24 22:27:01 -05:00
|
|
|
|
2025-02-26 23:30:00 -05:00
|
|
|
useFocusEffect(
|
|
|
|
useCallback(() => {
|
|
|
|
loadFavorites();
|
|
|
|
return () => {}; // Cleanup function
|
|
|
|
}, [])
|
|
|
|
);
|
2025-02-24 22:27:01 -05:00
|
|
|
|
|
|
|
const loadFavorites = async () => {
|
2025-02-25 15:03:45 -05:00
|
|
|
setIsLoadingFavorites(true);
|
2025-02-24 22:27:01 -05:00
|
|
|
try {
|
2025-02-25 15:03:45 -05:00
|
|
|
const favorites = await getFavorites();
|
2025-02-24 22:27:01 -05:00
|
|
|
|
2025-02-26 23:30:00 -05:00
|
|
|
const workoutTemplates = favorites.map(f => {
|
|
|
|
const content = f.content;
|
|
|
|
return {
|
|
|
|
id: content.id,
|
|
|
|
title: content.title || 'Untitled Workout',
|
|
|
|
description: content.description || '',
|
|
|
|
exercises: content.exercises.map(ex => ({
|
|
|
|
title: ex.exercise.title,
|
|
|
|
sets: ex.targetSets,
|
|
|
|
reps: ex.targetReps
|
|
|
|
})),
|
|
|
|
exerciseCount: content.exercises.length,
|
|
|
|
duration: content.metadata?.averageDuration,
|
|
|
|
isFavorited: true,
|
|
|
|
lastUsed: content.metadata?.lastUsed,
|
|
|
|
source: content.availability.source.includes('nostr')
|
|
|
|
? 'nostr'
|
|
|
|
: content.availability.source.includes('powr')
|
|
|
|
? 'powr'
|
|
|
|
: 'local'
|
|
|
|
} as FavoriteTemplateData;
|
|
|
|
});
|
2025-02-24 22:27:01 -05:00
|
|
|
|
2025-02-25 15:03:45 -05:00
|
|
|
setFavoriteWorkouts(workoutTemplates);
|
2025-02-24 22:27:01 -05:00
|
|
|
} catch (error) {
|
2025-02-25 15:03:45 -05:00
|
|
|
console.error('Error loading favorites:', error);
|
2025-02-24 22:27:01 -05:00
|
|
|
} finally {
|
2025-02-25 15:03:45 -05:00
|
|
|
setIsLoadingFavorites(false);
|
2025-02-24 22:27:01 -05:00
|
|
|
}
|
2025-02-25 15:03:45 -05:00
|
|
|
};
|
2025-02-24 22:27:01 -05:00
|
|
|
|
2025-02-25 15:03:45 -05:00
|
|
|
// Handle starting a template-based workout
|
2025-02-24 22:27:01 -05:00
|
|
|
const handleStartWorkout = async (templateId: string) => {
|
|
|
|
if (isActive) {
|
2025-02-25 15:03:45 -05:00
|
|
|
// Save what the user wants to do for later
|
|
|
|
setPendingAction({ type: 'template', templateId });
|
|
|
|
setShowActiveWorkoutModal(true);
|
|
|
|
return;
|
2025-02-24 22:27:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2025-02-25 15:03:45 -05:00
|
|
|
await startWorkoutFromTemplate(templateId);
|
|
|
|
router.push('/(workout)/create');
|
2025-02-24 22:27:01 -05:00
|
|
|
} catch (error) {
|
2025-02-25 15:03:45 -05:00
|
|
|
console.error('Error starting workout:', error);
|
2025-02-24 22:27:01 -05:00
|
|
|
}
|
2025-02-25 15:03:45 -05:00
|
|
|
};
|
2025-02-24 22:27:01 -05:00
|
|
|
|
2025-02-25 15:03:45 -05:00
|
|
|
// Handle selecting a template
|
|
|
|
const handleSelectTemplate = () => {
|
|
|
|
if (isActive) {
|
|
|
|
setPendingAction({ type: 'template-select' });
|
|
|
|
setShowActiveWorkoutModal(true);
|
|
|
|
return;
|
2025-02-24 22:27:01 -05:00
|
|
|
}
|
2025-02-25 15:03:45 -05:00
|
|
|
|
|
|
|
router.push('/(workout)/template-select');
|
|
|
|
};
|
2025-02-24 22:27:01 -05:00
|
|
|
|
2025-02-25 15:03:45 -05:00
|
|
|
// Handle quick start
|
2025-02-24 22:27:01 -05:00
|
|
|
const handleQuickStart = () => {
|
2025-02-25 15:03:45 -05:00
|
|
|
// Check if there's already an active workout
|
|
|
|
if (isActive) {
|
|
|
|
setPendingAction({ type: 'quick-start' });
|
|
|
|
setShowActiveWorkoutModal(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-02-24 22:27:01 -05:00
|
|
|
// Initialize a new workout with a random funny title
|
|
|
|
startWorkout({
|
|
|
|
title: getRandomWorkoutTitle(),
|
|
|
|
type: 'strength',
|
|
|
|
exercises: []
|
|
|
|
});
|
|
|
|
|
|
|
|
router.push('/(workout)/create');
|
|
|
|
};
|
|
|
|
|
2025-02-25 15:03:45 -05:00
|
|
|
// Handle starting a new workout (after ending the current one)
|
|
|
|
const handleStartNew = async () => {
|
|
|
|
if (!pendingAction) return;
|
|
|
|
|
|
|
|
setShowActiveWorkoutModal(false);
|
|
|
|
|
|
|
|
// End the current workout first
|
|
|
|
await endWorkout();
|
|
|
|
|
|
|
|
// Now handle the pending action
|
|
|
|
switch (pendingAction.type) {
|
|
|
|
case 'quick-start':
|
|
|
|
// Start a new quick workout
|
|
|
|
startWorkout({
|
|
|
|
title: getRandomWorkoutTitle(),
|
|
|
|
type: 'strength',
|
|
|
|
exercises: []
|
|
|
|
});
|
|
|
|
router.push('/(workout)/create');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'template':
|
|
|
|
// Start a workout from the selected template
|
|
|
|
await startWorkoutFromTemplate(pendingAction.templateId);
|
|
|
|
router.push('/(workout)/create');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'template-select':
|
|
|
|
// Navigate to template selection
|
|
|
|
router.push('/(workout)/template-select');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the pending action
|
|
|
|
setPendingAction(null);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Handle continuing the existing workout
|
|
|
|
const handleContinueExisting = () => {
|
|
|
|
setShowActiveWorkoutModal(false);
|
|
|
|
setPendingAction(null);
|
|
|
|
router.push('/(workout)/create');
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleFavoritePress = async (templateId: string) => {
|
|
|
|
try {
|
|
|
|
await removeFavorite(templateId);
|
|
|
|
await loadFavorites();
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error toggling favorite:', error);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-02-09 20:38:38 -05:00
|
|
|
return (
|
2025-02-15 14:03:42 -05:00
|
|
|
<TabScreen>
|
2025-02-27 20:24:04 -05:00
|
|
|
<Header
|
|
|
|
useLogo={true}
|
|
|
|
rightElement={
|
|
|
|
<Button
|
|
|
|
variant="ghost"
|
|
|
|
size="icon"
|
|
|
|
onPress={() => console.log('Open notifications')}
|
|
|
|
>
|
|
|
|
<View className="relative">
|
|
|
|
<Bell className="text-foreground" />
|
|
|
|
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
|
|
|
</View>
|
|
|
|
</Button>
|
|
|
|
}
|
|
|
|
/>
|
2025-02-24 22:27:01 -05:00
|
|
|
|
|
|
|
<ScrollView
|
2025-02-25 15:03:45 -05:00
|
|
|
className="flex-1 px-4 pt-4"
|
2025-02-24 22:27:01 -05:00
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
|
contentContainerStyle={{ paddingBottom: 20 }}
|
|
|
|
>
|
|
|
|
<HomeWorkout
|
2025-02-25 15:03:45 -05:00
|
|
|
onStartBlank={handleQuickStart}
|
|
|
|
onSelectTemplate={handleSelectTemplate}
|
2025-02-24 22:27:01 -05:00
|
|
|
/>
|
|
|
|
|
|
|
|
{/* Favorites section */}
|
|
|
|
<Card className="mt-6">
|
|
|
|
<CardHeader>
|
|
|
|
<CardTitle>Favorites</CardTitle>
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
|
|
{isLoadingFavorites ? (
|
|
|
|
<View className="p-6">
|
|
|
|
<Text className="text-muted-foreground text-center">
|
|
|
|
Loading favorites...
|
|
|
|
</Text>
|
|
|
|
</View>
|
|
|
|
) : favoriteWorkouts.length === 0 ? (
|
|
|
|
<View className="p-6">
|
|
|
|
<Text className="text-muted-foreground text-center">
|
|
|
|
Star workouts from your library to see them here
|
|
|
|
</Text>
|
|
|
|
</View>
|
|
|
|
) : (
|
|
|
|
<View className="gap-4">
|
|
|
|
{favoriteWorkouts.map(template => (
|
|
|
|
<FavoriteTemplate
|
|
|
|
key={template.id}
|
|
|
|
title={template.title}
|
|
|
|
exercises={template.exercises}
|
|
|
|
duration={template.duration}
|
|
|
|
exerciseCount={template.exerciseCount}
|
|
|
|
isFavorited={true}
|
|
|
|
onPress={() => handleStartWorkout(template.id)}
|
|
|
|
onFavoritePress={() => handleFavoritePress(template.id)}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</View>
|
|
|
|
)}
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
</ScrollView>
|
|
|
|
|
|
|
|
<AlertDialog open={showActiveWorkoutModal}>
|
|
|
|
<AlertDialogContent>
|
|
|
|
<AlertDialogHeader>
|
|
|
|
<AlertDialogTitle>Active Workout</AlertDialogTitle>
|
|
|
|
<AlertDialogDescription>
|
2025-02-25 15:03:45 -05:00
|
|
|
<Text>You have an active workout in progress. Would you like to continue it or start a new workout?</Text>
|
2025-02-24 22:27:01 -05:00
|
|
|
</AlertDialogDescription>
|
|
|
|
</AlertDialogHeader>
|
|
|
|
<View className="flex-row justify-end gap-3">
|
2025-02-25 15:03:45 -05:00
|
|
|
<AlertDialogCancel onPress={handleStartNew}>
|
2025-02-24 22:27:01 -05:00
|
|
|
<Text>Start New</Text>
|
|
|
|
</AlertDialogCancel>
|
|
|
|
<AlertDialogAction onPress={handleContinueExisting}>
|
|
|
|
<Text>Continue Workout</Text>
|
|
|
|
</AlertDialogAction>
|
|
|
|
</View>
|
|
|
|
</AlertDialogContent>
|
|
|
|
</AlertDialog>
|
2025-02-15 14:03:42 -05:00
|
|
|
</TabScreen>
|
2025-02-25 15:03:45 -05:00
|
|
|
);
|
2025-02-09 20:38:38 -05:00
|
|
|
}
|