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

+195 -136
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>
);
}