91 lines
5.2 KiB
TypeScript
91 lines
5.2 KiB
TypeScript
// Chat-Bubble + Spezial-Cards (Spiele/Überwunden) für den SOS-Chat-Stream
|
|
// sowie GameHeader für die aktive Spiel-Session.
|
|
import { View, Text, Pressable, StyleSheet } from 'react-native';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { type GameType, GAME_META, GamePickerGrid } from './UrgeGames';
|
|
import { RiveAvatar, type Emotion as LyraEmotion } from '../RiveAvatar';
|
|
|
|
export type CardType = 'games' | 'overcome';
|
|
export type SosMsg = { id: string; role: 'user' | 'assistant'; content: string; cardType?: CardType; timestamp: Date };
|
|
|
|
// ── GameCard ─────────────────────────────────────────────────────────────────
|
|
export function GameCard({ onSelect }: { onSelect: (game: GameType) => void }) {
|
|
return (
|
|
<View style={st.gameCard}>
|
|
<Text style={st.gameCardTitle}>Welches Spiel?</Text>
|
|
<GamePickerGrid onSelect={onSelect} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// ── OvercomeCard ─────────────────────────────────────────────────────────────
|
|
export function OvercomeCard() {
|
|
return (
|
|
<View style={st.overcomeCard}>
|
|
<Text style={{ fontSize: 36 }}>🎉</Text>
|
|
<Text style={st.overcomeTitle}>Gut gemacht.</Text>
|
|
<Text style={st.overcomeSub}>Du hast den Impuls überwunden.</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// ── MessageRow ───────────────────────────────────────────────────────────────
|
|
type MessageRowProps = {
|
|
item: SosMsg;
|
|
onGameSelect: (game: GameType) => void;
|
|
onBreathingDone: () => void;
|
|
onSpeak?: (text: string) => Promise<void> | void;
|
|
};
|
|
|
|
export default function MessageRow({ item, onGameSelect }: MessageRowProps) {
|
|
const isUser = item.role === 'user';
|
|
if (item.cardType === 'games') return <View style={st.msgRowAssistant}><GameCard onSelect={onGameSelect} /></View>;
|
|
if (item.cardType === 'overcome') return <View style={st.msgRowAssistant}><OvercomeCard /></View>;
|
|
return (
|
|
<View style={[st.msgRow, isUser ? st.msgRowUser : st.msgRowAssistant]}>
|
|
<View style={[st.bubbleCol, isUser ? st.bubbleColUser : st.bubbleColAssistant]}>
|
|
<View style={[st.bubble, isUser ? st.bubbleUser : st.bubbleAssistant]}>
|
|
<Text style={[st.bubbleText, isUser ? st.bubbleTextUser : st.bubbleTextAssistant]}>{item.content}</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// ── GameHeader ───────────────────────────────────────────────────────────────
|
|
export function GameHeader({ game, emotion, onBack }: { game: GameType; emotion: LyraEmotion; onBack: () => void }) {
|
|
const meta = GAME_META.find((g) => g.id === game);
|
|
return (
|
|
<View style={st.gameHeader}>
|
|
<Pressable style={st.backBtn} onPress={onBack} hitSlop={12}><Ionicons name="chevron-back" size={22} color="#374151" /></Pressable>
|
|
<View style={{ alignItems: 'center', flex: 1 }}>
|
|
<View style={{ transform: [{ scale: 0.65 }], marginBottom: -8 }}><RiveAvatar emotion={emotion} size="sm" /></View>
|
|
<Text style={{ fontFamily: 'Nunito_700Bold', fontSize: 13, color: '#111827', marginTop: 2 }}>{meta?.id ?? game}</Text>
|
|
</View>
|
|
<View style={{ width: 40 }} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const st = StyleSheet.create({
|
|
msgRow: { flexDirection: 'row', marginBottom: 4, alignItems: 'flex-end' },
|
|
msgRowUser: { justifyContent: 'flex-end' },
|
|
msgRowAssistant: { justifyContent: 'flex-start', marginBottom: 6 },
|
|
bubbleCol: { maxWidth: '75%', gap: 2 },
|
|
bubbleColUser: { alignItems: 'flex-end' },
|
|
bubbleColAssistant: { alignItems: 'flex-start' },
|
|
bubble: { borderRadius: 20, paddingHorizontal: 14, paddingVertical: 9 },
|
|
bubbleUser: { backgroundColor: '#007AFF', borderBottomRightRadius: 4 },
|
|
bubbleAssistant: { backgroundColor: '#f0f0f0', borderBottomLeftRadius: 4 },
|
|
bubbleText: { fontSize: 15, lineHeight: 22 },
|
|
bubbleTextUser: { color: '#ffffff', fontFamily: 'Nunito_400Regular' },
|
|
bubbleTextAssistant: { color: '#1a1a1a', fontFamily: 'Nunito_400Regular' },
|
|
gameCard: { backgroundColor: '#f0f9ff', borderRadius: 16, borderWidth: 1, borderColor: '#bae6fd', padding: 12, maxWidth: '92%' },
|
|
gameCardTitle: { fontFamily: 'Nunito_700Bold', color: '#0369a1', fontSize: 13, marginBottom: 10 },
|
|
overcomeCard: { backgroundColor: '#f0fdf4', borderRadius: 16, borderWidth: 1, borderColor: '#86efac', padding: 16, alignItems: 'center', maxWidth: '88%', gap: 4 },
|
|
overcomeTitle: { fontFamily: 'Nunito_800ExtraBold', fontSize: 18, color: '#15803d' },
|
|
overcomeSub: { fontFamily: 'Nunito_400Regular', fontSize: 13, color: '#166534', textAlign: 'center' },
|
|
gameHeader: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#f3f4f6', backgroundColor: '#fff' },
|
|
backBtn: { width: 40, height: 40, borderRadius: 20, backgroundColor: '#f3f4f6', alignItems: 'center', justifyContent: 'center' },
|
|
});
|