fixed favorites refresh and fixed but in powrpacksection that wasn't showing filtered powr packs (directly adding powrpacks by hex id now)

This commit is contained in:
DocNR 2025-03-18 15:55:00 -04:00
parent c6a9af080c
commit 24e7f53ac3
3 changed files with 280 additions and 61 deletions

View File

@ -20,6 +20,7 @@ import {
import { useWorkoutStore } from '@/stores/workoutStore'; import { useWorkoutStore } from '@/stores/workoutStore';
import { useTemplates } from '@/lib/hooks/useTemplates'; import { useTemplates } from '@/lib/hooks/useTemplates';
import { useIconColor } from '@/lib/theme/iconUtils'; import { useIconColor } from '@/lib/theme/iconUtils';
import { useLibraryStore } from '@/lib/stores/libraryStore';
// Default available filters // Default available filters
const availableFilters = { const availableFilters = {
@ -136,10 +137,13 @@ export default function TemplatesScreen() {
} else { } else {
await useWorkoutStore.getState().addFavorite(workoutTemplate); await useWorkoutStore.getState().addFavorite(workoutTemplate);
} }
// Add this line to trigger a refresh after favorite toggle
useLibraryStore.getState().refreshTemplates();
} catch (error) { } catch (error) {
console.error('Error toggling favorite status:', error); console.error('Error toggling favorite status:', error);
} }
}; };
const handleApplyFilters = (filters: FilterOptions) => { const handleApplyFilters = (filters: FilterOptions) => {
setCurrentFilters(filters); setCurrentFilters(filters);

View File

@ -7,7 +7,7 @@ import { useRouter } from 'expo-router';
import { useSettingsDrawer } from '@/lib/contexts/SettingsDrawerContext'; import { useSettingsDrawer } from '@/lib/contexts/SettingsDrawerContext';
import { import {
Moon, Sun, LogOut, User, ChevronRight, X, Bell, HelpCircle, Moon, Sun, LogOut, User, ChevronRight, X, Bell, HelpCircle,
Smartphone, Database, Zap, RefreshCw, AlertTriangle, Globe, PackageOpen Smartphone, Database, Zap, RefreshCw, AlertTriangle, Globe, PackageOpen, Trash2
} from 'lucide-react-native'; } from 'lucide-react-native';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -28,6 +28,10 @@ import {
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
} from '@/components/ui/alert-dialog'; } from '@/components/ui/alert-dialog';
import { FIXED_COLORS } from '@/lib/theme/colors';
import { useSQLiteContext } from 'expo-sqlite';
import { useLibraryStore } from '@/lib/stores/libraryStore';
import { useWorkoutStore } from '@/stores/workoutStore';
const { width: SCREEN_WIDTH } = Dimensions.get('window'); const { width: SCREEN_WIDTH } = Dimensions.get('window');
const DRAWER_WIDTH = SCREEN_WIDTH * 0.85; const DRAWER_WIDTH = SCREEN_WIDTH * 0.85;
@ -38,6 +42,7 @@ type MenuItem = {
label: string; label: string;
onPress: () => void; onPress: () => void;
rightElement?: React.ReactNode; rightElement?: React.ReactNode;
variant?: 'default' | 'destructive';
}; };
export default function SettingsDrawer() { export default function SettingsDrawer() {
@ -50,6 +55,10 @@ export default function SettingsDrawer() {
const [isLoginSheetOpen, setIsLoginSheetOpen] = useState(false); const [isLoginSheetOpen, setIsLoginSheetOpen] = useState(false);
const [showSignOutAlert, setShowSignOutAlert] = useState(false); const [showSignOutAlert, setShowSignOutAlert] = useState(false);
const [showRelayManager, setShowRelayManager] = useState(false); const [showRelayManager, setShowRelayManager] = useState(false);
const [showResetDataAlert, setShowResetDataAlert] = useState(false);
// Database access for reset functionality
const db = useSQLiteContext();
const slideAnim = useRef(new Animated.Value(-DRAWER_WIDTH)).current; const slideAnim = useRef(new Animated.Value(-DRAWER_WIDTH)).current;
const fadeAnim = useRef(new Animated.Value(0)).current; const fadeAnim = useRef(new Animated.Value(0)).current;
@ -143,6 +152,47 @@ export default function SettingsDrawer() {
console.log('Show Nostr settings'); console.log('Show Nostr settings');
} }
}; };
// Handle reset app data button click
const handleResetData = () => {
setShowResetDataAlert(true);
};
// Reset app data function
const resetAllData = async () => {
try {
// Clear database tables
await db.execAsync(`
DELETE FROM workouts;
DELETE FROM workout_exercises;
DELETE FROM workout_sets;
DELETE FROM templates;
DELETE FROM template_exercises;
DELETE FROM exercises;
DELETE FROM exercise_tags;
DELETE FROM powr_packs;
DELETE FROM powr_pack_items;
DELETE FROM favorites; /* Add this line */
`);
// Clear store state
useLibraryStore.getState().clearCache();
useLibraryStore.getState().refreshAll();
// Also reset the workout store to clear favorite IDs in memory
useWorkoutStore.getState().reset();
// Close dialogs
setShowResetDataAlert(false);
closeDrawer();
// Show success message
alert("All app data has been reset successfully.");
} catch (error) {
console.error("Error resetting data:", error);
alert("There was a problem resetting your data. Please try again.");
}
};
// Define menu items // Define menu items
const menuItems: MenuItem[] = [ const menuItems: MenuItem[] = [
@ -185,6 +235,21 @@ export default function SettingsDrawer() {
label: 'About', label: 'About',
onPress: () => closeDrawer(), onPress: () => closeDrawer(),
}, },
// Add separator before danger zone
{
id: 'separator',
icon: () => null,
label: '',
onPress: () => {},
},
// Reset App Data option - danger zone
{
id: 'reset-data',
icon: Trash2,
label: 'Reset App Data',
onPress: handleResetData,
variant: 'destructive'
},
]; ];
if (!isDrawerOpen) return null; if (!isDrawerOpen) return null;
@ -275,24 +340,36 @@ export default function SettingsDrawer() {
> >
{menuItems.map((item, index) => ( {menuItems.map((item, index) => (
<View key={item.id}> <View key={item.id}>
<TouchableOpacity {item.id === 'separator' ? (
style={styles.menuItem} <View className="my-3">
onPress={item.onPress} <Text className="text-xs text-muted-foreground mb-1">DANGER ZONE</Text>
activeOpacity={0.7} <Separator className="mb-1" />
>
<View style={styles.menuItemLeft}>
<item.icon size={22} color={theme.colors.text} />
<Text className="text-base ml-3">{item.label}</Text>
</View> </View>
) : (
{item.rightElement ? ( <TouchableOpacity
item.rightElement style={styles.menuItem}
) : ( onPress={item.onPress}
<ChevronRight size={20} color={theme.colors.text} /> activeOpacity={0.7}
)} >
</TouchableOpacity> <View style={styles.menuItemLeft}>
<item.icon
size={22}
color={item.variant === 'destructive' ? FIXED_COLORS.destructive : theme.colors.text}
/>
<Text className={`text-base ml-3 ${item.variant === 'destructive' ? 'text-destructive' : ''}`}>
{item.label}
</Text>
</View>
{item.rightElement ? (
item.rightElement
) : (
<ChevronRight size={20} color={theme.colors.text} />
)}
</TouchableOpacity>
)}
{index < menuItems.length - 1 && ( {index < menuItems.length - 1 && item.id !== 'separator' && (
<Separator className="mb-1 mt-1" /> <Separator className="mb-1 mt-1" />
)} )}
</View> </View>
@ -333,25 +410,64 @@ export default function SettingsDrawer() {
<AlertDialog open={showSignOutAlert} onOpenChange={setShowSignOutAlert}> <AlertDialog open={showSignOutAlert} onOpenChange={setShowSignOutAlert}>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>For Real?</AlertDialogTitle> <AlertDialogTitle>
<Text className="text-xl font-semibold text-foreground">For Real?</Text>
</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
<Text> <Text className="text-muted-foreground">
Are you sure you want to sign out? Make sure you've backed up your private key. Are you sure you want to sign out? Make sure you've backed up your private key.
Lost keys cannot be recovered and all your data will be inaccessible. Lost keys cannot be recovered and all your data will be inaccessible.
</Text> </Text>
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <View className="flex-row justify-end gap-3">
<AlertDialogCancel onPress={() => setShowSignOutAlert(false)}> <AlertDialogCancel asChild>
<Text>Cancel</Text> <Button variant="outline" className="mr-2">
<Text>Cancel</Text>
</Button>
</AlertDialogCancel> </AlertDialogCancel>
<AlertDialogAction <AlertDialogAction asChild>
onPress={confirmSignOut} <Button
className="bg-destructive text-destructive-foreground" variant="destructive"
> onPress={confirmSignOut}
<Text>Sign Out</Text> style={{ backgroundColor: FIXED_COLORS.destructive }}
>
<Text style={{ color: '#FFFFFF' }}>Sign Out</Text>
</Button>
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </View>
</AlertDialogContent>
</AlertDialog>
{/* Reset App Data Alert Dialog */}
<AlertDialog open={showResetDataAlert} onOpenChange={setShowResetDataAlert}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
<Text className="text-xl font-semibold text-foreground">Reset App Data</Text>
</AlertDialogTitle>
<AlertDialogDescription>
<Text className="text-muted-foreground">
This will delete ALL workouts, templates and exercises. This action cannot be undone.
</Text>
</AlertDialogDescription>
</AlertDialogHeader>
<View className="flex-row justify-end gap-3">
<AlertDialogCancel asChild>
<Button variant="outline" className="mr-2">
<Text>Cancel</Text>
</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button
variant="destructive"
onPress={resetAllData}
style={{ backgroundColor: FIXED_COLORS.destructive }}
>
<Text style={{ color: '#FFFFFF' }}>Reset Everything</Text>
</Button>
</AlertDialogAction>
</View>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
</> </>

View File

@ -13,40 +13,114 @@ import { NDKEvent } from '@nostr-dev-kit/ndk-mobile';
import { Clipboard } from 'react-native'; import { Clipboard } from 'react-native';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
// Define a simplified version of a pack for state management
interface SimplifiedPack {
id: string;
pubkey: string;
tags: string[][];
content: string;
created_at?: number;
kind?: number;
}
export default function POWRPackSection() { export default function POWRPackSection() {
const { ndk } = useNDK(); const { ndk } = useNDK();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [featuredPacks, setFeaturedPacks] = useState<NDKEvent[]>([]); // Change the state type to allow simplified pack structure
const [featuredPacks, setFeaturedPacks] = useState<SimplifiedPack[]>([]);
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
// Manual fetch function // Manual fetch function
const handleFetchPacks = async () => { const handleFetchPacks = async () => {
if (!ndk) return; if (!ndk) return;
// Known reliable POWR publishers
const knownPublishers = [
"55127fc9e1c03c6b459a3bab72fdb99def1644c5f239bdd09f3e5fb401ed9b21", // Walter Sobchak account
// Add other trusted publishers here as you discover them
];
// List of known good relays
const relays = ['wss://relay.damus.io', 'wss://nos.lol', 'wss://relay.nostr.band', 'wss://purplepag.es'];
console.log('Explicitly connecting to relays:', relays);
// Connect to each relay
for (const relay of relays) {
try {
// Use type assertion to avoid TypeScript errors
if (typeof (ndk as any).addRelay === 'function') {
await (ndk as any).addRelay(relay);
console.log(`Connected to relay: ${relay}`);
} else if (ndk.pool) {
// Alternative approach using pool
console.log('Using pool to connect to relay:', relay);
}
} catch (err) {
console.error(`Error connecting to relay ${relay}:`, err);
}
}
console.log('NDK connection status:', ndk.pool?.relays?.size || 0, 'relays connected');
try { try {
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
console.log('Manually fetching POWR packs'); console.log('Fetching POWR packs from known publishers');
const events = await ndk.fetchEvents({
kinds: [30004],
"#t": ["powrpack"],
limit: 20
});
const eventsArray = Array.from(events);
console.log(`Fetched ${eventsArray.length} events`); // 1. Only use the most reliable approach - fetch from known publishers
console.log('Fetching from known publishers:', knownPublishers);
// Filter to find POWR packs const publisherEvents = await ndk.fetchEvents({
const powrPacks = eventsArray.filter(event => { kinds: [30004],
// Check if any tag has 'powrpack', 'fitness', or 'workout' authors: knownPublishers,
return event.tags.some(tag => limit: 30
tag[0] === 't' && ['powrpack', 'fitness', 'workout'].includes(tag[1])
);
}); });
console.log(`Found ${powrPacks.length} POWR packs`); // Debug log
setFeaturedPacks(powrPacks); console.log(`Fetched ${publisherEvents.size} events from known publishers`);
// Process the events directly
if (publisherEvents.size > 0) {
const events = Array.from(publisherEvents);
// Log first event info
const firstEvent = events[0];
console.log('First event basic info:', {
id: firstEvent.id,
kind: firstEvent.kind,
pubkey: firstEvent.pubkey,
tagsCount: firstEvent.tags?.length || 0,
contentPreview: firstEvent.content?.substring(0, 50)
});
// Create simplified packs immediately
const simplifiedPacks: SimplifiedPack[] = events.map(event => {
return {
id: event.id,
pubkey: event.pubkey,
kind: event.kind,
tags: [...event.tags], // Create a copy
content: event.content,
created_at: event.created_at
};
});
console.log(`Created ${simplifiedPacks.length} simplified packs`);
// For deeper debugging
if (simplifiedPacks.length > 0) {
console.log('First simplified pack:', {
id: simplifiedPacks[0].id,
tagsCount: simplifiedPacks[0].tags.length
});
}
// Set state with simplified packs
setFeaturedPacks(simplifiedPacks);
console.log('Set featuredPacks state with simplified packs');
} else {
console.log('No packs found from known publishers');
}
} catch (err) { } catch (err) {
console.error('Error fetching packs:', err); console.error('Error fetching packs:', err);
setError(err instanceof Error ? err : new Error('Failed to fetch packs')); setError(err instanceof Error ? err : new Error('Failed to fetch packs'));
@ -56,19 +130,19 @@ export default function POWRPackSection() {
}; };
// Handle pack click // Handle pack click
const handlePackClick = (packEvent: NDKEvent) => { const handlePackClick = (pack: SimplifiedPack) => {
try { try {
// Get dTag for the pack // Get dTag for the pack
const dTag = findTagValue(packEvent.tags, 'd'); const dTag = findTagValue(pack.tags, 'd');
if (!dTag) { if (!dTag) {
throw new Error('Pack is missing identifier (d tag)'); throw new Error('Pack is missing identifier (d tag)');
} }
// Get relay hints from event tags // Get relay hints from event tags
const relayHints = packEvent.tags const relayHints = pack.tags
.filter(tag => tag[0] === 'r') .filter(tag => tag[0] === 'r')
.map(tag => tag[1]) .map(tag => tag[1])
.filter(relay => relay.startsWith('wss://')); .filter(relay => relay && relay.startsWith('wss://'));
// Default relays if none found // Default relays if none found
const relays = relayHints.length > 0 const relays = relayHints.length > 0
@ -77,8 +151,8 @@ export default function POWRPackSection() {
// Create shareable naddr // Create shareable naddr
const naddr = nip19.naddrEncode({ const naddr = nip19.naddrEncode({
kind: 30004, kind: pack.kind || 30004,
pubkey: packEvent.pubkey, pubkey: pack.pubkey,
identifier: dTag, identifier: dTag,
relays relays
}); });
@ -102,11 +176,26 @@ export default function POWRPackSection() {
// Fetch packs when mounted // Fetch packs when mounted
React.useEffect(() => { React.useEffect(() => {
console.log('NDK status:', ndk ? 'initialized' : 'not initialized');
if (ndk) { if (ndk) {
handleFetchPacks(); // Add a small delay to ensure NDK is fully connected to relays
const timer = setTimeout(() => {
console.log('Attempting fetch after delay');
handleFetchPacks();
}, 2000); // 2 second delay
return () => clearTimeout(timer);
} }
}, [ndk]); }, [ndk]);
// Add debug logging for rendering
console.log('Rendering packs, count:', featuredPacks.length);
if (featuredPacks.length > 0) {
console.log('First pack keys:', Object.keys(featuredPacks[0]));
console.log('First pack has tags:', featuredPacks[0].tags ? featuredPacks[0].tags.length : 'no tags');
}
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.header}> <View style={styles.header}>
@ -150,16 +239,26 @@ export default function POWRPackSection() {
)) ))
) : featuredPacks.length > 0 ? ( ) : featuredPacks.length > 0 ? (
// Pack cards // Pack cards
featuredPacks.map(pack => { featuredPacks.map((pack, idx) => {
const title = findTagValue(pack.tags, 'name') || 'Unnamed Pack'; console.log(`Rendering pack ${idx}, tags exist:`, pack.tags ? 'yes' : 'no');
const description = findTagValue(pack.tags, 'about') || ''; const title = findTagValue(pack.tags || [], 'name') || 'Unnamed Pack';
const image = findTagValue(pack.tags, 'image') || null; const description = findTagValue(pack.tags || [], 'about') || '';
const exerciseCount = pack.tags.filter(t => t[0] === 'a' && t[1].startsWith('33401')).length; const image = findTagValue(pack.tags || [], 'image') || null;
const templateCount = pack.tags.filter(t => t[0] === 'a' && t[1].startsWith('33402')).length;
// Add fallback for tags
const tags = pack.tags || [];
const exerciseCount = tags.filter(t =>
t[0] === 'a' && t[1]?.startsWith('33401:')
).length;
const templateCount = tags.filter(t =>
t[0] === 'a' && t[1]?.startsWith('33402:')
).length;
return ( return (
<TouchableOpacity <TouchableOpacity
key={pack.id} key={pack.id || `pack-${idx}`}
onPress={() => handlePackClick(pack)} onPress={() => handlePackClick(pack)}
activeOpacity={0.7} activeOpacity={0.7}
> >