import { useState, useCallback } from 'react'; import { View, Text, FlatList, Pressable, ActivityIndicator, Image, RefreshControl, StyleSheet, } from 'react-native'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { apiFetch } from '../../lib/api'; import { AppHeader } from '../../components/AppHeader'; import { RoomCard, type Room } from '../../components/chat/RoomCard'; import { CreateRoomSheet } from '../../components/chat/CreateRoomSheet'; import { colors } from '../../lib/theme'; type DmConversation = { partnerId: string; partnerName: string; partnerAvatar: string | null; lastMessage: string; lastMessageAt: string; unreadCount: number; isOwn: boolean; }; function formatTime(ts: string, justNowLabel: string): string { const diff = Date.now() - new Date(ts).getTime(); if (diff < 60_000) return justNowLabel; if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m`; if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h`; return new Date(ts).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); } function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void }) { const { t } = useTranslation(); const hasUnread = conv.unreadCount > 0; return ( {({ pressed }) => ( {conv.partnerAvatar ? ( ) : ( {conv.partnerName.slice(0, 2).toUpperCase()} )} {conv.partnerName} {formatTime(conv.lastMessageAt, t('chat.just_now'))} {conv.isOwn ? t('chat.you') : ''} {conv.lastMessage} {hasUnread && ( {conv.unreadCount} )} )} ); } export default function ChatScreen() { const { t } = useTranslation(); const router = useRouter(); const [tab, setTab] = useState<'groups' | 'direct'>('groups'); const [createOpen, setCreateOpen] = useState(false); const { data: rooms = [], isLoading: loadingRooms, isRefetching: refetchingRooms, refetch: refetchRooms, } = useQuery({ queryKey: ['chat-rooms'], queryFn: () => apiFetch('/api/chat/rooms'), staleTime: 30_000, }); const { data: convs = [], isLoading: loadingDms, isRefetching: refetchingDms, refetch: refetchDms, } = useQuery({ queryKey: ['dm-conversations'], queryFn: () => apiFetch('/api/chat/dm-conversations'), staleTime: 30_000, enabled: tab === 'direct', }); const unreadDms = convs.reduce((s, c) => s + (c.unreadCount ?? 0), 0); const openRoom = useCallback( (roomId: string) => { router.push(`/room?roomId=${roomId}`); }, [router], ); const openDm = useCallback( (userId: string) => { router.push(`/dm?userId=${userId}`); }, [router], ); return ( {/* Header */} {t('chat.title')} {tab === 'groups' && ( setCreateOpen(true)} style={({ pressed }) => [styles.createBtn, { opacity: pressed ? 0.7 : 1 }]} > )} {/* Tabs */} setTab('groups')} style={[styles.tab, tab === 'groups' && styles.tabActive]} > {t('chat.groups')} setTab('direct')} style={[styles.tab, tab === 'direct' && styles.tabActive]} > {t('chat.direct')} {unreadDms > 0 && ( {unreadDms} )} {tab === 'groups' ? ( item.id} refreshControl={ } ListEmptyComponent={ loadingRooms ? ( ) : ( {t('chat.no_rooms')} ) } renderItem={({ item }) => openRoom(item.id)} />} contentContainerStyle={{ paddingBottom: 100 }} /> ) : ( item.partnerId} refreshControl={ } ListEmptyComponent={ loadingDms ? ( ) : ( {t('chat.no_chats')} ) } renderItem={({ item }) => openDm(item.partnerId)} />} contentContainerStyle={{ paddingBottom: 100 }} /> )} setCreateOpen(false)} onCreated={(room) => { refetchRooms(); openRoom(room.id); }} /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fafafa' }, headerSection: { paddingHorizontal: 16, paddingTop: 14, paddingBottom: 10, backgroundColor: '#fff', borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#e5e5e5', }, titleRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, title: { fontSize: 22, fontFamily: 'Nunito_800ExtraBold', color: '#171717', }, createBtn: { width: 34, height: 34, borderRadius: 17, backgroundColor: '#007AFF', alignItems: 'center', justifyContent: 'center', }, tabs: { flexDirection: 'row', marginTop: 12, backgroundColor: '#f5f5f5', borderRadius: 10, padding: 3, }, tab: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 7, borderRadius: 8, }, tabActive: { backgroundColor: '#fff', shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 2, shadowOffset: { width: 0, height: 1 }, }, tabText: { fontSize: 12, fontFamily: 'Nunito_600SemiBold', color: '#737373', marginLeft: 5, }, tabTextActive: { color: '#007AFF', fontFamily: 'Nunito_700Bold', }, tabBadge: { minWidth: 16, height: 16, borderRadius: 8, backgroundColor: '#007AFF', paddingHorizontal: 4, alignItems: 'center', justifyContent: 'center', marginLeft: 5, }, tabBadgeText: { fontSize: 9, fontFamily: 'Nunito_700Bold', color: '#fff', }, emptyBox: { alignItems: 'center', justifyContent: 'center', paddingVertical: 60, paddingHorizontal: 32, }, emptyText: { fontSize: 13, fontFamily: 'Nunito_600SemiBold', color: '#a3a3a3', marginTop: 12, }, // DM row styles dmRow: { width: '100%', flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 11, backgroundColor: '#fff', borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#f5f5f5', }, dmAvatar: { width: 42, height: 42, borderRadius: 21, backgroundColor: '#e5e5e5', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', marginRight: 10, }, dmAvatarImg: { width: 42, height: 42 }, dmAvatarInitials: { fontSize: 13, fontFamily: 'Nunito_700Bold', color: '#525252', }, dmInfo: { flex: 1, minWidth: 0 }, dmHeaderRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, dmName: { fontSize: 14, fontFamily: 'Nunito_700Bold', color: '#171717', flexShrink: 1, marginRight: 6, }, dmTime: { fontSize: 11, fontFamily: 'Nunito_600SemiBold' }, dmBottomRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginTop: 2, }, dmLast: { fontSize: 12, flex: 1 }, unreadBadge: { minWidth: 20, height: 20, paddingHorizontal: 6, borderRadius: 10, backgroundColor: '#007AFF', alignItems: 'center', justifyContent: 'center', marginLeft: 8, }, unreadBadgeText: { fontSize: 10, fontFamily: 'Nunito_700Bold', color: '#fff', }, });