diff --git a/apps/rebreak-native/components/games/GameOverScreen.tsx b/apps/rebreak-native/components/games/GameOverScreen.tsx index cc7c9d8..42b297f 100644 --- a/apps/rebreak-native/components/games/GameOverScreen.tsx +++ b/apps/rebreak-native/components/games/GameOverScreen.tsx @@ -1,9 +1,10 @@ import { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, + Alert, Animated, Easing, - Keyboard, + KeyboardAvoidingView, Modal, Platform, ScrollView, @@ -18,6 +19,7 @@ import * as Haptics from 'expo-haptics'; import { Ionicons } from '@expo/vector-icons'; import { RiveAvatar } from '../RiveAvatar'; import { StarRating } from './StarRating'; +import { Button } from '../Button'; import { useColors } from '../../lib/theme'; import { apiFetch } from '../../lib/api'; @@ -59,36 +61,7 @@ export function GameOverScreen({ const colors = useColors(); const insets = useSafeAreaInsets(); - // Slide-In Spring für den Sheet-Auftritt (eigene Bouncy-Animation behalten) const slideAnim = useRef(new Animated.Value(500)).current; - // Keyboard-Lift via plain RN Keyboard.addListener (funktioniert in Modals, - // anders als react-native-keyboard-controller's useKeyboardAnimation). - const keyboardLift = useRef(new Animated.Value(0)).current; - - useEffect(() => { - const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; - const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'; - const showSub = Keyboard.addListener(showEvent, (e) => { - Animated.timing(keyboardLift, { - toValue: e.endCoordinates.height, - duration: Platform.OS === 'ios' ? (e.duration ?? 250) : 220, - easing: Easing.out(Easing.cubic), - useNativeDriver: true, - }).start(); - }); - const hideSub = Keyboard.addListener(hideEvent, (e) => { - Animated.timing(keyboardLift, { - toValue: 0, - duration: Platform.OS === 'ios' ? (e?.duration ?? 250) : 220, - easing: Easing.out(Easing.cubic), - useNativeDriver: true, - }).start(); - }); - return () => { - showSub.remove(); - hideSub.remove(); - }; - }, [keyboardLift]); const [rating, setRating] = useState(0); const [feedback, setFeedback] = useState(''); @@ -97,7 +70,9 @@ export function GameOverScreen({ const [shareSectionOpen, setShareSectionOpen] = useState(false); const [shareText, setShareText] = useState(''); + const lyraShareTextRef = useRef(''); const [shareTextLoading, setShareTextLoading] = useState(false); + const [regenLoading, setRegenLoading] = useState(false); const [sharing, setSharing] = useState(false); const [posted, setPosted] = useState(false); const [postError, setPostError] = useState(false); @@ -118,14 +93,12 @@ export function GameOverScreen({ }).start(); }, []); - // Negativer Lift — translateY -keyboardHeight schiebt Sheet nach oben. - const keyboardLiftY = Animated.multiply(keyboardLift, -1); - function handleExit() { Animated.timing(slideAnim, { toValue: 500, duration: 220, useNativeDriver: true, + easing: Easing.in(Easing.cubic), }).start(() => onExit()); } @@ -149,29 +122,69 @@ export function GameOverScreen({ } } + async function fetchShareText() { + const data = await apiFetch<{ text: string }>('/api/games/share-text', { + method: 'POST', + body: { + gameName: gameName.toLowerCase(), + score, + scoreLabel, + bestScore, + isNewRecord: score > bestScore, + mode: 'game', + }, + }); + return data.text || `${gameName}: ${score} ${scoreLabel ?? 'Punkte'}\n${t('gameOver.share_challenge')}`; + } + 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')}`); + const text = await fetchShareText(); + lyraShareTextRef.current = text; + setShareText(text); } catch { - setShareText(`${gameName}: ${score} ${scoreLabel ?? 'Punkte'}\n${t('gameOver.share_challenge')}`); + const fallback = `${gameName}: ${score} ${scoreLabel ?? 'Punkte'}\n${t('gameOver.share_challenge')}`; + lyraShareTextRef.current = fallback; + setShareText(fallback); } finally { setShareTextLoading(false); } } + function regenerateShareText() { + if (regenLoading || shareTextLoading) return; + + const doRegen = async () => { + setRegenLoading(true); + try { + const text = await fetchShareText(); + lyraShareTextRef.current = text; + setShareText(text); + } catch { + // keep existing text on error + } finally { + setRegenLoading(false); + } + }; + + const userModified = shareText.trim() !== lyraShareTextRef.current.trim(); + + if (userModified) { + Alert.alert( + t('gameOver.regen_confirm_title'), + t('gameOver.regen_confirm_body'), + [ + { text: t('common.cancel'), style: 'cancel' }, + { text: t('gameOver.regen_confirm_ok'), onPress: doRegen }, + ] + ); + } else { + doRegen(); + } + } + async function submitCommunityPost() { if (!shareText.trim()) return; setSharing(true); @@ -202,20 +215,19 @@ export function GameOverScreen({ return ( - + {/* Grab-handle */} @@ -231,10 +243,11 @@ export function GameOverScreen({ }} /> + {/* Scrollable body */} {/* Lyra avatar + message */} @@ -319,77 +332,16 @@ export function GameOverScreen({ textAlignVertical: 'top', }} /> - - - {saving ? t('common.loading') : t('gameOver.save_rating')} - - + loading={saving} + variant="primary" + size="md" + /> ) : null} - {/* Primary action row */} - - { - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); - onRetry(); - }} - activeOpacity={0.85} - style={{ - flex: 1, - backgroundColor: '#007AFF', - borderRadius: 12, - minHeight: 40, - paddingVertical: 10, - paddingHorizontal: 16, - alignItems: 'center', - justifyContent: 'center', - }} - > - - {t('gameOver.retry')} - - - - { - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); - handleExit(); - }} - activeOpacity={0.75} - style={{ - flex: 1, - backgroundColor: '#e5e7eb', - borderRadius: 12, - minHeight: 40, - paddingVertical: 14, - paddingHorizontal: 20, - alignItems: 'center', - justifyContent: 'center', - borderWidth: 1, - borderColor: 'rgba(0,0,0,0.08)', - }} - > - - {t('gameOver.exit')} - - - - {/* Share section */} {posted ? ( @@ -439,6 +391,21 @@ export function GameOverScreen({ /> )} + {/* Regenerate suggestion button */} + {!shareTextLoading ? ( + +