2025-03-23 15:53:34 -04:00
|
|
|
// lib/hooks/useWorkoutHistory.ts
|
|
|
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
|
|
import { useSQLiteContext } from 'expo-sqlite';
|
|
|
|
import { useNDKCurrentUser } from '@/lib/hooks/useNDK';
|
|
|
|
import { UnifiedWorkoutHistoryService, WorkoutFilters } from '@/lib/db/services/UnifiedWorkoutHistoryService';
|
|
|
|
import { Workout } from '@/types/workout';
|
|
|
|
|
|
|
|
interface UseWorkoutHistoryOptions {
|
|
|
|
includeNostr?: boolean;
|
|
|
|
filters?: WorkoutFilters;
|
|
|
|
realtime?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook for fetching and managing workout history from both local database and Nostr
|
|
|
|
* This hook uses the UnifiedWorkoutHistoryService to provide a consistent interface
|
|
|
|
* for working with workouts from multiple sources.
|
|
|
|
*/
|
|
|
|
export function useWorkoutHistory(options: UseWorkoutHistoryOptions = {}) {
|
|
|
|
const {
|
|
|
|
includeNostr = true,
|
|
|
|
filters,
|
|
|
|
realtime = false
|
|
|
|
} = options;
|
|
|
|
|
|
|
|
const db = useSQLiteContext();
|
|
|
|
const { currentUser, isAuthenticated } = useNDKCurrentUser();
|
|
|
|
const [workouts, setWorkouts] = useState<Workout[]>([]);
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const [error, setError] = useState<Error | null>(null);
|
|
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
|
|
|
|
// Initialize service
|
|
|
|
const workoutHistoryService = useMemo(() => new UnifiedWorkoutHistoryService(db), [db]);
|
|
|
|
|
|
|
|
// Load workouts function
|
|
|
|
const loadWorkouts = useCallback(async () => {
|
|
|
|
try {
|
|
|
|
setLoading(true);
|
|
|
|
setError(null);
|
|
|
|
|
|
|
|
let result: Workout[];
|
|
|
|
|
|
|
|
if (filters) {
|
|
|
|
// Use filters if provided
|
|
|
|
result = await workoutHistoryService.filterWorkouts(filters);
|
|
|
|
} else {
|
|
|
|
// Otherwise get all workouts
|
|
|
|
result = await workoutHistoryService.getAllWorkouts({
|
|
|
|
includeNostr,
|
|
|
|
isAuthenticated
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
setWorkouts(result);
|
|
|
|
return result;
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Error loading workouts:', err);
|
|
|
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
|
|
return [];
|
|
|
|
} finally {
|
|
|
|
setLoading(false);
|
|
|
|
setRefreshing(false);
|
|
|
|
}
|
|
|
|
}, [workoutHistoryService, filters, includeNostr, isAuthenticated]);
|
|
|
|
|
|
|
|
// Initial load
|
|
|
|
useEffect(() => {
|
|
|
|
loadWorkouts();
|
|
|
|
}, [loadWorkouts]);
|
|
|
|
|
|
|
|
// Set up real-time subscription if enabled
|
|
|
|
useEffect(() => {
|
2025-03-28 07:24:09 -07:00
|
|
|
if (!realtime || !isAuthenticated || !currentUser?.pubkey) {
|
2025-03-23 15:53:34 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-28 07:24:09 -07:00
|
|
|
// Only create the subscription when we need it (not dependent on includeNostr to prevent re-subs)
|
2025-03-23 15:53:34 -04:00
|
|
|
const subId = workoutHistoryService.subscribeToNostrWorkouts(
|
|
|
|
currentUser.pubkey,
|
|
|
|
(newWorkout) => {
|
2025-03-28 07:24:09 -07:00
|
|
|
// Only update state if we're including Nostr workouts
|
|
|
|
if (includeNostr) {
|
|
|
|
setWorkouts(prev => {
|
|
|
|
// Check if workout already exists
|
|
|
|
const exists = prev.some(w => w.id === newWorkout.id);
|
|
|
|
if (exists) {
|
|
|
|
// Update existing workout
|
|
|
|
return prev.map(w => w.id === newWorkout.id ? newWorkout : w);
|
|
|
|
} else {
|
|
|
|
// Add new workout
|
|
|
|
return [newWorkout, ...prev];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2025-03-23 15:53:34 -04:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Clean up subscription
|
|
|
|
return () => {
|
|
|
|
workoutHistoryService.unsubscribeFromNostrWorkouts(subId);
|
|
|
|
};
|
2025-03-28 07:24:09 -07:00
|
|
|
// Remove includeNostr from dependencies to prevent re-subs when it changes
|
|
|
|
// We handle the includeNostr state inside the callback instead
|
|
|
|
}, [workoutHistoryService, currentUser?.pubkey, isAuthenticated, realtime]);
|
2025-03-23 15:53:34 -04:00
|
|
|
|
|
|
|
// Refresh function for pull-to-refresh
|
|
|
|
const refresh = useCallback(() => {
|
|
|
|
setRefreshing(true);
|
|
|
|
return loadWorkouts();
|
|
|
|
}, [loadWorkouts]);
|
|
|
|
|
|
|
|
// Get workouts for a specific date
|
|
|
|
const getWorkoutsByDate = useCallback(async (date: Date): Promise<Workout[]> => {
|
|
|
|
try {
|
|
|
|
return await workoutHistoryService.getWorkoutsByDate(date);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Error getting workouts by date:', err);
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}, [workoutHistoryService]);
|
|
|
|
|
|
|
|
// Get workout details
|
|
|
|
const getWorkoutDetails = useCallback(async (workoutId: string): Promise<Workout | null> => {
|
|
|
|
try {
|
|
|
|
return await workoutHistoryService.getWorkoutDetails(workoutId);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Error getting workout details:', err);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}, [workoutHistoryService]);
|
|
|
|
|
|
|
|
// Publish workout to Nostr
|
|
|
|
const publishWorkoutToNostr = useCallback(async (workoutId: string): Promise<string> => {
|
|
|
|
try {
|
|
|
|
return await workoutHistoryService.publishWorkoutToNostr(workoutId);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Error publishing workout to Nostr:', err);
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}, [workoutHistoryService]);
|
|
|
|
|
|
|
|
// Import Nostr workout to local
|
|
|
|
const importNostrWorkoutToLocal = useCallback(async (eventId: string): Promise<string> => {
|
|
|
|
try {
|
|
|
|
return await workoutHistoryService.importNostrWorkoutToLocal(eventId);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Error importing Nostr workout:', err);
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}, [workoutHistoryService]);
|
|
|
|
|
|
|
|
return {
|
|
|
|
workouts,
|
|
|
|
loading,
|
|
|
|
error,
|
|
|
|
refreshing,
|
|
|
|
refresh,
|
|
|
|
getWorkoutsByDate,
|
|
|
|
getWorkoutDetails,
|
|
|
|
publishWorkoutToNostr,
|
|
|
|
importNostrWorkoutToLocal,
|
|
|
|
service: workoutHistoryService
|
|
|
|
};
|
|
|
|
}
|