From a8ccfab274f4271480fd35c00e5948824d3d8aad Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Sat, 16 May 2026 01:28:04 +0200 Subject: [PATCH] =?UTF-8?q?feat(native):=20chat=20v1.0=20=E2=80=94=20DM-on?= =?UTF-8?q?ly=20layout,=20search=20field,=20theme=20colors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes 2-tab Groups/DMs layout; Chat screen is now DM-only for v1.0. Groups tab state, rooms query, RoomCard/CreateRoomSheet imports removed. Replaces static title+create-button header with sticky search field (client-side filter on partnerName + lastMessage). No create-DM button added — /dm-new route does not exist yet (follow-up task). All #007AFF in chat.tsx replaced with colors.brandOrange. Adds chat.search_placeholder to de/en/fr locales. Tab-bar styles kept in makeStyles (dead code, v1.1 Groups comeback path). Co-Authored-By: Claude Sonnet 4.6 --- apps/rebreak-native/app/(app)/chat.tsx | 302 +++++++++---------------- apps/rebreak-native/locales/de.json | 3 +- apps/rebreak-native/locales/en.json | 3 +- apps/rebreak-native/locales/fr.json | 3 +- 4 files changed, 115 insertions(+), 196 deletions(-) diff --git a/apps/rebreak-native/app/(app)/chat.tsx b/apps/rebreak-native/app/(app)/chat.tsx index 6e7c45a..85e4f5c 100644 --- a/apps/rebreak-native/app/(app)/chat.tsx +++ b/apps/rebreak-native/app/(app)/chat.tsx @@ -4,6 +4,7 @@ import { Text, FlatList, TouchableOpacity, + TextInput, ActivityIndicator, Image, RefreshControl, @@ -15,8 +16,6 @@ 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 { useColors } from '../../lib/theme'; type DmConversation = { @@ -60,7 +59,7 @@ function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void } {conv.partnerName} - + {formatTime(conv.lastMessageAt, t('chat.just_now'))} @@ -97,19 +96,7 @@ export default function ChatScreen() { const router = useRouter(); const colors = useColors(); const styles = makeStyles(colors); - 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 [search, setSearch] = useState(''); const { data: convs = [], @@ -120,17 +107,14 @@ export default function ChatScreen() { 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 filtered = search.trim() + ? convs.filter((c) => + c.partnerName.toLowerCase().includes(search.toLowerCase()) || + c.lastMessage.toLowerCase().includes(search.toLowerCase()), + ) + : convs; const openDm = useCallback( (userId: string) => { @@ -143,120 +127,53 @@ export default function ChatScreen() { - {/* Header */} + {/* Search header */} - - {t('chat.title')} - {tab === 'groups' && ( - setCreateOpen(true)} - activeOpacity={0.7} - style={styles.createBtn} - > - + + + + {search.length > 0 && ( + setSearch('')} activeOpacity={0.7} hitSlop={8}> + )} - - {/* Tabs */} - - setTab('groups')} - activeOpacity={0.7} - style={[styles.tab, tab === 'groups' && styles.tabActive]} - > - - - {t('chat.groups')} - - - setTab('direct')} - activeOpacity={0.7} - 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); - }} + item.partnerId} + refreshControl={ + + } + ListEmptyComponent={ + loadingDms ? ( + + + + ) : ( + + + {t('chat.no_chats')} + + ) + } + renderItem={({ item }) => openDm(item.partnerId)} />} + contentContainerStyle={{ paddingBottom: 100 }} /> ); @@ -267,76 +184,27 @@ function makeStyles(colors: ReturnType) { container: { flex: 1, backgroundColor: colors.bg }, headerSection: { paddingHorizontal: 16, - paddingTop: 14, + paddingTop: 12, paddingBottom: 10, backgroundColor: colors.bg, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border, }, - titleRow: { + searchRow: { flexDirection: 'row', alignItems: 'center', - justifyContent: 'space-between', - }, - title: { - fontSize: 22, - fontFamily: 'Nunito_800ExtraBold', - color: colors.text, - }, - createBtn: { - width: 34, - height: 34, - borderRadius: 17, - backgroundColor: '#007AFF', - alignItems: 'center', - justifyContent: 'center', - }, - tabs: { - flexDirection: 'row', - marginTop: 12, backgroundColor: colors.surfaceElevated, borderRadius: 10, - padding: 3, + paddingHorizontal: 10, + paddingVertical: 8, }, - tab: { + searchIcon: { marginRight: 7 }, + searchInput: { 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: '#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', + fontSize: 14, + fontFamily: 'Nunito_500Medium', + color: colors.text, + paddingVertical: 0, }, emptyBox: { alignItems: 'center', @@ -403,7 +271,7 @@ function makeStyles(colors: ReturnType) { height: 20, paddingHorizontal: 6, borderRadius: 10, - backgroundColor: '#007AFF', + backgroundColor: colors.brandOrange, alignItems: 'center', justifyContent: 'center', marginLeft: 8, @@ -413,5 +281,53 @@ function makeStyles(colors: ReturnType) { 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', + }, }); } diff --git a/apps/rebreak-native/locales/de.json b/apps/rebreak-native/locales/de.json index 1222a0a..48bfe69 100644 --- a/apps/rebreak-native/locales/de.json +++ b/apps/rebreak-native/locales/de.json @@ -741,7 +741,8 @@ "approve": "Annehmen", "reject": "Ablehnen", "avatar_updated": "Gruppenbild aktualisiert", - "send": "Senden" + "send": "Senden", + "search_placeholder": "Konversationen durchsuchen…" }, "community": { "compose_placeholder": "Was bewegt dich gerade?", diff --git a/apps/rebreak-native/locales/en.json b/apps/rebreak-native/locales/en.json index 361b1f8..9e5e395 100644 --- a/apps/rebreak-native/locales/en.json +++ b/apps/rebreak-native/locales/en.json @@ -741,7 +741,8 @@ "approve": "Approve", "reject": "Reject", "avatar_updated": "Group photo updated", - "send": "Send" + "send": "Send", + "search_placeholder": "Search conversations…" }, "community": { "compose_placeholder": "What's on your mind?", diff --git a/apps/rebreak-native/locales/fr.json b/apps/rebreak-native/locales/fr.json index c92f74c..413ab14 100644 --- a/apps/rebreak-native/locales/fr.json +++ b/apps/rebreak-native/locales/fr.json @@ -741,7 +741,8 @@ "approve": "Accepter", "reject": "Refuser", "avatar_updated": "Photo du groupe mise à jour", - "send": "Envoyer" + "send": "Envoyer", + "search_placeholder": "Rechercher des conversations…" }, "community": { "compose_placeholder": "Qu'est-ce qui vous préoccupe en ce moment ?",