diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index 412988c..d99639a 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -1383,13 +1383,17 @@ const ChatScreen: React.FC = () => { // Such-Treffer: alle Message-IDs die zur Query passen, in chronologischer // Reihenfolge (aelteste zuerst). Bei Query-Change resetten wir den Index. + // WICHTIG: nur in chatVisibleMessages suchen — Spezial-Bubbles (Memory/ + // Skill/Trigger) sind im Chat-Stream nicht sichtbar und Treffer auf die + // wuerden zu „ID nicht im FlatList → findIndex=-1 → kein Scroll"-Fail + // fuehren (Cessna in einer Memory-Bubble → springt zur falschen Stelle). const searchMatchIds = useMemo(() => { const q = searchQuery.trim().toLowerCase(); if (!q) return [] as string[]; - return messages + return chatVisibleMessages .filter(m => (m.text || '').toLowerCase().includes(q)) .map(m => m.id); - }, [messages, searchQuery]); + }, [chatVisibleMessages, searchQuery]); useEffect(() => { setSearchIndex(0); @@ -1425,11 +1429,11 @@ const ChatScreen: React.FC = () => { // Den aktuellen Snapshot von invertedMessages holen wir via Ref. const invertedMessagesRef = useRef(invertedMessages); invertedMessagesRef.current = invertedMessages; - // Schaetzwert fuer durchschnittliche Bubble-Hoehe. Wird fuer den - // ersten Pre-Scroll genutzt wenn FlatList noch keine Layouts hat. - // 150 px ist Mittel zwischen kurzen Voice-Bubbles (~70) und langen - // ARIA-Antworten (~250). - const AVG_BUBBLE_HEIGHT = 150; + // Cache fuer echte Bubble-Hoehen, gefuettert per onLayout in + // renderMessage. Wird beim Pre-Scroll genutzt damit der grobe Sprung + // praezise landet (statt mit dem 150-px-Pauschalwert weit daneben). + const itemHeights = useRef>(new Map()); + const AVG_BUBBLE_HEIGHT = 150; // Fallback fuer noch nicht gemessene Items useEffect(() => { if (!searchMatchIds.length) { lastSearchScrollKey.current = ''; @@ -1447,15 +1451,22 @@ const ChatScreen: React.FC = () => { clearPendingScrollRetry(); const idx = invertedMessagesRef.current.findIndex(m => m.id === id); if (idx < 0 || !flatListRef.current) return; - // Pre-Scroll: erst grob mit konstanter Hoehe in die Naehe springen. - // Damit hat FlatList ueberhaupt die Chance die Layouts der Bubbles in - // der Naehe zu rendern — sonst basiert info.averageItemLength im - // Failed-Handler nur auf den ersten 10 Items (Default initialNumToRender) - // und liefert beim ersten Such-Versuch nach App-Start einen voellig - // falschen Sprung. + // Pre-Scroll: erst grob in die Naehe springen, damit FlatList die + // Bubbles in der Umgebung ueberhaupt rendert (sonst basiert + // averageItemLength im Failed-Handler nur auf den ersten ~10 Items + // und liefert einen voellig falschen Sprung). + // Offset = Summe echter Hoehen (aus itemHeights-Cache, gefuettert per + // onLayout) + Fallback AVG fuer noch nicht gemessene. Bei „cold start" + // ist der Cache leer → AVG fuer alle → grob. Beim zweiten Such-Versuch + // sind die Bubbles in der Naehe gemessen → genauer. + let preOffset = 0; + const inv = invertedMessagesRef.current; + for (let i = 0; i < idx; i++) { + preOffset += itemHeights.current.get(inv[i].id) || AVG_BUBBLE_HEIGHT; + } try { flatListRef.current?.scrollToOffset({ - offset: idx * AVG_BUBBLE_HEIGHT, + offset: preOffset, animated: false, }); } catch {} @@ -1885,7 +1896,15 @@ const ChatScreen: React.FC = () => { } return ( - + { + // Echte Hoehe in Cache speichern — Pre-Scroll der Suche nutzt + // die summierten Cache-Werte fuer praezisen Sprung. Bei + // unbekannten Items faellt's auf AVG_BUBBLE_HEIGHT zurueck. + itemHeights.current.set(item.id, e.nativeEvent.layout.height); + }} + > {/* Anhang-Vorschau */} {item.attachments?.map((att, idx) => (