mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-23 01:01:27 +00:00
splash screen, fixed delete exercises and templates when deleting powr pack bug, ui improvement to import powr pack screen
This commit is contained in:
parent
f1411e8568
commit
6ac0e80e45
6
app.json
6
app.json
@ -11,7 +11,7 @@
|
|||||||
"splash": {
|
"splash": {
|
||||||
"image": "./assets/images/splash.png",
|
"image": "./assets/images/splash.png",
|
||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#000000"
|
||||||
},
|
},
|
||||||
"assetBundlePatterns": [
|
"assetBundlePatterns": [
|
||||||
"**/*"
|
"**/*"
|
||||||
@ -56,7 +56,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"expo-secure-store"
|
"expo-secure-store",
|
||||||
|
"expo-av",
|
||||||
|
"expo-splash-screen"
|
||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
// app/(packs)/import.tsx
|
// app/(packs)/import.tsx
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { View, ScrollView, StyleSheet, ActivityIndicator, Platform } from 'react-native';
|
import { View, ScrollView, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
|
||||||
import { router, Stack } from 'expo-router';
|
import { router, Stack, useLocalSearchParams } from 'expo-router';
|
||||||
import { useNDK } from '@/lib/hooks/useNDK';
|
import { useNDK } from '@/lib/hooks/useNDK';
|
||||||
import { Text } from '@/components/ui/text';
|
import { Text } from '@/components/ui/text';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { nip19 } from 'nostr-tools'; // Fix import from nostr-tools
|
import { nip19 } from 'nostr-tools';
|
||||||
import { findTagValue } from '@/utils/nostr-utils';
|
import { findTagValue } from '@/utils/nostr-utils';
|
||||||
import POWRPackService from '@/lib/db/services/POWRPackService';
|
import { usePOWRPackService } from '@/components/DatabaseProvider';
|
||||||
import { usePOWRPackService } from '@/components/DatabaseProvider'; // Use the proper hook
|
|
||||||
import { POWRPackImport, POWRPackSelection } from '@/types/powr-pack';
|
import { POWRPackImport, POWRPackSelection } from '@/types/powr-pack';
|
||||||
import { InfoIcon } from 'lucide-react-native';
|
import { InfoIcon, X, CheckCircle2 } from 'lucide-react-native';
|
||||||
|
import { useIconColor } from '@/lib/theme/iconUtils';
|
||||||
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
|
import { COLORS } from '@/lib/theme/colors';
|
||||||
|
|
||||||
export default function ImportPOWRPackScreen() {
|
export default function ImportPOWRPackScreen() {
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const powrPackService = usePOWRPackService();
|
const powrPackService = usePOWRPackService();
|
||||||
const [naddrInput, setNaddrInput] = useState('');
|
const params = useLocalSearchParams<{ naddr?: string }>();
|
||||||
|
const [naddrInput, setNaddrInput] = useState(params.naddr || '');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [packData, setPackData] = useState<POWRPackImport | null>(null);
|
const [packData, setPackData] = useState<POWRPackImport | null>(null);
|
||||||
@ -27,6 +30,25 @@ export default function ImportPOWRPackScreen() {
|
|||||||
const [dependencies, setDependencies] = useState<Record<string, string[]>>({});
|
const [dependencies, setDependencies] = useState<Record<string, string[]>>({});
|
||||||
const [isImporting, setIsImporting] = useState(false);
|
const [isImporting, setIsImporting] = useState(false);
|
||||||
const [importSuccess, setImportSuccess] = useState(false);
|
const [importSuccess, setImportSuccess] = useState(false);
|
||||||
|
const { getIconProps } = useIconColor();
|
||||||
|
const { isDarkColorScheme } = useColorScheme();
|
||||||
|
|
||||||
|
// Auto-fetch pack when naddr is provided via params
|
||||||
|
useEffect(() => {
|
||||||
|
if (params.naddr && !isLoading && !packData) {
|
||||||
|
setIsLoading(true);
|
||||||
|
handleFetchPack()
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Auto-fetch error:", err);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [params.naddr]);
|
||||||
|
|
||||||
|
// Handle close button press
|
||||||
|
const handleClose = () => {
|
||||||
|
router.back();
|
||||||
|
};
|
||||||
|
|
||||||
// Handle fetch button click
|
// Handle fetch button click
|
||||||
const handleFetchPack = async () => {
|
const handleFetchPack = async () => {
|
||||||
@ -184,6 +206,12 @@ export default function ImportPOWRPackScreen() {
|
|||||||
options={{
|
options={{
|
||||||
title: 'Import POWR Pack',
|
title: 'Import POWR Pack',
|
||||||
headerShown: true,
|
headerShown: true,
|
||||||
|
// Add a close button for iOS
|
||||||
|
headerRight: () => (
|
||||||
|
<TouchableOpacity onPress={handleClose} style={styles.closeButton}>
|
||||||
|
<X {...getIconProps('primary')} size={24} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -192,10 +220,10 @@ export default function ImportPOWRPackScreen() {
|
|||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
>
|
>
|
||||||
{/* Input section */}
|
{/* Input section */}
|
||||||
<Card>
|
<Card className="mb-4">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<Text className="text-xl font-semibold">Enter POWR Pack Address</Text>
|
<Text className="text-xl font-semibold text-foreground">Enter POWR Pack Address</Text>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<Text className="text-muted-foreground">Paste a POWR Pack naddr to import</Text>
|
<Text className="text-muted-foreground">Paste a POWR Pack naddr to import</Text>
|
||||||
@ -212,35 +240,68 @@ export default function ImportPOWRPackScreen() {
|
|||||||
placeholder="naddr1..."
|
placeholder="naddr1..."
|
||||||
value={naddrInput}
|
value={naddrInput}
|
||||||
onChangeText={setNaddrInput}
|
onChangeText={setNaddrInput}
|
||||||
style={styles.input}
|
style={[styles.input, { height: 80 }]}
|
||||||
|
multiline={true}
|
||||||
|
numberOfLines={3}
|
||||||
|
textAlignVertical="top"
|
||||||
|
className="border-input"
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
<Button
|
{packData ? (
|
||||||
onPress={handleFetchPack}
|
// Success indicator when pack is loaded
|
||||||
disabled={isLoading || !naddrInput.trim()}
|
<View className="w-full flex-row items-center justify-center rounded-md p-3" style={{
|
||||||
className="w-full"
|
backgroundColor: isDarkColorScheme ? 'rgba(34, 197, 94, 0.1)' : 'rgba(34, 197, 94, 0.1)',
|
||||||
>
|
borderWidth: 1,
|
||||||
{isLoading ? (
|
borderColor: isDarkColorScheme ? 'rgba(34, 197, 94, 0.2)' : 'rgba(34, 197, 94, 0.2)'
|
||||||
<ActivityIndicator size="small" color="#fff" />
|
}}>
|
||||||
) : (
|
<CheckCircle2 {...getIconProps('success')} size={16} />
|
||||||
<Text className="text-primary-foreground">Fetch Pack</Text>
|
<Text style={{
|
||||||
)}
|
color: COLORS.success,
|
||||||
</Button>
|
marginLeft: 8,
|
||||||
|
fontWeight: '500'
|
||||||
|
}}>
|
||||||
|
POWR Pack loaded successfully!
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
// Fetch button when no pack is loaded
|
||||||
|
<Button
|
||||||
|
onPress={handleFetchPack}
|
||||||
|
disabled={isLoading || !naddrInput.trim()}
|
||||||
|
className="w-full"
|
||||||
|
style={{ backgroundColor: COLORS.purple.DEFAULT }}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<ActivityIndicator size="small" color="#fff" />
|
||||||
|
) : (
|
||||||
|
<Text style={{ color: '#fff', fontWeight: '500' }}>Fetch Pack</Text>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Error message */}
|
{/* Error message */}
|
||||||
{error && (
|
{error && (
|
||||||
<View className="mb-4 mt-4 p-4 bg-destructive/10 border border-destructive rounded-md flex-row items-center">
|
<View className="mb-4 p-4 rounded-md flex-row items-center" style={{
|
||||||
<Text className="text-destructive ml-2">{error}</Text>
|
backgroundColor: isDarkColorScheme ? 'rgba(239, 68, 68, 0.1)' : 'rgba(239, 68, 68, 0.1)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: isDarkColorScheme ? 'rgba(239, 68, 68, 0.2)' : 'rgba(239, 68, 68, 0.2)'
|
||||||
|
}}>
|
||||||
|
<Text style={{ color: COLORS.destructive, marginLeft: 8 }}>{error}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Success message */}
|
{/* Success message */}
|
||||||
{importSuccess && (
|
{importSuccess && (
|
||||||
<View className="mb-4 mt-4 p-4 bg-green-50 border border-green-200 rounded-md flex-row items-center">
|
<View className="mb-4 p-4 rounded-md flex-row items-center" style={{
|
||||||
<Text className="ml-2 text-green-800">Pack successfully imported!</Text>
|
backgroundColor: isDarkColorScheme ? 'rgba(34, 197, 94, 0.1)' : 'rgba(34, 197, 94, 0.1)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: isDarkColorScheme ? 'rgba(34, 197, 94, 0.2)' : 'rgba(34, 197, 94, 0.2)'
|
||||||
|
}}>
|
||||||
|
<CheckCircle2 {...getIconProps('success')} size={16} style={{ marginRight: 8 }} />
|
||||||
|
<Text style={{ color: COLORS.success, fontWeight: '500' }}>Pack successfully imported!</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -250,7 +311,7 @@ export default function ImportPOWRPackScreen() {
|
|||||||
<Card className="mb-4">
|
<Card className="mb-4">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<Text className="text-xl font-semibold">{getPackTitle()}</Text>
|
<Text className="text-xl font-semibold text-foreground">{getPackTitle()}</Text>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
{getPackDescription() ? (
|
{getPackDescription() ? (
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
@ -259,7 +320,7 @@ export default function ImportPOWRPackScreen() {
|
|||||||
) : null}
|
) : null}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Text className="mb-2">Select items to import:</Text>
|
<Text className="mb-2 text-foreground">Select items to import:</Text>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@ -268,7 +329,7 @@ export default function ImportPOWRPackScreen() {
|
|||||||
<Card className="mb-4">
|
<Card className="mb-4">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<Text className="text-lg font-semibold">Workout Templates</Text>
|
<Text className="text-lg font-semibold text-foreground">Workout Templates</Text>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<Text className="text-muted-foreground">{packData.templates.length} templates available</Text>
|
<Text className="text-muted-foreground">{packData.templates.length} templates available</Text>
|
||||||
@ -285,10 +346,14 @@ export default function ImportPOWRPackScreen() {
|
|||||||
handleTemplateChange(template.id, checked === true)
|
handleTemplateChange(template.id, checked === true)
|
||||||
}
|
}
|
||||||
id={`template-${template.id}`}
|
id={`template-${template.id}`}
|
||||||
|
className="border-input"
|
||||||
/>
|
/>
|
||||||
<Text className="ml-2 flex-1" onPress={() =>
|
<Text
|
||||||
handleTemplateChange(template.id, !selectedTemplates.includes(template.id))
|
className="ml-3 flex-1 text-foreground"
|
||||||
}>
|
onPress={() =>
|
||||||
|
handleTemplateChange(template.id, !selectedTemplates.includes(template.id))
|
||||||
|
}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@ -309,7 +374,7 @@ export default function ImportPOWRPackScreen() {
|
|||||||
<Card className="mb-4">
|
<Card className="mb-4">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<Text className="text-lg font-semibold">Exercises</Text>
|
<Text className="text-lg font-semibold text-foreground">Exercises</Text>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<Text className="text-muted-foreground">{packData.exercises.length} exercises available</Text>
|
<Text className="text-muted-foreground">{packData.exercises.length} exercises available</Text>
|
||||||
@ -329,9 +394,10 @@ export default function ImportPOWRPackScreen() {
|
|||||||
}
|
}
|
||||||
disabled={isRequired}
|
disabled={isRequired}
|
||||||
id={`exercise-${exercise.id}`}
|
id={`exercise-${exercise.id}`}
|
||||||
|
className="border-input"
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
className={`ml-2 flex-1 ${isRequired ? 'font-medium' : ''}`}
|
className={`ml-3 flex-1 ${isRequired ? 'font-medium' : ''} text-foreground`}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (!isRequired) {
|
if (!isRequired) {
|
||||||
handleExerciseChange(exercise.id, !selectedExercises.includes(exercise.id))
|
handleExerciseChange(exercise.id, !selectedExercises.includes(exercise.id))
|
||||||
@ -341,9 +407,11 @@ export default function ImportPOWRPackScreen() {
|
|||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
{isRequired && (
|
{isRequired && (
|
||||||
<View style={styles.requiredBadge}>
|
<View style={[styles.requiredBadge, {
|
||||||
<InfoIcon size={14} color="#6b7280" />
|
backgroundColor: isDarkColorScheme ? COLORS.dark.muted : COLORS.light.muted
|
||||||
<Text className="text-xs text-gray-500 ml-1">Required</Text>
|
}]}>
|
||||||
|
<InfoIcon {...getIconProps('muted')} size={14} />
|
||||||
|
<Text className="text-xs text-muted-foreground ml-1">Required</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@ -363,12 +431,13 @@ export default function ImportPOWRPackScreen() {
|
|||||||
<Button
|
<Button
|
||||||
onPress={handleImport}
|
onPress={handleImport}
|
||||||
disabled={isImporting || (selectedTemplates.length === 0 && selectedExercises.length === 0)}
|
disabled={isImporting || (selectedTemplates.length === 0 && selectedExercises.length === 0)}
|
||||||
className="w-full"
|
className="w-full mb-8"
|
||||||
|
style={{ backgroundColor: COLORS.purple.DEFAULT }}
|
||||||
>
|
>
|
||||||
{isImporting ? (
|
{isImporting ? (
|
||||||
<ActivityIndicator size="small" color="#fff" />
|
<ActivityIndicator size="small" color="#fff" />
|
||||||
) : (
|
) : (
|
||||||
<Text className="text-primary-foreground">
|
<Text style={{ color: '#fff', fontWeight: '500' }}>
|
||||||
Import {selectedTemplates.length + selectedExercises.length} Items
|
Import {selectedTemplates.length + selectedExercises.length} Items
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@ -389,25 +458,27 @@ const styles = StyleSheet.create({
|
|||||||
paddingBottom: 80,
|
paddingBottom: 80,
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
marginBottom: 16,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
packContent: {
|
packContent: {
|
||||||
marginTop: 16,
|
marginTop: 8,
|
||||||
},
|
},
|
||||||
itemRow: {
|
itemRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingVertical: 8,
|
paddingVertical: 10,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: '#f0f0f0',
|
borderBottomColor: '#f0f0f0',
|
||||||
},
|
},
|
||||||
requiredBadge: {
|
requiredBadge: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: '#f9fafb',
|
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 2,
|
paddingVertical: 2,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
marginLeft: 8,
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
padding: 8,
|
||||||
}
|
}
|
||||||
});
|
});
|
@ -8,10 +8,12 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
|||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
|
||||||
import { POWRPackWithContent } from '@/types/powr-pack';
|
import { POWRPackWithContent } from '@/types/powr-pack';
|
||||||
// Fix database context import
|
|
||||||
import { usePOWRPackService } from '@/components/DatabaseProvider';
|
import { usePOWRPackService } from '@/components/DatabaseProvider';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { Trash2, PackageOpen, Plus } from 'lucide-react-native';
|
import { Trash2, PackageOpen, Plus, X } from 'lucide-react-native';
|
||||||
|
import { useIconColor } from '@/lib/theme/iconUtils';
|
||||||
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
|
import { COLORS } from '@/lib/theme/colors';
|
||||||
|
|
||||||
export default function ManagePOWRPacksScreen() {
|
export default function ManagePOWRPacksScreen() {
|
||||||
const powrPackService = usePOWRPackService();
|
const powrPackService = usePOWRPackService();
|
||||||
@ -19,7 +21,8 @@ export default function ManagePOWRPacksScreen() {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [selectedPackId, setSelectedPackId] = useState<string | null>(null);
|
const [selectedPackId, setSelectedPackId] = useState<string | null>(null);
|
||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
const [keepItems, setKeepItems] = useState(true);
|
const { getIconProps } = useIconColor();
|
||||||
|
const { isDarkColorScheme } = useColorScheme();
|
||||||
|
|
||||||
// Load imported packs
|
// Load imported packs
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -44,6 +47,11 @@ export default function ManagePOWRPacksScreen() {
|
|||||||
router.push('/(packs)/import');
|
router.push('/(packs)/import');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle close button press
|
||||||
|
const handleClose = () => {
|
||||||
|
router.back();
|
||||||
|
};
|
||||||
|
|
||||||
// Handle delete button click
|
// Handle delete button click
|
||||||
const handleDeleteClick = (packId: string) => {
|
const handleDeleteClick = (packId: string) => {
|
||||||
setSelectedPackId(packId);
|
setSelectedPackId(packId);
|
||||||
@ -55,7 +63,8 @@ export default function ManagePOWRPacksScreen() {
|
|||||||
if (!selectedPackId) return;
|
if (!selectedPackId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await powrPackService.deletePack(selectedPackId, keepItems);
|
// Always delete everything (we no longer need the keepItems parameter)
|
||||||
|
await powrPackService.deletePack(selectedPackId, false);
|
||||||
// Refresh the list
|
// Refresh the list
|
||||||
loadPacks();
|
loadPacks();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -77,34 +86,41 @@ export default function ManagePOWRPacksScreen() {
|
|||||||
options={{
|
options={{
|
||||||
title: 'Manage POWR Packs',
|
title: 'Manage POWR Packs',
|
||||||
headerShown: true,
|
headerShown: true,
|
||||||
|
// Add a close button for iOS
|
||||||
|
headerRight: () => (
|
||||||
|
<TouchableOpacity onPress={handleClose} style={styles.closeButton}>
|
||||||
|
<X {...getIconProps('primary')} size={24} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||||
{/* Import button - fix icon usage */}
|
{/* Import button */}
|
||||||
<Button
|
<Button
|
||||||
onPress={handleImport}
|
onPress={handleImport}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
|
style={{ backgroundColor: COLORS.purple.DEFAULT }}
|
||||||
>
|
>
|
||||||
<Plus size={18} color="#fff" className="mr-2" />
|
<Plus size={18} color="#fff" style={{ marginRight: 8 }} />
|
||||||
<Text className="text-primary-foreground">Import New Pack</Text>
|
<Text style={{ color: '#fff', fontWeight: '500' }}>Import New Pack</Text>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* No packs message */}
|
{/* No packs message */}
|
||||||
{!isLoading && packs.length === 0 && (
|
{!isLoading && packs.length === 0 && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="py-8 items-center">
|
<CardContent className="py-8 items-center">
|
||||||
<PackageOpen size={48} color="#6b7280" />
|
<PackageOpen size={48} {...getIconProps('muted')} />
|
||||||
<Text className="text-lg font-medium mt-4 text-center">No POWR Packs Imported</Text>
|
<Text className="text-lg font-medium mt-4 text-center text-foreground">No POWR Packs Imported</Text>
|
||||||
<Text className="text-center mt-2 text-gray-500">
|
<Text className="text-center mt-2 text-muted-foreground">
|
||||||
Import workout packs shared by the community to get started.
|
Import workout packs shared by the community to get started.
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
onPress={handleImport}
|
onPress={handleImport}
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
variant="outline"
|
style={{ backgroundColor: COLORS.purple.DEFAULT }}
|
||||||
>
|
>
|
||||||
<Text>Import Your First Pack</Text>
|
<Text style={{ color: '#fff', fontWeight: '500' }}>Import Your First Pack</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -120,7 +136,7 @@ export default function ManagePOWRPacksScreen() {
|
|||||||
<View style={styles.cardHeaderContent}>
|
<View style={styles.cardHeaderContent}>
|
||||||
<View style={styles.cardHeaderText}>
|
<View style={styles.cardHeaderText}>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<Text className="text-lg font-semibold">{pack.title}</Text>
|
<Text className="text-lg font-semibold text-foreground">{pack.title}</Text>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
{pack.description && (
|
{pack.description && (
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
@ -132,18 +148,18 @@ export default function ManagePOWRPacksScreen() {
|
|||||||
onPress={() => handleDeleteClick(pack.id)}
|
onPress={() => handleDeleteClick(pack.id)}
|
||||||
style={styles.deleteButton}
|
style={styles.deleteButton}
|
||||||
>
|
>
|
||||||
<Trash2 size={20} color="#ef4444" />
|
<Trash2 {...getIconProps('destructive')} size={20} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<View style={styles.statsRow}>
|
<View style={styles.statsRow}>
|
||||||
<Text className="text-gray-500">
|
<Text className="text-muted-foreground">
|
||||||
{templates.length} template{templates.length !== 1 ? 's' : ''} • {exercises.length} exercise{exercises.length !== 1 ? 's' : ''}
|
{templates.length} template{templates.length !== 1 ? 's' : ''} • {exercises.length} exercise{exercises.length !== 1 ? 's' : ''}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Separator className="my-2" />
|
<Separator className="my-2" />
|
||||||
<Text className="text-sm text-gray-500">
|
<Text className="text-sm text-muted-foreground">
|
||||||
Imported {formatImportDate(pack.importDate)}
|
Imported {formatImportDate(pack.importDate)}
|
||||||
</Text>
|
</Text>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -156,47 +172,21 @@ export default function ManagePOWRPacksScreen() {
|
|||||||
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Delete Pack?</AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
|
<Text>Delete Pack</Text>
|
||||||
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
<Text>
|
<Text>This will remove the POWR Pack and all its associated templates and exercises from your library.</Text>
|
||||||
This will remove the POWR Pack from your library. Do you want to keep the imported exercises and templates?
|
|
||||||
</Text>
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<View className="flex-row justify-center gap-3 px-4 mt-2">
|
||||||
<View style={styles.dialogOptions}>
|
<AlertDialogCancel onPress={() => setShowDeleteDialog(false)}>
|
||||||
<Button
|
<Text>Cancel</Text>
|
||||||
variant={keepItems ? "default" : "outline"}
|
</AlertDialogCancel>
|
||||||
onPress={() => setKeepItems(true)}
|
<AlertDialogAction onPress={handleDeleteConfirm} className='bg-destructive'>
|
||||||
className="flex-1 mr-2"
|
<Text className='text-destructive-foreground'>Delete Pack</Text>
|
||||||
>
|
</AlertDialogAction>
|
||||||
<Text className={keepItems ? "text-primary-foreground" : ""}>
|
</View>
|
||||||
Keep Items
|
|
||||||
</Text>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={!keepItems ? "default" : "outline"}
|
|
||||||
onPress={() => setKeepItems(false)}
|
|
||||||
className="flex-1"
|
|
||||||
>
|
|
||||||
<Text className={!keepItems ? "text-primary-foreground" : ""}>
|
|
||||||
Delete All
|
|
||||||
</Text>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
<View style={styles.dialogActions}>
|
|
||||||
<AlertDialogCancel asChild>
|
|
||||||
<Button variant="outline" className="mr-2">
|
|
||||||
<Text>Cancel</Text>
|
|
||||||
</Button>
|
|
||||||
</AlertDialogCancel>
|
|
||||||
<AlertDialogAction asChild>
|
|
||||||
<Button variant="destructive" onPress={handleDeleteConfirm}>
|
|
||||||
<Text className="text-destructive-foreground">Confirm</Text>
|
|
||||||
</Button>
|
|
||||||
</AlertDialogAction>
|
|
||||||
</View>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
</View>
|
</View>
|
||||||
@ -228,12 +218,11 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
},
|
},
|
||||||
dialogOptions: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
dialogActions: {
|
dialogActions: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
padding: 8,
|
||||||
}
|
}
|
||||||
});
|
});
|
116
app/_layout.tsx
116
app/_layout.tsx
@ -18,6 +18,42 @@ import SettingsDrawer from '@/components/SettingsDrawer';
|
|||||||
import RelayInitializer from '@/components/RelayInitializer';
|
import RelayInitializer from '@/components/RelayInitializer';
|
||||||
import { useNDKStore } from '@/lib/stores/ndk';
|
import { useNDKStore } from '@/lib/stores/ndk';
|
||||||
import { useWorkoutStore } from '@/stores/workoutStore';
|
import { useWorkoutStore } from '@/stores/workoutStore';
|
||||||
|
// Import splash screens with fallback mechanism
|
||||||
|
let SplashComponent: React.ComponentType<{onFinish: () => void}>;
|
||||||
|
|
||||||
|
// First try to import the video splash screen
|
||||||
|
try {
|
||||||
|
// Try to dynamically import the Video component
|
||||||
|
const Video = require('expo-av').Video;
|
||||||
|
// If successful, import the VideoSplashScreen
|
||||||
|
SplashComponent = require('@/components/VideoSplashScreen').default;
|
||||||
|
console.log('Successfully imported VideoSplashScreen');
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to import VideoSplashScreen or expo-av:', e);
|
||||||
|
// If that fails, use the simple splash screen
|
||||||
|
try {
|
||||||
|
SplashComponent = require('@/components/SimpleSplashScreen').default;
|
||||||
|
console.log('Using SimpleSplashScreen as fallback');
|
||||||
|
} catch (simpleSplashError) {
|
||||||
|
console.warn('Failed to import SimpleSplashScreen:', simpleSplashError);
|
||||||
|
// Last resort fallback is an inline component
|
||||||
|
SplashComponent = ({onFinish}) => {
|
||||||
|
React.useEffect(() => {
|
||||||
|
// Call onFinish after a short delay
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
onFinish();
|
||||||
|
}, 500);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [onFinish]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="flex-1 items-center justify-center bg-black">
|
||||||
|
<Text className="text-white text-xl">Loading POWR...</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log('_layout.tsx loaded');
|
console.log('_layout.tsx loaded');
|
||||||
|
|
||||||
@ -33,45 +69,89 @@ const DARK_THEME = {
|
|||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
const [isInitialized, setIsInitialized] = React.useState(false);
|
const [isInitialized, setIsInitialized] = React.useState(false);
|
||||||
|
const [isSplashFinished, setIsSplashFinished] = React.useState(false);
|
||||||
const { colorScheme, isDarkColorScheme } = useColorScheme();
|
const { colorScheme, isDarkColorScheme } = useColorScheme();
|
||||||
const { init } = useNDKStore();
|
const { init } = useNDKStore();
|
||||||
|
const initializationPromise = React.useRef<Promise<void> | null>(null);
|
||||||
|
|
||||||
|
// Start app initialization immediately
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
async function initApp() {
|
if (!initializationPromise.current) {
|
||||||
try {
|
initializationPromise.current = (async () => {
|
||||||
if (Platform.OS === 'web') {
|
try {
|
||||||
document.documentElement.classList.add('bg-background');
|
console.log('Starting app initialization in background...');
|
||||||
|
if (Platform.OS === 'web') {
|
||||||
|
document.documentElement.classList.add('bg-background');
|
||||||
|
}
|
||||||
|
setAndroidNavigationBar(colorScheme);
|
||||||
|
|
||||||
|
// Initialize NDK
|
||||||
|
await init();
|
||||||
|
|
||||||
|
// Load favorites from SQLite
|
||||||
|
await useWorkoutStore.getState().loadFavorites();
|
||||||
|
|
||||||
|
console.log('App initialization completed!');
|
||||||
|
setIsInitialized(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize:', error);
|
||||||
}
|
}
|
||||||
setAndroidNavigationBar(colorScheme);
|
})();
|
||||||
|
|
||||||
// Initialize NDK
|
|
||||||
await init();
|
|
||||||
|
|
||||||
// Load favorites from SQLite
|
|
||||||
await useWorkoutStore.getState().loadFavorites();
|
|
||||||
|
|
||||||
setIsInitialized(true);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to initialize:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initApp();
|
return () => {
|
||||||
|
// This is just for cleanup, the promise will continue executing
|
||||||
|
initializationPromise.current = null;
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Function to handle splash finish - will check if initialization is also complete
|
||||||
|
const handleSplashFinish = React.useCallback(() => {
|
||||||
|
console.log('Splash video finished playing');
|
||||||
|
setIsSplashFinished(true);
|
||||||
|
|
||||||
|
// If initialization isn't done yet, we'll show a loading indicator
|
||||||
|
if (!isInitialized) {
|
||||||
|
console.log('Waiting for initialization to complete...');
|
||||||
|
}
|
||||||
|
}, [isInitialized]);
|
||||||
|
|
||||||
|
// Show splash screen if not finished
|
||||||
|
if (!isSplashFinished) {
|
||||||
|
try {
|
||||||
|
return <SplashComponent onFinish={handleSplashFinish} />;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error rendering splash screen:', e);
|
||||||
|
// Skip splash screen if there's an error
|
||||||
|
if (!isInitialized) {
|
||||||
|
return (
|
||||||
|
<View className="flex-1 items-center justify-center bg-background">
|
||||||
|
<Text className="text-foreground">Loading...</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Force continue to main app
|
||||||
|
setIsSplashFinished(true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If splash is done but initialization isn't, show loading
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
return (
|
return (
|
||||||
<View className="flex-1 items-center justify-center bg-background">
|
<View className="flex-1 items-center justify-center bg-background">
|
||||||
<Text className="text-foreground">Initializing...</Text>
|
<Text className="text-foreground">Finalizing setup...</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Main app UI wrapped in error boundary
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||||
<DatabaseProvider>
|
<DatabaseProvider>
|
||||||
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
|
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
|
||||||
|
{/* Ensure SettingsDrawerProvider wraps everything */}
|
||||||
<SettingsDrawerProvider>
|
<SettingsDrawerProvider>
|
||||||
{/* Add RelayInitializer here - it loads relay data once NDK is available */}
|
{/* Add RelayInitializer here - it loads relay data once NDK is available */}
|
||||||
<RelayInitializer />
|
<RelayInitializer />
|
||||||
|
BIN
assets/splash.mov
Normal file
BIN
assets/splash.mov
Normal file
Binary file not shown.
BIN
assets/v1-splash.mov
Normal file
BIN
assets/v1-splash.mov
Normal file
Binary file not shown.
64
components/SimpleSplashScreen.tsx
Normal file
64
components/SimpleSplashScreen.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// components/SimpleSplashScreen.tsx
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { View, Image, ActivityIndicator, StyleSheet } from 'react-native';
|
||||||
|
import * as SplashScreen from 'expo-splash-screen';
|
||||||
|
|
||||||
|
// Keep the splash screen visible while we fetch resources
|
||||||
|
SplashScreen.preventAutoHideAsync().catch(() => {
|
||||||
|
/* ignore error */
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SplashScreenProps {
|
||||||
|
onFinish: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SimpleSplashScreen: React.FC<SplashScreenProps> = ({ onFinish }) => {
|
||||||
|
useEffect(() => {
|
||||||
|
// Hide the native splash screen
|
||||||
|
SplashScreen.hideAsync().catch(() => {
|
||||||
|
/* ignore error */
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate video duration with a timeout
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
onFinish();
|
||||||
|
}, 2000); // 2 seconds splash display
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [onFinish]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{/* Use a static image as fallback */}
|
||||||
|
<Image
|
||||||
|
source={require('../assets/images/splash.png')}
|
||||||
|
style={styles.image}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
<ActivityIndicator
|
||||||
|
size="large"
|
||||||
|
color="#ffffff"
|
||||||
|
style={styles.loader}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: '80%',
|
||||||
|
height: '80%',
|
||||||
|
},
|
||||||
|
loader: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SimpleSplashScreen;
|
102
components/VideoSplashScreen.tsx
Normal file
102
components/VideoSplashScreen.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// components/VideoSplashScreen.tsx
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { View, StyleSheet, ActivityIndicator } from 'react-native';
|
||||||
|
import { Video, ResizeMode, AVPlaybackStatus } from 'expo-av';
|
||||||
|
import * as SplashScreen from 'expo-splash-screen';
|
||||||
|
|
||||||
|
// Keep the splash screen visible while we fetch resources
|
||||||
|
SplashScreen.preventAutoHideAsync();
|
||||||
|
|
||||||
|
interface VideoSplashScreenProps {
|
||||||
|
onFinish: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VideoSplashScreen: React.FC<VideoSplashScreenProps> = ({ onFinish }) => {
|
||||||
|
const videoRef = useRef<Video>(null);
|
||||||
|
const [isVideoLoaded, setIsVideoLoaded] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Hide the native splash screen once our component is ready
|
||||||
|
if (isVideoLoaded) {
|
||||||
|
console.log('Video loaded, hiding native splash screen');
|
||||||
|
SplashScreen.hideAsync().catch(e => console.log('Error hiding splash screen:', e));
|
||||||
|
}
|
||||||
|
}, [isVideoLoaded]);
|
||||||
|
|
||||||
|
const handleVideoLoad = () => {
|
||||||
|
console.log('Video loaded successfully');
|
||||||
|
setIsVideoLoaded(true);
|
||||||
|
// Start playing the video once it's loaded
|
||||||
|
videoRef.current?.playAsync().catch(e => {
|
||||||
|
console.error('Error playing video:', e);
|
||||||
|
// If video fails to play, just call onFinish
|
||||||
|
onFinish();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVideoError = (error: string) => {
|
||||||
|
console.error('Video error:', error);
|
||||||
|
setError(error);
|
||||||
|
// On error, skip the video and move to the app
|
||||||
|
onFinish();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePlaybackStatusUpdate = (status: AVPlaybackStatus) => {
|
||||||
|
// Type-safe handling of playback status
|
||||||
|
if (!status.isLoaded) {
|
||||||
|
// Handle error state
|
||||||
|
if (status.error) {
|
||||||
|
handleVideoError(`Video error: ${status.error}`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When video finishes playing, call the onFinish callback
|
||||||
|
if (status.didJustFinish) {
|
||||||
|
console.log('Video finished playing');
|
||||||
|
onFinish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Video
|
||||||
|
ref={videoRef}
|
||||||
|
source={require('../assets/splash.mov')}
|
||||||
|
style={styles.video}
|
||||||
|
resizeMode={ResizeMode.COVER}
|
||||||
|
onLoad={handleVideoLoad}
|
||||||
|
onError={(error) => handleVideoError(error.toString())}
|
||||||
|
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
|
||||||
|
shouldPlay={false} // We'll play it manually after load
|
||||||
|
progressUpdateIntervalMillis={50} // More frequent updates
|
||||||
|
/>
|
||||||
|
{!isVideoLoaded && (
|
||||||
|
<View style={styles.loaderContainer}>
|
||||||
|
<ActivityIndicator size="large" color="#ffffff" />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#000',
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
flex: 1,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
loaderContainer: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default VideoSplashScreen;
|
@ -83,14 +83,12 @@ export default function POWRPackSection() {
|
|||||||
relays
|
relays
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy to clipboard
|
// Navigate to import screen with the naddr as a parameter
|
||||||
Clipboard.setString(naddr);
|
router.push({
|
||||||
|
pathname: '/(packs)/import',
|
||||||
|
params: { naddr }
|
||||||
|
});
|
||||||
|
|
||||||
// Navigate to import screen
|
|
||||||
router.push('/(packs)/import');
|
|
||||||
|
|
||||||
// Alert user that the address has been copied
|
|
||||||
alert('Pack address copied to clipboard. Paste it in the import field.');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error handling pack click:', error);
|
console.error('Error handling pack click:', error);
|
||||||
alert('Failed to prepare pack for import. Please try again.');
|
alert('Failed to prepare pack for import. Please try again.');
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- boost (1.84.0)
|
- boost (1.84.0)
|
||||||
- DoubleConversion (1.1.6)
|
- DoubleConversion (1.1.6)
|
||||||
|
- EXAV (15.0.2):
|
||||||
|
- ExpoModulesCore
|
||||||
|
- ReactCommon/turbomodule/core
|
||||||
- EXConstants (17.0.6):
|
- EXConstants (17.0.6):
|
||||||
- ExpoModulesCore
|
- ExpoModulesCore
|
||||||
- EXJSONUtils (0.14.0)
|
- EXJSONUtils (0.14.0)
|
||||||
@ -2110,6 +2113,7 @@ PODS:
|
|||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
|
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
|
||||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||||
|
- EXAV (from `../node_modules/expo-av/ios`)
|
||||||
- EXConstants (from `../node_modules/expo-constants/ios`)
|
- EXConstants (from `../node_modules/expo-constants/ios`)
|
||||||
- EXJSONUtils (from `../node_modules/expo-json-utils/ios`)
|
- EXJSONUtils (from `../node_modules/expo-json-utils/ios`)
|
||||||
- EXManifests (from `../node_modules/expo-manifests/ios`)
|
- EXManifests (from `../node_modules/expo-manifests/ios`)
|
||||||
@ -2215,6 +2219,8 @@ EXTERNAL SOURCES:
|
|||||||
:podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec"
|
:podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec"
|
||||||
DoubleConversion:
|
DoubleConversion:
|
||||||
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
||||||
|
EXAV:
|
||||||
|
:path: "../node_modules/expo-av/ios"
|
||||||
EXConstants:
|
EXConstants:
|
||||||
:path: "../node_modules/expo-constants/ios"
|
:path: "../node_modules/expo-constants/ios"
|
||||||
EXJSONUtils:
|
EXJSONUtils:
|
||||||
@ -2406,6 +2412,7 @@ EXTERNAL SOURCES:
|
|||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
|
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
|
||||||
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
|
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
|
||||||
|
EXAV: 4c41f0a6ae54a0b96597abc90e37962ab2ee0d85
|
||||||
EXConstants: f5c27bfa40ba650257535ab958f4f5876ee93edd
|
EXConstants: f5c27bfa40ba650257535ab958f4f5876ee93edd
|
||||||
EXJSONUtils: 01fc7492b66c234e395dcffdd5f53439c5c29c93
|
EXJSONUtils: 01fc7492b66c234e395dcffdd5f53439c5c29c93
|
||||||
EXManifests: 807ab5394ca9f8dd5e64283f02876b2f85c4eb72
|
EXManifests: 807ab5394ca9f8dd5e64283f02876b2f85c4eb72
|
||||||
|
@ -537,7 +537,10 @@ export class NostrIntegration {
|
|||||||
exerciseRefs: string[]
|
exerciseRefs: string[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log(`Saving ${exerciseIds.length} exercise relationships for template ${templateId}`);
|
console.log(`=== SAVING TEMPLATE EXERCISES ===`);
|
||||||
|
console.log(`Template ID: ${templateId}`);
|
||||||
|
console.log(`Exercise IDs (${exerciseIds.length}):`, exerciseIds);
|
||||||
|
console.log(`Exercise Refs (${exerciseRefs.length}):`, exerciseRefs);
|
||||||
|
|
||||||
// Check if nostr_reference column exists
|
// Check if nostr_reference column exists
|
||||||
const hasNostrReference = await this.columnExists('template_exercises', 'nostr_reference');
|
const hasNostrReference = await this.columnExists('template_exercises', 'nostr_reference');
|
||||||
@ -555,6 +558,12 @@ export class NostrIntegration {
|
|||||||
await this.db.execAsync(`ALTER TABLE template_exercises ADD COLUMN relay_hints TEXT`);
|
await this.db.execAsync(`ALTER TABLE template_exercises ADD COLUMN relay_hints TEXT`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear out any existing entries for this template
|
||||||
|
await this.db.runAsync(
|
||||||
|
`DELETE FROM template_exercises WHERE template_id = ?`,
|
||||||
|
[templateId]
|
||||||
|
);
|
||||||
|
|
||||||
// Create template exercise records
|
// Create template exercise records
|
||||||
for (let i = 0; i < exerciseIds.length; i++) {
|
for (let i = 0; i < exerciseIds.length; i++) {
|
||||||
const exerciseId = exerciseIds[i];
|
const exerciseId = exerciseIds[i];
|
||||||
@ -615,7 +624,15 @@ export class NostrIntegration {
|
|||||||
console.log(`Saved template-exercise relationship: template=${templateId}, exercise=${exerciseId} with ${relayHints.length} relay hints`);
|
console.log(`Saved template-exercise relationship: template=${templateId}, exercise=${exerciseId} with ${relayHints.length} relay hints`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Successfully saved ${exerciseIds.length} template-exercise relationships for template ${templateId}`);
|
// Verify the relationships were saved
|
||||||
|
const savedRelationships = await this.db.getAllAsync<{id: string, exercise_id: string}>(
|
||||||
|
`SELECT id, exercise_id FROM template_exercises WHERE template_id = ?`,
|
||||||
|
[templateId]
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Saved ${savedRelationships.length} template-exercise relationships for template ${templateId}`);
|
||||||
|
savedRelationships.forEach(rel => console.log(` - Exercise ID: ${rel.exercise_id}`));
|
||||||
|
console.log(`=== END SAVING TEMPLATE EXERCISES ===`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving template exercises with parameters:', error);
|
console.error('Error saving template exercises with parameters:', error);
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -417,7 +417,8 @@ export default class POWRPackService {
|
|||||||
*/
|
*/
|
||||||
async importPack(packImport: POWRPackImport, selection: POWRPackSelection): Promise<void> {
|
async importPack(packImport: POWRPackImport, selection: POWRPackSelection): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log(`Importing ${selection.selectedExercises.length} exercises...`);
|
console.log(`=== STARTING PACK IMPORT ===`);
|
||||||
|
console.log(`Importing ${selection.selectedExercises.length} exercises and ${selection.selectedTemplates.length} templates...`);
|
||||||
|
|
||||||
// Map to track imported exercise IDs by various reference formats
|
// Map to track imported exercise IDs by various reference formats
|
||||||
const exerciseIdMap = new Map<string, string>();
|
const exerciseIdMap = new Map<string, string>();
|
||||||
@ -425,7 +426,10 @@ export default class POWRPackService {
|
|||||||
// First, import the selected exercises
|
// First, import the selected exercises
|
||||||
for (const exerciseId of selection.selectedExercises) {
|
for (const exerciseId of selection.selectedExercises) {
|
||||||
const exerciseEvent = packImport.exercises.find(e => e.id === exerciseId);
|
const exerciseEvent = packImport.exercises.find(e => e.id === exerciseId);
|
||||||
if (!exerciseEvent) continue;
|
if (!exerciseEvent) {
|
||||||
|
console.log(`Exercise event ${exerciseId} not found in pack data`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the d-tag value from the event
|
// Get the d-tag value from the event
|
||||||
const dTag = exerciseEvent.tagValue('d');
|
const dTag = exerciseEvent.tagValue('d');
|
||||||
@ -456,12 +460,17 @@ export default class POWRPackService {
|
|||||||
console.log(`Imported exercise: ${exerciseModel.title} (${localId}) from Nostr event ${exerciseId}`);
|
console.log(`Imported exercise: ${exerciseModel.title} (${localId}) from Nostr event ${exerciseId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Importing ${selection.selectedTemplates.length} templates...`);
|
console.log(`=== EXERCISE IMPORT COMPLETE ===`);
|
||||||
|
console.log(`Total exercise reference mappings: ${exerciseIdMap.size}`);
|
||||||
|
console.log(`Now importing ${selection.selectedTemplates.length} templates...`);
|
||||||
|
|
||||||
// Then, import the selected templates
|
// Then, import the selected templates
|
||||||
for (const templateId of selection.selectedTemplates) {
|
for (const templateId of selection.selectedTemplates) {
|
||||||
const templateEvent = packImport.templates.find(t => t.id === templateId);
|
const templateEvent = packImport.templates.find(t => t.id === templateId);
|
||||||
if (!templateEvent) continue;
|
if (!templateEvent) {
|
||||||
|
console.log(`Template event ${templateId} not found in pack data`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Convert to local model
|
// Convert to local model
|
||||||
const templateModel = this.nostrIntegration.convertNostrTemplateToLocal(templateEvent);
|
const templateModel = this.nostrIntegration.convertNostrTemplateToLocal(templateEvent);
|
||||||
@ -481,6 +490,10 @@ export default class POWRPackService {
|
|||||||
const templateExerciseIds: string[] = [];
|
const templateExerciseIds: string[] = [];
|
||||||
const matchedRefs: string[] = [];
|
const matchedRefs: string[] = [];
|
||||||
|
|
||||||
|
console.log(`=== DEBUG: Template ID Mapping Process ===`);
|
||||||
|
console.log(`Template Event: ${templateEvent.id}`);
|
||||||
|
console.log(`Template Local ID: ${localTemplateId}`);
|
||||||
|
|
||||||
for (const ref of exerciseRefs) {
|
for (const ref of exerciseRefs) {
|
||||||
// Extract the base reference (before any parameters)
|
// Extract the base reference (before any parameters)
|
||||||
const refParts = ref.split('::');
|
const refParts = ref.split('::');
|
||||||
@ -496,12 +509,12 @@ export default class POWRPackService {
|
|||||||
templateExerciseIds.push(localExerciseId);
|
templateExerciseIds.push(localExerciseId);
|
||||||
matchedRefs.push(ref);
|
matchedRefs.push(ref);
|
||||||
|
|
||||||
console.log(`Mapped reference ${baseRef} to local exercise ID ${localExerciseId}`);
|
console.log(`✅ Direct match found! Mapped reference ${baseRef} to local exercise ID ${localExerciseId}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not found by direct reference, try to match by examining individual components
|
// If not found by direct reference, try to match by examining individual components
|
||||||
console.log(`No direct match for reference: ${baseRef}. Trying to match by components...`);
|
console.log(`⚠️ No direct match for reference: ${baseRef}. Trying to match by components...`);
|
||||||
|
|
||||||
// Parse the reference for fallback matching
|
// Parse the reference for fallback matching
|
||||||
const refSegments = baseRef.split(':');
|
const refSegments = baseRef.split(':');
|
||||||
@ -510,44 +523,53 @@ export default class POWRPackService {
|
|||||||
const refPubkey = refSegments[1];
|
const refPubkey = refSegments[1];
|
||||||
const refDTag = refSegments[2];
|
const refDTag = refSegments[2];
|
||||||
|
|
||||||
// Try to find the matching exercise by looking at both event ID and d-tag
|
// Log all entries in exerciseIdMap for debugging
|
||||||
for (const [key, value] of exerciseIdMap.entries()) {
|
console.log(`Looking among ${exerciseIdMap.size} existing mappings:`);
|
||||||
// Check if this is potentially the same exercise with a different reference format
|
[...exerciseIdMap.entries()].forEach(([key, value]) => {
|
||||||
if (key.includes(refPubkey) && (key.includes(refDTag) || key.endsWith(refDTag))) {
|
console.log(` - ${key} -> ${value}`);
|
||||||
templateExerciseIds.push(value);
|
});
|
||||||
matchedRefs.push(ref);
|
|
||||||
|
|
||||||
// Also add this reference format to map for future lookups
|
// Try to find the matching exercise by direct dTag match
|
||||||
exerciseIdMap.set(baseRef, value);
|
const directDTagMatch = [...exerciseIdMap.entries()].find(([key, _]) => {
|
||||||
|
return key.includes(refPubkey) && key.includes(refDTag);
|
||||||
|
});
|
||||||
|
|
||||||
console.log(`Found potential match using partial comparison: ${key} -> ${value}`);
|
if (directDTagMatch) {
|
||||||
break;
|
const [matchKey, matchValue] = directDTagMatch;
|
||||||
}
|
templateExerciseIds.push(matchValue);
|
||||||
|
matchedRefs.push(ref);
|
||||||
|
|
||||||
|
// Also add this reference format to map for future lookups
|
||||||
|
exerciseIdMap.set(baseRef, matchValue);
|
||||||
|
|
||||||
|
console.log(`✅ Found match by dTag: ${matchKey} -> ${matchValue}`);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no match found yet, check if there's a direct event ID match
|
// Try to find the matching exercise by event ID
|
||||||
if (templateExerciseIds.length === templateExerciseIds.lastIndexOf(refDTag) + 1) {
|
const matchingEvent = packImport.exercises.find(e => e.id === refDTag);
|
||||||
// Didn't add anything in the above loop, try direct event ID lookup
|
|
||||||
const matchingEvent = packImport.exercises.find(e => e.id === refDTag);
|
|
||||||
|
|
||||||
if (matchingEvent && exerciseIdMap.has(matchingEvent.id)) {
|
if (matchingEvent && exerciseIdMap.has(matchingEvent.id)) {
|
||||||
const localExerciseId = exerciseIdMap.get(matchingEvent.id) || '';
|
const localExerciseId = exerciseIdMap.get(matchingEvent.id) || '';
|
||||||
templateExerciseIds.push(localExerciseId);
|
templateExerciseIds.push(localExerciseId);
|
||||||
matchedRefs.push(ref);
|
matchedRefs.push(ref);
|
||||||
|
|
||||||
// Add this reference to our map for future use
|
// Add this reference to our map for future use
|
||||||
exerciseIdMap.set(baseRef, localExerciseId);
|
exerciseIdMap.set(baseRef, localExerciseId);
|
||||||
|
|
||||||
console.log(`Found match by event ID: ${matchingEvent.id} -> ${localExerciseId}`);
|
console.log(`✅ Found match by event ID: ${matchingEvent.id} -> ${localExerciseId}`);
|
||||||
} else {
|
continue;
|
||||||
console.log(`No matching exercise found for reference components: kind=${refKind}, pubkey=${refPubkey}, d-tag=${refDTag}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`❌ No matching exercise found for reference: ${baseRef}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Invalid reference format: ${baseRef}`);
|
console.log(`❌ Invalid reference format: ${baseRef}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Mapped ${templateExerciseIds.length}/${exerciseRefs.length} references for template ${templateModel.title}`);
|
||||||
|
console.log(`=== END DEBUG ===`);
|
||||||
|
|
||||||
// Save template-exercise relationships with parameters
|
// Save template-exercise relationships with parameters
|
||||||
if (templateExerciseIds.length > 0) {
|
if (templateExerciseIds.length > 0) {
|
||||||
await this.nostrIntegration.saveTemplateExercisesWithParams(
|
await this.nostrIntegration.saveTemplateExercisesWithParams(
|
||||||
@ -564,7 +586,7 @@ export default class POWRPackService {
|
|||||||
);
|
);
|
||||||
console.log(`Template ${templateModel.title} has ${templateExercises.length} exercises associated`);
|
console.log(`Template ${templateModel.title} has ${templateExercises.length} exercises associated`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`No exercise relationships to save for template ${localTemplateId}`);
|
console.log(`⚠️ No exercise relationships to save for template ${localTemplateId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,10 +605,21 @@ export default class POWRPackService {
|
|||||||
`SELECT id, title FROM templates WHERE source = 'nostr'`
|
`SELECT id, title FROM templates WHERE source = 'nostr'`
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Template IDs:`);
|
console.log(`Imported Template IDs:`);
|
||||||
templates.forEach(t => {
|
templates.forEach(t => {
|
||||||
console.log(` - ${t.title}: ${t.id}`);
|
console.log(` - ${t.title}: ${t.id}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Verify template-exercise relationships
|
||||||
|
for (const template of templates) {
|
||||||
|
const relationships = await this.db.getAllAsync<{ count: number }>(
|
||||||
|
`SELECT COUNT(*) as count FROM template_exercises WHERE template_id = ?`,
|
||||||
|
[template.id]
|
||||||
|
);
|
||||||
|
console.log(`Template ${template.title}: ${relationships[0]?.count || 0} exercises`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`=== PACK IMPORT COMPLETE ===`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error importing pack:', error);
|
console.error('Error importing pack:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@ -814,70 +847,112 @@ export default class POWRPackService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a POWR Pack
|
* Delete a POWR Pack and all its associated content
|
||||||
* @param packId The ID of the pack to delete
|
* @param packId The ID of the pack to delete
|
||||||
* @param keepItems Whether to keep the imported templates and exercises
|
* @param keepItems DEPRECATED parameter kept for backward compatibility
|
||||||
*/
|
*/
|
||||||
async deletePack(packId: string, keepItems: boolean = true): Promise<void> {
|
async deletePack(packId: string, keepItems: boolean = false): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (!keepItems) {
|
console.log(`[POWRPackService] Starting deletion of pack ${packId}`);
|
||||||
// Get all templates and exercises from this pack
|
|
||||||
const templates = await this.db.getAllAsync<{ id: string }>(
|
// Always delete everything, ignore keepItems parameter
|
||||||
`SELECT t.id
|
await this.db.withTransactionAsync(async () => {
|
||||||
FROM templates t
|
// First, get all templates and exercises from this pack
|
||||||
JOIN powr_pack_items ppi ON ppi.item_id = t.nostr_event_id
|
console.log(`[POWRPackService] Finding templates associated with pack ${packId}`);
|
||||||
WHERE ppi.pack_id = ? AND ppi.item_type = 'template'`,
|
const templates = await this.db.getAllAsync<{ id: string, nostr_event_id: string }>(
|
||||||
|
`SELECT t.id, t.nostr_event_id
|
||||||
|
FROM templates t
|
||||||
|
JOIN powr_pack_items ppi ON ppi.item_id = t.nostr_event_id
|
||||||
|
WHERE ppi.pack_id = ? AND ppi.item_type = 'template'`,
|
||||||
[packId]
|
[packId]
|
||||||
);
|
);
|
||||||
|
console.log(`[POWRPackService] Found ${templates.length} templates to delete`);
|
||||||
|
|
||||||
const exercises = await this.db.getAllAsync<{ id: string }>(
|
console.log(`[POWRPackService] Finding exercises associated with pack ${packId}`);
|
||||||
`SELECT e.id
|
const exercises = await this.db.getAllAsync<{ id: string, nostr_event_id: string }>(
|
||||||
FROM exercises e
|
`SELECT e.id, e.nostr_event_id
|
||||||
JOIN powr_pack_items ppi ON ppi.item_id = e.nostr_event_id
|
FROM exercises e
|
||||||
WHERE ppi.pack_id = ? AND ppi.item_type = 'exercise'`,
|
JOIN powr_pack_items ppi ON ppi.item_id = e.nostr_event_id
|
||||||
|
WHERE ppi.pack_id = ? AND ppi.item_type = 'exercise'`,
|
||||||
[packId]
|
[packId]
|
||||||
);
|
);
|
||||||
|
console.log(`[POWRPackService] Found ${exercises.length} exercises to delete`);
|
||||||
|
|
||||||
// Delete the templates
|
// Find any additional exercises that are only referenced by templates in this pack
|
||||||
|
console.log(`[POWRPackService] Finding template exercises`);
|
||||||
|
const templateIds = templates.map(t => t.id);
|
||||||
|
let additionalExerciseIds: string[] = [];
|
||||||
|
|
||||||
|
if (templateIds.length > 0) {
|
||||||
|
const templateExercises = await this.db.getAllAsync<{ exercise_id: string }>(
|
||||||
|
`SELECT DISTINCT exercise_id
|
||||||
|
FROM template_exercises
|
||||||
|
WHERE template_id IN (${templateIds.map(() => '?').join(',')})`,
|
||||||
|
templateIds
|
||||||
|
);
|
||||||
|
|
||||||
|
additionalExerciseIds = templateExercises.map(te => te.exercise_id);
|
||||||
|
console.log(`[POWRPackService] Found ${additionalExerciseIds.length} additional exercises from templates`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the templates and their exercises
|
||||||
for (const template of templates) {
|
for (const template of templates) {
|
||||||
|
console.log(`[POWRPackService] Deleting template ${template.id}`);
|
||||||
|
|
||||||
|
// Delete template exercises first
|
||||||
await this.db.runAsync(
|
await this.db.runAsync(
|
||||||
`DELETE FROM template_exercises WHERE template_id = ?`,
|
`DELETE FROM template_exercises WHERE template_id = ?`,
|
||||||
[template.id]
|
[template.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Then delete the template
|
||||||
await this.db.runAsync(
|
await this.db.runAsync(
|
||||||
`DELETE FROM templates WHERE id = ?`,
|
`DELETE FROM templates WHERE id = ?`,
|
||||||
[template.id]
|
[template.id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a set of all exercise IDs to delete
|
||||||
|
const exerciseIdsToDelete = new Set([
|
||||||
|
...exercises.map(e => e.id),
|
||||||
|
...additionalExerciseIds
|
||||||
|
]);
|
||||||
|
|
||||||
// Delete the exercises
|
// Delete the exercises
|
||||||
for (const exercise of exercises) {
|
for (const exerciseId of exerciseIdsToDelete) {
|
||||||
|
console.log(`[POWRPackService] Deleting exercise ${exerciseId}`);
|
||||||
|
|
||||||
|
// Delete exercise tags first
|
||||||
await this.db.runAsync(
|
await this.db.runAsync(
|
||||||
`DELETE FROM exercise_tags WHERE exercise_id = ?`,
|
`DELETE FROM exercise_tags WHERE exercise_id = ?`,
|
||||||
[exercise.id]
|
[exerciseId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Then delete the exercise
|
||||||
await this.db.runAsync(
|
await this.db.runAsync(
|
||||||
`DELETE FROM exercises WHERE id = ?`,
|
`DELETE FROM exercises WHERE id = ?`,
|
||||||
[exercise.id]
|
[exerciseId]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the pack items
|
// Delete the pack items
|
||||||
await this.db.runAsync(
|
console.log(`[POWRPackService] Deleting pack items for pack ${packId}`);
|
||||||
`DELETE FROM powr_pack_items WHERE pack_id = ?`,
|
await this.db.runAsync(
|
||||||
[packId]
|
`DELETE FROM powr_pack_items WHERE pack_id = ?`,
|
||||||
);
|
[packId]
|
||||||
|
);
|
||||||
|
|
||||||
// Finally, delete the pack itself
|
// Finally, delete the pack itself
|
||||||
await this.db.runAsync(
|
console.log(`[POWRPackService] Deleting pack ${packId}`);
|
||||||
`DELETE FROM powr_packs WHERE id = ?`,
|
await this.db.runAsync(
|
||||||
[packId]
|
`DELETE FROM powr_packs WHERE id = ?`,
|
||||||
);
|
[packId]
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`[POWRPackService] Successfully deleted pack ${packId} with ${templates.length} templates and ${exerciseIdsToDelete.size} exercises`);
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting pack:', error);
|
console.error(`[POWRPackService] Error deleting pack ${packId}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,6 +487,14 @@ export class TemplateService {
|
|||||||
try {
|
try {
|
||||||
console.log(`Fetching exercises for template ${templateId}`);
|
console.log(`Fetching exercises for template ${templateId}`);
|
||||||
|
|
||||||
|
// First, just count how many there should be
|
||||||
|
const countResult = await this.db.getFirstAsync<{ count: number }>(
|
||||||
|
`SELECT COUNT(*) as count FROM template_exercises WHERE template_id = ?`,
|
||||||
|
[templateId]
|
||||||
|
);
|
||||||
|
console.log(`Expected template exercises: ${countResult?.count || 0}`);
|
||||||
|
|
||||||
|
// Now get the actual records
|
||||||
const exercises = await this.db.getAllAsync<{
|
const exercises = await this.db.getAllAsync<{
|
||||||
id: string;
|
id: string;
|
||||||
exercise_id: string;
|
exercise_id: string;
|
||||||
@ -507,6 +515,12 @@ export class TemplateService {
|
|||||||
console.log(`Found ${exercises.length} template exercises in database`);
|
console.log(`Found ${exercises.length} template exercises in database`);
|
||||||
|
|
||||||
if (exercises.length === 0) {
|
if (exercises.length === 0) {
|
||||||
|
console.log(`No exercises found for template ${templateId} - verifying template exists...`);
|
||||||
|
const templateExists = await this.db.getFirstAsync<{ id: string }>(
|
||||||
|
`SELECT id FROM templates WHERE id = ?`,
|
||||||
|
[templateId]
|
||||||
|
);
|
||||||
|
console.log(`Template exists: ${templateExists ? 'Yes' : 'No'}`);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,6 +528,7 @@ export class TemplateService {
|
|||||||
const result: TemplateExerciseWithData[] = [];
|
const result: TemplateExerciseWithData[] = [];
|
||||||
|
|
||||||
for (const exerciseRow of exercises) {
|
for (const exerciseRow of exercises) {
|
||||||
|
console.log(`Looking up exercise with ID: ${exerciseRow.exercise_id}`);
|
||||||
const exerciseData = await this.exerciseService.getExercise(exerciseRow.exercise_id);
|
const exerciseData = await this.exerciseService.getExercise(exerciseRow.exercise_id);
|
||||||
|
|
||||||
if (exerciseData) {
|
if (exerciseData) {
|
||||||
@ -527,6 +542,8 @@ export class TemplateService {
|
|||||||
notes: exerciseRow.notes ?? undefined, // Convert null to undefined
|
notes: exerciseRow.notes ?? undefined, // Convert null to undefined
|
||||||
nostrReference: exerciseRow.nostr_reference ?? undefined, // Convert null to undefined
|
nostrReference: exerciseRow.nostr_reference ?? undefined, // Convert null to undefined
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.log(`⚠️ Could not find exercise with ID: ${exerciseRow.exercise_id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,9 +62,10 @@ export function usePOWRPacks() {
|
|||||||
}, [powrPackService, loadPacks]);
|
}, [powrPackService, loadPacks]);
|
||||||
|
|
||||||
// Delete a pack
|
// Delete a pack
|
||||||
const deletePack = useCallback(async (packId: string, keepItems: boolean = false) => {
|
const deletePack = useCallback(async (packId: string) => {
|
||||||
try {
|
try {
|
||||||
await powrPackService.deletePack(packId, keepItems);
|
// Always delete everything
|
||||||
|
await powrPackService.deletePack(packId, false);
|
||||||
await loadPacks(); // Refresh the list
|
await loadPacks(); // Refresh the list
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
20
package-lock.json
generated
20
package-lock.json
generated
@ -50,6 +50,7 @@
|
|||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"expo": "^52.0.35",
|
"expo": "^52.0.35",
|
||||||
|
"expo-av": "~15.0.2",
|
||||||
"expo-crypto": "~14.0.2",
|
"expo-crypto": "~14.0.2",
|
||||||
"expo-dev-client": "~5.0.12",
|
"expo-dev-client": "~5.0.12",
|
||||||
"expo-file-system": "~18.0.10",
|
"expo-file-system": "~18.0.10",
|
||||||
@ -59,7 +60,7 @@
|
|||||||
"expo-random": "^14.0.1",
|
"expo-random": "^14.0.1",
|
||||||
"expo-router": "~4.0.16",
|
"expo-router": "~4.0.16",
|
||||||
"expo-secure-store": "~14.0.1",
|
"expo-secure-store": "~14.0.1",
|
||||||
"expo-splash-screen": "~0.29.20",
|
"expo-splash-screen": "~0.29.22",
|
||||||
"expo-sqlite": "~15.1.2",
|
"expo-sqlite": "~15.1.2",
|
||||||
"expo-status-bar": "~2.0.1",
|
"expo-status-bar": "~2.0.1",
|
||||||
"expo-system-ui": "~4.0.8",
|
"expo-system-ui": "~4.0.8",
|
||||||
@ -12208,6 +12209,23 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-av": {
|
||||||
|
"version": "15.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-av/-/expo-av-15.0.2.tgz",
|
||||||
|
"integrity": "sha512-AHIHXdqLgK1dfHZF0JzX3YSVySGMrWn9QtPzaVjw54FAzvXfMt4sIoq4qRL/9XWCP9+ICcCs/u3EcvmxQjrfcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*",
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*",
|
||||||
|
"react-native-web": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-native-web": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-constants": {
|
"node_modules/expo-constants": {
|
||||||
"version": "17.0.6",
|
"version": "17.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.0.6.tgz",
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
"expo-random": "^14.0.1",
|
"expo-random": "^14.0.1",
|
||||||
"expo-router": "~4.0.16",
|
"expo-router": "~4.0.16",
|
||||||
"expo-secure-store": "~14.0.1",
|
"expo-secure-store": "~14.0.1",
|
||||||
"expo-splash-screen": "~0.29.20",
|
"expo-splash-screen": "~0.29.22",
|
||||||
"expo-sqlite": "~15.1.2",
|
"expo-sqlite": "~15.1.2",
|
||||||
"expo-status-bar": "~2.0.1",
|
"expo-status-bar": "~2.0.1",
|
||||||
"expo-system-ui": "~4.0.8",
|
"expo-system-ui": "~4.0.8",
|
||||||
@ -97,7 +97,8 @@
|
|||||||
"tailwind-merge": "^2.2.1",
|
"tailwind-merge": "^2.2.1",
|
||||||
"tailwindcss": "3.3.5",
|
"tailwindcss": "3.3.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zustand": "^4.5.6"
|
"zustand": "^4.5.6",
|
||||||
|
"expo-av": "~15.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.26.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user