From 15c973f33341c3afec07e6d69a6c75922e9fcade Mon Sep 17 00:00:00 2001 From: DocNR Date: Thu, 27 Feb 2025 20:24:04 -0500 Subject: [PATCH] updated header in tabs and searchbar in library --- app/(tabs)/history.tsx | 21 +- app/(tabs)/index.tsx | 18 +- app/(tabs)/library/_layout.tsx | 57 +--- app/(tabs)/library/exercises.tsx | 129 ++++--- app/(tabs)/library/programs.tsx | 323 +++++++++++------- app/(tabs)/library/templates.tsx | 157 +++++---- app/(tabs)/profile.tsx | 27 +- app/(workout)/template/[id]/_layout.tsx | 21 +- .../template/[id]/_templateContext.tsx | 27 ++ app/(workout)/template/[id]/history.tsx | 2 +- app/(workout)/template/[id]/index.tsx | 2 +- app/(workout)/template/[id]/social.tsx | 2 +- components/Header.tsx | 25 +- components/PowerLogo.tsx | 57 ++++ .../exercises/SimplifiedExerciseList.tsx | 42 --- types/templates.ts | 1 + 16 files changed, 537 insertions(+), 374 deletions(-) create mode 100644 app/(workout)/template/[id]/_templateContext.tsx create mode 100644 components/PowerLogo.tsx diff --git a/app/(tabs)/history.tsx b/app/(tabs)/history.tsx index 0d4f8c0..34aeee1 100644 --- a/app/(tabs)/history.tsx +++ b/app/(tabs)/history.tsx @@ -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 (
console.log('Open filters')} + onPress={() => console.log('Open notifications')} > - + + + + } /> - History Screen + Social Screen ); diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 27da755..bc3dafc 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -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 ( -
+
console.log('Open notifications')} + > + + + + + + } + /> (initialFilters); - - const handleApplyFilters = (filters: FilterOptions) => { - setCurrentFilters(filters); - const totalFilters = Object.values(filters).reduce( - (acc, curr) => acc + curr.length, - 0 - ); - setActiveFilters(totalFilters); - }; return ( -
- - setFilterSheetOpen(true)} - /> - - - } - /> - - setFilterSheetOpen(false)} - options={currentFilters} - onApplyFilters={handleApplyFilters} - availableFilters={availableFilters} - /> +
(null); const [selectedExercise, setSelectedExercise] = useState(null); + const [filterSheetOpen, setFilterSheetOpen] = useState(false); + const [currentFilters, setCurrentFilters] = useState(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 ( - - - Loading exercises... - - ); - } - if (error) { - return ( - - - {error.message} - - - + 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 ( - {/* Search bar */} - - - - + {/* Search bar with filter button */} + + + + + + + + + + - + {/* Filter Sheet */} + setFilterSheetOpen(false)} + options={currentFilters} + onApplyFilters={handleApplyFilters} + availableFilters={availableFilters} + /> + {/* Exercises list */} (null); + const [searchQuery, setSearchQuery] = useState(''); + const [filterSheetOpen, setFilterSheetOpen] = useState(false); + const [currentFilters, setCurrentFilters] = useState(initialFilters); + const [activeFilters, setActiveFilters] = useState(0); useEffect(() => { checkDatabase(); @@ -200,132 +220,189 @@ export default function ProgramsScreen() { } }; - return ( - - - Database Debug Panel - - {/* Schema Inspector Card */} - - - - - Database Schema ({Platform.OS}) - - - - - {schemas.map((table) => ( - - {table.name} - - {table.sql} - - - ))} - - - - + 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 */} - - - - - Database Status - - - - - Initialized: {dbStatus.initialized ? '✅' : '❌'} - Tables Found: {dbStatus.tables.length} - - {dbStatus.tables.map(table => ( - • {table} + return ( + + {/* Search bar with filter button */} + + + + + + + + + + + + + + + {/* Filter Sheet */} + setFilterSheetOpen(false)} + options={currentFilters} + onApplyFilters={handleApplyFilters} + availableFilters={availableFilters} + /> + + + + Programs Coming Soon + + Training programs will allow you to organize your workouts into structured training plans. + + + Database Debug Panel + + {/* Schema Inspector Card */} + + + + + Database Schema ({Platform.OS}) + + + + + {schemas.map((table) => ( + + {table.name} + + {table.sql} + + ))} - {dbStatus.error && ( - - - - Error - - {dbStatus.error} - - )} - - - + + + - {/* Operations Card */} - - - - - Database Operations - - - - - - - - - {testResults && ( - - - {testResults.success ? ( - - ) : ( - - )} - - {testResults.success ? "Success" : "Error"} - - - - - {testResults.message} - - + {/* Status Card */} + + + + + Database Status + + + + + Initialized: {dbStatus.initialized ? '✅' : '❌'} + Tables Found: {dbStatus.tables.length} + + {dbStatus.tables.map(table => ( + • {table} + ))} - )} - - - - - + {dbStatus.error && ( + + + + Error + + {dbStatus.error} + + )} + + + + + {/* Operations Card */} + + + + + Database Operations + + + + + + + + + {testResults && ( + + + {testResults.success ? ( + + ) : ( + + )} + + {testResults.success ? "Success" : "Error"} + + + + + {testResults.message} + + + + )} + + + + + + ); } \ No newline at end of file diff --git a/app/(tabs)/library/templates.tsx b/app/(tabs)/library/templates.tsx index a7000fe..7d5522c 100644 --- a/app/(tabs)/library/templates.tsx +++ b/app/(tabs)/library/templates.tsx @@ -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(null); - // Remove selectedTemplate state since we're not using it anymore - + const [filterSheetOpen, setFilterSheetOpen] = useState(false); + const [currentFilters, setCurrentFilters] = useState(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 ( - {/* Search bar */} - - - - + {/* Search bar with filter button */} + + + + + + + + + + - -{/* // Category filters - - - - {TEMPLATE_CATEGORIES.map((category) => ( - - ))} - - */} + {/* Filter Sheet */} + setFilterSheetOpen(false)} + options={currentFilters} + onApplyFilters={handleApplyFilters} + availableFilters={availableFilters} + /> {/* Templates list */} - + {/* Favorites Section */} {favoriteTemplates.length > 0 && ( - - + + Favorites @@ -211,8 +236,8 @@ export default function TemplatesScreen() { )} {/* All Templates Section */} - - + + All Templates {regularTemplates.length > 0 ? ( @@ -241,8 +266,6 @@ export default function TemplatesScreen() { - {/* Remove the TemplateDetails component since we're using router navigation now */} - setShowNewTemplate(true)} diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx index 31c3396..43cba7a 100644 --- a/app/(tabs)/profile.tsx +++ b/app/(tabs)/profile.tsx @@ -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 ( -
+
console.log('Open notifications')} + > + + + + + + } + />
console.log('Open settings')} + onPress={() => console.log('Open notifications')} > - + + + + } /> diff --git a/app/(workout)/template/[id]/_layout.tsx b/app/(workout)/template/[id]/_layout.tsx index a1a9dce..8a17f08 100644 --- a/app/(workout)/template/[id]/_layout.tsx +++ b/app/(workout)/template/[id]/_layout.tsx @@ -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() { ); -} - -// Create a context to share the template with the tab screens -interface TemplateContextType { - template: WorkoutTemplate | null; -} - -export const TemplateContext = React.createContext({ - 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; } \ No newline at end of file diff --git a/app/(workout)/template/[id]/_templateContext.tsx b/app/(workout)/template/[id]/_templateContext.tsx new file mode 100644 index 0000000..41ccfdb --- /dev/null +++ b/app/(workout)/template/[id]/_templateContext.tsx @@ -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({ + 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; +} \ No newline at end of file diff --git a/app/(workout)/template/[id]/history.tsx b/app/(workout)/template/[id]/history.tsx index 74ff1eb..c8af71b 100644 --- a/app/(workout)/template/[id]/history.tsx +++ b/app/(workout)/template/[id]/history.tsx @@ -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) => { diff --git a/app/(workout)/template/[id]/index.tsx b/app/(workout)/template/[id]/index.tsx index 34d4253..8879451 100644 --- a/app/(workout)/template/[id]/index.tsx +++ b/app/(workout)/template/[id]/index.tsx @@ -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, diff --git a/app/(workout)/template/[id]/social.tsx b/app/(workout)/template/[id]/social.tsx index 317b5d0..842108c 100644 --- a/app/(workout)/template/[id]/social.tsx +++ b/app/(workout)/template/[id]/social.tsx @@ -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 diff --git a/components/Header.tsx b/components/Header.tsx index 36fcde7..9ee0b6d 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -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 */} - - {title} + {/* Middle - Title or Logo */} + + {useLogo ? ( + + ) : ( + {title} + )} {/* Right side - Custom element or default notifications */} @@ -69,7 +76,11 @@ export default function Header({ size="icon" onPress={() => {}} > - + + + {/* Notification indicator - you can conditionally render this */} + + )} @@ -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, diff --git a/components/PowerLogo.tsx b/components/PowerLogo.tsx new file mode 100644 index 0000000..b6ccb4a --- /dev/null +++ b/components/PowerLogo.tsx @@ -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 ( + + + POWR + + + + ); +} \ No newline at end of file diff --git a/components/exercises/SimplifiedExerciseList.tsx b/components/exercises/SimplifiedExerciseList.tsx index 8c4dbca..e312e0e 100644 --- a/components/exercises/SimplifiedExerciseList.tsx +++ b/components/exercises/SimplifiedExerciseList.tsx @@ -176,48 +176,6 @@ export const SimplifiedExerciseList = ({ }} /> - - {/* Alphabet List */} - 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) => ( - - {letter} - - ))} - ); }; diff --git a/types/templates.ts b/types/templates.ts index 81b61a6..c4fc79b 100644 --- a/types/templates.ts +++ b/types/templates.ts @@ -26,6 +26,7 @@ export interface TemplateExerciseDisplay { title: string; targetSets: number; targetReps: number; + equipment?: string; notes?: string; }