334 lines
9.4 KiB
TypeScript
334 lines
9.4 KiB
TypeScript
import { View, Text, StyleSheet, ScrollView, RefreshControl, Image } from "react-native";
|
|
import { useUser } from "@clerk/clerk-expo";
|
|
import { LinearGradient } from "expo-linear-gradient";
|
|
import { useState, useCallback, useEffect } from "react";
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import { theme } from "../../styles/theme";
|
|
import { ActivityWidget } from "../../components/ActivityWidget";
|
|
import { QuickActionGrid } from "../../components/QuickActionGrid";
|
|
import { TrackMealModal } from "../../components/TrackMealModal";
|
|
import { AddWaterModal } from "../../components/AddWaterModal";
|
|
import { HydrationWidget } from "../../components/HydrationWidget";
|
|
import { ScanFoodModal } from "../../components/ScanFoodModal";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
|
|
export default function HomeScreen() {
|
|
const { user } = useUser();
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
const [trackMealModalVisible, setTrackMealModalVisible] = useState(false);
|
|
const [addWaterModalVisible, setAddWaterModalVisible] = useState(false);
|
|
const [scanFoodModalVisible, setScanFoodModalVisible] = useState(false);
|
|
const [calories, setCalories] = useState(0);
|
|
const [waterIntake, setWaterIntake] = useState(0);
|
|
|
|
const onRefresh = useCallback(() => {
|
|
setRefreshing(true);
|
|
setTimeout(() => {
|
|
setRefreshing(false);
|
|
}, 2000);
|
|
}, []);
|
|
|
|
const getGreeting = () => {
|
|
const hour = new Date().getHours();
|
|
if (hour < 12) return "Good Morning";
|
|
if (hour < 18) return "Good Afternoon";
|
|
return "Good Evening";
|
|
};
|
|
|
|
const handleSaveMeal = (meal: { type: string; name: string; calories: number }) => {
|
|
setCalories(prev => prev + meal.calories);
|
|
setTrackMealModalVisible(false);
|
|
};
|
|
|
|
const handleAddWater = (amount: number) => {
|
|
setWaterIntake(prev => prev + amount);
|
|
setAddWaterModalVisible(false);
|
|
};
|
|
|
|
const handleResetCalories = () => {
|
|
setCalories(0);
|
|
};
|
|
|
|
const handleResetWater = () => {
|
|
setWaterIntake(0);
|
|
};
|
|
|
|
const handleAddScannedFood = (scannedCalories: number) => {
|
|
setCalories(prev => prev + scannedCalories);
|
|
setScanFoodModalVisible(false);
|
|
};
|
|
|
|
const resetAllCounters = async () => {
|
|
setCalories(0);
|
|
setWaterIntake(0);
|
|
const today = new Date().toDateString();
|
|
await AsyncStorage.setItem('lastResetDate', today);
|
|
};
|
|
|
|
// Check for midnight reset
|
|
useEffect(() => {
|
|
const checkAndResetIfNeeded = async () => {
|
|
const lastResetDate = await AsyncStorage.getItem('lastResetDate');
|
|
const today = new Date().toDateString();
|
|
|
|
if (lastResetDate !== today) {
|
|
await resetAllCounters();
|
|
}
|
|
};
|
|
|
|
checkAndResetIfNeeded();
|
|
|
|
// Calculate time until midnight
|
|
const now = new Date();
|
|
const midnight = new Date();
|
|
midnight.setHours(24, 0, 0, 0);
|
|
const timeUntilMidnight = midnight.getTime() - now.getTime();
|
|
|
|
// Set timer for midnight reset
|
|
const midnightTimer = setTimeout(async () => {
|
|
await resetAllCounters();
|
|
|
|
// Set up daily interval after first midnight
|
|
const dailyInterval = setInterval(async () => {
|
|
await resetAllCounters();
|
|
}, 24 * 60 * 60 * 1000); // 24 hours
|
|
|
|
return () => clearInterval(dailyInterval);
|
|
}, timeUntilMidnight);
|
|
|
|
return () => clearTimeout(midnightTimer);
|
|
}, []);
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<ScrollView
|
|
contentContainerStyle={styles.scrollContent}
|
|
refreshControl={
|
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.colors.primary} />
|
|
}
|
|
>
|
|
{/* Header Section */}
|
|
<View style={styles.header}>
|
|
<View>
|
|
<Text style={styles.greeting}>{getGreeting()},</Text>
|
|
<Text style={styles.name}>{user?.firstName || "Athlete"}</Text>
|
|
</View>
|
|
<View style={styles.avatarContainer}>
|
|
{user?.imageUrl ? (
|
|
<Image source={{ uri: user.imageUrl }} style={styles.avatar} />
|
|
) : (
|
|
<View style={styles.placeholderAvatar}>
|
|
<Ionicons name="person" size={24} color="#fff" />
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{/* Activity Widget */}
|
|
<ActivityWidget
|
|
steps={8432}
|
|
calories={calories}
|
|
duration={45}
|
|
/>
|
|
|
|
{/* Hydration Widget */}
|
|
<HydrationWidget
|
|
current={waterIntake}
|
|
goal={2500}
|
|
/>
|
|
|
|
{/* Quick Actions */}
|
|
<QuickActionGrid
|
|
onTrackMealPress={() => setTrackMealModalVisible(true)}
|
|
onAddWaterPress={() => setAddWaterModalVisible(true)}
|
|
onScanFoodPress={() => setScanFoodModalVisible(true)}
|
|
/>
|
|
|
|
<TrackMealModal
|
|
visible={trackMealModalVisible}
|
|
onClose={() => setTrackMealModalVisible(false)}
|
|
onSave={handleSaveMeal}
|
|
onResetData={handleResetCalories}
|
|
/>
|
|
|
|
<AddWaterModal
|
|
visible={addWaterModalVisible}
|
|
onClose={() => setAddWaterModalVisible(false)}
|
|
onAdd={handleAddWater}
|
|
onResetData={handleResetWater}
|
|
/>
|
|
|
|
<ScanFoodModal
|
|
visible={scanFoodModalVisible}
|
|
onClose={() => setScanFoodModalVisible(false)}
|
|
onAddFood={handleAddScannedFood}
|
|
/>
|
|
|
|
{/* Recent Activity Section */}
|
|
<View style={styles.section}>
|
|
<View style={styles.sectionHeader}>
|
|
<Text style={styles.sectionTitle}>Recent Activity</Text>
|
|
<Text style={styles.seeAll}>See All</Text>
|
|
</View>
|
|
|
|
<View style={styles.activityCard}>
|
|
<LinearGradient
|
|
colors={['rgba(255, 255, 255, 0.8)', 'rgba(255, 255, 255, 0.5)']}
|
|
style={[styles.recentItem, theme.shadows.subtle]}
|
|
>
|
|
<View style={styles.recentIconContainer}>
|
|
<LinearGradient
|
|
colors={theme.gradients.primary}
|
|
style={styles.recentIcon}
|
|
>
|
|
<Ionicons name="barbell" size={20} color="#fff" />
|
|
</LinearGradient>
|
|
</View>
|
|
<View style={styles.recentInfo}>
|
|
<Text style={styles.recentTitle}>Upper Body Power</Text>
|
|
<Text style={styles.recentSubtitle}>Today, 10:00 AM</Text>
|
|
</View>
|
|
<Text style={styles.recentValue}>45m</Text>
|
|
</LinearGradient>
|
|
|
|
<LinearGradient
|
|
colors={['rgba(255, 255, 255, 0.8)', 'rgba(255, 255, 255, 0.5)']}
|
|
style={[styles.recentItem, theme.shadows.subtle]}
|
|
>
|
|
<View style={styles.recentIconContainer}>
|
|
<LinearGradient
|
|
colors={theme.gradients.success}
|
|
style={styles.recentIcon}
|
|
>
|
|
<Ionicons name="bicycle" size={20} color="#fff" />
|
|
</LinearGradient>
|
|
</View>
|
|
<View style={styles.recentInfo}>
|
|
<Text style={styles.recentTitle}>Morning Cardio</Text>
|
|
<Text style={styles.recentSubtitle}>Yesterday, 7:30 AM</Text>
|
|
</View>
|
|
<Text style={styles.recentValue}>30m</Text>
|
|
</LinearGradient>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Bottom Spacer for Tab Bar */}
|
|
<View style={{ height: 100 }} />
|
|
</ScrollView>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: theme.colors.background,
|
|
},
|
|
scrollContent: {
|
|
paddingTop: 60,
|
|
},
|
|
header: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
paddingHorizontal: 24,
|
|
marginBottom: 32,
|
|
},
|
|
greeting: {
|
|
fontSize: 16,
|
|
color: theme.colors.gray600,
|
|
fontWeight: "500",
|
|
marginBottom: 4,
|
|
},
|
|
name: {
|
|
fontSize: 32,
|
|
fontWeight: "800",
|
|
color: theme.colors.gray900,
|
|
letterSpacing: -0.5,
|
|
},
|
|
avatarContainer: {
|
|
shadowColor: "#000",
|
|
shadowOffset: { width: 0, height: 4 },
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 12,
|
|
elevation: 5,
|
|
},
|
|
avatar: {
|
|
width: 56,
|
|
height: 56,
|
|
borderRadius: 20,
|
|
borderWidth: 2,
|
|
borderColor: "#fff",
|
|
},
|
|
placeholderAvatar: {
|
|
width: 56,
|
|
height: 56,
|
|
borderRadius: 20,
|
|
backgroundColor: theme.colors.primary,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
borderWidth: 2,
|
|
borderColor: "#fff",
|
|
},
|
|
section: {
|
|
paddingHorizontal: 20,
|
|
marginBottom: 24,
|
|
},
|
|
sectionHeader: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
marginBottom: 16,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 18,
|
|
fontWeight: "700",
|
|
color: theme.colors.gray900,
|
|
},
|
|
seeAll: {
|
|
fontSize: 14,
|
|
color: theme.colors.primary,
|
|
fontWeight: "600",
|
|
},
|
|
activityCard: {
|
|
gap: 12,
|
|
},
|
|
recentItem: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
padding: 16,
|
|
borderRadius: 20,
|
|
backgroundColor: "#fff",
|
|
borderWidth: 1,
|
|
borderColor: "rgba(255, 255, 255, 0.6)",
|
|
},
|
|
recentIconContainer: {
|
|
marginRight: 16,
|
|
},
|
|
recentIcon: {
|
|
width: 48,
|
|
height: 48,
|
|
borderRadius: 16,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
},
|
|
recentInfo: {
|
|
flex: 1,
|
|
},
|
|
recentTitle: {
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
color: theme.colors.gray900,
|
|
marginBottom: 4,
|
|
},
|
|
recentSubtitle: {
|
|
fontSize: 12,
|
|
color: theme.colors.gray500,
|
|
},
|
|
recentValue: {
|
|
fontSize: 14,
|
|
fontWeight: "600",
|
|
color: theme.colors.gray900,
|
|
},
|
|
});
|