import { useEffect, useState } from 'react'; import { View, Text, Pressable, ScrollView } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { GAME_META, type GameType, MemoryGame, TicTacToeGame, SnakeGame, TetrisGame, } from '../components/urge/UrgeGames'; import { GameCard } from '../components/games/GameCard'; import { useColors } from '../lib/theme'; import { apiFetch } from '../lib/api'; type GameStat = { avgStars: number; count: number }; type GameStats = Record; const EMPTY_STATS: GameStats = { memory: { avgStars: 0, count: 0 }, tictactoe: { avgStars: 0, count: 0 }, snake: { avgStars: 0, count: 0 }, tetris: { avgStars: 0, count: 0 }, }; type LastScore = { game: GameType; score: number } | null; export default function GamesScreen() { const router = useRouter(); const { t } = useTranslation(); const colors = useColors(); const [active, setActive] = useState(null); const [lastScore, setLastScore] = useState(null); const [gameStats, setGameStats] = useState(EMPTY_STATS); useEffect(() => { let cancelled = false; (async () => { try { // Backend response: { ratings, stats: [{ gameName, avgStars, count }] } const data = await apiFetch<{ stats: Array<{ gameName: string; avgStars: number; count: number }>; }>('/api/games/ratings'); if (cancelled) return; const next: GameStats = { ...EMPTY_STATS }; for (const s of data.stats ?? []) { const key = s.gameName.toLowerCase() as GameType; if (key in next) { next[key] = { avgStars: s.avgStars ?? 0, count: s.count ?? 0 }; } } setGameStats(next); } catch { // Silent fail — UI shows 0 stars/count, kein Crash } })(); return () => { cancelled = true; }; }, []); function exit(score?: number) { if (typeof score === 'number' && active) { setLastScore({ game: active, score }); } setActive(null); } if (active) { return ( exit()} hitSlop={10} style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1, })} > {t('games.back_to_picker')} {/* Title bewusst entfernt — der Game-Picker hat das Spiel schon ausgewählt, Wiederholung im Header lenkt nur ab. Spacer balanciert den Back-Button. */} {active === 'memory' ? ( exit(s)} onAbandon={() => exit()} /> ) : null} {active === 'tictactoe' ? ( exit(s)} onAbandon={() => exit()} /> ) : null} {active === 'snake' ? ( exit(s)} onAbandon={() => exit()} /> ) : null} {active === 'tetris' ? ( exit(s)} onAbandon={() => exit()} /> ) : null} ); } return ( router.back()} hitSlop={8} style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1, })} > {t('games.title')} {t('games.subtitle')} {GAME_META.map((game) => { const stat = gameStats[game.id] ?? { avgStars: 0, count: 0 }; const recent = lastScore?.game === game.id ? lastScore.score : null; return ( setActive(id)} /> {recent !== null ? ( {t('games.last_score', { score: recent })} ) : null} ); })} {t('games.skeleton_footer')} ); }