redesign checkpoint

This commit is contained in:
echo 2026-03-12 15:32:52 +01:00
parent a5f761062e
commit 981208ab7b
6 changed files with 583 additions and 298 deletions

Binary file not shown.

View File

@ -104,7 +104,7 @@ export default function AttendanceScreen() {
{/* Header */} {/* Header */}
<View style={styles.header}> <View style={styles.header}>
<Text style={[typography.h1, { color: colors.textPrimary }]}> <Text style={[typography.h1, { color: colors.textPrimary }]}>
Attendance 📍 Attendance
</Text> </Text>
<Text <Text
style={[ style={[
@ -112,14 +112,18 @@ export default function AttendanceScreen() {
{ color: colors.textSecondary, marginTop: 4 }, { color: colors.textSecondary, marginTop: 4 },
]} ]}
> >
Track your gym visits {activeCheckIn
? "You're crushing it today! 💪"
: history.length === 0
? "Ready to start your fitness journey? 🚀"
: "Track your gym visits and build streaks! 🔥"}
</Text> </Text>
</View> </View>
{/* Check In/Out Section */} {/* Check In/Out Section */}
<View style={styles.section}> <View style={styles.section}>
{activeCheckIn ? ( {activeCheckIn ? (
<MinimalCard variant="elevated" style={styles.activeCard}> <MinimalCard variant="bordered" style={styles.activeCard}>
<View style={styles.activeHeader}> <View style={styles.activeHeader}>
<View style={styles.activeHeaderLeft}> <View style={styles.activeHeaderLeft}>
<IconContainer <IconContainer
@ -135,7 +139,7 @@ export default function AttendanceScreen() {
</IconContainer> </IconContainer>
<View style={{ marginLeft: 12 }}> <View style={{ marginLeft: 12 }}>
<Text style={[typography.h3, { color: colors.textPrimary }]}> <Text style={[typography.h3, { color: colors.textPrimary }]}>
Currently Checked In Currently Checked In
</Text> </Text>
<Text <Text
style={[ style={[
@ -165,7 +169,7 @@ export default function AttendanceScreen() {
</MinimalCard> </MinimalCard>
) : ( ) : (
<MinimalButton <MinimalButton
title="Check In" title="💪 Check In"
onPress={handleCheckIn} onPress={handleCheckIn}
variant="primary" variant="primary"
size="lg" size="lg"
@ -175,29 +179,19 @@ export default function AttendanceScreen() {
{/* Attendance Calendar */} {/* Attendance Calendar */}
<View style={styles.section}> <View style={styles.section}>
<SectionHeader title="Calendar" /> <SectionHeader title="📅 Calendar" />
<MinimalCard variant="default"> <MinimalCard variant="default" style={{ borderRadius: 20 }}>
<AttendanceCalendar attendanceRecords={history} /> <AttendanceCalendar attendanceRecords={history} />
</MinimalCard> </MinimalCard>
</View> </View>
{/* Recent History */} {/* Recent History */}
<View style={styles.section}> <View style={styles.section}>
<SectionHeader title="Recent History" /> <SectionHeader title="📊 Recent History" />
{history.length === 0 ? ( {history.length === 0 ? (
<MinimalCard variant="default"> <MinimalCard variant="default" style={{ borderRadius: 20 }}>
<View style={styles.emptyState}> <View style={styles.emptyState}>
<IconContainer <Text style={{ fontSize: 64 }}>📍</Text>
variant="colored"
backgroundColor={`${colors.primary}15`}
size="lg"
>
<Ionicons
name="calendar-outline"
size={32}
color={colors.primary}
/>
</IconContainer>
<Text <Text
style={[ style={[
typography.bodyEmphasis, typography.bodyEmphasis,
@ -216,7 +210,7 @@ export default function AttendanceScreen() {
}, },
]} ]}
> >
Your check-in history will appear here Check in to start building your streak! 🔥
</Text> </Text>
</View> </View>
</MinimalCard> </MinimalCard>
@ -232,7 +226,11 @@ export default function AttendanceScreen() {
: null; : null;
return ( return (
<MinimalCard key={index} variant="default"> <MinimalCard
key={index}
variant="bordered"
style={{ borderRadius: 16 }}
>
<View style={styles.historyItem}> <View style={styles.historyItem}>
<View style={styles.historyLeft}> <View style={styles.historyLeft}>
<IconContainer <IconContainer
@ -315,6 +313,7 @@ const styles = StyleSheet.create({
}, },
activeCard: { activeCard: {
padding: 20, padding: 20,
borderRadius: 20,
}, },
activeHeader: { activeHeader: {
flexDirection: "row", flexDirection: "row",

View File

@ -152,7 +152,7 @@ export default function GoalsScreen() {
<View style={styles.header}> <View style={styles.header}>
<View> <View>
<Text style={[typography.h1, { color: colors.textPrimary }]}> <Text style={[typography.h1, { color: colors.textPrimary }]}>
Fitness Goals 🎯 Fitness Goals
</Text> </Text>
<Text <Text
style={[ style={[
@ -160,7 +160,11 @@ export default function GoalsScreen() {
{ color: colors.textSecondary, marginTop: 4 }, { color: colors.textSecondary, marginTop: 4 },
]} ]}
> >
Track your fitness journey progress {activeGoals.length === 0
? "Ready to crush some goals? 💪"
: activeGoals.length === 1
? "You're on a mission! Keep it up! 🚀"
: `${activeGoals.length} goals in progress. Legend! ⭐`}
</Text> </Text>
</View> </View>
<TouchableOpacity <TouchableOpacity
@ -179,7 +183,7 @@ export default function GoalsScreen() {
{goals && goals.length > 0 && ( {goals && goals.length > 0 && (
<View style={styles.section}> <View style={styles.section}>
<View style={styles.statsRow}> <View style={styles.statsRow}>
<MinimalCard variant="elevated" style={styles.statCard}> <MinimalCard variant="bordered" style={styles.statCard}>
<Text style={[typography.stat, { color: colors.primary }]}> <Text style={[typography.stat, { color: colors.primary }]}>
{activeGoals.length} {activeGoals.length}
</Text> </Text>
@ -189,11 +193,11 @@ export default function GoalsScreen() {
{ color: colors.textTertiary, marginTop: 4 }, { color: colors.textTertiary, marginTop: 4 },
]} ]}
> >
Active 🎯 Active
</Text> </Text>
</MinimalCard> </MinimalCard>
<MinimalCard variant="elevated" style={styles.statCard}> <MinimalCard variant="bordered" style={styles.statCard}>
<Text style={[typography.stat, { color: colors.success }]}> <Text style={[typography.stat, { color: colors.success }]}>
{completedGoals.length} {completedGoals.length}
</Text> </Text>
@ -203,11 +207,11 @@ export default function GoalsScreen() {
{ color: colors.textTertiary, marginTop: 4 }, { color: colors.textTertiary, marginTop: 4 },
]} ]}
> >
Completed {completedGoals.length >= 5 ? "🏆" : "✅"} Completed
</Text> </Text>
</MinimalCard> </MinimalCard>
<MinimalCard variant="elevated" style={styles.statCard}> <MinimalCard variant="bordered" style={styles.statCard}>
<Text style={[typography.stat, { color: colors.textPrimary }]}> <Text style={[typography.stat, { color: colors.textPrimary }]}>
{avgProgress}% {avgProgress}%
</Text> </Text>
@ -217,7 +221,7 @@ export default function GoalsScreen() {
{ color: colors.textTertiary, marginTop: 4 }, { color: colors.textTertiary, marginTop: 4 },
]} ]}
> >
Avg Progress {avgProgress >= 75 ? "🔥" : "📊"} Progress
</Text> </Text>
</MinimalCard> </MinimalCard>
</View> </View>
@ -245,7 +249,7 @@ export default function GoalsScreen() {
{ color: colors.textPrimary, marginLeft: 8 }, { color: colors.textPrimary, marginLeft: 8 },
]} ]}
> >
Progress Analytics 📈 Progress Analytics
</Text> </Text>
</View> </View>
<Ionicons <Ionicons
@ -283,18 +287,14 @@ export default function GoalsScreen() {
{/* Active Goals */} {/* Active Goals */}
<View style={styles.section}> <View style={styles.section}>
<SectionHeader <SectionHeader
title={`Active Goals (${activeGoals.length})`} title={`🚀 Active Goals (${activeGoals.length})`}
actionLabel="Add New" actionLabel="Add New"
onActionPress={() => setIsModalVisible(true)} onActionPress={() => setIsModalVisible(true)}
/> />
{activeGoals.length === 0 ? ( {activeGoals.length === 0 ? (
<MinimalCard variant="default"> <MinimalCard variant="default">
<View style={styles.emptyState}> <View style={styles.emptyState}>
<Ionicons <Text style={{ fontSize: 64 }}>🎯</Text>
name="flag-outline"
size={48}
color={colors.borderLight}
/>
<Text <Text
style={[ style={[
typography.bodyEmphasis, typography.bodyEmphasis,
@ -309,7 +309,7 @@ export default function GoalsScreen() {
{ color: colors.textTertiary, marginTop: 4 }, { color: colors.textTertiary, marginTop: 4 },
]} ]}
> >
Tap "Add New" to create your first goal Tap "Add New" to set your first goal! 💪
</Text> </Text>
</View> </View>
</MinimalCard> </MinimalCard>
@ -331,7 +331,7 @@ export default function GoalsScreen() {
{completedGoals.length > 0 && ( {completedGoals.length > 0 && (
<View style={styles.section}> <View style={styles.section}>
<SectionHeader <SectionHeader
title={`Completed Goals (${completedGoals.length})`} title={`Completed Goals (${completedGoals.length})`}
/> />
<View style={styles.goalsList}> <View style={styles.goalsList}>
{completedGoals.map((goal) => ( {completedGoals.map((goal) => (
@ -405,6 +405,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
alignItems: "center", alignItems: "center",
paddingVertical: 20, paddingVertical: 20,
borderRadius: 20,
}, },
analyticsHeader: { analyticsHeader: {
flexDirection: "row", flexDirection: "row",

View File

@ -5,9 +5,11 @@ import {
ScrollView, ScrollView,
RefreshControl, RefreshControl,
Image, Image,
Animated,
TouchableOpacity,
} from "react-native"; } from "react-native";
import { useUser } from "@clerk/clerk-expo"; import { useUser } from "@clerk/clerk-expo";
import { useState, useCallback, useEffect } from "react"; import { useState, useCallback, useEffect, useRef } from "react";
import { useFocusEffect } from "@react-navigation/native"; import { useFocusEffect } from "@react-navigation/native";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
@ -36,6 +38,11 @@ export default function HomeScreen() {
const [calories, setCalories] = useState(0); const [calories, setCalories] = useState(0);
const [waterIntake, setWaterIntake] = useState(0); const [waterIntake, setWaterIntake] = useState(0);
// Animation values
const streakPulse = useRef(new Animated.Value(1)).current;
const caloriesBounce = useRef(new Animated.Value(1)).current;
const waterBounce = useRef(new Animated.Value(1)).current;
// Refetch statistics when screen comes into focus // Refetch statistics when screen comes into focus
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@ -51,9 +58,46 @@ export default function HomeScreen() {
const getGreeting = () => { const getGreeting = () => {
const hour = new Date().getHours(); const hour = new Date().getHours();
if (hour < 12) return "Good Morning"; const greetings = {
if (hour < 18) return "Good Afternoon"; morning: [
return "Good Evening"; "Rise and Shine",
"Good Morning",
"Morning Champion",
"Ready to Crush It",
],
afternoon: [
"Keep It Going",
"Good Afternoon",
"Stay Strong",
"You're Doing Great",
],
evening: [
"Evening Warrior",
"Good Evening",
"Almost There",
"Finish Strong",
],
};
const randomGreeting = (arr: string[]) =>
arr[Math.floor(Math.random() * arr.length)];
if (hour < 12) return randomGreeting(greetings.morning);
if (hour < 18) return randomGreeting(greetings.afternoon);
return randomGreeting(greetings.evening);
};
const getMotivationalEmoji = () => {
const emojis = ["💪", "🔥", "⚡", "🎯", "🚀", "✨"];
return emojis[Math.floor(Math.random() * emojis.length)];
};
const getStreakBadge = (streak: number) => {
if (streak >= 30) return "🏆";
if (streak >= 14) return "🔥";
if (streak >= 7) return "⭐";
if (streak >= 3) return "🌟";
return "💫";
}; };
const handleSaveMeal = (meal: { const handleSaveMeal = (meal: {
@ -63,11 +107,39 @@ export default function HomeScreen() {
}) => { }) => {
setCalories((prev) => prev + meal.calories); setCalories((prev) => prev + meal.calories);
setTrackMealModalVisible(false); setTrackMealModalVisible(false);
// Bounce animation
Animated.sequence([
Animated.timing(caloriesBounce, {
toValue: 1.2,
duration: 150,
useNativeDriver: true,
}),
Animated.timing(caloriesBounce, {
toValue: 1,
duration: 150,
useNativeDriver: true,
}),
]).start();
}; };
const handleAddWater = (amount: number) => { const handleAddWater = (amount: number) => {
setWaterIntake((prev) => prev + amount); setWaterIntake((prev) => prev + amount);
setAddWaterModalVisible(false); setAddWaterModalVisible(false);
// Bounce animation
Animated.sequence([
Animated.timing(waterBounce, {
toValue: 1.2,
duration: 150,
useNativeDriver: true,
}),
Animated.timing(waterBounce, {
toValue: 1,
duration: 150,
useNativeDriver: true,
}),
]).start();
}; };
const handleResetCalories = () => { const handleResetCalories = () => {
@ -130,6 +202,9 @@ export default function HomeScreen() {
persistWater(); persistWater();
}, [waterIntake]); }, [waterIntake]);
const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0;
const currentStreak = statistics?.attendance.currentStreak || 0;
// Check for midnight reset // Check for midnight reset
useEffect(() => { useEffect(() => {
const checkAndResetIfNeeded = async () => { const checkAndResetIfNeeded = async () => {
@ -167,8 +242,29 @@ export default function HomeScreen() {
return () => clearTimeout(midnightTimer); return () => clearTimeout(midnightTimer);
}, []); }, []);
const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0; // Streak pulse animation
const currentStreak = statistics?.attendance.currentStreak || 0; useEffect(() => {
const pulse = Animated.loop(
Animated.sequence([
Animated.timing(streakPulse, {
toValue: 1.1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(streakPulse, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
]),
);
if (currentStreak >= 3) {
pulse.start();
}
return () => pulse.stop();
}, [currentStreak]);
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={[styles.container, { backgroundColor: colors.background }]}>
@ -184,20 +280,30 @@ export default function HomeScreen() {
> >
{/* Header Section */} {/* Header Section */}
<View style={styles.header}> <View style={styles.header}>
<View> <View style={styles.greetingContainer}>
<Text style={[typography.body, { color: colors.textSecondary }]}>
{getGreeting()},
</Text>
<Text <Text
style={[ style={[
typography.h1, typography.body,
{ color: colors.textPrimary, marginTop: 4 }, { color: colors.textSecondary, marginBottom: 4 },
]} ]}
>
{getGreeting()} {getMotivationalEmoji()}
</Text>
<Text
style={[typography.h1, { color: colors.textPrimary }]}
numberOfLines={1}
> >
{user?.firstName || "Athlete"} {user?.firstName || "Athlete"}
</Text> </Text>
{currentStreak >= 3 && (
<View style={styles.streakBadge}>
<Text style={styles.streakBadgeText}>
{getStreakBadge(currentStreak)} {currentStreak} Day Streak!
</Text>
</View>
)}
</View> </View>
<View> <TouchableOpacity activeOpacity={0.8}>
{user?.imageUrl ? ( {user?.imageUrl ? (
<Image source={{ uri: user.imageUrl }} style={styles.avatar} /> <Image source={{ uri: user.imageUrl }} style={styles.avatar} />
) : ( ) : (
@ -210,193 +316,273 @@ export default function HomeScreen() {
<Ionicons name="person" size={24} color={colors.white} /> <Ionicons name="person" size={24} color={colors.white} />
</View> </View>
)} )}
</View> </TouchableOpacity>
</View> </View>
{/* Daily Stats Card */} {/* Daily Stats Card - More Playful */}
<View style={styles.section}> <View style={styles.section}>
<MinimalCard variant="elevated"> <MinimalCard variant="elevated" style={styles.statsCard}>
<View style={styles.statsRow}> <View style={styles.statsRow}>
<View style={styles.statItem}> <View style={styles.statItem}>
<IconContainer <IconContainer
variant="colored" variant="colored"
backgroundColor={`${colors.info}20`} backgroundColor={colors.info}
size="lg"
> >
<Ionicons <Ionicons
name="checkmark-circle" name="checkmark-circle"
size={24} size={28}
color={colors.info} color={colors.white}
/> />
</IconContainer> </IconContainer>
<Text <Text
style={[ style={[
typography.stat, typography.stat,
{ color: colors.textPrimary, marginTop: 8 }, { color: colors.textPrimary, marginTop: 12 },
]} ]}
> >
{checkInsThisWeek} {checkInsThisWeek}
</Text> </Text>
<Text <Text
style={[typography.caption, { color: colors.textTertiary }]} style={[
typography.caption,
{ color: colors.textTertiary, textAlign: "center" },
]}
> >
This Week Check-ins{"\n"}This Week
</Text> </Text>
</View> </View>
<View <View
style={[styles.divider, { backgroundColor: colors.border }]} style={[
styles.divider,
{ backgroundColor: colors.borderLight },
]}
/> />
<View style={styles.statItem}> <Animated.View
style={[
styles.statItem,
{ transform: [{ scale: caloriesBounce }] },
]}
>
<IconContainer <IconContainer
variant="colored" variant="colored"
backgroundColor={`${colors.danger}20`} backgroundColor={colors.danger}
size="lg"
> >
<Ionicons name="flame" size={24} color={colors.danger} /> <Ionicons name="flame" size={28} color={colors.white} />
</IconContainer> </IconContainer>
<Text <Text
style={[ style={[
typography.stat, typography.stat,
{ color: colors.textPrimary, marginTop: 8 }, { color: colors.textPrimary, marginTop: 12 },
]} ]}
> >
{calories} {calories}
</Text> </Text>
<Text <Text
style={[typography.caption, { color: colors.textTertiary }]} style={[
typography.caption,
{ color: colors.textTertiary, textAlign: "center" },
]}
> >
Kcal Calories{"\n"}Tracked
</Text> </Text>
</View> </Animated.View>
<View <View
style={[styles.divider, { backgroundColor: colors.border }]} style={[
styles.divider,
{ backgroundColor: colors.borderLight },
]}
/> />
<View style={styles.statItem}> <Animated.View
style={[
styles.statItem,
{ transform: [{ scale: streakPulse }] },
]}
>
<IconContainer <IconContainer
variant="colored" variant="colored"
backgroundColor={`${colors.success}20`} backgroundColor={colors.warning}
size="lg"
> >
<Ionicons name="trophy" size={24} color={colors.success} /> <Text style={styles.emojiIcon}>
{getStreakBadge(currentStreak)}
</Text>
</IconContainer> </IconContainer>
<Text <Text
style={[ style={[
typography.stat, typography.stat,
{ color: colors.textPrimary, marginTop: 8 }, { color: colors.textPrimary, marginTop: 12 },
]} ]}
> >
{currentStreak} {currentStreak}
</Text> </Text>
<Text <Text
style={[typography.caption, { color: colors.textTertiary }]} style={[
typography.caption,
{ color: colors.textTertiary, textAlign: "center" },
]}
> >
Day Streak Day{"\n"}Streak
</Text> </Text>
</View> </Animated.View>
</View> </View>
</MinimalCard> </MinimalCard>
</View> </View>
{/* Quick Actions */} {/* Quick Actions - More Playful */}
<View style={styles.section}> <View style={styles.section}>
<SectionHeader title="Quick Actions" /> <SectionHeader
title="Quick Actions"
subtitle="What's your next move?"
/>
<View style={styles.actionGrid}> <View style={styles.actionGrid}>
<MinimalCard <TouchableOpacity
variant="default" onPress={() => console.log("Log workout tapped")}
onPress={() => { activeOpacity={0.7}
console.log("Log workout tapped"); style={[
}} styles.actionCard,
style={styles.actionCard} {
backgroundColor: `${colors.primary}15`,
borderColor: `${colors.primary}30`,
borderWidth: 2,
},
]}
> >
<IconContainer <IconContainer
variant="colored" variant="colored"
backgroundColor={`${colors.primary}20`} backgroundColor={colors.primary}
size="lg"
> >
<Ionicons name="barbell" size={24} color={colors.primary} /> <Ionicons name="barbell" size={28} color={colors.white} />
</IconContainer> </IconContainer>
<Text <Text
style={[ style={[
typography.h3, typography.h3,
{ color: colors.textPrimary, marginTop: 8 }, { color: colors.textPrimary, marginTop: 12 },
]} ]}
> >
Log Workout 💪 Workout
</Text> </Text>
</MinimalCard> </TouchableOpacity>
<MinimalCard <TouchableOpacity
variant="default"
onPress={() => setTrackMealModalVisible(true)} onPress={() => setTrackMealModalVisible(true)}
style={styles.actionCard} activeOpacity={0.7}
style={[
styles.actionCard,
{
backgroundColor: `${colors.success}15`,
borderColor: `${colors.success}30`,
borderWidth: 2,
},
]}
> >
<IconContainer <IconContainer
variant="colored" variant="colored"
backgroundColor={`${colors.success}20`} backgroundColor={colors.success}
size="lg"
> >
<Ionicons name="restaurant" size={24} color={colors.success} /> <Ionicons name="restaurant" size={28} color={colors.white} />
</IconContainer> </IconContainer>
<Text <Text
style={[ style={[
typography.h3, typography.h3,
{ color: colors.textPrimary, marginTop: 8 }, { color: colors.textPrimary, marginTop: 12 },
]} ]}
> >
Track Meal 🍽 Track Meal
</Text> </Text>
</MinimalCard> </TouchableOpacity>
<MinimalCard <TouchableOpacity
variant="default"
onPress={() => setAddWaterModalVisible(true)} onPress={() => setAddWaterModalVisible(true)}
style={styles.actionCard} activeOpacity={0.7}
style={[
styles.actionCard,
{
backgroundColor: `${colors.info}15`,
borderColor: `${colors.info}30`,
borderWidth: 2,
},
]}
> >
<IconContainer <Animated.View style={{ transform: [{ scale: waterBounce }] }}>
variant="colored" <IconContainer
backgroundColor={`${colors.info}20`} variant="colored"
> backgroundColor={colors.info}
<Ionicons name="water" size={24} color={colors.info} /> size="lg"
</IconContainer> >
<Ionicons name="water" size={28} color={colors.white} />
</IconContainer>
</Animated.View>
<Text <Text
style={[ style={[
typography.h3, typography.h3,
{ color: colors.textPrimary, marginTop: 8 }, { color: colors.textPrimary, marginTop: 12 },
]} ]}
> >
Add Water 💧 Add Water
</Text> </Text>
</MinimalCard> </TouchableOpacity>
<MinimalCard <TouchableOpacity
variant="default"
onPress={() => setScanFoodModalVisible(true)} onPress={() => setScanFoodModalVisible(true)}
style={styles.actionCard} activeOpacity={0.7}
style={[
styles.actionCard,
{
backgroundColor: `${colors.accent}15`,
borderColor: `${colors.accent}30`,
borderWidth: 2,
},
]}
> >
<IconContainer <IconContainer
variant="colored" variant="colored"
backgroundColor={`${colors.accent}20`} backgroundColor={colors.accent}
size="lg"
> >
<Ionicons name="scan" size={24} color={colors.accent} /> <Ionicons name="scan" size={28} color={colors.white} />
</IconContainer> </IconContainer>
<Text <Text
style={[ style={[
typography.h3, typography.h3,
{ color: colors.textPrimary, marginTop: 8 }, { color: colors.textPrimary, marginTop: 12 },
]} ]}
> >
Scan Food 📱 Scan Food
</Text> </Text>
</MinimalCard> </TouchableOpacity>
</View> </View>
</View> </View>
{/* Nutrition Progress */} {/* Nutrition Progress - More Playful */}
<View style={styles.section}> <View style={styles.section}>
<SectionHeader title="Nutrition" /> <SectionHeader
<MinimalCard variant="default"> title="Today's Nutrition"
subtitle={
calories >= CALORIE_GOAL
? "Goal smashed! 🎉"
: calories >= CALORIE_GOAL * 0.75
? "Almost there! Keep going 💪"
: "Let's fuel that body! 🔥"
}
/>
<MinimalCard
variant="bordered"
style={{
backgroundColor: `${colors.danger}08`,
borderRadius: 12,
}}
>
<View style={styles.progressHeader}> <View style={styles.progressHeader}>
<View style={styles.progressLabelRow}> <View style={styles.progressLabelRow}>
<Ionicons name="flame" size={20} color={colors.textSecondary} /> <Text style={styles.emojiIcon}>🔥</Text>
<Text <Text
style={[ style={[
typography.h3, typography.h3,
@ -407,42 +593,61 @@ export default function HomeScreen() {
</Text> </Text>
</View> </View>
<Text <Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]} style={[
typography.bodyEmphasis,
{ color: colors.danger, fontWeight: "700" },
]}
> >
{calories} / {CALORIE_GOAL} {calories} / {CALORIE_GOAL}
</Text> </Text>
</View> </View>
<ProgressBar <ProgressBar
progress={calories / CALORIE_GOAL} progress={Math.min(calories / CALORIE_GOAL, 1)}
color={colors.danger} color={colors.danger}
style={{ marginTop: 12 }} style={{ marginTop: 12 }}
/> />
</MinimalCard> </MinimalCard>
</View> </View>
{/* Hydration Progress */} {/* Hydration Progress - More Playful */}
<View style={styles.section}> <View style={styles.section}>
<MinimalCard variant="default"> <MinimalCard
variant="bordered"
style={{
backgroundColor: `${colors.info}08`,
borderRadius: 12,
}}
>
<View style={styles.progressHeader}> <View style={styles.progressHeader}>
<View style={styles.progressLabelRow}> <View style={styles.progressLabelRow}>
<Ionicons name="water" size={20} color={colors.textSecondary} /> <Text style={styles.emojiIcon}>💧</Text>
<Text <View style={{ marginLeft: 8 }}>
style={[ <Text style={[typography.h3, { color: colors.textPrimary }]}>
typography.h3, Hydration
{ color: colors.textPrimary, marginLeft: 8 }, </Text>
]} <Text
> style={[
Hydration typography.caption,
</Text> { color: colors.textTertiary, marginTop: 2 },
]}
>
{waterIntake >= WATER_GOAL
? "Perfectly hydrated! 🌊"
: "Stay hydrated, champ! 💪"}
</Text>
</View>
</View> </View>
<Text <Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]} style={[
typography.bodyEmphasis,
{ color: colors.info, fontWeight: "700" },
]}
> >
{waterIntake} / {WATER_GOAL} ml {waterIntake} / {WATER_GOAL} ml
</Text> </Text>
</View> </View>
<ProgressBar <ProgressBar
progress={waterIntake / WATER_GOAL} progress={Math.min(waterIntake / WATER_GOAL, 1)}
color={colors.info} color={colors.info}
style={{ marginTop: 12 }} style={{ marginTop: 12 }}
/> />
@ -557,14 +762,33 @@ const styles = StyleSheet.create({
header: { header: {
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "flex-start",
paddingHorizontal: 24, paddingHorizontal: 24,
marginBottom: 24, marginBottom: 24,
}, },
greetingContainer: {
flex: 1,
marginRight: 16,
},
streakBadge: {
marginTop: 8,
paddingHorizontal: 12,
paddingVertical: 6,
backgroundColor: "rgba(136, 192, 208, 0.15)",
borderRadius: 999,
alignSelf: "flex-start",
},
streakBadgeText: {
fontSize: 13,
fontWeight: "600",
color: "#88C0D0",
},
avatar: { avatar: {
width: 56, width: 56,
height: 56, height: 56,
borderRadius: 28, borderRadius: 28,
borderWidth: 3,
borderColor: "#88C0D0",
}, },
placeholderAvatar: { placeholderAvatar: {
width: 56, width: 56,
@ -572,15 +796,21 @@ const styles = StyleSheet.create({
borderRadius: 28, borderRadius: 28,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
borderWidth: 3,
borderColor: "#88C0D0",
}, },
section: { section: {
paddingHorizontal: 24, paddingHorizontal: 24,
marginBottom: 24, marginBottom: 24,
}, },
statsCard: {
borderRadius: 20,
},
statsRow: { statsRow: {
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
paddingVertical: 8,
}, },
statItem: { statItem: {
alignItems: "center", alignItems: "center",
@ -588,7 +818,10 @@ const styles = StyleSheet.create({
}, },
divider: { divider: {
width: 1, width: 1,
height: 60, height: 70,
},
emojiIcon: {
fontSize: 28,
}, },
actionGrid: { actionGrid: {
flexDirection: "row", flexDirection: "row",
@ -598,7 +831,8 @@ const styles = StyleSheet.create({
actionCard: { actionCard: {
width: "48%", width: "48%",
alignItems: "center", alignItems: "center",
paddingVertical: 20, paddingVertical: 24,
borderRadius: 20,
}, },
progressHeader: { progressHeader: {
flexDirection: "row", flexDirection: "row",

View File

@ -132,7 +132,7 @@ export default function RecommendationsScreen() {
<View style={styles.header}> <View style={styles.header}>
<View> <View>
<Text style={[typography.h1, { color: colors.textPrimary }]}> <Text style={[typography.h1, { color: colors.textPrimary }]}>
AI Recommendations AI Recommendations
</Text> </Text>
<Text <Text
style={[ style={[
@ -140,7 +140,9 @@ export default function RecommendationsScreen() {
{ color: colors.textSecondary, marginTop: 4 }, { color: colors.textSecondary, marginTop: 4 },
]} ]}
> >
Personalized fitness & nutrition plans {recommendations.length === 0
? "Let's create your perfect plan! 🚀"
: `${recommendations.length} plan${recommendations.length !== 1 ? "s" : ""} ready for you! 💪`}
</Text> </Text>
</View> </View>
<TouchableOpacity <TouchableOpacity
@ -172,7 +174,7 @@ export default function RecommendationsScreen() {
{/* Generate Button */} {/* Generate Button */}
<View style={styles.section}> <View style={styles.section}>
<MinimalButton <MinimalButton
title="Generate New Plan" title="🎯 Generate New Plan"
onPress={handleGenerateRecommendation} onPress={handleGenerateRecommendation}
variant="primary" variant="primary"
size="lg" size="lg"
@ -185,7 +187,7 @@ export default function RecommendationsScreen() {
{/* Recommendations List */} {/* Recommendations List */}
<View style={styles.section}> <View style={styles.section}>
<SectionHeader <SectionHeader
title="Your Plans" title="💡 Your Plans"
subtitle={ subtitle={
recommendations.length > 0 recommendations.length > 0
? `${recommendations.length} active plan${recommendations.length !== 1 ? "s" : ""}` ? `${recommendations.length} active plan${recommendations.length !== 1 ? "s" : ""}`
@ -194,19 +196,9 @@ export default function RecommendationsScreen() {
/> />
{recommendations.length === 0 ? ( {recommendations.length === 0 ? (
<MinimalCard variant="default"> <MinimalCard variant="default" style={{ borderRadius: 20 }}>
<View style={styles.emptyState}> <View style={styles.emptyState}>
<IconContainer <Text style={{ fontSize: 64 }}>🤖</Text>
variant="colored"
backgroundColor={`${colors.accent}15`}
size="lg"
>
<Ionicons
name="sparkles-outline"
size={32}
color={colors.accent}
/>
</IconContainer>
<Text <Text
style={[ style={[
typography.bodyEmphasis, typography.bodyEmphasis,
@ -226,7 +218,7 @@ export default function RecommendationsScreen() {
]} ]}
> >
Tap "Generate New Plan" to get personalized AI-powered fitness Tap "Generate New Plan" to get personalized AI-powered fitness
and nutrition recommendations based on your profile and goals. and nutrition recommendations! 🎯
</Text> </Text>
</View> </View>
</MinimalCard> </MinimalCard>
@ -258,7 +250,7 @@ function RecommendationCard({ recommendation }: RecommendationCardProps) {
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
return ( return (
<MinimalCard variant="elevated"> <MinimalCard variant="bordered" style={{ borderRadius: 20 }}>
{/* Header */} {/* Header */}
<TouchableOpacity <TouchableOpacity
onPress={() => setExpanded(!expanded)} onPress={() => setExpanded(!expanded)}
@ -278,7 +270,7 @@ function RecommendationCard({ recommendation }: RecommendationCardProps) {
</IconContainer> </IconContainer>
<View style={{ marginLeft: 12 }}> <View style={{ marginLeft: 12 }}>
<Text style={[typography.h3, { color: colors.textPrimary }]}> <Text style={[typography.h3, { color: colors.textPrimary }]}>
AI Fitness Plan AI Fitness Plan
</Text> </Text>
<Text style={[typography.caption, { color: colors.textTertiary }]}> <Text style={[typography.caption, { color: colors.textTertiary }]}>
{new Date(recommendation.generatedAt).toLocaleDateString()} {new Date(recommendation.generatedAt).toLocaleDateString()}
@ -320,7 +312,7 @@ function RecommendationCard({ recommendation }: RecommendationCardProps) {
{ color: colors.textPrimary, marginLeft: 8 }, { color: colors.textPrimary, marginLeft: 8 },
]} ]}
> >
Activity Plan 💪 Activity Plan
</Text> </Text>
</View> </View>
<Text <Text
@ -348,7 +340,7 @@ function RecommendationCard({ recommendation }: RecommendationCardProps) {
{ color: colors.textPrimary, marginLeft: 8 }, { color: colors.textPrimary, marginLeft: 8 },
]} ]}
> >
Diet Plan 🍽 Diet Plan
</Text> </Text>
</View> </View>
<Text <Text
@ -439,7 +431,7 @@ const styles = StyleSheet.create({
}, },
planSection: { planSection: {
padding: 16, padding: 16,
borderRadius: 12, borderRadius: 16,
}, },
planHeader: { planHeader: {
flexDirection: "row", flexDirection: "row",

View File

@ -1,5 +1,12 @@
import React from "react"; import React, { useEffect, useRef } from "react";
import { View, Text, StyleSheet, TouchableOpacity, Alert } from "react-native"; import {
View,
Text,
StyleSheet,
TouchableOpacity,
Alert,
Animated,
} from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import type { FitnessGoal } from "../services/fitnessGoals"; import type { FitnessGoal } from "../services/fitnessGoals";
import { useTheme } from "../contexts/ThemeContext"; import { useTheme } from "../contexts/ThemeContext";
@ -24,6 +31,49 @@ export function GoalProgressCard({
const { colors, typography } = useTheme(); const { colors, typography } = useTheme();
const isCompleted = goal.status === "completed"; const isCompleted = goal.status === "completed";
const progress = (goal.progress || 0) / 100; // Convert to 0-1 scale const progress = (goal.progress || 0) / 100; // Convert to 0-1 scale
const scaleAnim = useRef(new Animated.Value(1)).current;
// Celebration animation when goal is completed
useEffect(() => {
if (isCompleted) {
Animated.sequence([
Animated.spring(scaleAnim, {
toValue: 1.05,
friction: 3,
tension: 40,
useNativeDriver: true,
}),
Animated.spring(scaleAnim, {
toValue: 1,
friction: 3,
tension: 40,
useNativeDriver: true,
}),
]).start();
}
}, [isCompleted]);
const handleComplete = () => {
if (onComplete) {
// Trigger celebration animation
Animated.sequence([
Animated.spring(scaleAnim, {
toValue: 1.1,
friction: 3,
tension: 40,
useNativeDriver: true,
}),
Animated.spring(scaleAnim, {
toValue: 1,
friction: 3,
tension: 40,
useNativeDriver: true,
}),
]).start(() => {
onComplete();
});
}
};
// Calculate days remaining // Calculate days remaining
const daysRemaining = goal.targetDate const daysRemaining = goal.targetDate
@ -71,160 +121,169 @@ export function GoalProgressCard({
}; };
return ( return (
<TouchableOpacity onPress={onPress} activeOpacity={0.7}> <Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
<MinimalCard <TouchableOpacity onPress={onPress} activeOpacity={0.7}>
variant="bordered" <MinimalCard
style={[ variant="bordered"
styles.card, style={[
isCompleted && { styles.card,
backgroundColor: colors.overlayLight, isCompleted && {
}, backgroundColor: colors.overlayLight,
]} },
> ]}
{/* Header */} >
<View style={styles.header}> {/* Header */}
<View style={styles.titleRow}> <View style={styles.header}>
<IconContainer <View style={styles.titleRow}>
variant="colored" <IconContainer
backgroundColor={ variant="colored"
isCompleted ? colors.success : getPriorityColor(goal.priority) backgroundColor={
} isCompleted ? colors.success : getPriorityColor(goal.priority)
> }
<Ionicons
name={getGoalTypeIcon(goal.goalType) as any}
size={20}
color={colors.white}
/>
</IconContainer>
<View style={styles.titleContainer}>
<Text
style={[
typography.h3,
{ color: colors.textPrimary },
isCompleted && {
color: colors.textSecondary,
textDecorationLine: "line-through",
},
]}
> >
{goal.title} <Ionicons
</Text> name={getGoalTypeIcon(goal.goalType) as any}
{goal.description && ( size={20}
color={colors.white}
/>
</IconContainer>
<View style={styles.titleContainer}>
<Text <Text
style={[ style={[
typography.caption, typography.h3,
{ color: colors.textTertiary, marginTop: 2 }, { color: colors.textPrimary },
isCompleted && {
color: colors.textSecondary,
textDecorationLine: "line-through",
},
]} ]}
numberOfLines={2}
> >
{goal.description} {goal.title}
</Text> </Text>
{goal.description && (
<Text
style={[
typography.caption,
{ color: colors.textTertiary, marginTop: 2 },
]}
numberOfLines={2}
>
{goal.description}
</Text>
)}
</View>
</View>
{/* Action Buttons */}
<View style={styles.actions}>
{!isCompleted && onComplete && (
<TouchableOpacity
onPress={handleComplete}
style={styles.actionButton}
>
<Ionicons
name="checkmark-circle-outline"
size={24}
color={colors.success}
/>
</TouchableOpacity>
)}
{onDelete && (
<TouchableOpacity
onPress={handleDelete}
style={styles.actionButton}
>
<Ionicons
name="trash-outline"
size={22}
color={colors.danger}
/>
</TouchableOpacity>
)} )}
</View> </View>
</View> </View>
{/* Action Buttons */} {/* Progress Section */}
<View style={styles.actions}> {goal.targetValue && (
{!isCompleted && onComplete && ( <View style={styles.progressSection}>
<TouchableOpacity <View style={styles.progressInfo}>
onPress={onComplete} <Text
style={styles.actionButton} style={[typography.caption, { color: colors.textSecondary }]}
> >
<Ionicons {goal.currentValue || 0} / {goal.targetValue}{" "}
name="checkmark-circle-outline" {goal.unit || ""}
size={24} </Text>
color={colors.success} <Text
/> style={[
</TouchableOpacity> typography.bodyEmphasis,
)} {
{onDelete && ( color: isCompleted ? colors.success : colors.primary,
<TouchableOpacity },
onPress={handleDelete} ]}
style={styles.actionButton} >
> {(progress * 100).toFixed(0)}%
<Ionicons </Text>
name="trash-outline" </View>
size={22}
color={colors.danger}
/>
</TouchableOpacity>
)}
</View>
</View>
{/* Progress Section */} <ProgressBar
{goal.targetValue && ( progress={progress}
<View style={styles.progressSection}> color={
<View style={styles.progressInfo}> isCompleted ? colors.success : getPriorityColor(goal.priority)
<Text }
style={[typography.caption, { color: colors.textSecondary }]} />
> </View>
{goal.currentValue || 0} / {goal.targetValue} {goal.unit || ""} )}
</Text>
{/* Footer */}
<View style={styles.footer}>
{isCompleted ? (
<Badge variant="success" label="COMPLETED" />
) : (
<View>
{goal.priority === "high" && (
<Badge variant="danger" label={goal.priority.toUpperCase()} />
)}
{goal.priority === "medium" && (
<Badge
variant="warning"
label={goal.priority.toUpperCase()}
/>
)}
{goal.priority === "low" && (
<Badge
variant="success"
label={goal.priority.toUpperCase()}
/>
)}
</View>
)}
{daysRemaining !== null && !isCompleted && (
<Text <Text
style={[ style={[
typography.bodyEmphasis, typography.caption,
{ {
color: isCompleted ? colors.success : colors.primary, color:
daysRemaining < 0 ? colors.danger : colors.textTertiary,
}, },
]} ]}
> >
{(progress * 100).toFixed(0)}% {daysRemaining < 0
? `${Math.abs(daysRemaining)} days overdue`
: `${daysRemaining} days remaining`}
</Text> </Text>
</View> )}
<ProgressBar {isCompleted && goal.completedDate && (
progress={progress} <Text style={[typography.caption, { color: colors.success }]}>
color={ Completed {new Date(goal.completedDate).toLocaleDateString()}
isCompleted ? colors.success : getPriorityColor(goal.priority) </Text>
} )}
/>
</View> </View>
)} </MinimalCard>
</TouchableOpacity>
{/* Footer */} </Animated.View>
<View style={styles.footer}>
{isCompleted ? (
<Badge variant="success" label="COMPLETED" />
) : (
<View>
{goal.priority === "high" && (
<Badge variant="danger" label={goal.priority.toUpperCase()} />
)}
{goal.priority === "medium" && (
<Badge variant="warning" label={goal.priority.toUpperCase()} />
)}
{goal.priority === "low" && (
<Badge variant="success" label={goal.priority.toUpperCase()} />
)}
</View>
)}
{daysRemaining !== null && !isCompleted && (
<Text
style={[
typography.caption,
{
color:
daysRemaining < 0 ? colors.danger : colors.textTertiary,
},
]}
>
{daysRemaining < 0
? `${Math.abs(daysRemaining)} days overdue`
: `${daysRemaining} days remaining`}
</Text>
)}
{isCompleted && goal.completedDate && (
<Text style={[typography.caption, { color: colors.success }]}>
Completed {new Date(goal.completedDate).toLocaleDateString()}
</Text>
)}
</View>
</MinimalCard>
</TouchableOpacity>
); );
} }