mirror of
https://github.com/DocNR/POWR.git
synced 2025-06-05 08:42:05 +00:00
android icon and other bug fixes
This commit is contained in:
parent
43d6d7d12b
commit
43df1aeb79
@ -14,7 +14,7 @@ export default function HistoryLayout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TabScreen>
|
<TabScreen>
|
||||||
<Header title="History" useLogo={true} />
|
<Header useLogo={true} showNotifications={true} />
|
||||||
|
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
initialRouteName="history"
|
initialRouteName="history"
|
||||||
|
@ -20,6 +20,7 @@ import { Text } from '@/components/ui/text'
|
|||||||
import { getRandomWorkoutTitle } from '@/utils/workoutTitles'
|
import { getRandomWorkoutTitle } from '@/utils/workoutTitles'
|
||||||
import { Bell, Star, Clock, Dumbbell } from 'lucide-react-native';
|
import { Bell, Star, Clock, Dumbbell } from 'lucide-react-native';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useIconColor } from '@/lib/theme/iconUtils';
|
||||||
|
|
||||||
interface FavoriteTemplateData {
|
interface FavoriteTemplateData {
|
||||||
id: string;
|
id: string;
|
||||||
@ -62,6 +63,7 @@ export default function WorkoutScreen() {
|
|||||||
} = useWorkoutStore();
|
} = useWorkoutStore();
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { getIconProps } = useIconColor();
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
@ -232,24 +234,7 @@ export default function WorkoutScreen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TabScreen>
|
<TabScreen>
|
||||||
<Header
|
<Header useLogo={true} showNotifications={true} />
|
||||||
useLogo={true}
|
|
||||||
rightElement={
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onPress={() => console.log('Open notifications')}
|
|
||||||
>
|
|
||||||
<View className="relative">
|
|
||||||
<Bell size={24} color={Platform.select({
|
|
||||||
ios: undefined,
|
|
||||||
android: '#8B5CF6'
|
|
||||||
})} />
|
|
||||||
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
|
||||||
</View>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
className="flex-1 px-4 pt-4"
|
className="flex-1 px-4 pt-4"
|
||||||
|
@ -16,7 +16,7 @@ export default function LibraryLayout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TabScreen>
|
<TabScreen>
|
||||||
<Header useLogo={true} />
|
<Header useLogo={true} showNotifications={true} />
|
||||||
|
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
initialRouteName="templates"
|
initialRouteName="templates"
|
||||||
|
@ -94,8 +94,11 @@ export default function TemplatesScreen() {
|
|||||||
|
|
||||||
const handleStartWorkout = async (template: Template) => {
|
const handleStartWorkout = async (template: Template) => {
|
||||||
try {
|
try {
|
||||||
// Use the workoutStore action to start a workout from template
|
// Convert to WorkoutTemplate format
|
||||||
await useWorkoutStore.getState().startWorkoutFromTemplate(template.id);
|
const workoutTemplate = toWorkoutTemplate(template);
|
||||||
|
|
||||||
|
// Start the workout
|
||||||
|
await useWorkoutStore.getState().startWorkoutFromTemplate(template.id, workoutTemplate);
|
||||||
|
|
||||||
// Navigate to the active workout screen
|
// Navigate to the active workout screen
|
||||||
router.push('/(workout)/create');
|
router.push('/(workout)/create');
|
||||||
@ -285,7 +288,7 @@ export default function TemplatesScreen() {
|
|||||||
) : (
|
) : (
|
||||||
<View className="px-4">
|
<View className="px-4">
|
||||||
<Text className="text-muted-foreground">
|
<Text className="text-muted-foreground">
|
||||||
No templates found. Create one by clicking the + button.
|
So empty! Create a new workout template by clicking the + button.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
@ -76,7 +76,7 @@ export default function ProfileScreen() {
|
|||||||
onPress={() => console.log('Open notifications')}
|
onPress={() => console.log('Open notifications')}
|
||||||
>
|
>
|
||||||
<View className="relative">
|
<View className="relative">
|
||||||
<Bell className="text-foreground" />
|
<Bell size={24} className="text-primary" />
|
||||||
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
||||||
</View>
|
</View>
|
||||||
</Button>
|
</Button>
|
||||||
@ -115,21 +115,7 @@ export default function ProfileScreen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TabScreen>
|
<TabScreen>
|
||||||
<Header
|
<Header useLogo={true} showNotifications={true} />
|
||||||
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
|
<ScrollView
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
|
@ -16,7 +16,7 @@ export default function SocialLayout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TabScreen>
|
<TabScreen>
|
||||||
<Header useLogo={true} />
|
<Header useLogo={true} showNotifications={true} />
|
||||||
|
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
initialRouteName="powr"
|
initialRouteName="powr"
|
||||||
|
@ -7,7 +7,7 @@ import { X } from 'lucide-react-native';
|
|||||||
import { useWorkoutStore } from '@/stores/workoutStore';
|
import { useWorkoutStore } from '@/stores/workoutStore';
|
||||||
import { WorkoutCompletionFlow } from '@/components/workout/WorkoutCompletionFlow';
|
import { WorkoutCompletionFlow } from '@/components/workout/WorkoutCompletionFlow';
|
||||||
import { WorkoutCompletionOptions } from '@/types/workout';
|
import { WorkoutCompletionOptions } from '@/types/workout';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Workout Completion Screen
|
* Workout Completion Screen
|
||||||
|
@ -25,8 +25,9 @@ import { formatTime } from '@/utils/formatTime';
|
|||||||
import { ParamListBase } from '@react-navigation/native';
|
import { ParamListBase } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import SetInput from '@/components/workout/SetInput';
|
import SetInput from '@/components/workout/SetInput';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
import { WorkoutAlertDialog } from '@/components/workout/WorkoutAlertDialog';
|
import { WorkoutAlertDialog } from '@/components/workout/WorkoutAlertDialog';
|
||||||
|
import { useIconColor } from '@/lib/theme/iconUtils';
|
||||||
|
|
||||||
export default function CreateWorkoutScreen() {
|
export default function CreateWorkoutScreen() {
|
||||||
const {
|
const {
|
||||||
@ -51,6 +52,8 @@ export default function CreateWorkoutScreen() {
|
|||||||
|
|
||||||
// Get theme colors
|
// Get theme colors
|
||||||
const { isDarkColorScheme } = useColorScheme();
|
const { isDarkColorScheme } = useColorScheme();
|
||||||
|
// Get icon utilities
|
||||||
|
const { getIconProps } = useIconColor();
|
||||||
|
|
||||||
// Create dynamic styles based on theme
|
// Create dynamic styles based on theme
|
||||||
const dynamicStyles = StyleSheet.create({
|
const dynamicStyles = StyleSheet.create({
|
||||||
@ -239,7 +242,7 @@ export default function CreateWorkoutScreen() {
|
|||||||
onPress={() => useWorkoutStore.getState().extendRest(30)}
|
onPress={() => useWorkoutStore.getState().extendRest(30)}
|
||||||
>
|
>
|
||||||
<View>
|
<View>
|
||||||
<Plus className="mr-2 text-foreground" size={18} />
|
<Plus {...getIconProps('primary')} size={18} />
|
||||||
</View>
|
</View>
|
||||||
<Text>Add 30s</Text>
|
<Text>Add 30s</Text>
|
||||||
</Button>
|
</Button>
|
||||||
@ -266,7 +269,7 @@ export default function CreateWorkoutScreen() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View>
|
<View>
|
||||||
<ChevronLeft className="text-foreground" />
|
<ChevronLeft {...getIconProps('primary')} />
|
||||||
</View>
|
</View>
|
||||||
</Button>
|
</Button>
|
||||||
<Text className="text-xl font-semibold ml-2">Back</Text>
|
<Text className="text-xl font-semibold ml-2">Back</Text>
|
||||||
@ -312,7 +315,7 @@ export default function CreateWorkoutScreen() {
|
|||||||
onPress={pauseWorkout}
|
onPress={pauseWorkout}
|
||||||
>
|
>
|
||||||
<View>
|
<View>
|
||||||
<Pause className="text-foreground" />
|
<Pause {...getIconProps('primary')} />
|
||||||
</View>
|
</View>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
@ -323,7 +326,7 @@ export default function CreateWorkoutScreen() {
|
|||||||
onPress={resumeWorkout}
|
onPress={resumeWorkout}
|
||||||
>
|
>
|
||||||
<View>
|
<View>
|
||||||
<Play className="text-foreground" />
|
<Play {...getIconProps('primary')} />
|
||||||
</View>
|
</View>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@ -351,7 +354,7 @@ export default function CreateWorkoutScreen() {
|
|||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity onPress={() => console.log('Open exercise options')}>
|
<TouchableOpacity onPress={() => console.log('Open exercise options')}>
|
||||||
<View>
|
<View>
|
||||||
<MoreHorizontal size={20} color={isDarkColorScheme ? "#999" : "#666"} />
|
<MoreHorizontal {...getIconProps('muted')} size={20} />
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@ -400,7 +403,7 @@ export default function CreateWorkoutScreen() {
|
|||||||
onPress={() => handleAddSet(exerciseIndex)}
|
onPress={() => handleAddSet(exerciseIndex)}
|
||||||
>
|
>
|
||||||
<View>
|
<View>
|
||||||
<Plus size={18} className="text-foreground mr-2" />
|
<Plus {...getIconProps('primary')} size={18} />
|
||||||
</View>
|
</View>
|
||||||
<Text className="text-foreground">Add Set</Text>
|
<Text className="text-foreground">Add Set</Text>
|
||||||
</Button>
|
</Button>
|
||||||
@ -430,7 +433,7 @@ export default function CreateWorkoutScreen() {
|
|||||||
// Empty State with nice message and icon
|
// Empty State with nice message and icon
|
||||||
<View className="flex-1 justify-center items-center px-4">
|
<View className="flex-1 justify-center items-center px-4">
|
||||||
<View>
|
<View>
|
||||||
<Dumbbell className="text-muted-foreground mb-6" size={80} />
|
<Dumbbell {...getIconProps('muted')} size={80} />
|
||||||
</View>
|
</View>
|
||||||
<Text className="text-xl font-semibold text-center mb-2">
|
<Text className="text-xl font-semibold text-center mb-2">
|
||||||
No exercises added
|
No exercises added
|
||||||
@ -494,12 +497,12 @@ export default function CreateWorkoutScreen() {
|
|||||||
<Text>Are you sure you want to cancel this workout? All progress will be lost.</Text>
|
<Text>Are you sure you want to cancel this workout? All progress will be lost.</Text>
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<View className="flex-row justify-end gap-3">
|
<View className="flex-row justify-center gap-3 px-4 mt-2">
|
||||||
<AlertDialogCancel onPress={() => setShowCancelDialog(false)}>
|
<AlertDialogCancel onPress={() => setShowCancelDialog(false)}>
|
||||||
<Text>Continue Workout</Text>
|
<Text>Continue Workout</Text>
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction onPress={confirmCancelWorkout}>
|
<AlertDialogAction onPress={confirmCancelWorkout} className='bg-destructive'>
|
||||||
<Text>Cancel Workout</Text>
|
<Text className='text-destructive-foreground'>Cancel Workout</Text>
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</View>
|
</View>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
@ -6,8 +6,8 @@ import { Stack } from 'expo-router';
|
|||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, Text, Platform } from 'react-native';
|
import { View, Text, Platform } from 'react-native';
|
||||||
import { NAV_THEME } from '@/lib/constants';
|
import { NAV_THEME } from '@/lib/theme/constants';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
import { PortalHost } from '@rn-primitives/portal';
|
import { PortalHost } from '@rn-primitives/portal';
|
||||||
import { setAndroidNavigationBar } from '@/lib/android-navigation-bar';
|
import { setAndroidNavigationBar } from '@/lib/android-navigation-bar';
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||||
|
@ -12,7 +12,8 @@ import {
|
|||||||
import { Text } from '@/components/ui/text';
|
import { Text } from '@/components/ui/text';
|
||||||
import { Check, Edit2 } from 'lucide-react-native';
|
import { Check, Edit2 } from 'lucide-react-native';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
|
import { useIconColor } from '@/lib/theme/iconUtils';
|
||||||
|
|
||||||
interface EditableTextProps {
|
interface EditableTextProps {
|
||||||
value: string;
|
value: string;
|
||||||
@ -37,6 +38,7 @@ export default function EditableText({
|
|||||||
const [tempValue, setTempValue] = useState(value);
|
const [tempValue, setTempValue] = useState(value);
|
||||||
const inputRef = useRef<TextInput>(null);
|
const inputRef = useRef<TextInput>(null);
|
||||||
const { isDarkColorScheme } = useColorScheme();
|
const { isDarkColorScheme } = useColorScheme();
|
||||||
|
const { getIconProps } = useIconColor();
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (tempValue.trim()) {
|
if (tempValue.trim()) {
|
||||||
@ -72,7 +74,10 @@ export default function EditableText({
|
|||||||
onPress={handleSubmit}
|
onPress={handleSubmit}
|
||||||
className="p-2 ml-2"
|
className="p-2 ml-2"
|
||||||
>
|
>
|
||||||
<Check className="text-primary" size={20} />
|
<Check
|
||||||
|
size={20}
|
||||||
|
{...getIconProps('success')}
|
||||||
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
@ -93,7 +98,10 @@ export default function EditableText({
|
|||||||
</Text>
|
</Text>
|
||||||
<View className="mt-1">
|
<View className="mt-1">
|
||||||
<View className="flex-row items-center self-start px-1.5 py-1 rounded bg-muted/20">
|
<View className="flex-row items-center self-start px-1.5 py-1 rounded bg-muted/20">
|
||||||
<Edit2 size={14} className="text-muted-foreground" />
|
<Edit2
|
||||||
|
size={14}
|
||||||
|
{...getIconProps('muted')}
|
||||||
|
/>
|
||||||
<Text className="text-xs text-muted-foreground ml-1">Edit</Text>
|
<Text className="text-xs text-muted-foreground ml-1">Edit</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
@ -10,19 +10,42 @@ import UserAvatar from '@/components/UserAvatar';
|
|||||||
import PowerLogo from '@/components/PowerLogo';
|
import PowerLogo from '@/components/PowerLogo';
|
||||||
import { useSettingsDrawer } from '@/lib/contexts/SettingsDrawerContext';
|
import { useSettingsDrawer } from '@/lib/contexts/SettingsDrawerContext';
|
||||||
import { useNDKCurrentUser } from '@/lib/hooks/useNDK';
|
import { useNDKCurrentUser } from '@/lib/hooks/useNDK';
|
||||||
|
import { useIconColor } from '@/lib/theme/iconUtils';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
hideTitle?: boolean;
|
hideTitle?: boolean;
|
||||||
rightElement?: React.ReactNode;
|
rightElement?: React.ReactNode;
|
||||||
useLogo?: boolean;
|
useLogo?: boolean;
|
||||||
|
showNotifications?: boolean; // New prop
|
||||||
|
}
|
||||||
|
|
||||||
|
function NotificationBell() {
|
||||||
|
const { getIconProps } = useIconColor();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onPress={() => console.log('Open notifications')}
|
||||||
|
>
|
||||||
|
<View className="relative">
|
||||||
|
<Bell
|
||||||
|
size={24}
|
||||||
|
{...getIconProps('primary')}
|
||||||
|
/>
|
||||||
|
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
||||||
|
</View>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Header({
|
export default function Header({
|
||||||
title,
|
title,
|
||||||
hideTitle = false,
|
hideTitle = false,
|
||||||
rightElement,
|
rightElement,
|
||||||
useLogo = false
|
useLogo = false,
|
||||||
|
showNotifications = true // Default to true
|
||||||
}: HeaderProps) {
|
}: HeaderProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
@ -39,6 +62,13 @@ export default function Header({
|
|||||||
|
|
||||||
if (hideTitle) return null;
|
if (hideTitle) return null;
|
||||||
|
|
||||||
|
// Determine right element: custom, notification bell, or nothing
|
||||||
|
const headerRightElement = rightElement
|
||||||
|
? rightElement
|
||||||
|
: showNotifications
|
||||||
|
? <NotificationBell />
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@ -68,21 +98,9 @@ export default function Header({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Right side - Custom element or default notifications */}
|
{/* Right side - Custom element, notifications, or nothing */}
|
||||||
<View style={styles.rightContainer}>
|
<View style={styles.rightContainer}>
|
||||||
{rightElement || (
|
{headerRightElement}
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onPress={() => {}}
|
|
||||||
>
|
|
||||||
<View className="relative">
|
|
||||||
<Bell size={24} color={theme.colors.text} />
|
|
||||||
{/* 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>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
@ -14,7 +14,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Text } from '@/components/ui/text';
|
import { Text } from '@/components/ui/text';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
import NostrLoginSheet from '@/components/sheets/NostrLoginSheet';
|
import NostrLoginSheet from '@/components/sheets/NostrLoginSheet';
|
||||||
import RelayManagement from '@/components/RelayManagement';
|
import RelayManagement from '@/components/RelayManagement';
|
||||||
import { useNDKCurrentUser, useNDKAuth } from '@/lib/hooks/useNDK';
|
import { useNDKCurrentUser, useNDKAuth } from '@/lib/hooks/useNDK';
|
||||||
|
@ -2,7 +2,7 @@ import { Pressable, View } from 'react-native';
|
|||||||
import { setAndroidNavigationBar } from '@/lib/android-navigation-bar';
|
import { setAndroidNavigationBar } from '@/lib/android-navigation-bar';
|
||||||
import { MoonStar } from '@/lib/icons/MoonStar';
|
import { MoonStar } from '@/lib/icons/MoonStar';
|
||||||
import { Sun } from '@/lib/icons/Sun';
|
import { Sun } from '@/lib/icons/Sun';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export function ThemeToggle() {
|
export function ThemeToggle() {
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
} from 'lucide-react-native';
|
} from 'lucide-react-native';
|
||||||
import { ExerciseDisplay } from '@/types/exercise';
|
import { ExerciseDisplay } from '@/types/exercise';
|
||||||
import { useTheme } from '@react-navigation/native';
|
import { useTheme } from '@react-navigation/native';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
import type { CustomTheme } from '@/lib/theme';
|
import type { CustomTheme } from '@/lib/theme';
|
||||||
|
|
||||||
const Tab = createMaterialTopTabNavigator();
|
const Tab = createMaterialTopTabNavigator();
|
||||||
|
@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { generateId } from '@/utils/ids';
|
import { generateId } from '@/utils/ids';
|
||||||
import { X } from 'lucide-react-native';
|
import { X } from 'lucide-react-native';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
import {
|
import {
|
||||||
BaseExercise,
|
BaseExercise,
|
||||||
ExerciseType,
|
ExerciseType,
|
||||||
|
@ -17,7 +17,7 @@ import { generateId } from '@/utils/ids';
|
|||||||
import { useSQLiteContext } from 'expo-sqlite';
|
import { useSQLiteContext } from 'expo-sqlite';
|
||||||
import { LibraryService } from '@/lib/db/services/LibraryService';
|
import { LibraryService } from '@/lib/db/services/LibraryService';
|
||||||
import { ChevronLeft, Dumbbell, Clock, RotateCw, List, Search, X } from 'lucide-react-native';
|
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 {
|
interface NewTemplateSheetProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { X, Info } from 'lucide-react-native';
|
import { X, Info } from 'lucide-react-native';
|
||||||
import { useNDKAuth } from '@/lib/hooks/useNDK';
|
import { useNDKAuth } from '@/lib/hooks/useNDK';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
|
|
||||||
interface NostrLoginSheetProps {
|
interface NostrLoginSheetProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -9,7 +9,7 @@ import { Card, CardContent } from '@/components/ui/card';
|
|||||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { useTheme } 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 { WorkoutTemplate } from '@/types/templates';
|
||||||
import { useWorkoutStore } from '@/stores/workoutStore';
|
import { useWorkoutStore } from '@/stores/workoutStore';
|
||||||
import { formatTime } from '@/utils/formatTime';
|
import { formatTime } from '@/utils/formatTime';
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from '@/components/ui/alert-dialog';
|
} from '@/components/ui/alert-dialog';
|
||||||
import { Template, TemplateExerciseDisplay } from '@/types/templates';
|
import { Template, TemplateExerciseDisplay } from '@/types/templates';
|
||||||
|
import { useIconColor } from '@/lib/theme/iconUtils';
|
||||||
|
|
||||||
interface TemplateCardProps {
|
interface TemplateCardProps {
|
||||||
template: Template;
|
template: Template;
|
||||||
@ -36,6 +37,7 @@ export function TemplateCard({
|
|||||||
}: TemplateCardProps) {
|
}: TemplateCardProps) {
|
||||||
const [showDeleteAlert, setShowDeleteAlert] = React.useState(false);
|
const [showDeleteAlert, setShowDeleteAlert] = React.useState(false);
|
||||||
const lastUsed = template.metadata?.lastUsed ? new Date(template.metadata.lastUsed) : undefined;
|
const lastUsed = template.metadata?.lastUsed ? new Date(template.metadata.lastUsed) : undefined;
|
||||||
|
const { getIconProps, getIconColor } = useIconColor();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@ -147,7 +149,10 @@ export function TemplateCard({
|
|||||||
className="native:h-10 native:w-10"
|
className="native:h-10 native:w-10"
|
||||||
accessibilityLabel="Start workout"
|
accessibilityLabel="Start workout"
|
||||||
>
|
>
|
||||||
<Play className="text-primary" size={20} />
|
<Play
|
||||||
|
size={20}
|
||||||
|
{...getIconProps('primary')}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -157,9 +162,9 @@ export function TemplateCard({
|
|||||||
accessibilityLabel={isFavorite ? "Remove from favorites" : "Add to favorites"}
|
accessibilityLabel={isFavorite ? "Remove from favorites" : "Add to favorites"}
|
||||||
>
|
>
|
||||||
<Star
|
<Star
|
||||||
className={isFavorite ? "text-primary" : "text-muted-foreground"}
|
size={20}
|
||||||
fill={isFavorite ? "currentColor" : "none"}
|
{...getIconProps(isFavorite ? 'primary' : 'muted')}
|
||||||
size={20}
|
fill={isFavorite ? getIconColor('primary') : "none"}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
<AlertDialog open={showDeleteAlert} onOpenChange={setShowDeleteAlert}>
|
<AlertDialog open={showDeleteAlert} onOpenChange={setShowDeleteAlert}>
|
||||||
@ -171,12 +176,8 @@ export function TemplateCard({
|
|||||||
accessibilityLabel="Delete template"
|
accessibilityLabel="Delete template"
|
||||||
>
|
>
|
||||||
<Trash2
|
<Trash2
|
||||||
size={20}
|
size={20}
|
||||||
color={Platform.select({
|
{...getIconProps('destructive')}
|
||||||
ios: undefined,
|
|
||||||
android: '#8B5CF6'
|
|
||||||
})}
|
|
||||||
className="text-destructive"
|
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TouchableOpacity, View, StyleSheet } from 'react-native';
|
import { TouchableOpacity, View, StyleSheet } from 'react-native';
|
||||||
import { X } from 'lucide-react-native';
|
import { X } from 'lucide-react-native';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
import { NAV_THEME } from '@/lib/constants';
|
import { NAV_THEME } from '@/lib/theme/constants';
|
||||||
|
|
||||||
interface CloseButtonProps {
|
interface CloseButtonProps {
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
|
@ -7,7 +7,7 @@ import Animated, {
|
|||||||
useDerivedValue,
|
useDerivedValue,
|
||||||
withTiming,
|
withTiming,
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
const SwitchWeb = React.forwardRef<SwitchPrimitives.RootRef, SwitchPrimitives.RootProps>(
|
const SwitchWeb = React.forwardRef<SwitchPrimitives.RootRef, SwitchPrimitives.RootProps>(
|
||||||
|
@ -5,7 +5,7 @@ import { Text } from '@/components/ui/text';
|
|||||||
import { Circle, CheckCircle } from 'lucide-react-native'; // Lucide React icons
|
import { Circle, CheckCircle } from 'lucide-react-native'; // Lucide React icons
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useWorkoutStore } from '@/stores/workoutStore';
|
import { useWorkoutStore } from '@/stores/workoutStore';
|
||||||
import { useColorScheme } from '@/lib/useColorScheme';
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||||||
import type { WorkoutSet } from '@/types/workout';
|
import type { WorkoutSet } from '@/types/workout';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as NavigationBar from 'expo-navigation-bar';
|
import * as NavigationBar from 'expo-navigation-bar';
|
||||||
import { Platform } from 'react-native';
|
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') {
|
export async function setAndroidNavigationBar(theme: 'light' | 'dark') {
|
||||||
if (Platform.OS !== 'android') return;
|
if (Platform.OS !== 'android') return;
|
||||||
|
@ -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%)',
|
|
||||||
},
|
|
||||||
};
|
|
63
lib/theme/colors.ts
Normal file
63
lib/theme/colors.ts
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
};
|
42
lib/theme/constants.ts
Normal file
42
lib/theme/constants.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
};
|
41
lib/theme/iconUtils.ts
Normal file
41
lib/theme/iconUtils.ts
Normal file
@ -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,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
9
lib/theme/index.ts
Normal file
9
lib/theme/index.ts
Normal file
@ -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';
|
@ -1,3 +1,4 @@
|
|||||||
|
// lib/theme/useColorScheme.ts
|
||||||
import { useColorScheme as useNativewindColorScheme } from 'nativewind';
|
import { useColorScheme as useNativewindColorScheme } from 'nativewind';
|
||||||
|
|
||||||
export function useColorScheme() {
|
export function useColorScheme() {
|
||||||
@ -8,4 +9,4 @@ export function useColorScheme() {
|
|||||||
setColorScheme,
|
setColorScheme,
|
||||||
toggleColorScheme,
|
toggleColorScheme,
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -604,42 +604,42 @@ const useWorkoutStoreBase = create<ExtendedWorkoutState & ExtendedWorkoutActions
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Template Management
|
// Template Management
|
||||||
startWorkoutFromTemplate: async (templateId: string) => {
|
startWorkoutFromTemplate: async (templateId: string, templateData?: WorkoutTemplate) => {
|
||||||
// Get template from your template store/service
|
// If template data is provided directly, use it
|
||||||
const template = await getTemplate(templateId);
|
const template = templateData || await getTemplate(templateId);
|
||||||
if (!template) return;
|
if (!template) return;
|
||||||
|
|
||||||
// Convert template exercises to workout exercises
|
// Convert template exercises to workout exercises
|
||||||
const exercises: WorkoutExercise[] = template.exercises.map(templateExercise => ({
|
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'),
|
id: generateId('local'),
|
||||||
title: templateExercise.exercise.title,
|
type: 'normal',
|
||||||
type: templateExercise.exercise.type,
|
weight: 0,
|
||||||
category: templateExercise.exercise.category,
|
reps: templateExercise.targetReps || 0,
|
||||||
equipment: templateExercise.exercise.equipment,
|
isCompleted: false
|
||||||
tags: templateExercise.exercise.tags || [],
|
})),
|
||||||
availability: {
|
isCompleted: false,
|
||||||
source: ['local']
|
notes: templateExercise.notes || ''
|
||||||
},
|
}));
|
||||||
created_at: Date.now(),
|
|
||||||
sets: Array(templateExercise.targetSets || 3).fill(0).map(() => ({
|
// Start workout with template data
|
||||||
id: generateId('local'),
|
get().startWorkout({
|
||||||
type: 'normal',
|
title: template.title,
|
||||||
weight: 0,
|
type: template.type || 'strength',
|
||||||
reps: templateExercise.targetReps || 0,
|
exercises,
|
||||||
isCompleted: false
|
templateId: template.id
|
||||||
})),
|
});
|
||||||
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) => {
|
updateWorkoutTitle: (title: string) => {
|
||||||
const { activeWorkout } = get();
|
const { activeWorkout } = get();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user