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 */}
<View style={styles.header}>
<Text style={[typography.h1, { color: colors.textPrimary }]}>
Attendance
📍 Attendance
</Text>
<Text
style={[
@ -112,14 +112,18 @@ export default function AttendanceScreen() {
{ 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>
</View>
{/* Check In/Out Section */}
<View style={styles.section}>
{activeCheckIn ? (
<MinimalCard variant="elevated" style={styles.activeCard}>
<MinimalCard variant="bordered" style={styles.activeCard}>
<View style={styles.activeHeader}>
<View style={styles.activeHeaderLeft}>
<IconContainer
@ -135,7 +139,7 @@ export default function AttendanceScreen() {
</IconContainer>
<View style={{ marginLeft: 12 }}>
<Text style={[typography.h3, { color: colors.textPrimary }]}>
Currently Checked In
Currently Checked In
</Text>
<Text
style={[
@ -165,7 +169,7 @@ export default function AttendanceScreen() {
</MinimalCard>
) : (
<MinimalButton
title="Check In"
title="💪 Check In"
onPress={handleCheckIn}
variant="primary"
size="lg"
@ -175,29 +179,19 @@ export default function AttendanceScreen() {
{/* Attendance Calendar */}
<View style={styles.section}>
<SectionHeader title="Calendar" />
<MinimalCard variant="default">
<SectionHeader title="📅 Calendar" />
<MinimalCard variant="default" style={{ borderRadius: 20 }}>
<AttendanceCalendar attendanceRecords={history} />
</MinimalCard>
</View>
{/* Recent History */}
<View style={styles.section}>
<SectionHeader title="Recent History" />
<SectionHeader title="📊 Recent History" />
{history.length === 0 ? (
<MinimalCard variant="default">
<MinimalCard variant="default" style={{ borderRadius: 20 }}>
<View style={styles.emptyState}>
<IconContainer
variant="colored"
backgroundColor={`${colors.primary}15`}
size="lg"
>
<Ionicons
name="calendar-outline"
size={32}
color={colors.primary}
/>
</IconContainer>
<Text style={{ fontSize: 64 }}>📍</Text>
<Text
style={[
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>
</View>
</MinimalCard>
@ -232,7 +226,11 @@ export default function AttendanceScreen() {
: null;
return (
<MinimalCard key={index} variant="default">
<MinimalCard
key={index}
variant="bordered"
style={{ borderRadius: 16 }}
>
<View style={styles.historyItem}>
<View style={styles.historyLeft}>
<IconContainer
@ -315,6 +313,7 @@ const styles = StyleSheet.create({
},
activeCard: {
padding: 20,
borderRadius: 20,
},
activeHeader: {
flexDirection: "row",

View File

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

View File

@ -5,9 +5,11 @@ import {
ScrollView,
RefreshControl,
Image,
Animated,
TouchableOpacity,
} from "react-native";
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 AsyncStorage from "@react-native-async-storage/async-storage";
import { Ionicons } from "@expo/vector-icons";
@ -36,6 +38,11 @@ export default function HomeScreen() {
const [calories, setCalories] = 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
useFocusEffect(
useCallback(() => {
@ -51,9 +58,46 @@ export default function HomeScreen() {
const getGreeting = () => {
const hour = new Date().getHours();
if (hour < 12) return "Good Morning";
if (hour < 18) return "Good Afternoon";
return "Good Evening";
const greetings = {
morning: [
"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: {
@ -63,11 +107,39 @@ export default function HomeScreen() {
}) => {
setCalories((prev) => prev + meal.calories);
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) => {
setWaterIntake((prev) => prev + amount);
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 = () => {
@ -130,6 +202,9 @@ export default function HomeScreen() {
persistWater();
}, [waterIntake]);
const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0;
const currentStreak = statistics?.attendance.currentStreak || 0;
// Check for midnight reset
useEffect(() => {
const checkAndResetIfNeeded = async () => {
@ -167,8 +242,29 @@ export default function HomeScreen() {
return () => clearTimeout(midnightTimer);
}, []);
const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0;
const currentStreak = statistics?.attendance.currentStreak || 0;
// Streak pulse animation
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 (
<View style={[styles.container, { backgroundColor: colors.background }]}>
@ -184,20 +280,30 @@ export default function HomeScreen() {
>
{/* Header Section */}
<View style={styles.header}>
<View>
<Text style={[typography.body, { color: colors.textSecondary }]}>
{getGreeting()},
</Text>
<View style={styles.greetingContainer}>
<Text
style={[
typography.h1,
{ color: colors.textPrimary, marginTop: 4 },
typography.body,
{ color: colors.textSecondary, marginBottom: 4 },
]}
>
{getGreeting()} {getMotivationalEmoji()}
</Text>
<Text
style={[typography.h1, { color: colors.textPrimary }]}
numberOfLines={1}
>
{user?.firstName || "Athlete"}
</Text>
{currentStreak >= 3 && (
<View style={styles.streakBadge}>
<Text style={styles.streakBadgeText}>
{getStreakBadge(currentStreak)} {currentStreak} Day Streak!
</Text>
</View>
)}
</View>
<View>
<TouchableOpacity activeOpacity={0.8}>
{user?.imageUrl ? (
<Image source={{ uri: user.imageUrl }} style={styles.avatar} />
) : (
@ -210,193 +316,273 @@ export default function HomeScreen() {
<Ionicons name="person" size={24} color={colors.white} />
</View>
)}
</View>
</TouchableOpacity>
</View>
{/* Daily Stats Card */}
{/* Daily Stats Card - More Playful */}
<View style={styles.section}>
<MinimalCard variant="elevated">
<MinimalCard variant="elevated" style={styles.statsCard}>
<View style={styles.statsRow}>
<View style={styles.statItem}>
<IconContainer
variant="colored"
backgroundColor={`${colors.info}20`}
backgroundColor={colors.info}
size="lg"
>
<Ionicons
name="checkmark-circle"
size={24}
color={colors.info}
size={28}
color={colors.white}
/>
</IconContainer>
<Text
style={[
typography.stat,
{ color: colors.textPrimary, marginTop: 8 },
{ color: colors.textPrimary, marginTop: 12 },
]}
>
{checkInsThisWeek}
</Text>
<Text
style={[typography.caption, { color: colors.textTertiary }]}
style={[
typography.caption,
{ color: colors.textTertiary, textAlign: "center" },
]}
>
This Week
Check-ins{"\n"}This Week
</Text>
</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
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>
<Text
style={[
typography.stat,
{ color: colors.textPrimary, marginTop: 8 },
{ color: colors.textPrimary, marginTop: 12 },
]}
>
{calories}
</Text>
<Text
style={[typography.caption, { color: colors.textTertiary }]}
style={[
typography.caption,
{ color: colors.textTertiary, textAlign: "center" },
]}
>
Kcal
Calories{"\n"}Tracked
</Text>
</View>
</Animated.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
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>
<Text
style={[
typography.stat,
{ color: colors.textPrimary, marginTop: 8 },
{ color: colors.textPrimary, marginTop: 12 },
]}
>
{currentStreak}
</Text>
<Text
style={[typography.caption, { color: colors.textTertiary }]}
style={[
typography.caption,
{ color: colors.textTertiary, textAlign: "center" },
]}
>
Day Streak
Day{"\n"}Streak
</Text>
</View>
</Animated.View>
</View>
</MinimalCard>
</View>
{/* Quick Actions */}
{/* Quick Actions - More Playful */}
<View style={styles.section}>
<SectionHeader title="Quick Actions" />
<SectionHeader
title="Quick Actions"
subtitle="What's your next move?"
/>
<View style={styles.actionGrid}>
<MinimalCard
variant="default"
onPress={() => {
console.log("Log workout tapped");
}}
style={styles.actionCard}
<TouchableOpacity
onPress={() => console.log("Log workout tapped")}
activeOpacity={0.7}
style={[
styles.actionCard,
{
backgroundColor: `${colors.primary}15`,
borderColor: `${colors.primary}30`,
borderWidth: 2,
},
]}
>
<IconContainer
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>
<Text
style={[
typography.h3,
{ color: colors.textPrimary, marginTop: 8 },
{ color: colors.textPrimary, marginTop: 12 },
]}
>
Log Workout
💪 Workout
</Text>
</MinimalCard>
</TouchableOpacity>
<MinimalCard
variant="default"
<TouchableOpacity
onPress={() => setTrackMealModalVisible(true)}
style={styles.actionCard}
activeOpacity={0.7}
style={[
styles.actionCard,
{
backgroundColor: `${colors.success}15`,
borderColor: `${colors.success}30`,
borderWidth: 2,
},
]}
>
<IconContainer
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>
<Text
style={[
typography.h3,
{ color: colors.textPrimary, marginTop: 8 },
{ color: colors.textPrimary, marginTop: 12 },
]}
>
Track Meal
🍽 Track Meal
</Text>
</MinimalCard>
</TouchableOpacity>
<MinimalCard
variant="default"
<TouchableOpacity
onPress={() => setAddWaterModalVisible(true)}
style={styles.actionCard}
activeOpacity={0.7}
style={[
styles.actionCard,
{
backgroundColor: `${colors.info}15`,
borderColor: `${colors.info}30`,
borderWidth: 2,
},
]}
>
<IconContainer
variant="colored"
backgroundColor={`${colors.info}20`}
>
<Ionicons name="water" size={24} color={colors.info} />
</IconContainer>
<Animated.View style={{ transform: [{ scale: waterBounce }] }}>
<IconContainer
variant="colored"
backgroundColor={colors.info}
size="lg"
>
<Ionicons name="water" size={28} color={colors.white} />
</IconContainer>
</Animated.View>
<Text
style={[
typography.h3,
{ color: colors.textPrimary, marginTop: 8 },
{ color: colors.textPrimary, marginTop: 12 },
]}
>
Add Water
💧 Add Water
</Text>
</MinimalCard>
</TouchableOpacity>
<MinimalCard
variant="default"
<TouchableOpacity
onPress={() => setScanFoodModalVisible(true)}
style={styles.actionCard}
activeOpacity={0.7}
style={[
styles.actionCard,
{
backgroundColor: `${colors.accent}15`,
borderColor: `${colors.accent}30`,
borderWidth: 2,
},
]}
>
<IconContainer
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>
<Text
style={[
typography.h3,
{ color: colors.textPrimary, marginTop: 8 },
{ color: colors.textPrimary, marginTop: 12 },
]}
>
Scan Food
📱 Scan Food
</Text>
</MinimalCard>
</TouchableOpacity>
</View>
</View>
{/* Nutrition Progress */}
{/* Nutrition Progress - More Playful */}
<View style={styles.section}>
<SectionHeader title="Nutrition" />
<MinimalCard variant="default">
<SectionHeader
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.progressLabelRow}>
<Ionicons name="flame" size={20} color={colors.textSecondary} />
<Text style={styles.emojiIcon}>🔥</Text>
<Text
style={[
typography.h3,
@ -407,42 +593,61 @@ export default function HomeScreen() {
</Text>
</View>
<Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
style={[
typography.bodyEmphasis,
{ color: colors.danger, fontWeight: "700" },
]}
>
{calories} / {CALORIE_GOAL}
</Text>
</View>
<ProgressBar
progress={calories / CALORIE_GOAL}
progress={Math.min(calories / CALORIE_GOAL, 1)}
color={colors.danger}
style={{ marginTop: 12 }}
/>
</MinimalCard>
</View>
{/* Hydration Progress */}
{/* Hydration Progress - More Playful */}
<View style={styles.section}>
<MinimalCard variant="default">
<MinimalCard
variant="bordered"
style={{
backgroundColor: `${colors.info}08`,
borderRadius: 12,
}}
>
<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>
<Text style={styles.emojiIcon}>💧</Text>
<View style={{ marginLeft: 8 }}>
<Text style={[typography.h3, { color: colors.textPrimary }]}>
Hydration
</Text>
<Text
style={[
typography.caption,
{ color: colors.textTertiary, marginTop: 2 },
]}
>
{waterIntake >= WATER_GOAL
? "Perfectly hydrated! 🌊"
: "Stay hydrated, champ! 💪"}
</Text>
</View>
</View>
<Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
style={[
typography.bodyEmphasis,
{ color: colors.info, fontWeight: "700" },
]}
>
{waterIntake} / {WATER_GOAL} ml
</Text>
</View>
<ProgressBar
progress={waterIntake / WATER_GOAL}
progress={Math.min(waterIntake / WATER_GOAL, 1)}
color={colors.info}
style={{ marginTop: 12 }}
/>
@ -557,14 +762,33 @@ const styles = StyleSheet.create({
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
alignItems: "flex-start",
paddingHorizontal: 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: {
width: 56,
height: 56,
borderRadius: 28,
borderWidth: 3,
borderColor: "#88C0D0",
},
placeholderAvatar: {
width: 56,
@ -572,15 +796,21 @@ const styles = StyleSheet.create({
borderRadius: 28,
justifyContent: "center",
alignItems: "center",
borderWidth: 3,
borderColor: "#88C0D0",
},
section: {
paddingHorizontal: 24,
marginBottom: 24,
},
statsCard: {
borderRadius: 20,
},
statsRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 8,
},
statItem: {
alignItems: "center",
@ -588,7 +818,10 @@ const styles = StyleSheet.create({
},
divider: {
width: 1,
height: 60,
height: 70,
},
emojiIcon: {
fontSize: 28,
},
actionGrid: {
flexDirection: "row",
@ -598,7 +831,8 @@ const styles = StyleSheet.create({
actionCard: {
width: "48%",
alignItems: "center",
paddingVertical: 20,
paddingVertical: 24,
borderRadius: 20,
},
progressHeader: {
flexDirection: "row",

View File

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

View File

@ -1,5 +1,12 @@
import React from "react";
import { View, Text, StyleSheet, TouchableOpacity, Alert } from "react-native";
import React, { useEffect, useRef } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Alert,
Animated,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import type { FitnessGoal } from "../services/fitnessGoals";
import { useTheme } from "../contexts/ThemeContext";
@ -24,6 +31,49 @@ export function GoalProgressCard({
const { colors, typography } = useTheme();
const isCompleted = goal.status === "completed";
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
const daysRemaining = goal.targetDate
@ -71,160 +121,169 @@ export function GoalProgressCard({
};
return (
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
<MinimalCard
variant="bordered"
style={[
styles.card,
isCompleted && {
backgroundColor: colors.overlayLight,
},
]}
>
{/* Header */}
<View style={styles.header}>
<View style={styles.titleRow}>
<IconContainer
variant="colored"
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",
},
]}
<Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
<MinimalCard
variant="bordered"
style={[
styles.card,
isCompleted && {
backgroundColor: colors.overlayLight,
},
]}
>
{/* Header */}
<View style={styles.header}>
<View style={styles.titleRow}>
<IconContainer
variant="colored"
backgroundColor={
isCompleted ? colors.success : getPriorityColor(goal.priority)
}
>
{goal.title}
</Text>
{goal.description && (
<Ionicons
name={getGoalTypeIcon(goal.goalType) as any}
size={20}
color={colors.white}
/>
</IconContainer>
<View style={styles.titleContainer}>
<Text
style={[
typography.caption,
{ color: colors.textTertiary, marginTop: 2 },
typography.h3,
{ color: colors.textPrimary },
isCompleted && {
color: colors.textSecondary,
textDecorationLine: "line-through",
},
]}
numberOfLines={2}
>
{goal.description}
{goal.title}
</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>
{/* Action Buttons */}
<View style={styles.actions}>
{!isCompleted && onComplete && (
<TouchableOpacity
onPress={onComplete}
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>
{/* Progress Section */}
{goal.targetValue && (
<View style={styles.progressSection}>
<View style={styles.progressInfo}>
<Text
style={[typography.caption, { color: colors.textSecondary }]}
>
{goal.currentValue || 0} / {goal.targetValue}{" "}
{goal.unit || ""}
</Text>
<Text
style={[
typography.bodyEmphasis,
{
color: isCompleted ? colors.success : colors.primary,
},
]}
>
{(progress * 100).toFixed(0)}%
</Text>
</View>
{/* Progress Section */}
{goal.targetValue && (
<View style={styles.progressSection}>
<View style={styles.progressInfo}>
<Text
style={[typography.caption, { color: colors.textSecondary }]}
>
{goal.currentValue || 0} / {goal.targetValue} {goal.unit || ""}
</Text>
<ProgressBar
progress={progress}
color={
isCompleted ? colors.success : getPriorityColor(goal.priority)
}
/>
</View>
)}
{/* 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
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>
</View>
)}
<ProgressBar
progress={progress}
color={
isCompleted ? colors.success : getPriorityColor(goal.priority)
}
/>
{isCompleted && goal.completedDate && (
<Text style={[typography.caption, { color: colors.success }]}>
Completed {new Date(goal.completedDate).toLocaleDateString()}
</Text>
)}
</View>
)}
{/* 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
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>
</MinimalCard>
</TouchableOpacity>
</Animated.View>
);
}