redesign checkpoint

This commit is contained in:
dimitar committed 2026-03-12 15:32:52 +01:00
1 parent a5f761062e
commit 981208ab7b
6 files changed
+583 -298

No files matched your search

+335 -101
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",