redesign checkpoint
This commit is contained in:
parent
a5f761062e
commit
981208ab7b
Binary file not shown.
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user