From 708eac51c03f5fdc00e9e03922c1248c63c7adfa Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Fri, 22 May 2026 19:53:31 +0200 Subject: [PATCH] =?UTF-8?q?fix(chat):=20Listen-Spinner-H=C3=A4nger=20+=20A?= =?UTF-8?q?uto-Scroll=20bei=20Bild-Nachrichten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1 — Chat-Liste: RefreshControl nutzte React-Querys `isRefetching`, das bei JEDEM Background-Refetch (focus-/stale-getriggert) true wird → nach Zurück-Navigation hing der Pull-to-Refresh-Spinner endlos. Fix: eigener `userRefreshing`-State, nur bei explizitem Pull-to-Refresh true, im finally zurückgesetzt. Bug 2 — Conversation scrollte nicht bis zur letzten Nachricht, wenn die ein Bild war: onContentSizeChange-scrollToEnd feuerte vor dem Bild-Load. Fix: ChatBubble bekommt onImageLoad-Callback, die letzte Bild-Nachricht triggert nach dem Laden erneut scrollToBottom. Co-Authored-By: Claude Opus 4.7 --- apps/rebreak-native/app/(app)/chat.tsx | 15 ++++++++++++--- apps/rebreak-native/app/dm.tsx | 15 ++++++++++++--- .../rebreak-native/components/chat/ChatBubble.tsx | 3 +++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/rebreak-native/app/(app)/chat.tsx b/apps/rebreak-native/app/(app)/chat.tsx index 65c35d8..b003632 100644 --- a/apps/rebreak-native/app/(app)/chat.tsx +++ b/apps/rebreak-native/app/(app)/chat.tsx @@ -94,11 +94,11 @@ export default function ChatScreen() { const colors = useColors(); const styles = makeStyles(colors); const [search, setSearch] = useState(''); + const [userRefreshing, setUserRefreshing] = useState(false); const { data: convs = [], isLoading: loadingDms, - isRefetching: refetchingDms, refetch: refetchDms, } = useQuery({ queryKey: ['dm-conversations'], @@ -106,6 +106,15 @@ export default function ChatScreen() { 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()) || @@ -152,8 +161,8 @@ export default function ChatScreen() { keyExtractor={(item) => item.partnerId} refreshControl={ } diff --git a/apps/rebreak-native/app/dm.tsx b/apps/rebreak-native/app/dm.tsx index 6406012..978e9ac 100644 --- a/apps/rebreak-native/app/dm.tsx +++ b/apps/rebreak-native/app/dm.tsx @@ -93,6 +93,7 @@ export default function DmScreen() { setPartner(null); partnerRef.current = null; setReplyTo(null); + isInitialLoad.current = true; }, [userId]); // Lade DM-History — staleTime:0 erzwingt immer frischen Fetch (kein Cache-Hit-Bug) @@ -174,12 +175,20 @@ export default function DmScreen() { ); useDmRealtime(userId, onDmInsert, !!myUserId); + const isInitialLoad = useRef(true); + + const scrollToBottom = useCallback((animated: boolean) => { + setTimeout(() => flatRef.current?.scrollToEnd({ animated }), 80); + }, []); + // Auto-Scroll bei neuen Messages useEffect(() => { if (messages.length > 0) { - requestAnimationFrame(() => flatRef.current?.scrollToEnd({ animated: true })); + const animated = !isInitialLoad.current; + isInitialLoad.current = false; + scrollToBottom(animated); } - }, [messages.length]); + }, [messages.length, scrollToBottom]); async function handleSend(payload: SendPayload) { if (sending) return; @@ -310,12 +319,12 @@ export default function DmScreen() { onReply={startReply} onLike={toggleLike} onOpenImage={() => {}} + onImageLoad={index === messages.length - 1 ? () => scrollToBottom(false) : undefined} /> )} keyExtractor={(m) => m.id} contentContainerStyle={{ paddingTop: 12, paddingBottom: 8 }} showsVerticalScrollIndicator={false} - onContentSizeChange={() => flatRef.current?.scrollToEnd({ animated: false })} /> )} diff --git a/apps/rebreak-native/components/chat/ChatBubble.tsx b/apps/rebreak-native/components/chat/ChatBubble.tsx index 5227264..a218b08 100644 --- a/apps/rebreak-native/components/chat/ChatBubble.tsx +++ b/apps/rebreak-native/components/chat/ChatBubble.tsx @@ -47,6 +47,7 @@ type Props = { onReply: (msg: ChatMsg) => void; onLike: (msg: ChatMsg) => void; onOpenImage: (url: string) => void; + onImageLoad?: () => void; }; function formatTime(ts: string) { @@ -75,6 +76,7 @@ export function ChatBubble({ onReply, onLike, onOpenImage, + onImageLoad, }: Props) { const { t } = useTranslation(); const colors = useColors(); @@ -201,6 +203,7 @@ export function ChatBubble({ contentFit="cover" cachePolicy="memory-disk" transition={200} + onLoad={onImageLoad ? () => onImageLoad() : undefined} /> {isImageOnly && (