hydration calories persistance

This commit is contained in:
echo 2026-03-31 16:17:08 +02:00
parent 871f33bf5a
commit 3c3dfb6cd6

View File

@ -8,6 +8,7 @@ import {
Animated, Animated,
TouchableOpacity, TouchableOpacity,
Alert, Alert,
AppState,
} from "react-native"; } from "react-native";
import { useUser } from "@clerk/clerk-expo"; import { useUser } from "@clerk/clerk-expo";
import { useState, useCallback, useEffect, useRef, useMemo } from "react"; import { useState, useCallback, useEffect, useRef, useMemo } from "react";
@ -43,6 +44,22 @@ const CALORIE_GOAL = 2000;
const WATER_GOAL = 2000; const WATER_GOAL = 2000;
const WORKOUT_GOAL = 3; const WORKOUT_GOAL = 3;
const MOTIVATION_KEY_PREFIX = "home-motivation"; const MOTIVATION_KEY_PREFIX = "home-motivation";
const HOME_METRICS_KEY_PREFIX = "home-metrics";
const getLocalDateKey = () => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const getMillisecondsUntilNextMidnight = () => {
const now = new Date();
const nextMidnight = new Date(now);
nextMidnight.setHours(24, 0, 0, 0);
return Math.max(1000, nextMidnight.getTime() - now.getTime());
};
const getRandomMotivation = () => { const getRandomMotivation = () => {
const messages = [ const messages = [
@ -74,12 +91,102 @@ export default function HomeScreen() {
const caloriesBounce = useRef(new Animated.Value(1)).current; const caloriesBounce = useRef(new Animated.Value(1)).current;
const waterBounce = useRef(new Animated.Value(1)).current; const waterBounce = useRef(new Animated.Value(1)).current;
const caloriesRef = useRef(0);
const waterRef = useRef(0);
const currentDateRef = useRef(getLocalDateKey());
const midnightResetTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
null,
);
useEffect(() => {
caloriesRef.current = calories;
}, [calories]);
useEffect(() => {
waterRef.current = waterIntake;
}, [waterIntake]);
const getMetricsStorageKey = useCallback(
() => `${HOME_METRICS_KEY_PREFIX}_${user?.id || "guest"}`,
[user?.id],
);
const persistDailyMetrics = useCallback(
async (nextCalories: number, nextWaterIntake: number, dateKey?: string) => {
if (!user?.id) return;
const targetDate = dateKey || currentDateRef.current;
await AsyncStorage.setItem(
getMetricsStorageKey(),
JSON.stringify({
date: targetDate,
calories: nextCalories,
waterIntake: nextWaterIntake,
}),
);
},
[getMetricsStorageKey, user?.id],
);
const reconcileDailyMetrics = useCallback(async () => {
const today = getLocalDateKey();
currentDateRef.current = today;
if (!user?.id) {
setCalories(0);
setWaterIntake(0);
return;
}
const stored = await AsyncStorage.getItem(getMetricsStorageKey());
if (!stored) {
setCalories(0);
setWaterIntake(0);
await persistDailyMetrics(0, 0, today);
return;
}
try {
const parsed = JSON.parse(stored) as {
date?: string;
calories?: number;
waterIntake?: number;
};
if (parsed.date === today) {
const nextCalories = Number(parsed.calories) || 0;
const nextWater = Number(parsed.waterIntake) || 0;
setCalories(nextCalories);
setWaterIntake(nextWater);
} else {
setCalories(0);
setWaterIntake(0);
await persistDailyMetrics(0, 0, today);
}
} catch {
setCalories(0);
setWaterIntake(0);
await persistDailyMetrics(0, 0, today);
}
}, [getMetricsStorageKey, persistDailyMetrics, user?.id]);
const scheduleMidnightReset = useCallback(() => {
if (midnightResetTimerRef.current) {
clearTimeout(midnightResetTimerRef.current);
}
midnightResetTimerRef.current = setTimeout(() => {
void reconcileDailyMetrics();
scheduleMidnightReset();
}, getMillisecondsUntilNextMidnight() + 50);
}, [reconcileDailyMetrics]);
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
void reconcileDailyMetrics();
refetchStatistics(); refetchStatistics();
refetchGoals(); refetchGoals();
}, [refetchStatistics, refetchGoals]), }, [reconcileDailyMetrics, refetchStatistics, refetchGoals]),
); );
const onRefresh = useCallback(async () => { const onRefresh = useCallback(async () => {
@ -100,7 +207,11 @@ export default function HomeScreen() {
name: string; name: string;
calories: number; calories: number;
}) => { }) => {
setCalories((prev) => prev + meal.calories); setCalories((prev) => {
const next = prev + meal.calories;
void persistDailyMetrics(next, waterRef.current);
return next;
});
setTrackMealModalVisible(false); setTrackMealModalVisible(false);
Animated.sequence([ Animated.sequence([
Animated.timing(caloriesBounce, { Animated.timing(caloriesBounce, {
@ -117,7 +228,11 @@ export default function HomeScreen() {
}; };
const handleAddWater = (amount: number) => { const handleAddWater = (amount: number) => {
setWaterIntake((prev) => prev + amount); setWaterIntake((prev) => {
const next = prev + amount;
void persistDailyMetrics(caloriesRef.current, next);
return next;
});
setAddWaterModalVisible(false); setAddWaterModalVisible(false);
Animated.sequence([ Animated.sequence([
Animated.timing(waterBounce, { Animated.timing(waterBounce, {
@ -133,20 +248,23 @@ export default function HomeScreen() {
]).start(); ]).start();
}; };
const handleResetCalories = () => setCalories(0); const handleResetCalories = () => {
const handleResetWater = () => setWaterIntake(0); setCalories(0);
const handleAddScannedFood = (scannedCalories: number) => { void persistDailyMetrics(0, waterRef.current);
setCalories((prev) => prev + scannedCalories);
setScanFoodModalVisible(false);
}; };
const resetAllCounters = async () => { const handleResetWater = () => {
setCalories(0);
setWaterIntake(0); setWaterIntake(0);
const today = new Date().toDateString(); void persistDailyMetrics(caloriesRef.current, 0);
await AsyncStorage.setItem("lastResetDate", today); };
await AsyncStorage.removeItem(`calories_${today}`);
await AsyncStorage.removeItem(`water_${today}`); const handleAddScannedFood = (scannedCalories: number) => {
setCalories((prev) => {
const next = prev + scannedCalories;
void persistDailyMetrics(next, waterRef.current);
return next;
});
setScanFoodModalVisible(false);
}; };
useEffect(() => { useEffect(() => {
@ -183,42 +301,28 @@ export default function HomeScreen() {
}, [user?.id]); }, [user?.id]);
useEffect(() => { useEffect(() => {
const loadPersistedData = async () => { void reconcileDailyMetrics();
const today = new Date().toDateString(); }, [reconcileDailyMetrics]);
const storedCalories = await AsyncStorage.getItem(`calories_${today}`);
const storedWater = await AsyncStorage.getItem(`water_${today}`);
if (storedCalories) setCalories(parseInt(storedCalories, 10));
if (storedWater) setWaterIntake(parseInt(storedWater, 10));
};
loadPersistedData();
}, []);
useEffect(() => { useEffect(() => {
const persistCalories = async () => { const appStateSubscription = AppState.addEventListener(
const today = new Date().toDateString(); "change",
await AsyncStorage.setItem(`calories_${today}`, calories.toString()); (state) => {
}; if (state === "active") {
persistCalories(); void reconcileDailyMetrics();
}, [calories]); }
},
);
useEffect(() => { scheduleMidnightReset();
const persistWater = async () => {
const today = new Date().toDateString();
await AsyncStorage.setItem(`water_${today}`, waterIntake.toString());
};
persistWater();
}, [waterIntake]);
useEffect(() => { return () => {
const checkAndResetIfNeeded = async () => { appStateSubscription.remove();
const lastResetDate = await AsyncStorage.getItem("lastResetDate"); if (midnightResetTimerRef.current) {
const today = new Date().toDateString(); clearTimeout(midnightResetTimerRef.current);
if (lastResetDate !== today) {
await resetAllCounters();
} }
}; };
checkAndResetIfNeeded(); }, [reconcileDailyMetrics, scheduleMidnightReset]);
}, []);
const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0; const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0;
const currentStreak = statistics?.attendance.currentStreak || 0; const currentStreak = statistics?.attendance.currentStreak || 0;