fix(state): refresh history + game progress + profile on screen focus
useHistory and useGameProgress each instantiate a private state per
consumer (no global store), so an addScan from Scanner did NOT appear
in Home's RecentScans nor bump Profile stats until the app was killed
and relaunched.
- RecentScans now reload()s its history from AsyncStorage on
useFocusEffect — covers the post-scan return to Home.
- ProfileScreen reload()s both useGameProgress and useUserProfile on
useFocusEffect so the Bento stats and avatar are fresh after edits.
- useGameProgress wraps loadProgress in useCallback and exposes it,
enabling external triggers (used by the two screens above).
- ProfileScreen also fixes a brittle stat lookup: the BENTO_STATS keys
("scans"/"grapes"/"streak"/"xp") didn't match the GameProgress shape
("totalScans"/"uniqueGrapes.length"/"bestStreak"/"xp"). Now an
explicit statValues map drives the render.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a3cd906a6d
commit
59d4ae8ee4
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback } from "react";
|
||||
import { View, StyleSheet, Platform } from "react-native";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
|
@ -17,7 +18,17 @@ const MAX_RECENT = 3;
|
|||
export default function RecentScans() {
|
||||
const { t } = useTranslation();
|
||||
const navigation = useNavigation<Nav>();
|
||||
const { history, isLoading, toggleFavorite, deleteScan } = useHistory();
|
||||
const { history, isLoading, toggleFavorite, deleteScan, reload } =
|
||||
useHistory();
|
||||
|
||||
// useHistory() crée une instance state locale par consumer → l'addScan fait
|
||||
// depuis Scanner ne remonte pas ici. On reload depuis AsyncStorage à chaque
|
||||
// focus du tab Home pour récupérer les scans ajoutés ailleurs.
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
reload();
|
||||
}, [reload]),
|
||||
);
|
||||
|
||||
// Loading + pas encore de cache → skeleton
|
||||
if (isLoading && history.length === 0) {
|
||||
|
|
|
|||
|
|
@ -40,16 +40,16 @@ export function useGameProgress() {
|
|||
const [newlyUnlockedBadges, setNewlyUnlockedBadges] = useState<BadgeId[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadProgress();
|
||||
}, []);
|
||||
|
||||
async function loadProgress() {
|
||||
const loadProgress = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
const saved = await storage.get<GameProgress>(storage.KEYS.GAME_PROGRESS);
|
||||
setProgress(saved ?? INITIAL_PROGRESS);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadProgress();
|
||||
}, [loadProgress]);
|
||||
|
||||
const processDetection = useCallback(async (detection: Detection): Promise<number> => {
|
||||
let xpEarned = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import {
|
||||
View,
|
||||
ScrollView,
|
||||
|
|
@ -8,7 +8,7 @@ import {
|
|||
TouchableOpacity,
|
||||
} from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
|
|
@ -27,19 +27,34 @@ const { width } = Dimensions.get("window");
|
|||
const STAT_CARD_SIZE = (width - 56) / 2;
|
||||
|
||||
const BENTO_STATS = [
|
||||
{ key: "scans", icon: "scan-outline", iconColor: "#F59E0B", label: "profile.totalScans" },
|
||||
{ key: "grapes", icon: "leaf-outline", iconColor: "#10B981", label: "profile.uniqueGrapes" },
|
||||
{ key: "streak", icon: "flame-outline", iconColor: "#EF4444", label: "profile.bestStreak" },
|
||||
{ key: "totalScans", icon: "scan-outline", iconColor: "#F59E0B", label: "profile.totalScans" },
|
||||
{ key: "uniqueGrapes", icon: "leaf-outline", iconColor: "#10B981", label: "profile.uniqueGrapes" },
|
||||
{ key: "bestStreak", icon: "flame-outline", iconColor: "#EF4444", label: "profile.bestStreak" },
|
||||
{ key: "xp", icon: "star-outline", iconColor: "#6366F1", label: "profile.xpTotal" },
|
||||
];
|
||||
] as const;
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const { t } = useTranslation();
|
||||
const navigation = useNavigation<Nav>();
|
||||
const { progress } = useGameProgress();
|
||||
const { profile, updateProfile } = useUserProfile();
|
||||
const { progress, reload: reloadProgress } = useGameProgress();
|
||||
const { profile, updateProfile, reload: reloadProfile } = useUserProfile();
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
// Refresh stats + profil quand le screen reprend le focus (post-scan, post-edit, etc.)
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
reloadProgress();
|
||||
reloadProfile();
|
||||
}, [reloadProgress, reloadProfile]),
|
||||
);
|
||||
|
||||
const statValues: Record<string, number> = {
|
||||
totalScans: progress.totalScans ?? 0,
|
||||
uniqueGrapes: progress.uniqueGrapes?.length ?? 0,
|
||||
bestStreak: progress.bestStreak ?? 0,
|
||||
xp: progress.xp ?? 0,
|
||||
};
|
||||
|
||||
function handleBack() {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
|
|
@ -109,9 +124,7 @@ export default function ProfileScreen() {
|
|||
<Ionicons name={stat.icon as keyof typeof Ionicons.glyphMap} size={22} color={stat.iconColor} />
|
||||
</View>
|
||||
<Text style={styles.statValue}>
|
||||
{stat.key === "grapes"
|
||||
? progress.uniqueGrapes?.length ?? 0
|
||||
: (progress[stat.key as keyof typeof progress] as number) ?? 0}
|
||||
{statValues[stat.key] ?? 0}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>{t(stat.label)}</Text>
|
||||
</View>
|
||||
|
|
|
|||
Loading…
Reference in a new issue