import React, { useState, useEffect, useCallback, useRef } from 'react'; import { View, Text, StyleSheet, FlatList, TouchableOpacity, SafeAreaView, Alert, ActivityIndicator, TextInput, Pressable, Platform, ScrollView } from 'react-native'; import { useTheme, useFocusEffect } from '@react-navigation/native'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { useAuth } from '../context/AuthProvider'; import NewChatButton from '../components/NewChatButton'; import ConversationStarter, { ConversationStarterRef } from '../components/ConversationStarter'; import CustomDrawer from '../components/CustomDrawer'; import { useAppTheme } from '../theme/ThemeProvider'; import { getConversations, getMessages, deleteConversation, archiveConversation } from '../services/conversation'; import { getUserSpaces, Space } from '../services/space'; import { supabase } from '../utils/supabase'; // Typendefinitionen für Konversationen type ConversationItem = { id: string; modelName: string; title: string; lastMessage: string; timestamp: Date; mode: 'frei' | 'geführt' | 'vorlage'; }; // Hilfsfunktion zur Formatierung des Datums const formatDate = (date: Date) => { const day = date.getDate().toString().padStart(2, '0'); const month = new Intl.DateTimeFormat('de-DE', { month: 'short' }).format(date); const hours = date.getHours().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0'); return `${day}. ${month}, ${hours}:${minutes}`; }; export default function HomeScreen() { const { colors } = useTheme(); const router = useRouter(); const { user, signOut } = useAuth(); const [conversations, setConversations] = useState([]); const [spaces, setSpaces] = useState([]); const [selectedSpaceId, setSelectedSpaceId] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isLoadingSpaces, setIsLoadingSpaces] = useState(true); const { isDarkMode } = useAppTheme(); const [isDrawerOpen, setIsDrawerOpen] = useState(false); const chatInputRef = useRef(null); // Eine Funktion, die Konversationen lädt und wiederverwendet werden kann // Fokussiere das Eingabefeld beim ersten Laden useEffect(() => { // Kurze Verzögerung, um sicherzustellen, dass die Komponente vollständig gerendert ist setTimeout(() => { if (chatInputRef.current) { chatInputRef.current.focus(); } }, 300); }, []); const loadConversations = async () => { if (!user) return; setIsLoading(true); try { console.log("Lade Konversationen für User:", user.id); console.log("Selected Space ID:", selectedSpaceId || "Alle Spaces"); // Lade Konversationen des Benutzers, gefiltert nach Space wenn ausgewählt const userConversations = await getConversations(user.id, selectedSpaceId || undefined); console.log(`${userConversations.length} Konversationen geladen`, new Date().toLocaleTimeString()); // Lade für jede Konversation die letzte Nachricht und das Modell const conversationItems: ConversationItem[] = []; for (const conv of userConversations) { try { // Lade die Nachrichten der Konversation const messages = await getMessages(conv.id); // Lade das Modell aus der Datenbank const { data: modelData } = await supabase .from('models') .select('name') .eq('id', conv.model_id) .single(); // Finde die letzte Nachricht (die nicht vom System ist) const lastMessage = messages .filter(msg => msg.sender !== 'system') .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0]; if (lastMessage) { conversationItems.push({ id: conv.id, modelName: modelData?.name || 'Unbekanntes Modell', title: conv.title || 'Unbenannte Konversation', lastMessage: lastMessage.message_text, timestamp: new Date(conv.updated_at), mode: conv.conversation_mode === 'free' ? 'frei' : conv.conversation_mode === 'guided' ? 'geführt' : 'vorlage' }); } } catch (error) { console.error(`Fehler beim Laden der Details für Konversation ${conv.id}:`, error); } } setConversations(conversationItems); } catch (error) { console.error('Fehler beim Laden der Konversationen:', error); Alert.alert('Fehler', 'Die Konversationen konnten nicht geladen werden.'); } finally { setIsLoading(false); } }; // Lade Spaces const loadSpaces = useCallback(async () => { if (!user) return; setIsLoadingSpaces(true); try { const userSpaces = await getUserSpaces(user.id); setSpaces(userSpaces); } catch (error) { console.error('Fehler beim Laden der Spaces:', error); } finally { setIsLoadingSpaces(false); } }, [user]); // Lade die Konversationen beim ersten Rendern und wenn sich der User oder selectedSpaceId ändert useEffect(() => { loadConversations(); }, [user, selectedSpaceId]); // Lade Spaces beim ersten Rendern useEffect(() => { loadSpaces(); }, [loadSpaces]); // Lade Konversationen und Spaces erneut, wenn der Screen fokussiert wird useFocusEffect( useCallback(() => { if (user) { loadConversations(); loadSpaces(); } return () => {}; }, [user, loadSpaces, selectedSpaceId]) ); // Space auswählen const handleSpaceSelect = (spaceId: string | null) => { console.log("Space ausgewählt:", spaceId); setSelectedSpaceId(spaceId); // Alert für Debug-Zwecke Alert.alert( "Space ausgewählt", `Space ID: ${spaceId || 'Alle Spaces'}` ); }; const handleNewChat = () => { // Navigiere zum Modellauswahl-Screen router.push('/model-selection'); }; const handleLogout = async () => { try { await signOut(); router.replace('/auth/login'); } catch (error) { console.error('Fehler beim Abmelden:', error); Alert.alert('Fehler', 'Bei der Abmeldung ist ein Fehler aufgetreten.'); } }; const handleConversationPress = (id: string) => { // Navigiere zum Konversations-Screen mit der ID router.push(`/conversation/${id}`); }; // Löschen einer Konversation const handleDeleteConversation = (id: string) => { Alert.alert( "Konversation löschen", "Möchtest du diese Konversation wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", [ { text: "Abbrechen", style: "cancel" }, { text: "Löschen", style: "destructive", onPress: async () => { try { const success = await deleteConversation(id); if (success) { // Aus der lokalen Liste entfernen setConversations(prev => prev.filter(conv => conv.id !== id)); Alert.alert("Erfolg", "Die Konversation wurde gelöscht."); } else { Alert.alert("Fehler", "Die Konversation konnte nicht gelöscht werden."); } } catch (error) { console.error('Fehler beim Löschen der Konversation:', error); Alert.alert("Fehler", "Die Konversation konnte nicht gelöscht werden."); } } } ] ); }; // Archivieren einer Konversation const handleArchiveConversation = async (id: string) => { try { const success = await archiveConversation(id); if (success) { // Aus der lokalen Liste entfernen setConversations(prev => prev.filter(conv => conv.id !== id)); Alert.alert("Erfolg", "Die Konversation wurde archiviert."); } else { Alert.alert("Fehler", "Die Konversation konnte nicht archiviert werden."); } } catch (error) { console.error('Fehler beim Archivieren der Konversation:', error); Alert.alert("Fehler", "Die Konversation konnte nicht archiviert werden."); } }; // Zustandsverwaltung für die Optionsmenüs der Konversationselemente const [expandedConversationId, setExpandedConversationId] = useState(null); // Toggle-Funktion für das Optionsmenü const toggleOptionsMenu = (id: string) => { setExpandedConversationId(expandedConversationId === id ? null : id); }; const renderConversationItem = ({ item }: { item: ConversationItem }) => { const showOptions = expandedConversationId === item.id; return ( [ styles.conversationItem, hovered && { backgroundColor: colors.cardHover }, pressed && { opacity: 0.9 } ]} onPress={() => handleConversationPress(item.id)} onLongPress={() => toggleOptionsMenu(item.id)} > {({ pressed, hovered }) => ( <> {item.title} {item.modelName} {item.mode === 'frei' ? 'Frei' : item.mode === 'geführt' ? 'Geführt' : 'Vorlage'} {formatDate(item.timestamp)} {item.lastMessage} [ styles.optionsButton, hovered && { backgroundColor: colors.menuItemHover }, pressed && { opacity: 0.7 } ]} onPress={() => toggleOptionsMenu(item.id)} > {({ pressed, hovered }) => ( )} )} {showOptions && ( [ styles.optionButton, hovered && { backgroundColor: colors.menuItemHover }, pressed && { opacity: 0.8 } ]} onPress={() => handleArchiveConversation(item.id)} > {({ pressed, hovered }) => ( <> Archivieren )} [ styles.optionButton, hovered && { backgroundColor: colors.dangerHover }, pressed && { opacity: 0.8 } ]} onPress={() => handleDeleteConversation(item.id)} > {({ pressed, hovered }) => ( <> Löschen )} )} ); }; // Fokussiere das Eingabefeld, wenn der Benutzer auf "Neuen Chat starten" klickt const handleFocusInput = useCallback(() => { if (chatInputRef.current) { chatInputRef.current.focus(); } }, [chatInputRef]); return ( {/* Permanenter Drawer links */} {isDrawerOpen && ( setIsDrawerOpen(false)} /> )} {/* Hauptinhalt */} [ styles.menuButton, hovered && { backgroundColor: colors.menuItemHover }, pressed && { opacity: 0.7 } ]} onPress={() => setIsDrawerOpen(!isDrawerOpen)} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > {({ pressed, hovered }) => ( )} Chats {/* Space-Auswahl */} {spaces.length > 0 && ( handleSpaceSelect(null)} activeOpacity={0.7} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > Alle {spaces.map(space => ( handleSpaceSelect(space.id)} activeOpacity={0.7} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > {space.name} ))} router.push('/spaces')} activeOpacity={0.7} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > Verwalten )} {/* Zentrierter ConversationStarter */} {/* Konversationsliste unten */} Letzte Konversationen {conversations.length > 0 && ( [ styles.viewAllButton, hovered && { backgroundColor: colors.buttonHover }, pressed && { opacity: 0.8 } ]} onPress={() => router.push('/conversations')} > {({ pressed, hovered }) => ( Alle anzeigen )} )} {isLoading ? ( Konversationen werden geladen... ) : conversations.length > 0 ? ( item.id} renderItem={renderConversationItem} contentContainerStyle={styles.gridContent} horizontal={true} showsHorizontalScrollIndicator={false} snapToAlignment="start" decelerationRate="fast" snapToInterval={396} // 380px Kartenbreite + 16px Abstand /> ) : ( Keine Konversationen vorhanden Stelle eine Frage im Eingabefeld oben )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, mainLayout: { flex: 1, flexDirection: 'row', }, mainContainer: { flex: 1, alignItems: 'center', }, drawerContainer: { width: 260, height: '100%', position: 'absolute', left: 0, top: 0, bottom: 0, zIndex: 10, }, contentContainer: { flex: 1, maxWidth: 1200, width: '100%', }, header: { paddingHorizontal: 20, paddingTop: 16, paddingBottom: 8, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', maxWidth: 800, width: '100%', alignSelf: 'center', zIndex: 10, // Stelle sicher, dass der Header über allem anderen liegt elevation: 10, // Für Android }, menuButton: { padding: 10, marginRight: 8, zIndex: 5, borderRadius: 20, width: 40, height: 40, alignItems: 'center', justifyContent: 'center', }, title: { fontSize: 28, fontWeight: 'bold', }, spaceSelector: { paddingTop: 8, paddingBottom: 12, zIndex: 20, // Erhöht, um über anderen Elementen zu liegen elevation: 20, // Für Android position: 'relative', // Setzt einen neuen Stacking-Kontext }, spacePills: { paddingHorizontal: 16, gap: 8, }, spacePill: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, borderWidth: 1, minWidth: 60, minHeight: 32, justifyContent: 'center', alignItems: 'center', zIndex: 25, // Noch höher als spaceSelector elevation: 25, // Für Android }, spacePillText: { fontSize: 14, fontWeight: '500', }, spacePillAdd: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, borderWidth: 1, borderStyle: 'dashed', minWidth: 100, minHeight: 32, justifyContent: 'center', alignItems: 'center', zIndex: 25, // Gleich wie normaler spacePill elevation: 25, // Für Android }, spacePillAddContent: { flexDirection: 'row', alignItems: 'center', }, spacePillAddText: { fontSize: 14, fontWeight: '500', marginLeft: 4, }, centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16, marginTop: 20, // Erhöht, um mehr Platz für Space-Pills zu lassen zIndex: 10, // Zwischen Space-Selector und den Pills }, bottomSection: { flex: 0.4, // Nimmt 40% des verfügbaren Platzes ein width: '100%', }, gridContent: { paddingLeft: 16, paddingRight: 4, // Reduziertes Padding rechts, da die Karten marginRight haben paddingBottom: 20, paddingTop: 10, }, conversationItemWrapper: { borderRadius: 12, overflow: 'hidden', width: 380, // Breitere Karten height: 180, // Feste Höhe für einheitlichere Darstellung marginRight: 16, // Abstand zwischen den Karten ...Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, android: { elevation: 3, }, web: { boxShadow: '0 2px 6px rgba(0, 0, 0, 0.1)', }, }), }, conversationItem: { flexDirection: 'row', alignItems: 'flex-start', padding: 16, }, conversationContent: { flex: 1, display: 'flex', flexDirection: 'column', height: '100%', }, optionsContainer: { flexDirection: 'row', justifyContent: 'flex-end', paddingHorizontal: 16, paddingBottom: 12, paddingTop: 8, }, optionButton: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, paddingVertical: 6, marginLeft: 8, borderRadius: 6, }, optionText: { fontSize: 14, marginLeft: 6, fontWeight: '500', }, conversationHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6, }, titleRow: { flexDirection: 'row', alignItems: 'center', flex: 1, }, titleIcon: { marginRight: 8, }, title: { fontSize: 16, fontWeight: '600', flex: 1, marginBottom: 2, }, modelName: { fontSize: 12, fontWeight: '400', }, badgeContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 10, gap: 8, flexWrap: 'wrap', }, modelBadge: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 12, }, modelName: { fontSize: 12, fontWeight: '500', }, modeBadge: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 12, }, timestamp: { fontSize: 11, marginLeft: 'auto', // Um es an den rechten Rand zu schieben }, lastMessage: { fontSize: 14, marginBottom: 6, lineHeight: 20, marginTop: 4, flex: 1, // Damit die Nachricht den verbleibenden Platz einnimmt }, modeText: { fontSize: 11, fontWeight: '500', }, optionsButton: { width: 36, height: 36, borderRadius: 18, alignItems: 'center', justifyContent: 'center', }, // Container für den Ladezustand loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32, marginTop: -40, }, loadingText: { fontSize: 16, marginTop: 16, textAlign: 'center', }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32, marginTop: -80, // Nach oben verschieben, um Platz für das Eingabefeld zu machen }, emptyText: { fontSize: 18, fontWeight: '600', marginTop: 16, textAlign: 'center', }, emptySubtext: { fontSize: 14, textAlign: 'center', marginTop: 8, }, userContainer: { flexDirection: 'column', alignItems: 'flex-end', }, userEmail: { fontSize: 12, marginBottom: 4, }, logoutButton: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 10, paddingVertical: 6, borderRadius: 16, }, logoutText: { color: 'white', fontSize: 12, marginLeft: 4, fontWeight: '500', marginTop: 8, textAlign: 'center', }, buttonContainer: { position: 'absolute', bottom: 24, right: 24, }, sectionHeader: { paddingHorizontal: 20, paddingTop: 12, paddingBottom: 4, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', maxWidth: 800, alignSelf: 'center', width: '100%', }, sectionTitle: { fontSize: 18, fontWeight: '600', }, viewAllButton: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, }, viewAllText: { fontSize: 14, fontWeight: '500', }, });