diff --git a/app/(tabs)/history/_layout.tsx b/app/(tabs)/history/_layout.tsx index a05eb92..59d5cd5 100644 --- a/app/(tabs)/history/_layout.tsx +++ b/app/(tabs)/history/_layout.tsx @@ -14,7 +14,7 @@ export default function HistoryLayout() { return ( -
+
{ @@ -232,24 +234,7 @@ export default function WorkoutScreen() { return ( -
console.log('Open notifications')} - > - - - - - - } - /> +
-
+
{ try { - // Use the workoutStore action to start a workout from template - await useWorkoutStore.getState().startWorkoutFromTemplate(template.id); + // Convert to WorkoutTemplate format + const workoutTemplate = toWorkoutTemplate(template); + + // Start the workout + await useWorkoutStore.getState().startWorkoutFromTemplate(template.id, workoutTemplate); // Navigate to the active workout screen router.push('/(workout)/create'); @@ -285,7 +288,7 @@ export default function TemplatesScreen() { ) : ( - No templates found. Create one by clicking the + button. + So empty! Create a new workout template by clicking the + button. )} diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx index 43cba7a..7e1e744 100644 --- a/app/(tabs)/profile.tsx +++ b/app/(tabs)/profile.tsx @@ -76,7 +76,7 @@ export default function ProfileScreen() { onPress={() => console.log('Open notifications')} > - + @@ -115,21 +115,7 @@ export default function ProfileScreen() { return ( -
console.log('Open notifications')} - > - - - - - - } - /> +
-
+
useWorkoutStore.getState().extendRest(30)} > - + Add 30s @@ -266,7 +269,7 @@ export default function CreateWorkoutScreen() { }} > - + Back @@ -312,7 +315,7 @@ export default function CreateWorkoutScreen() { onPress={pauseWorkout} > - + ) : ( @@ -323,7 +326,7 @@ export default function CreateWorkoutScreen() { onPress={resumeWorkout} > - + )} @@ -351,7 +354,7 @@ export default function CreateWorkoutScreen() { console.log('Open exercise options')}> - + @@ -400,7 +403,7 @@ export default function CreateWorkoutScreen() { onPress={() => handleAddSet(exerciseIndex)} > - + Add Set @@ -430,7 +433,7 @@ export default function CreateWorkoutScreen() { // Empty State with nice message and icon - + No exercises added @@ -494,12 +497,12 @@ export default function CreateWorkoutScreen() { Are you sure you want to cancel this workout? All progress will be lost. - + setShowCancelDialog(false)}> Continue Workout - - Cancel Workout + + Cancel Workout diff --git a/app/_layout.tsx b/app/_layout.tsx index 5db6cc4..07a3dbf 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -6,8 +6,8 @@ import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import * as React from 'react'; import { View, Text, Platform } from 'react-native'; -import { NAV_THEME } from '@/lib/constants'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { NAV_THEME } from '@/lib/theme/constants'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; import { PortalHost } from '@rn-primitives/portal'; import { setAndroidNavigationBar } from '@/lib/android-navigation-bar'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; diff --git a/components/EditableText.tsx b/components/EditableText.tsx index 24366b4..399e40d 100644 --- a/components/EditableText.tsx +++ b/components/EditableText.tsx @@ -12,7 +12,8 @@ import { import { Text } from '@/components/ui/text'; import { Check, Edit2 } from 'lucide-react-native'; import { cn } from '@/lib/utils'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; +import { useIconColor } from '@/lib/theme/iconUtils'; interface EditableTextProps { value: string; @@ -37,6 +38,7 @@ export default function EditableText({ const [tempValue, setTempValue] = useState(value); const inputRef = useRef(null); const { isDarkColorScheme } = useColorScheme(); + const { getIconProps } = useIconColor(); const handleSubmit = () => { if (tempValue.trim()) { @@ -72,7 +74,10 @@ export default function EditableText({ onPress={handleSubmit} className="p-2 ml-2" > - + ) : ( @@ -93,7 +98,10 @@ export default function EditableText({ - + Edit diff --git a/components/Header.tsx b/components/Header.tsx index d8160ff..29c268a 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -10,19 +10,42 @@ import UserAvatar from '@/components/UserAvatar'; import PowerLogo from '@/components/PowerLogo'; import { useSettingsDrawer } from '@/lib/contexts/SettingsDrawerContext'; import { useNDKCurrentUser } from '@/lib/hooks/useNDK'; +import { useIconColor } from '@/lib/theme/iconUtils'; interface HeaderProps { title?: string; hideTitle?: boolean; rightElement?: React.ReactNode; useLogo?: boolean; + showNotifications?: boolean; // New prop +} + +function NotificationBell() { + const { getIconProps } = useIconColor(); + + return ( + + ); } export default function Header({ title, hideTitle = false, rightElement, - useLogo = false + useLogo = false, + showNotifications = true // Default to true }: HeaderProps) { const theme = useTheme(); const insets = useSafeAreaInsets(); @@ -39,6 +62,13 @@ export default function Header({ if (hideTitle) return null; + // Determine right element: custom, notification bell, or nothing + const headerRightElement = rightElement + ? rightElement + : showNotifications + ? + : null; + return ( - {/* Right side - Custom element or default notifications */} + {/* Right side - Custom element, notifications, or nothing */} - {rightElement || ( - - )} + {headerRightElement} diff --git a/components/SettingsDrawer.tsx b/components/SettingsDrawer.tsx index f7c208f..7b75e4c 100644 --- a/components/SettingsDrawer.tsx +++ b/components/SettingsDrawer.tsx @@ -14,7 +14,7 @@ import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; import { Separator } from '@/components/ui/separator'; import { Text } from '@/components/ui/text'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; import NostrLoginSheet from '@/components/sheets/NostrLoginSheet'; import RelayManagement from '@/components/RelayManagement'; import { useNDKCurrentUser, useNDKAuth } from '@/lib/hooks/useNDK'; diff --git a/components/ThemeToggle.tsx b/components/ThemeToggle.tsx index 248f0ff..10eb2db 100644 --- a/components/ThemeToggle.tsx +++ b/components/ThemeToggle.tsx @@ -2,7 +2,7 @@ import { Pressable, View } from 'react-native'; import { setAndroidNavigationBar } from '@/lib/android-navigation-bar'; import { MoonStar } from '@/lib/icons/MoonStar'; import { Sun } from '@/lib/icons/Sun'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; import { cn } from '@/lib/utils'; export function ThemeToggle() { diff --git a/components/exercises/ModalExerciseDetails.tsx b/components/exercises/ModalExerciseDetails.tsx index 2054e9a..824d4cb 100644 --- a/components/exercises/ModalExerciseDetails.tsx +++ b/components/exercises/ModalExerciseDetails.tsx @@ -19,7 +19,7 @@ import { } from 'lucide-react-native'; import { ExerciseDisplay } from '@/types/exercise'; import { useTheme } from '@react-navigation/native'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; import type { CustomTheme } from '@/lib/theme'; const Tab = createMaterialTopTabNavigator(); diff --git a/components/library/ExerciseSheet.tsx b/components/library/ExerciseSheet.tsx index 431e650..b6eb936 100644 --- a/components/library/ExerciseSheet.tsx +++ b/components/library/ExerciseSheet.tsx @@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { generateId } from '@/utils/ids'; import { X } from 'lucide-react-native'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; import { BaseExercise, ExerciseType, diff --git a/components/library/NewTemplateSheet.tsx b/components/library/NewTemplateSheet.tsx index 0f294b9..8e739c3 100644 --- a/components/library/NewTemplateSheet.tsx +++ b/components/library/NewTemplateSheet.tsx @@ -17,7 +17,7 @@ import { generateId } from '@/utils/ids'; import { useSQLiteContext } from 'expo-sqlite'; import { LibraryService } from '@/lib/db/services/LibraryService'; import { ChevronLeft, Dumbbell, Clock, RotateCw, List, Search, X } from 'lucide-react-native'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; interface NewTemplateSheetProps { isOpen: boolean; diff --git a/components/sheets/NostrLoginSheet.tsx b/components/sheets/NostrLoginSheet.tsx index 8888355..5fde49b 100644 --- a/components/sheets/NostrLoginSheet.tsx +++ b/components/sheets/NostrLoginSheet.tsx @@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { X, Info } from 'lucide-react-native'; import { useNDKAuth } from '@/lib/hooks/useNDK'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; interface NostrLoginSheetProps { open: boolean; diff --git a/components/templates/ModalTemplateDetails.tsx b/components/templates/ModalTemplateDetails.tsx index 6f08338..2acb77d 100644 --- a/components/templates/ModalTemplateDetails.tsx +++ b/components/templates/ModalTemplateDetails.tsx @@ -9,7 +9,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; import { NavigationContainer } from '@react-navigation/native'; import { useTheme } from '@react-navigation/native'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; import { WorkoutTemplate } from '@/types/templates'; import { useWorkoutStore } from '@/stores/workoutStore'; import { formatTime } from '@/utils/formatTime'; diff --git a/components/templates/TemplateCard.tsx b/components/templates/TemplateCard.tsx index 48acb90..b1346a5 100644 --- a/components/templates/TemplateCard.tsx +++ b/components/templates/TemplateCard.tsx @@ -18,6 +18,7 @@ import { AlertDialogTrigger, } from '@/components/ui/alert-dialog'; import { Template, TemplateExerciseDisplay } from '@/types/templates'; +import { useIconColor } from '@/lib/theme/iconUtils'; interface TemplateCardProps { template: Template; @@ -36,6 +37,7 @@ export function TemplateCard({ }: TemplateCardProps) { const [showDeleteAlert, setShowDeleteAlert] = React.useState(false); const lastUsed = template.metadata?.lastUsed ? new Date(template.metadata.lastUsed) : undefined; + const { getIconProps, getIconColor } = useIconColor(); const { id, @@ -147,7 +149,10 @@ export function TemplateCard({ className="native:h-10 native:w-10" accessibilityLabel="Start workout" > - + @@ -171,12 +176,8 @@ export function TemplateCard({ accessibilityLabel="Delete template" > diff --git a/components/ui/sheet/CloseButton.tsx b/components/ui/sheet/CloseButton.tsx index 069c270..c1e6ed1 100644 --- a/components/ui/sheet/CloseButton.tsx +++ b/components/ui/sheet/CloseButton.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { TouchableOpacity, View, StyleSheet } from 'react-native'; import { X } from 'lucide-react-native'; -import { useColorScheme } from '@/lib/useColorScheme'; -import { NAV_THEME } from '@/lib/constants'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; +import { NAV_THEME } from '@/lib/theme/constants'; interface CloseButtonProps { onPress: () => void; diff --git a/components/ui/switch.tsx b/components/ui/switch.tsx index 27237c0..8fb91bc 100644 --- a/components/ui/switch.tsx +++ b/components/ui/switch.tsx @@ -7,7 +7,7 @@ import Animated, { useDerivedValue, withTiming, } from 'react-native-reanimated'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; import { cn } from '@/lib/utils'; const SwitchWeb = React.forwardRef( diff --git a/components/workout/SetInput.tsx b/components/workout/SetInput.tsx index edaaa6c..56cc0c8 100644 --- a/components/workout/SetInput.tsx +++ b/components/workout/SetInput.tsx @@ -5,7 +5,7 @@ import { Text } from '@/components/ui/text'; import { Circle, CheckCircle } from 'lucide-react-native'; // Lucide React icons import { cn } from '@/lib/utils'; import { useWorkoutStore } from '@/stores/workoutStore'; -import { useColorScheme } from '@/lib/useColorScheme'; +import { useColorScheme } from '@/lib/theme/useColorScheme'; import type { WorkoutSet } from '@/types/workout'; import debounce from 'lodash/debounce'; diff --git a/lib/android-navigation-bar.ts b/lib/android-navigation-bar.ts index ad163f0..b137fbb 100644 --- a/lib/android-navigation-bar.ts +++ b/lib/android-navigation-bar.ts @@ -1,6 +1,6 @@ import * as NavigationBar from 'expo-navigation-bar'; import { Platform } from 'react-native'; -import { NAV_THEME } from '@/lib/constants'; +import { NAV_THEME } from '@/lib/theme/constants'; export async function setAndroidNavigationBar(theme: 'light' | 'dark') { if (Platform.OS !== 'android') return; diff --git a/lib/constants.ts b/lib/constants.ts deleted file mode 100644 index 6962073..0000000 --- a/lib/constants.ts +++ /dev/null @@ -1,30 +0,0 @@ -// lib/constants.ts -import type { NavigationThemeColors } from './theme'; - -export const NAV_THEME: { - light: NavigationThemeColors; - dark: NavigationThemeColors; -} = { - light: { - background: 'hsl(0, 0%, 100%)', - border: 'hsl(240, 5.9%, 90%)', - card: 'hsl(0, 0%, 100%)', - notification: 'hsl(0, 84.2%, 60.2%)', - primary: 'hsl(261, 90%, 66%)', - text: 'hsl(240, 10%, 3.9%)', - tabActive: '#000000', - tabInactive: '#737373', - tabIndicator: 'hsl(261, 90%, 66%)', - }, - dark: { - background: 'hsl(240, 10%, 3.9%)', - border: 'hsl(240, 3.7%, 15.9%)', - card: 'hsl(240, 10%, 3.9%)', - notification: 'hsl(0, 72%, 51%)', - primary: 'hsl(261, 90%, 66%)', - text: 'hsl(0, 0%, 98%)', - tabActive: '#FFFFFF', - tabInactive: '#A3A3A3', - tabIndicator: 'hsl(261, 90%, 66%)', - }, -}; \ No newline at end of file diff --git a/lib/theme/colors.ts b/lib/theme/colors.ts new file mode 100644 index 0000000..f8cf309 --- /dev/null +++ b/lib/theme/colors.ts @@ -0,0 +1,63 @@ +// lib/theme/colors.ts +export const COLORS = { + // Primary brand colors + purple: { + DEFAULT: 'hsl(261, 90%, 66%)', + pressed: 'hsl(262, 84%, 58%)', + light: 'hsl(261, 90%, 85%)', + dark: 'hsl(261, 90%, 45%)', + }, + + // Semantic colors + success: 'hsl(142, 71%, 45%)', + warning: 'hsl(38, 92%, 50%)', + destructive: 'hsl(0, 84.2%, 60.2%)', + + // Light mode + light: { + background: 'hsl(0, 0%, 100%)', + foreground: 'hsl(240, 10%, 3.9%)', + card: 'hsl(0, 0%, 100%)', + cardForeground: 'hsl(240, 10%, 3.9%)', + border: 'hsl(240, 5.9%, 90%)', + input: 'hsl(240, 5.9%, 90%)', + muted: 'hsl(240, 4.8%, 95.9%)', + mutedForeground: 'hsl(240, 3.8%, 46.1%)', + }, + + // Dark mode + dark: { + background: 'hsl(240, 10%, 3.9%)', + foreground: 'hsl(0, 0%, 98%)', + card: 'hsl(240, 10%, 5.9%)', + cardForeground: 'hsl(0, 0%, 98%)', + border: 'hsl(240, 3.7%, 25%)', + input: 'hsl(240, 3.7%, 25%)', + muted: 'hsl(240, 3.7%, 18%)', + mutedForeground: 'hsl(240, 5%, 64.9%)', + }, + }; + + // Fixed color values to use when className doesn't work (especially for icons on Android) + export const FIXED_COLORS = { + // Primary colors + primary: COLORS.purple.DEFAULT, + primaryDark: COLORS.purple.dark, + + // Text colors + text: { + light: COLORS.light.foreground, + dark: COLORS.dark.foreground, + }, + + // Semantic colors + destructive: 'hsl(0, 84.2%, 60.2%)', + success: 'hsl(142, 71%, 45%)', + warning: 'hsl(38, 92%, 50%)', + + // Muted colors + muted: { + light: COLORS.light.mutedForeground, + dark: COLORS.dark.mutedForeground, + } + }; \ No newline at end of file diff --git a/lib/theme/constants.ts b/lib/theme/constants.ts new file mode 100644 index 0000000..bbfe775 --- /dev/null +++ b/lib/theme/constants.ts @@ -0,0 +1,42 @@ +// lib/theme/constants.ts +import { COLORS } from './colors'; + +export interface NavigationThemeColors { + background: string; + border: string; + card: string; + notification: string; + primary: string; + text: string; + tabActive: string; + tabInactive: string; + tabIndicator: string; +} + +export const NAV_THEME: { + light: NavigationThemeColors; + dark: NavigationThemeColors; +} = { + light: { + background: COLORS.light.background, + border: COLORS.light.border, + card: COLORS.light.card, + notification: COLORS.destructive, + primary: COLORS.purple.DEFAULT, + text: COLORS.light.foreground, + tabActive: COLORS.light.foreground, + tabInactive: COLORS.light.mutedForeground, + tabIndicator: COLORS.purple.DEFAULT, + }, + dark: { + background: COLORS.dark.background, + border: COLORS.dark.border, + card: COLORS.dark.card, + notification: COLORS.destructive, + primary: COLORS.purple.DEFAULT, + text: COLORS.dark.foreground, + tabActive: COLORS.dark.foreground, + tabInactive: COLORS.dark.mutedForeground, + tabIndicator: COLORS.purple.DEFAULT, + }, +}; \ No newline at end of file diff --git a/lib/theme/iconUtils.ts b/lib/theme/iconUtils.ts new file mode 100644 index 0000000..a5aa143 --- /dev/null +++ b/lib/theme/iconUtils.ts @@ -0,0 +1,41 @@ +// lib/theme/iconUtils.ts +import { Platform } from 'react-native'; +import { useColorScheme } from './useColorScheme'; +import { FIXED_COLORS } from './colors'; + +export type IconVariant = + | 'primary' + | 'muted' + | 'destructive' + | 'success' + | 'warning'; + +export function useIconColor() { + const { isDarkColorScheme } = useColorScheme(); + + const getIconColor = (variant: IconVariant = 'primary') => { + // Use fixed colors that work on both platforms + switch (variant) { + case 'primary': + return FIXED_COLORS.primary; + case 'muted': + return isDarkColorScheme ? FIXED_COLORS.muted.dark : FIXED_COLORS.muted.light; + case 'destructive': + return FIXED_COLORS.destructive; + case 'success': + return FIXED_COLORS.success; + case 'warning': + return FIXED_COLORS.warning; + default: + return FIXED_COLORS.primary; + } + }; + + return { + getIconColor, + getIconProps: (variant: IconVariant = 'primary') => ({ + color: getIconColor(variant), + strokeWidth: Platform.OS === 'android' ? 2 : 1.5, + }) + }; +} \ No newline at end of file diff --git a/lib/theme/index.ts b/lib/theme/index.ts new file mode 100644 index 0000000..95c3500 --- /dev/null +++ b/lib/theme/index.ts @@ -0,0 +1,9 @@ +// lib/theme/index.ts +export * from './colors'; +export * from './constants'; +export * from './iconUtils'; +export * from './useColorScheme'; + +// Also re-export any types +export type { NavigationThemeColors } from '@/lib/theme/constants'; +export type { IconVariant } from './iconUtils'; \ No newline at end of file diff --git a/lib/useColorScheme.tsx b/lib/theme/useColorScheme.tsx similarity index 91% rename from lib/useColorScheme.tsx rename to lib/theme/useColorScheme.tsx index 8e171b6..d1c3cb9 100644 --- a/lib/useColorScheme.tsx +++ b/lib/theme/useColorScheme.tsx @@ -1,3 +1,4 @@ +// lib/theme/useColorScheme.ts import { useColorScheme as useNativewindColorScheme } from 'nativewind'; export function useColorScheme() { @@ -8,4 +9,4 @@ export function useColorScheme() { setColorScheme, toggleColorScheme, }; -} +} \ No newline at end of file diff --git a/stores/workoutStore.ts b/stores/workoutStore.ts index 97e8b1b..8e9b2e0 100644 --- a/stores/workoutStore.ts +++ b/stores/workoutStore.ts @@ -604,42 +604,42 @@ const useWorkoutStoreBase = create { - // Get template from your template store/service - const template = await getTemplate(templateId); + startWorkoutFromTemplate: async (templateId: string, templateData?: WorkoutTemplate) => { + // If template data is provided directly, use it + const template = templateData || await getTemplate(templateId); if (!template) return; // Convert template exercises to workout exercises const exercises: WorkoutExercise[] = template.exercises.map(templateExercise => ({ + id: generateId('local'), + title: templateExercise.exercise.title, + type: templateExercise.exercise.type, + category: templateExercise.exercise.category, + equipment: templateExercise.exercise.equipment, + tags: templateExercise.exercise.tags || [], + availability: { + source: ['local'] + }, + created_at: Date.now(), + sets: Array(templateExercise.targetSets || 3).fill(0).map(() => ({ id: generateId('local'), - title: templateExercise.exercise.title, - type: templateExercise.exercise.type, - category: templateExercise.exercise.category, - equipment: templateExercise.exercise.equipment, - tags: templateExercise.exercise.tags || [], - availability: { - source: ['local'] - }, - created_at: Date.now(), - sets: Array(templateExercise.targetSets || 3).fill(0).map(() => ({ - id: generateId('local'), - type: 'normal', - weight: 0, - reps: templateExercise.targetReps || 0, - isCompleted: false - })), - isCompleted: false, - notes: templateExercise.notes || '' - })); - - // Start workout with template data - get().startWorkout({ - title: template.title, - type: template.type || 'strength', - exercises, - templateId: template.id - }); - }, + type: 'normal', + weight: 0, + reps: templateExercise.targetReps || 0, + isCompleted: false + })), + isCompleted: false, + notes: templateExercise.notes || '' + })); + + // Start workout with template data + get().startWorkout({ + title: template.title, + type: template.type || 'strength', + exercises, + templateId: template.id + }); + }, updateWorkoutTitle: (title: string) => { const { activeWorkout } = get();