mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-19 10:51:19 +00:00
updated header in tabs and searchbar in library
This commit is contained in:
parent
0d460e8f3e
commit
15c973f333
@ -1,28 +1,31 @@
|
||||
// app/(tabs)/history.tsx
|
||||
// app/(tabs)/social.tsx
|
||||
import { View } from 'react-native';
|
||||
import { Text } from '@/components/ui/text';
|
||||
import { TabScreen } from '@/components/layout/TabScreen';
|
||||
import Header from '@/components/Header';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Filter } from 'lucide-react-native';
|
||||
import { Bell } from 'lucide-react-native';
|
||||
import Header from '@/components/Header';
|
||||
import { TabScreen } from '@/components/layout/TabScreen';
|
||||
|
||||
export default function HistoryScreen() {
|
||||
export default function SocialScreen() {
|
||||
return (
|
||||
<TabScreen>
|
||||
<Header
|
||||
title="History"
|
||||
useLogo={true}
|
||||
rightElement={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onPress={() => console.log('Open filters')}
|
||||
onPress={() => console.log('Open notifications')}
|
||||
>
|
||||
<Filter className="text-foreground" />
|
||||
<View className="relative">
|
||||
<Bell className="text-foreground" />
|
||||
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
||||
</View>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<View className="flex-1 items-center justify-center">
|
||||
<Text>History Screen</Text>
|
||||
<Text>Social Screen</Text>
|
||||
</View>
|
||||
</TabScreen>
|
||||
);
|
||||
|
@ -20,6 +20,8 @@ import FavoriteTemplate from '@/components/workout/FavoriteTemplate'
|
||||
import { useWorkoutStore } from '@/stores/workoutStore'
|
||||
import { Text } from '@/components/ui/text'
|
||||
import { getRandomWorkoutTitle } from '@/utils/workoutTitles'
|
||||
import { Bell } from 'lucide-react-native';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
interface FavoriteTemplateData {
|
||||
id: string;
|
||||
@ -204,7 +206,21 @@ export default function WorkoutScreen() {
|
||||
|
||||
return (
|
||||
<TabScreen>
|
||||
<Header title="Workout" />
|
||||
<Header
|
||||
useLogo={true}
|
||||
rightElement={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onPress={() => console.log('Open notifications')}
|
||||
>
|
||||
<View className="relative">
|
||||
<Bell className="text-foreground" />
|
||||
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
||||
</View>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<ScrollView
|
||||
className="flex-1 px-4 pt-4"
|
||||
|
@ -1,77 +1,22 @@
|
||||
// app/(tabs)/library/_layout.tsx
|
||||
import { View } from 'react-native';
|
||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
||||
import { ThemeToggle } from '@/components/ThemeToggle';
|
||||
import { SearchPopover } from '@/components/library/SearchPopover';
|
||||
import { FilterPopover } from '@/components/library/FilterPopover';
|
||||
import { FilterSheet, type FilterOptions, type SourceType } from '@/components/library/FilterSheet';
|
||||
import ExercisesScreen from './exercises';
|
||||
import TemplatesScreen from './templates';
|
||||
import ProgramsScreen from './programs';
|
||||
import Header from '@/components/Header';
|
||||
import { useState } from 'react';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import type { CustomTheme } from '@/lib/theme';
|
||||
import { TabScreen } from '@/components/layout/TabScreen';
|
||||
|
||||
const Tab = createMaterialTopTabNavigator();
|
||||
|
||||
// Default available filters
|
||||
const availableFilters = {
|
||||
equipment: ['Barbell', 'Dumbbell', 'Bodyweight', 'Machine', 'Cables', 'Other'],
|
||||
tags: ['Strength', 'Cardio', 'Mobility', 'Recovery'],
|
||||
source: ['local', 'powr', 'nostr'] as SourceType[]
|
||||
};
|
||||
|
||||
// Initial filter state
|
||||
const initialFilters: FilterOptions = {
|
||||
equipment: [],
|
||||
tags: [],
|
||||
source: []
|
||||
};
|
||||
|
||||
export default function LibraryLayout() {
|
||||
const theme = useTheme() as CustomTheme;
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeFilters, setActiveFilters] = useState(0);
|
||||
const [filterSheetOpen, setFilterSheetOpen] = useState(false);
|
||||
const [currentFilters, setCurrentFilters] = useState<FilterOptions>(initialFilters);
|
||||
|
||||
const handleApplyFilters = (filters: FilterOptions) => {
|
||||
setCurrentFilters(filters);
|
||||
const totalFilters = Object.values(filters).reduce(
|
||||
(acc, curr) => acc + curr.length,
|
||||
0
|
||||
);
|
||||
setActiveFilters(totalFilters);
|
||||
};
|
||||
|
||||
return (
|
||||
<TabScreen>
|
||||
<Header
|
||||
title="Library"
|
||||
rightElement={
|
||||
<View className="flex-row items-center gap-2">
|
||||
<SearchPopover
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={setSearchQuery}
|
||||
/>
|
||||
<FilterPopover
|
||||
activeFilters={activeFilters}
|
||||
onOpenFilters={() => setFilterSheetOpen(true)}
|
||||
/>
|
||||
<ThemeToggle />
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
|
||||
<FilterSheet
|
||||
isOpen={filterSheetOpen}
|
||||
onClose={() => setFilterSheetOpen(false)}
|
||||
options={currentFilters}
|
||||
onApplyFilters={handleApplyFilters}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
<Header useLogo={true} />
|
||||
|
||||
<Tab.Navigator
|
||||
initialRouteName="templates"
|
||||
|
@ -1,22 +1,38 @@
|
||||
// app/(tabs)/library/exercises.tsx
|
||||
import React, { useState } from 'react';
|
||||
import { View, ActivityIndicator } from 'react-native';
|
||||
import { Text } from '@/components/ui/text';
|
||||
import { View, ActivityIndicator, ScrollView } from 'react-native';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Search, Dumbbell } from 'lucide-react-native';
|
||||
import { Search, Dumbbell, ListFilter } from 'lucide-react-native';
|
||||
import { FloatingActionButton } from '@/components/shared/FloatingActionButton';
|
||||
import { NewExerciseSheet } from '@/components/library/NewExerciseSheet';
|
||||
import { SimplifiedExerciseList } from '@/components/exercises/SimplifiedExerciseList';
|
||||
import { ExerciseDetails } from '@/components/exercises/ExerciseDetails';
|
||||
import { ExerciseDisplay, ExerciseType, BaseExercise } from '@/types/exercise';
|
||||
import { ExerciseDisplay, ExerciseType, BaseExercise, Equipment } from '@/types/exercise';
|
||||
import { useExercises } from '@/lib/hooks/useExercises';
|
||||
import { FilterSheet, type FilterOptions, type SourceType } from '@/components/library/FilterSheet';
|
||||
|
||||
// Default available filters
|
||||
const availableFilters = {
|
||||
equipment: ['Barbell', 'Dumbbell', 'Bodyweight', 'Machine', 'Cables', 'Other'],
|
||||
tags: ['Strength', 'Cardio', 'Mobility', 'Recovery'],
|
||||
source: ['local', 'powr', 'nostr'] as SourceType[]
|
||||
};
|
||||
|
||||
// Initial filter state
|
||||
const initialFilters: FilterOptions = {
|
||||
equipment: [],
|
||||
tags: [],
|
||||
source: []
|
||||
};
|
||||
|
||||
export default function ExercisesScreen() {
|
||||
const [showNewExercise, setShowNewExercise] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeFilter, setActiveFilter] = useState<ExerciseType | null>(null);
|
||||
const [selectedExercise, setSelectedExercise] = useState<ExerciseDisplay | null>(null);
|
||||
const [filterSheetOpen, setFilterSheetOpen] = useState(false);
|
||||
const [currentFilters, setCurrentFilters] = useState<FilterOptions>(initialFilters);
|
||||
const [activeFilters, setActiveFilters] = useState(0);
|
||||
|
||||
const {
|
||||
exercises,
|
||||
@ -38,15 +54,6 @@ export default function ExercisesScreen() {
|
||||
}
|
||||
}, [searchQuery, updateFilters]);
|
||||
|
||||
// Update type filter when activeFilter changes
|
||||
React.useEffect(() => {
|
||||
if (activeFilter) {
|
||||
updateFilters({ type: [activeFilter] });
|
||||
} else {
|
||||
clearFilters();
|
||||
}
|
||||
}, [activeFilter, updateFilters, clearFilters]);
|
||||
|
||||
const handleExercisePress = (exercise: ExerciseDisplay) => {
|
||||
setSelectedExercise(exercise);
|
||||
};
|
||||
@ -68,45 +75,79 @@ export default function ExercisesScreen() {
|
||||
await createExercise(exerciseWithSource);
|
||||
setShowNewExercise(false);
|
||||
};
|
||||
if (loading) {
|
||||
return (
|
||||
<View className="flex-1 items-center justify-center bg-background">
|
||||
<ActivityIndicator size="large" color="#8B5CF6" />
|
||||
<Text className="mt-4 text-muted-foreground">Loading exercises...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<View className="flex-1 items-center justify-center p-4 bg-background">
|
||||
<Text className="text-destructive text-center mb-4">
|
||||
{error.message}
|
||||
</Text>
|
||||
<Button onPress={refreshExercises}>
|
||||
<Text className="text-primary-foreground">Retry</Text>
|
||||
</Button>
|
||||
</View>
|
||||
const handleApplyFilters = (filters: FilterOptions) => {
|
||||
setCurrentFilters(filters);
|
||||
const totalFilters = Object.values(filters).reduce(
|
||||
(acc, curr) => acc + curr.length,
|
||||
0
|
||||
);
|
||||
}
|
||||
setActiveFilters(totalFilters);
|
||||
|
||||
// Update the exercises hook filters with proper type casting
|
||||
if (filters.equipment.length > 0) {
|
||||
// Convert string[] to Equipment[]
|
||||
const typedEquipment = filters.equipment.filter(eq =>
|
||||
['bodyweight', 'barbell', 'dumbbell', 'kettlebell', 'machine', 'cable', 'other'].includes(eq.toLowerCase())
|
||||
) as Equipment[];
|
||||
|
||||
updateFilters({ equipment: typedEquipment });
|
||||
}
|
||||
|
||||
if (filters.tags.length > 0) {
|
||||
updateFilters({ tags: filters.tags });
|
||||
}
|
||||
if (filters.source.length > 0) {
|
||||
updateFilters({ source: filters.source as any[] });
|
||||
}
|
||||
|
||||
if (totalFilters === 0) {
|
||||
clearFilters();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-background">
|
||||
{/* Search bar */}
|
||||
<View className="px-4 py-3">
|
||||
<View className="relative flex-row items-center bg-muted rounded-xl">
|
||||
<View className="absolute left-3 z-10">
|
||||
<Search size={18} className="text-muted-foreground" />
|
||||
{/* Search bar with filter button */}
|
||||
<View className="px-4 py-2 border-b border-border">
|
||||
<View className="flex-row items-center">
|
||||
<View className="relative flex-1">
|
||||
<View className="absolute left-3 z-10 h-full justify-center">
|
||||
<Search size={18} className="text-muted-foreground" />
|
||||
</View>
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChangeText={setSearchQuery}
|
||||
placeholder="Search exercises"
|
||||
className="pl-9 pr-10 bg-muted/50 border-0"
|
||||
/>
|
||||
<View className="absolute right-2 z-10 h-full justify-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onPress={() => setFilterSheetOpen(true)}
|
||||
>
|
||||
<View className="relative">
|
||||
<ListFilter className="text-muted-foreground" size={20} />
|
||||
{activeFilters > 0 && (
|
||||
<View className="absolute -top-1 -right-1 w-2.5 h-2.5 rounded-full" style={{ backgroundColor: '#f7931a' }} />
|
||||
)}
|
||||
</View>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChangeText={setSearchQuery}
|
||||
placeholder="Search"
|
||||
className="pl-9 bg-transparent h-10 flex-1"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Filter Sheet */}
|
||||
<FilterSheet
|
||||
isOpen={filterSheetOpen}
|
||||
onClose={() => setFilterSheetOpen(false)}
|
||||
options={currentFilters}
|
||||
onApplyFilters={handleApplyFilters}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
|
||||
{/* Exercises list */}
|
||||
<SimplifiedExerciseList
|
||||
exercises={exercises}
|
||||
|
@ -3,13 +3,15 @@ import React, { useState, useEffect } from 'react';
|
||||
import { View, ScrollView } from 'react-native';
|
||||
import { Text } from '@/components/ui/text';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { AlertCircle, CheckCircle2, Database, RefreshCcw, Trash2, Code } from 'lucide-react-native';
|
||||
import { AlertCircle, CheckCircle2, Database, RefreshCcw, Trash2, Code, Search, ListFilter } from 'lucide-react-native';
|
||||
import { useSQLiteContext } from 'expo-sqlite';
|
||||
import { ExerciseType, ExerciseCategory, Equipment } from '@/types/exercise';
|
||||
import { SQLTransaction, SQLResultSet, SQLError } from '@/lib/db/types';
|
||||
import { schema } from '@/lib/db/schema';
|
||||
import { Platform } from 'react-native';
|
||||
import { FilterSheet, type FilterOptions, type SourceType } from '@/components/library/FilterSheet';
|
||||
|
||||
interface TableInfo {
|
||||
name: string;
|
||||
@ -37,6 +39,20 @@ interface ExerciseRow {
|
||||
format_units_json: string;
|
||||
}
|
||||
|
||||
// Default available filters for programs - can be adjusted later
|
||||
const availableFilters = {
|
||||
equipment: ['Barbell', 'Dumbbell', 'Bodyweight', 'Machine', 'Cables', 'Other'],
|
||||
tags: ['Strength', 'Cardio', 'Mobility', 'Recovery'],
|
||||
source: ['local', 'powr', 'nostr'] as SourceType[]
|
||||
};
|
||||
|
||||
// Initial filter state
|
||||
const initialFilters: FilterOptions = {
|
||||
equipment: [],
|
||||
tags: [],
|
||||
source: []
|
||||
};
|
||||
|
||||
export default function ProgramsScreen() {
|
||||
const db = useSQLiteContext();
|
||||
const [dbStatus, setDbStatus] = useState<{
|
||||
@ -52,6 +68,10 @@ export default function ProgramsScreen() {
|
||||
success: boolean;
|
||||
message: string;
|
||||
} | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [filterSheetOpen, setFilterSheetOpen] = useState(false);
|
||||
const [currentFilters, setCurrentFilters] = useState<FilterOptions>(initialFilters);
|
||||
const [activeFilters, setActiveFilters] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
checkDatabase();
|
||||
@ -200,132 +220,189 @@ export default function ProgramsScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView className="flex-1 bg-background p-4">
|
||||
<View className="py-4 space-y-4">
|
||||
<Text className="text-lg font-semibold text-center mb-4">Database Debug Panel</Text>
|
||||
|
||||
{/* Schema Inspector Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex-row items-center gap-2">
|
||||
<Code size={20} className="text-foreground" />
|
||||
<Text className="text-lg font-semibold">Database Schema ({Platform.OS})</Text>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<View className="space-y-4">
|
||||
{schemas.map((table) => (
|
||||
<View key={table.name} className="space-y-2">
|
||||
<Text className="font-semibold">{table.name}</Text>
|
||||
<Text className="text-muted-foreground text-sm">
|
||||
{table.sql}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<Button
|
||||
className="mt-4"
|
||||
onPress={inspectDatabase}
|
||||
>
|
||||
<Text className="text-primary-foreground">Refresh Schema</Text>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
const handleApplyFilters = (filters: FilterOptions) => {
|
||||
setCurrentFilters(filters);
|
||||
const totalFilters = Object.values(filters).reduce(
|
||||
(acc, curr) => acc + curr.length,
|
||||
0
|
||||
);
|
||||
setActiveFilters(totalFilters);
|
||||
// Implement filtering logic for programs when available
|
||||
};
|
||||
|
||||
{/* Status Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex-row items-center gap-2">
|
||||
<Database size={20} className="text-foreground" />
|
||||
<Text className="text-lg font-semibold">Database Status</Text>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<View className="space-y-2">
|
||||
<Text>Initialized: {dbStatus.initialized ? '✅' : '❌'}</Text>
|
||||
<Text>Tables Found: {dbStatus.tables.length}</Text>
|
||||
<View className="pl-4">
|
||||
{dbStatus.tables.map(table => (
|
||||
<Text key={table} className="text-muted-foreground">• {table}</Text>
|
||||
return (
|
||||
<View className="flex-1 bg-background">
|
||||
{/* Search bar with filter button */}
|
||||
<View className="px-4 py-2 border-b border-border">
|
||||
<View className="flex-row items-center">
|
||||
<View className="relative flex-1">
|
||||
<View className="absolute left-3 z-10 h-full justify-center">
|
||||
<Search size={18} className="text-muted-foreground" />
|
||||
</View>
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChangeText={setSearchQuery}
|
||||
placeholder="Search programs"
|
||||
className="pl-9 pr-10 bg-muted/50 border-0"
|
||||
/>
|
||||
<View className="absolute right-2 z-10 h-full justify-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onPress={() => setFilterSheetOpen(true)}
|
||||
>
|
||||
<View className="relative">
|
||||
<ListFilter className="text-muted-foreground" size={20} />
|
||||
{activeFilters > 0 && (
|
||||
<View className="absolute -top-1 -right-1 w-2.5 h-2.5 rounded-full" style={{ backgroundColor: '#f7931a' }} />
|
||||
)}
|
||||
</View>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Filter Sheet */}
|
||||
<FilterSheet
|
||||
isOpen={filterSheetOpen}
|
||||
onClose={() => setFilterSheetOpen(false)}
|
||||
options={currentFilters}
|
||||
onApplyFilters={handleApplyFilters}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
|
||||
<ScrollView className="flex-1 p-4">
|
||||
<View className="py-4 space-y-4">
|
||||
<Text className="text-lg font-semibold text-center mb-4">Programs Coming Soon</Text>
|
||||
<Text className="text-center text-muted-foreground mb-6">
|
||||
Training programs will allow you to organize your workouts into structured training plans.
|
||||
</Text>
|
||||
|
||||
<Text className="text-lg font-semibold text-center mb-4">Database Debug Panel</Text>
|
||||
|
||||
{/* Schema Inspector Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex-row items-center gap-2">
|
||||
<Code size={20} className="text-foreground" />
|
||||
<Text className="text-lg font-semibold">Database Schema ({Platform.OS})</Text>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<View className="space-y-4">
|
||||
{schemas.map((table) => (
|
||||
<View key={table.name} className="space-y-2">
|
||||
<Text className="font-semibold">{table.name}</Text>
|
||||
<Text className="text-muted-foreground text-sm">
|
||||
{table.sql}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{dbStatus.error && (
|
||||
<View className="mt-4 p-4 bg-destructive/10 rounded-lg border border-destructive">
|
||||
<View className="flex-row items-center gap-2">
|
||||
<AlertCircle className="text-destructive" size={20} />
|
||||
<Text className="font-semibold text-destructive">Error</Text>
|
||||
</View>
|
||||
<Text className="mt-2 text-destructive">{dbStatus.error}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Button
|
||||
className="mt-4"
|
||||
onPress={inspectDatabase}
|
||||
>
|
||||
<Text className="text-primary-foreground">Refresh Schema</Text>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Operations Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex-row items-center gap-2">
|
||||
<RefreshCcw size={20} className="text-foreground" />
|
||||
<Text className="text-lg font-semibold">Database Operations</Text>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<View className="space-y-4">
|
||||
<Button
|
||||
onPress={runTestInsert}
|
||||
className="w-full"
|
||||
>
|
||||
<Text className="text-primary-foreground">Run Test Insert</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onPress={resetDatabase}
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
>
|
||||
<Trash2 size={18} className="mr-2" />
|
||||
<Text className="text-destructive-foreground">Reset Database</Text>
|
||||
</Button>
|
||||
|
||||
{testResults && (
|
||||
<View className={`mt-4 p-4 rounded-lg border ${
|
||||
testResults.success
|
||||
? 'bg-primary/10 border-primary'
|
||||
: 'bg-destructive/10 border-destructive'
|
||||
}`}>
|
||||
<View className="flex-row items-center gap-2">
|
||||
{testResults.success ? (
|
||||
<CheckCircle2
|
||||
className="text-primary"
|
||||
size={20}
|
||||
/>
|
||||
) : (
|
||||
<AlertCircle
|
||||
className="text-destructive"
|
||||
size={20}
|
||||
/>
|
||||
)}
|
||||
<Text className={`font-semibold ${
|
||||
testResults.success ? 'text-primary' : 'text-destructive'
|
||||
}`}>
|
||||
{testResults.success ? "Success" : "Error"}
|
||||
</Text>
|
||||
</View>
|
||||
<ScrollView className="mt-2">
|
||||
<Text className={`${
|
||||
testResults.success ? 'text-foreground' : 'text-destructive'
|
||||
}`}>
|
||||
{testResults.message}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
{/* Status Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex-row items-center gap-2">
|
||||
<Database size={20} className="text-foreground" />
|
||||
<Text className="text-lg font-semibold">Database Status</Text>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<View className="space-y-2">
|
||||
<Text>Initialized: {dbStatus.initialized ? '✅' : '❌'}</Text>
|
||||
<Text>Tables Found: {dbStatus.tables.length}</Text>
|
||||
<View className="pl-4">
|
||||
{dbStatus.tables.map(table => (
|
||||
<Text key={table} className="text-muted-foreground">• {table}</Text>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</View>
|
||||
</ScrollView>
|
||||
{dbStatus.error && (
|
||||
<View className="mt-4 p-4 bg-destructive/10 rounded-lg border border-destructive">
|
||||
<View className="flex-row items-center gap-2">
|
||||
<AlertCircle className="text-destructive" size={20} />
|
||||
<Text className="font-semibold text-destructive">Error</Text>
|
||||
</View>
|
||||
<Text className="mt-2 text-destructive">{dbStatus.error}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Operations Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex-row items-center gap-2">
|
||||
<RefreshCcw size={20} className="text-foreground" />
|
||||
<Text className="text-lg font-semibold">Database Operations</Text>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<View className="space-y-4">
|
||||
<Button
|
||||
onPress={runTestInsert}
|
||||
className="w-full"
|
||||
>
|
||||
<Text className="text-primary-foreground">Run Test Insert</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onPress={resetDatabase}
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
>
|
||||
<Trash2 size={18} className="mr-2" />
|
||||
<Text className="text-destructive-foreground">Reset Database</Text>
|
||||
</Button>
|
||||
|
||||
{testResults && (
|
||||
<View className={`mt-4 p-4 rounded-lg border ${
|
||||
testResults.success
|
||||
? 'bg-primary/10 border-primary'
|
||||
: 'bg-destructive/10 border-destructive'
|
||||
}`}>
|
||||
<View className="flex-row items-center gap-2">
|
||||
{testResults.success ? (
|
||||
<CheckCircle2
|
||||
className="text-primary"
|
||||
size={20}
|
||||
/>
|
||||
) : (
|
||||
<AlertCircle
|
||||
className="text-destructive"
|
||||
size={20}
|
||||
/>
|
||||
)}
|
||||
<Text className={`font-semibold ${
|
||||
testResults.success ? 'text-primary' : 'text-destructive'
|
||||
}`}>
|
||||
{testResults.success ? "Success" : "Error"}
|
||||
</Text>
|
||||
</View>
|
||||
<ScrollView className="mt-2">
|
||||
<Text className={`${
|
||||
testResults.success ? 'text-foreground' : 'text-destructive'
|
||||
}`}>
|
||||
{testResults.message}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
// app/(tabs)/library/templates.tsx
|
||||
import React, { useState } from 'react';
|
||||
import { View, ScrollView } from 'react-native';
|
||||
import { router } from 'expo-router'; // Add this import
|
||||
import { router } from 'expo-router';
|
||||
import { Text } from '@/components/ui/text';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { Search, Plus } from 'lucide-react-native';
|
||||
import { Search, Plus, ListFilter } from 'lucide-react-native';
|
||||
import { FloatingActionButton } from '@/components/shared/FloatingActionButton';
|
||||
import { NewTemplateSheet } from '@/components/library/NewTemplateSheet';
|
||||
import { FilterSheet, type FilterOptions, type SourceType } from '@/components/library/FilterSheet';
|
||||
import { TemplateCard } from '@/components/templates/TemplateCard';
|
||||
// Remove TemplateDetails import since we're not using it anymore
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Template,
|
||||
TemplateCategory,
|
||||
@ -17,13 +18,19 @@ import {
|
||||
} from '@/types/templates';
|
||||
import { useWorkoutStore } from '@/stores/workoutStore';
|
||||
|
||||
const TEMPLATE_CATEGORIES: TemplateCategory[] = [
|
||||
'Full Body',
|
||||
'Push/Pull/Legs',
|
||||
'Upper/Lower',
|
||||
'Conditioning',
|
||||
'Custom'
|
||||
];
|
||||
// Default available filters
|
||||
const availableFilters = {
|
||||
equipment: ['Barbell', 'Dumbbell', 'Bodyweight', 'Machine', 'Cables', 'Other'],
|
||||
tags: ['Strength', 'Cardio', 'Mobility', 'Recovery'],
|
||||
source: ['local', 'powr', 'nostr'] as SourceType[]
|
||||
};
|
||||
|
||||
// Initial filter state
|
||||
const initialFilters: FilterOptions = {
|
||||
equipment: [],
|
||||
tags: [],
|
||||
source: []
|
||||
};
|
||||
|
||||
// Mock data - move to a separate file later
|
||||
const initialTemplates: Template[] = [
|
||||
@ -61,14 +68,14 @@ export default function TemplatesScreen() {
|
||||
const [showNewTemplate, setShowNewTemplate] = useState(false);
|
||||
const [templates, setTemplates] = useState(initialTemplates);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeCategory, setActiveCategory] = useState<TemplateCategory | null>(null);
|
||||
// Remove selectedTemplate state since we're not using it anymore
|
||||
|
||||
const [filterSheetOpen, setFilterSheetOpen] = useState(false);
|
||||
const [currentFilters, setCurrentFilters] = useState<FilterOptions>(initialFilters);
|
||||
const [activeFilters, setActiveFilters] = useState(0);
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
setTemplates(current => current.filter(t => t.id !== id));
|
||||
};
|
||||
|
||||
// Update to navigate to the template details screen
|
||||
const handleTemplatePress = (template: Template) => {
|
||||
router.push(`/template/${template.id}`);
|
||||
};
|
||||
@ -109,6 +116,15 @@ export default function TemplatesScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleApplyFilters = (filters: FilterOptions) => {
|
||||
setCurrentFilters(filters);
|
||||
const totalFilters = Object.values(filters).reduce(
|
||||
(acc, curr) => acc + curr.length,
|
||||
0
|
||||
);
|
||||
setActiveFilters(totalFilters);
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
// Refresh template favorite status when tab gains focus
|
||||
@ -125,12 +141,29 @@ export default function TemplatesScreen() {
|
||||
setShowNewTemplate(false);
|
||||
};
|
||||
|
||||
// Filter templates based on category and search
|
||||
// Filter templates based on search and applied filters
|
||||
const filteredTemplates = templates.filter(template => {
|
||||
const matchesCategory = !activeCategory || template.category === activeCategory;
|
||||
// Filter by search query
|
||||
const matchesSearch = !searchQuery ||
|
||||
template.title.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
return matchesCategory && matchesSearch;
|
||||
|
||||
// Filter by equipment if any selected
|
||||
const matchesEquipment = currentFilters.equipment.length === 0 ||
|
||||
(template.exercises.some(ex =>
|
||||
currentFilters.equipment.includes(ex.equipment || '')
|
||||
));
|
||||
|
||||
// Filter by tags if any selected
|
||||
const matchesTags = currentFilters.tags.length === 0 ||
|
||||
(template.tags && template.tags.some(tag =>
|
||||
currentFilters.tags.includes(tag)
|
||||
));
|
||||
|
||||
// Filter by source if any selected
|
||||
const matchesSource = currentFilters.source.length === 0 ||
|
||||
currentFilters.source.includes(template.source as SourceType);
|
||||
|
||||
return matchesSearch && matchesEquipment && matchesTags && matchesSource;
|
||||
});
|
||||
|
||||
// Separate favorites and regular templates
|
||||
@ -139,60 +172,52 @@ export default function TemplatesScreen() {
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-background">
|
||||
{/* Search bar */}
|
||||
<View className="px-4 py-2">
|
||||
<View className="relative flex-row items-center bg-muted rounded-xl">
|
||||
<View className="absolute left-3 z-10">
|
||||
<Search size={18} className="text-muted-foreground" />
|
||||
{/* Search bar with filter button */}
|
||||
<View className="px-4 py-2 border-b border-border">
|
||||
<View className="flex-row items-center">
|
||||
<View className="relative flex-1">
|
||||
<View className="absolute left-3 z-10 h-full justify-center">
|
||||
<Search size={18} className="text-muted-foreground" />
|
||||
</View>
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChangeText={setSearchQuery}
|
||||
placeholder="Search templates"
|
||||
className="pl-9 pr-10 bg-muted/50 border-0"
|
||||
/>
|
||||
<View className="absolute right-2 z-10 h-full justify-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onPress={() => setFilterSheetOpen(true)}
|
||||
>
|
||||
<View className="relative">
|
||||
<ListFilter className="text-muted-foreground" size={20} />
|
||||
{activeFilters > 0 && (
|
||||
<View className="absolute -top-1 -right-1 w-2.5 h-2.5 rounded-full" style={{ backgroundColor: '#f7931a' }} />
|
||||
)}
|
||||
</View>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChangeText={setSearchQuery}
|
||||
placeholder="Search templates"
|
||||
className="pl-9 bg-transparent h-10 flex-1"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* // Category filters
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
className="px-4 py-2 border-b border-border"
|
||||
>
|
||||
<View className="flex-row gap-2">
|
||||
<Button
|
||||
variant={activeCategory === null ? "default" : "outline"}
|
||||
size="sm"
|
||||
className="rounded-full"
|
||||
onPress={() => setActiveCategory(null)}
|
||||
>
|
||||
<Text className={activeCategory === null ? "text-primary-foreground" : ""}>
|
||||
All
|
||||
</Text>
|
||||
</Button>
|
||||
{TEMPLATE_CATEGORIES.map((category) => (
|
||||
<Button
|
||||
key={category}
|
||||
variant={activeCategory === category ? "default" : "outline"}
|
||||
size="sm"
|
||||
className="rounded-full"
|
||||
onPress={() => setActiveCategory(activeCategory === category ? null : category)}
|
||||
>
|
||||
<Text className={activeCategory === category ? "text-primary-foreground" : ""}>
|
||||
{category}
|
||||
</Text>
|
||||
</Button>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView> */}
|
||||
{/* Filter Sheet */}
|
||||
<FilterSheet
|
||||
isOpen={filterSheetOpen}
|
||||
onClose={() => setFilterSheetOpen(false)}
|
||||
options={currentFilters}
|
||||
onApplyFilters={handleApplyFilters}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
|
||||
{/* Templates list */}
|
||||
<ScrollView>
|
||||
<ScrollView className="flex-1">
|
||||
{/* Favorites Section */}
|
||||
{favoriteTemplates.length > 0 && (
|
||||
<View>
|
||||
<Text className="text-lg font-semibold px-4 py-2">
|
||||
<View className="py-4">
|
||||
<Text className="text-lg font-semibold mb-4 px-4">
|
||||
Favorites
|
||||
</Text>
|
||||
<View className="gap-3">
|
||||
@ -211,8 +236,8 @@ export default function TemplatesScreen() {
|
||||
)}
|
||||
|
||||
{/* All Templates Section */}
|
||||
<View>
|
||||
<Text className="text-lg font-semibold px-4 py-2">
|
||||
<View className="py-4">
|
||||
<Text className="text-lg font-semibold mb-4 px-4">
|
||||
All Templates
|
||||
</Text>
|
||||
{regularTemplates.length > 0 ? (
|
||||
@ -241,8 +266,6 @@ export default function TemplatesScreen() {
|
||||
<View className="h-20" />
|
||||
</ScrollView>
|
||||
|
||||
{/* Remove the TemplateDetails component since we're using router navigation now */}
|
||||
|
||||
<FloatingActionButton
|
||||
icon={Plus}
|
||||
onPress={() => setShowNewTemplate(true)}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// app/(tabs)/profile.tsx
|
||||
import { View, ScrollView, ImageBackground } from 'react-native';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Settings, LogIn } from 'lucide-react-native';
|
||||
import { Settings, LogIn, Bell } from 'lucide-react-native';
|
||||
import { H1 } from '@/components/ui/typography';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Text } from '@/components/ui/text';
|
||||
@ -67,7 +67,21 @@ export default function ProfileScreen() {
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<TabScreen>
|
||||
<Header title="Profile" />
|
||||
<Header
|
||||
useLogo={true}
|
||||
rightElement={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onPress={() => console.log('Open notifications')}
|
||||
>
|
||||
<View className="relative">
|
||||
<Bell className="text-foreground" />
|
||||
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
||||
</View>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<View className="flex-1 items-center justify-center p-6">
|
||||
<View className="items-center mb-8">
|
||||
<UserAvatar
|
||||
@ -102,14 +116,17 @@ export default function ProfileScreen() {
|
||||
return (
|
||||
<TabScreen>
|
||||
<Header
|
||||
title="Profile"
|
||||
useLogo={true}
|
||||
rightElement={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onPress={() => console.log('Open settings')}
|
||||
onPress={() => console.log('Open notifications')}
|
||||
>
|
||||
<Settings className="text-foreground" />
|
||||
<View className="relative">
|
||||
<Bell className="text-foreground" />
|
||||
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
||||
</View>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
@ -23,6 +23,9 @@ import OverviewTab from './index';
|
||||
import SocialTab from './social';
|
||||
import HistoryTab from './history';
|
||||
|
||||
// Import the shared context
|
||||
import { TemplateContext } from './_templateContext';
|
||||
|
||||
// Types
|
||||
import { WorkoutTemplate } from '@/types/templates';
|
||||
import type { CustomTheme } from '@/lib/theme';
|
||||
@ -262,22 +265,4 @@ export default function TemplateDetailsLayout() {
|
||||
</View>
|
||||
</TabScreen>
|
||||
);
|
||||
}
|
||||
|
||||
// Create a context to share the template with the tab screens
|
||||
interface TemplateContextType {
|
||||
template: WorkoutTemplate | null;
|
||||
}
|
||||
|
||||
export const TemplateContext = React.createContext<TemplateContextType>({
|
||||
template: null
|
||||
});
|
||||
|
||||
// Custom hook to access the template
|
||||
export function useTemplate() {
|
||||
const context = React.useContext(TemplateContext);
|
||||
if (!context.template) {
|
||||
throw new Error('useTemplate must be used within a TemplateContext.Provider');
|
||||
}
|
||||
return context.template;
|
||||
}
|
27
app/(workout)/template/[id]/_templateContext.tsx
Normal file
27
app/(workout)/template/[id]/_templateContext.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
// app/(workout)/template/[id]/templateContext.tsx
|
||||
import React from 'react';
|
||||
import { WorkoutTemplate } from '@/types/templates';
|
||||
|
||||
// Create a context to share the template with the tab screens
|
||||
interface TemplateContextType {
|
||||
template: WorkoutTemplate | null;
|
||||
}
|
||||
|
||||
export const TemplateContext = React.createContext<TemplateContextType>({
|
||||
template: null
|
||||
});
|
||||
|
||||
// Custom hook to access the template
|
||||
export function useTemplate() {
|
||||
const context = React.useContext(TemplateContext);
|
||||
if (!context.template) {
|
||||
throw new Error('useTemplate must be used within a TemplateContext.Provider');
|
||||
}
|
||||
return context.template;
|
||||
}
|
||||
// Add a default export to satisfy Expo Router
|
||||
// The _ prefix in the filename would also work to exclude it from routing
|
||||
export default function TemplateContextProvider() {
|
||||
// This component won't actually be used
|
||||
return null;
|
||||
}
|
@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Calendar } from 'lucide-react-native';
|
||||
import { useTemplate } from './_layout';
|
||||
import { useTemplate } from './_templateContext';
|
||||
|
||||
// Format date helper
|
||||
const formatDate = (date: Date) => {
|
||||
|
@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useTemplate } from './_layout';
|
||||
import { useTemplate } from './_templateContext';
|
||||
import { formatTime } from '@/utils/formatTime';
|
||||
import {
|
||||
Edit2,
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
Repeat,
|
||||
Bookmark
|
||||
} from 'lucide-react-native';
|
||||
import { useTemplate } from './_layout';
|
||||
import { useTemplate } from './_templateContext';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Mock social feed data
|
||||
|
@ -7,6 +7,7 @@ import { Bell } from 'lucide-react-native';
|
||||
import { Text } from '@/components/ui/text';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import UserAvatar from '@/components/UserAvatar';
|
||||
import PowerLogo from '@/components/PowerLogo';
|
||||
import { useSettingsDrawer } from '@/lib/contexts/SettingsDrawerContext';
|
||||
import { useNDKCurrentUser } from '@/lib/hooks/useNDK';
|
||||
|
||||
@ -14,12 +15,14 @@ interface HeaderProps {
|
||||
title?: string;
|
||||
hideTitle?: boolean;
|
||||
rightElement?: React.ReactNode;
|
||||
useLogo?: boolean;
|
||||
}
|
||||
|
||||
export default function Header({
|
||||
title,
|
||||
hideTitle = false,
|
||||
rightElement
|
||||
rightElement,
|
||||
useLogo = false
|
||||
}: HeaderProps) {
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
@ -56,9 +59,13 @@ export default function Header({
|
||||
fallback={fallbackLetter}
|
||||
/>
|
||||
|
||||
{/* Middle - Title */}
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
{/* Middle - Title or Logo */}
|
||||
<View style={[styles.titleContainer, { marginLeft: 10 }]}>
|
||||
{useLogo ? (
|
||||
<PowerLogo size="md" />
|
||||
) : (
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Right side - Custom element or default notifications */}
|
||||
@ -69,7 +76,11 @@ export default function Header({
|
||||
size="icon"
|
||||
onPress={() => {}}
|
||||
>
|
||||
<Bell size={24} className="text-foreground" />
|
||||
<View className="relative">
|
||||
<Bell size={24} className="text-foreground" />
|
||||
{/* Notification indicator - you can conditionally render this */}
|
||||
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
||||
</View>
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
@ -93,7 +104,9 @@ const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 0, // Remove padding to allow more precise positioning
|
||||
height: '100%',
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
|
57
components/PowerLogo.tsx
Normal file
57
components/PowerLogo.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
// components/PowerLogo.tsx
|
||||
import React from 'react';
|
||||
import { View, Text as RNText, useColorScheme } from 'react-native';
|
||||
import { Zap } from 'lucide-react-native';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
|
||||
interface PowerLogoProps {
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
export default function PowerLogo({ size = 'md' }: PowerLogoProps) {
|
||||
const theme = useTheme();
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
const fontSize = {
|
||||
sm: 18,
|
||||
md: 22,
|
||||
lg: 26,
|
||||
}[size];
|
||||
|
||||
const iconSize = {
|
||||
sm: 14,
|
||||
md: 16,
|
||||
lg: 20,
|
||||
}[size];
|
||||
|
||||
// Use theme colors to ensure visibility in both light and dark mode
|
||||
const textColor = theme.colors.primary || (colorScheme === 'dark' ? '#9c5cff' : '#6b21a8');
|
||||
|
||||
return (
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 4, // Add padding to prevent clipping
|
||||
}}>
|
||||
<RNText
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
fontStyle: 'italic',
|
||||
fontSize: fontSize,
|
||||
color: textColor,
|
||||
includeFontPadding: false, // Helps with text clipping
|
||||
textAlignVertical: 'center',
|
||||
}}
|
||||
>
|
||||
POWR
|
||||
</RNText>
|
||||
<Zap
|
||||
size={iconSize}
|
||||
color="#FFD700" // Gold color for the lightning bolt
|
||||
fill="#FFD700"
|
||||
style={{ marginLeft: 2 }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
@ -176,48 +176,6 @@ export const SimplifiedExerciseList = ({
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Alphabet List */}
|
||||
<View
|
||||
className="w-8 justify-center bg-transparent px-1"
|
||||
onStartShouldSetResponder={() => true}
|
||||
onResponderMove={(evt) => {
|
||||
const touch = evt.nativeEvent;
|
||||
const element = evt.target;
|
||||
|
||||
if (element) {
|
||||
(element as any).measure((x: number, y: number, width: number, height: number, pageX: number, pageY: number) => {
|
||||
const totalHeight = height;
|
||||
const letterHeight = totalHeight / alphabet.length;
|
||||
const touchY = touch.pageY - pageY;
|
||||
const index = Math.min(
|
||||
Math.max(Math.floor(touchY / letterHeight), 0),
|
||||
alphabet.length - 1
|
||||
);
|
||||
|
||||
const letter = alphabet[index];
|
||||
if (availableLetters.has(letter)) {
|
||||
scrollToSection(letter);
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{alphabet.map((letter) => (
|
||||
<Text
|
||||
key={letter}
|
||||
className={
|
||||
letter === currentSection
|
||||
? 'text-xs text-center text-primary font-bold py-0.5'
|
||||
: availableLetters.has(letter)
|
||||
? 'text-xs text-center text-primary font-medium py-0.5'
|
||||
: 'text-xs text-center text-muted-foreground py-0.5'
|
||||
}
|
||||
>
|
||||
{letter}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -26,6 +26,7 @@ export interface TemplateExerciseDisplay {
|
||||
title: string;
|
||||
targetSets: number;
|
||||
targetReps: number;
|
||||
equipment?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user