import { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Animated, Modal, ScrollView, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import { useTranslation } from 'react-i18next'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as Haptics from 'expo-haptics'; import { Ionicons } from '@expo/vector-icons'; import { RiveAvatar } from '../RiveAvatar'; import { StarRating } from './StarRating'; import { useColors } from '../../lib/theme'; import { apiFetch } from '../../lib/api'; export type GameOverScreenProps = { score: number; bestScore: number; gameName: string; scoreLabel?: string; goodScore?: number; onRetry: () => void; onExit: () => void; isNewBest?: boolean; }; function lyraMsg( gameName: string, score: number, goodScore: number, isNewBest: boolean, t: (k: string) => string ): { title: string; body: string } { if (isNewBest) return { title: t('gameOver.lyra_title_record'), body: t('gameOver.lyra_body_record') }; if (score >= goodScore) return { title: t('gameOver.lyra_title_good'), body: t('gameOver.lyra_body_good') }; if (score > 0) return { title: t('gameOver.lyra_title_ok'), body: t('gameOver.lyra_body_ok') }; return { title: t('gameOver.lyra_title_low'), body: t('gameOver.lyra_body_low') }; } export function GameOverScreen({ score, bestScore, gameName, scoreLabel, goodScore = 5, onRetry, onExit, isNewBest = false, }: GameOverScreenProps) { const { t } = useTranslation(); const colors = useColors(); const insets = useSafeAreaInsets(); const slideAnim = useRef(new Animated.Value(500)).current; const [rating, setRating] = useState(0); const [feedback, setFeedback] = useState(''); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); const [shareSectionOpen, setShareSectionOpen] = useState(false); const [shareText, setShareText] = useState(''); const [shareTextLoading, setShareTextLoading] = useState(false); const [sharing, setSharing] = useState(false); const [posted, setPosted] = useState(false); const [postError, setPostError] = useState(false); console.log('[GameOver] colors:', colors); const emotion = isNewBest || score >= goodScore ? 'happy' : 'empathy'; const msg = lyraMsg(gameName, score, goodScore, isNewBest, t); const displayScore = score; const displayBest = Math.max(score, bestScore); useEffect(() => { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); Animated.spring(slideAnim, { toValue: 0, useNativeDriver: true, damping: 22, stiffness: 200, mass: 0.8, }).start(); }, []); function handleExit() { Animated.timing(slideAnim, { toValue: 500, duration: 220, useNativeDriver: true, }).start(() => onExit()); } async function submitRating() { setSaving(true); try { await apiFetch('/api/games/rating', { method: 'POST', body: { gameName: gameName.toLowerCase(), stars: rating, feedback: feedback.trim() || null, score, }, }); setSaved(true); } catch { // endpoint not yet live — silent } finally { setSaving(false); } } async function openShareSection() { setShareTextLoading(true); setShareSectionOpen(true); try { const data = await apiFetch<{ text: string }>('/api/games/share-text', { method: 'POST', body: { gameName: gameName.toLowerCase(), score, scoreLabel, bestScore, isNewRecord: score > bestScore, mode: 'game', }, }); setShareText(data.text || `${gameName}: ${score} ${scoreLabel ?? 'Punkte'}\n${t('gameOver.share_challenge')}`); } catch { setShareText(`${gameName}: ${score} ${scoreLabel ?? 'Punkte'}\n${t('gameOver.share_challenge')}`); } finally { setShareTextLoading(false); } } async function submitCommunityPost() { if (!shareText.trim()) return; setSharing(true); setPostError(false); try { const scoreLine = `${scoreLabel ?? 'Score'}: ${score}`; await apiFetch('/api/community/post', { method: 'POST', body: { category: 'game_share', content: `${gameName}\n${scoreLine}\n${shareText.trim()}`, }, }); setPosted(true); setShareSectionOpen(false); setTimeout(() => handleExit(), 1500); } catch (err) { console.error('[gameover/post] failed:', err); setPostError(true); } finally { setSharing(false); } } const pillBg = colors.surfaceElevated; const pillText = colors.text; const pillMuted = colors.textMuted; return ( {/* Grab-handle */} {/* Lyra avatar + message */} Lyra {msg.title} {msg.body} {/* Score pills */} {displayScore} {scoreLabel ?? t('gameOver.score')} {displayBest} {isNewBest ? t('gameOver.newBest') : t('gameOver.best')} {/* Star rating */} { if (!saved) setRating(v); }} /> {saved ? ( {t('gameOver.rating_saved')} ) : null} {/* Feedback textarea + save */} {rating > 0 && !saved ? ( {saving ? t('common.loading') : t('gameOver.save_rating')} ) : null} {/* Primary action row */} { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); onRetry(); }} activeOpacity={0.85} style={{ flex: 1, backgroundColor: '#f59e0b', borderRadius: 14, minHeight: 50, paddingVertical: 14, paddingHorizontal: 20, alignItems: 'center', justifyContent: 'center', }} > {t('gameOver.retry')} { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); handleExit(); }} activeOpacity={0.75} style={{ flex: 1, backgroundColor: '#e5e7eb', borderRadius: 14, minHeight: 50, paddingVertical: 14, paddingHorizontal: 20, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'rgba(0,0,0,0.08)', }} > {t('gameOver.exit')} {/* Share section */} {posted ? ( {t('gameOver.posted')} ) : !shareSectionOpen ? ( {t('gameOver.share_result')} ) : ( {shareTextLoading ? ( {t('gameOver.share_loading')} ) : ( )} {postError ? ( {t('gameOver.post_error')} ) : null} { setShareSectionOpen(false); setShareText(''); setPostError(false); }} activeOpacity={0.7} style={{ flex: 1, backgroundColor: '#e5e7eb', borderRadius: 14, minHeight: 50, paddingVertical: 14, paddingHorizontal: 20, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'rgba(0,0,0,0.08)', }} > {t('common.cancel')} {sharing ? ( ) : ( )} {t('gameOver.post_to_community')} )} ); }