import { useCallback, useState } from 'react'; import { View, Text, FlatList, TouchableOpacity, TextInput, ActivityIndicator, 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 { UserAvatar } from '../../components/UserAvatar'; import { useColors } 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 colors = useColors(); const styles = makeStyles(colors); const hasUnread = conv.unreadCount > 0; return ( {conv.partnerName} {formatTime(conv.lastMessageAt, t('chat.just_now'))} {conv.isOwn ? `${t('chat.you')} ` : ''} {conv.lastMessage} {hasUnread && ( {conv.unreadCount > 99 ? '99+' : conv.unreadCount} )} ); } export default function ChatScreen() { const { t } = useTranslation(); const router = useRouter(); const colors = useColors(); const styles = makeStyles(colors); const [search, setSearch] = useState(''); const [userRefreshing, setUserRefreshing] = useState(false); const { data: convs = [], isLoading: loadingDms, refetch: refetchDms, } = useQuery({ queryKey: ['dm-conversations'], queryFn: () => apiFetch('/api/chat/dm-conversations'), staleTime: 30_000, }); const handleRefresh = useCallback(async () => { setUserRefreshing(true); try { await refetchDms(); } finally { setUserRefreshing(false); } }, [refetchDms]); const filtered = search.trim() ? convs.filter((c) => c.partnerName.toLowerCase().includes(search.toLowerCase()) || c.lastMessage.toLowerCase().includes(search.toLowerCase()), ) : convs; const openDm = useCallback( (userId: string) => { router.push(`/dm?userId=${userId}`); }, [router], ); return ( {/* Search header */} {search.length > 0 && ( setSearch('')} activeOpacity={0.7} hitSlop={8}> )} item.partnerId} refreshControl={ } ListEmptyComponent={ loadingDms ? ( ) : ( {t('chat.no_chats')} ) } renderItem={({ item }) => openDm(item.partnerId)} />} contentContainerStyle={{ paddingBottom: 100 }} /> ); } function makeStyles(colors: ReturnType) { return StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bg }, headerSection: { paddingHorizontal: 16, paddingTop: 12, paddingBottom: 10, backgroundColor: colors.bg, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border, }, searchRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.surfaceElevated, borderRadius: 999, paddingHorizontal: 16, paddingVertical: 8, }, searchIcon: { marginRight: 8 }, searchInput: { flex: 1, fontSize: 14, fontFamily: 'Nunito_500Medium', color: colors.text, paddingVertical: 0, }, emptyBox: { alignItems: 'center', justifyContent: 'center', paddingVertical: 60, paddingHorizontal: 32, }, emptyText: { fontSize: 13, fontFamily: 'Nunito_600SemiBold', color: colors.textMuted, marginTop: 12, }, dmRow: { flexDirection: 'row', alignItems: 'center', gap: 12, paddingHorizontal: 16, paddingVertical: 12, backgroundColor: colors.bg, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border, minHeight: 68, }, dmAvatar: { width: 48, height: 48, borderRadius: 24, backgroundColor: colors.surfaceElevated, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', marginRight: 12, flexShrink: 0, }, dmAvatarImg: { width: 48, height: 48 }, dmAvatarInitials: { fontSize: 15, fontFamily: 'Nunito_700Bold', color: colors.textMuted, }, dmInfo: { flex: 1, minWidth: 0 }, dmHeaderRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, dmName: { fontSize: 15, fontFamily: 'Nunito_700Bold', color: colors.text, flexShrink: 1, marginRight: 6, }, dmTime: { fontSize: 11, fontFamily: 'Nunito_500Medium' }, 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: colors.brandOrange, alignItems: 'center', justifyContent: 'center', marginLeft: 8, }, unreadBadgeText: { fontSize: 10, fontFamily: 'Nunito_700Bold', color: '#fff', }, // Kept for v1.1 Groups comeback — tab styles no longer rendered tabs: { flexDirection: 'row', marginTop: 12, backgroundColor: colors.surfaceElevated, borderRadius: 10, padding: 3, }, tab: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 7, borderRadius: 8, }, tabActive: { backgroundColor: colors.surface, shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 2, shadowOffset: { width: 0, height: 1 }, }, tabText: { fontSize: 12, fontFamily: 'Nunito_600SemiBold', color: colors.textMuted, marginLeft: 5, }, tabTextActive: { color: colors.brandOrange, fontFamily: 'Nunito_700Bold', }, tabBadge: { minWidth: 16, height: 16, borderRadius: 8, backgroundColor: colors.brandOrange, paddingHorizontal: 4, alignItems: 'center', justifyContent: 'center', marginLeft: 5, }, tabBadgeText: { fontSize: 9, fontFamily: 'Nunito_700Bold', color: '#fff', }, }); }