redesign take 1
This commit is contained in:
1 parent
1143f8ca02
commit
e3a3c3fccf
19 files changed
+2783
-1493
No files matched your search
@@ -7,28 +7,28 @@ import {
|
||||
Image,
|
||||
} from "react-native";
|
||||
import { useUser } from "@clerk/clerk-expo";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
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 { Ionicons } from "@expo/vector-icons";
|
||||
import { useTheme } from "../../contexts/ThemeContext";
|
||||
import { useStatistics } from "../../contexts/StatisticsContext";
|
||||
import { MinimalCard } from "../../components/MinimalCard";
|
||||
import { SectionHeader } from "../../components/SectionHeader";
|
||||
import { IconContainer } from "../../components/IconContainer";
|
||||
import { ProgressBar } from "../../components/ProgressBar";
|
||||
import { TrackMealModal } from "../../components/TrackMealModal";
|
||||
import { AddWaterModal } from "../../components/AddWaterModal";
|
||||
import { HydrationWidget } from "../../components/HydrationWidget";
|
||||
import { NutritionWidget } from "../../components/NutritionWidget";
|
||||
import { WeeklyProgressWidget } from "../../components/WeeklyProgressWidget";
|
||||
import { ScanFoodModal } from "../../components/ScanFoodModal";
|
||||
import { useStatistics } from "../../contexts/StatisticsContext";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
const CALORIE_GOAL = 2000; // kcal
|
||||
const WATER_GOAL = 2000; // ml
|
||||
|
||||
export default function HomeScreen() {
|
||||
const { user } = useUser();
|
||||
const { refetchStatistics, forceRefresh } = useStatistics();
|
||||
const { colors, typography } = useTheme();
|
||||
const { refetchStatistics, forceRefresh, statistics, loading } =
|
||||
useStatistics();
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [trackMealModalVisible, setTrackMealModalVisible] = useState(false);
|
||||
const [addWaterModalVisible, setAddWaterModalVisible] = useState(false);
|
||||
@@ -45,7 +45,6 @@ export default function HomeScreen() {
|
||||
|
||||
const onRefresh = useCallback(async () => {
|
||||
setRefreshing(true);
|
||||
// Force refetch statistics bypassing cache
|
||||
await forceRefresh();
|
||||
setRefreshing(false);
|
||||
}, [forceRefresh]);
|
||||
@@ -168,57 +167,358 @@ export default function HomeScreen() {
|
||||
return () => clearTimeout(midnightTimer);
|
||||
}, []);
|
||||
|
||||
const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0;
|
||||
const currentStreak = statistics?.attendance.currentStreak || 0;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={theme.colors.primary}
|
||||
tintColor={colors.primary}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{/* Header Section */}
|
||||
<View style={styles.header}>
|
||||
<View>
|
||||
<Text style={styles.greeting}>{getGreeting()},</Text>
|
||||
<Text style={styles.name}>{user?.firstName || "Athlete"}</Text>
|
||||
<Text style={[typography.body, { color: colors.textSecondary }]}>
|
||||
{getGreeting()},
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
typography.h1,
|
||||
{ color: colors.textPrimary, marginTop: 4 },
|
||||
]}
|
||||
>
|
||||
{user?.firstName || "Athlete"}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.avatarContainer}>
|
||||
<View>
|
||||
{user?.imageUrl ? (
|
||||
<Image source={{ uri: user.imageUrl }} style={styles.avatar} />
|
||||
) : (
|
||||
<View style={styles.placeholderAvatar}>
|
||||
<Ionicons name="person" size={24} color="#fff" />
|
||||
<View
|
||||
style={[
|
||||
styles.placeholderAvatar,
|
||||
{ backgroundColor: colors.primary },
|
||||
]}
|
||||
>
|
||||
<Ionicons name="person" size={24} color={colors.white} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Activity Widget */}
|
||||
<ActivityWidget calories={calories} />
|
||||
{/* Daily Stats Card */}
|
||||
<View style={styles.section}>
|
||||
<MinimalCard variant="elevated">
|
||||
<View style={styles.statsRow}>
|
||||
<View style={styles.statItem}>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.info}20`}
|
||||
>
|
||||
<Ionicons
|
||||
name="checkmark-circle"
|
||||
size={24}
|
||||
color={colors.info}
|
||||
/>
|
||||
</IconContainer>
|
||||
<Text
|
||||
style={[
|
||||
typography.stat,
|
||||
{ color: colors.textPrimary, marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
{checkInsThisWeek}
|
||||
</Text>
|
||||
<Text
|
||||
style={[typography.caption, { color: colors.textTertiary }]}
|
||||
>
|
||||
This Week
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Quick Action Grid */}
|
||||
<QuickActionGrid
|
||||
onTrackMealPress={() => setTrackMealModalVisible(true)}
|
||||
onAddWaterPress={() => setAddWaterModalVisible(true)}
|
||||
onScanFoodPress={() => setScanFoodModalVisible(true)}
|
||||
onLogWorkoutPress={() => {
|
||||
// TODO: Implement workout logging
|
||||
console.log("Log workout tapped");
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={[styles.divider, { backgroundColor: colors.border }]}
|
||||
/>
|
||||
|
||||
{/* Nutrition Widget */}
|
||||
<NutritionWidget current={calories} goal={CALORIE_GOAL} />
|
||||
<View style={styles.statItem}>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.danger}20`}
|
||||
>
|
||||
<Ionicons name="flame" size={24} color={colors.danger} />
|
||||
</IconContainer>
|
||||
<Text
|
||||
style={[
|
||||
typography.stat,
|
||||
{ color: colors.textPrimary, marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
{calories}
|
||||
</Text>
|
||||
<Text
|
||||
style={[typography.caption, { color: colors.textTertiary }]}
|
||||
>
|
||||
Kcal
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Hydration Widget */}
|
||||
<HydrationWidget current={waterIntake} goal={WATER_GOAL} />
|
||||
<View
|
||||
style={[styles.divider, { backgroundColor: colors.border }]}
|
||||
/>
|
||||
|
||||
{/* Weekly Progress Widget */}
|
||||
<WeeklyProgressWidget />
|
||||
<View style={styles.statItem}>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.success}20`}
|
||||
>
|
||||
<Ionicons name="trophy" size={24} color={colors.success} />
|
||||
</IconContainer>
|
||||
<Text
|
||||
style={[
|
||||
typography.stat,
|
||||
{ color: colors.textPrimary, marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
{currentStreak}
|
||||
</Text>
|
||||
<Text
|
||||
style={[typography.caption, { color: colors.textTertiary }]}
|
||||
>
|
||||
Day Streak
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</MinimalCard>
|
||||
</View>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<View style={styles.section}>
|
||||
<SectionHeader title="Quick Actions" />
|
||||
<View style={styles.actionGrid}>
|
||||
<MinimalCard
|
||||
variant="default"
|
||||
onPress={() => {
|
||||
console.log("Log workout tapped");
|
||||
}}
|
||||
style={styles.actionCard}
|
||||
>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.primary}20`}
|
||||
>
|
||||
<Ionicons name="barbell" size={24} color={colors.primary} />
|
||||
</IconContainer>
|
||||
<Text
|
||||
style={[
|
||||
typography.h3,
|
||||
{ color: colors.textPrimary, marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
Log Workout
|
||||
</Text>
|
||||
</MinimalCard>
|
||||
|
||||
<MinimalCard
|
||||
variant="default"
|
||||
onPress={() => setTrackMealModalVisible(true)}
|
||||
style={styles.actionCard}
|
||||
>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.success}20`}
|
||||
>
|
||||
<Ionicons name="restaurant" size={24} color={colors.success} />
|
||||
</IconContainer>
|
||||
<Text
|
||||
style={[
|
||||
typography.h3,
|
||||
{ color: colors.textPrimary, marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
Track Meal
|
||||
</Text>
|
||||
</MinimalCard>
|
||||
|
||||
<MinimalCard
|
||||
variant="default"
|
||||
onPress={() => setAddWaterModalVisible(true)}
|
||||
style={styles.actionCard}
|
||||
>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.info}20`}
|
||||
>
|
||||
<Ionicons name="water" size={24} color={colors.info} />
|
||||
</IconContainer>
|
||||
<Text
|
||||
style={[
|
||||
typography.h3,
|
||||
{ color: colors.textPrimary, marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
Add Water
|
||||
</Text>
|
||||
</MinimalCard>
|
||||
|
||||
<MinimalCard
|
||||
variant="default"
|
||||
onPress={() => setScanFoodModalVisible(true)}
|
||||
style={styles.actionCard}
|
||||
>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.accent}20`}
|
||||
>
|
||||
<Ionicons name="scan" size={24} color={colors.accent} />
|
||||
</IconContainer>
|
||||
<Text
|
||||
style={[
|
||||
typography.h3,
|
||||
{ color: colors.textPrimary, marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
Scan Food
|
||||
</Text>
|
||||
</MinimalCard>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Nutrition Progress */}
|
||||
<View style={styles.section}>
|
||||
<SectionHeader title="Nutrition" />
|
||||
<MinimalCard variant="default">
|
||||
<View style={styles.progressHeader}>
|
||||
<View style={styles.progressLabelRow}>
|
||||
<Ionicons name="flame" size={20} color={colors.textSecondary} />
|
||||
<Text
|
||||
style={[
|
||||
typography.h3,
|
||||
{ color: colors.textPrimary, marginLeft: 8 },
|
||||
]}
|
||||
>
|
||||
Calories
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
|
||||
>
|
||||
{calories} / {CALORIE_GOAL}
|
||||
</Text>
|
||||
</View>
|
||||
<ProgressBar
|
||||
progress={calories / CALORIE_GOAL}
|
||||
color={colors.danger}
|
||||
style={{ marginTop: 12 }}
|
||||
/>
|
||||
</MinimalCard>
|
||||
</View>
|
||||
|
||||
{/* Hydration Progress */}
|
||||
<View style={styles.section}>
|
||||
<MinimalCard variant="default">
|
||||
<View style={styles.progressHeader}>
|
||||
<View style={styles.progressLabelRow}>
|
||||
<Ionicons name="water" size={20} color={colors.textSecondary} />
|
||||
<Text
|
||||
style={[
|
||||
typography.h3,
|
||||
{ color: colors.textPrimary, marginLeft: 8 },
|
||||
]}
|
||||
>
|
||||
Hydration
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
|
||||
>
|
||||
{waterIntake} / {WATER_GOAL} ml
|
||||
</Text>
|
||||
</View>
|
||||
<ProgressBar
|
||||
progress={waterIntake / WATER_GOAL}
|
||||
color={colors.info}
|
||||
style={{ marginTop: 12 }}
|
||||
/>
|
||||
</MinimalCard>
|
||||
</View>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<View style={styles.section}>
|
||||
<SectionHeader
|
||||
title="Recent Activity"
|
||||
actionLabel="See All"
|
||||
onActionPress={() => console.log("See all tapped")}
|
||||
/>
|
||||
|
||||
<View style={styles.activityList}>
|
||||
<MinimalCard variant="default" style={styles.activityItem}>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.primary}20`}
|
||||
>
|
||||
<Ionicons name="barbell" size={20} color={colors.primary} />
|
||||
</IconContainer>
|
||||
<View style={styles.activityInfo}>
|
||||
<Text style={[typography.h3, { color: colors.textPrimary }]}>
|
||||
Upper Body Power
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
typography.caption,
|
||||
{ color: colors.textTertiary, marginTop: 2 },
|
||||
]}
|
||||
>
|
||||
Today, 10:00 AM
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
typography.bodyEmphasis,
|
||||
{ color: colors.textSecondary },
|
||||
]}
|
||||
>
|
||||
45m
|
||||
</Text>
|
||||
</MinimalCard>
|
||||
|
||||
<MinimalCard variant="default" style={styles.activityItem}>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.success}20`}
|
||||
>
|
||||
<Ionicons name="bicycle" size={20} color={colors.success} />
|
||||
</IconContainer>
|
||||
<View style={styles.activityInfo}>
|
||||
<Text style={[typography.h3, { color: colors.textPrimary }]}>
|
||||
Morning Cardio
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
typography.caption,
|
||||
{ color: colors.textTertiary, marginTop: 2 },
|
||||
]}
|
||||
>
|
||||
Yesterday, 7:30 AM
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
typography.bodyEmphasis,
|
||||
{ color: colors.textSecondary },
|
||||
]}
|
||||
>
|
||||
30m
|
||||
</Text>
|
||||
</MinimalCard>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<TrackMealModal
|
||||
visible={trackMealModalVisible}
|
||||
@@ -240,54 +540,6 @@ export default function HomeScreen() {
|
||||
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>
|
||||
@@ -298,7 +550,6 @@ export default function HomeScreen() {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingTop: 60,
|
||||
@@ -308,102 +559,65 @@ const styles = StyleSheet.create({
|
||||
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,
|
||||
marginBottom: 24,
|
||||
},
|
||||
avatar: {
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 20,
|
||||
borderWidth: 2,
|
||||
borderColor: "#fff",
|
||||
borderRadius: 28,
|
||||
},
|
||||
placeholderAvatar: {
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 20,
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: 28,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderWidth: 2,
|
||||
borderColor: "#fff",
|
||||
},
|
||||
section: {
|
||||
paddingHorizontal: 20,
|
||||
paddingHorizontal: 24,
|
||||
marginBottom: 24,
|
||||
},
|
||||
sectionHeader: {
|
||||
statsRow: {
|
||||
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",
|
||||
statItem: {
|
||||
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,
|
||||
divider: {
|
||||
width: 1,
|
||||
height: 60,
|
||||
},
|
||||
recentSubtitle: {
|
||||
fontSize: 12,
|
||||
color: theme.colors.gray500,
|
||||
actionGrid: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
gap: 12,
|
||||
},
|
||||
recentValue: {
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
color: theme.colors.gray900,
|
||||
actionCard: {
|
||||
width: "48%",
|
||||
alignItems: "center",
|
||||
paddingVertical: 20,
|
||||
},
|
||||
progressHeader: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
},
|
||||
progressLabelRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
activityList: {
|
||||
gap: 12,
|
||||
},
|
||||
activityItem: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
},
|
||||
activityInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user