diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index 78977f0..b0a6df4 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -1415,7 +1415,10 @@ const ChatScreen: React.FC = () => { // verfuegbar wird (z.B. weil setMessages mitten in der Sequenz die // FlatList re-rendert). const scrollRetryCount = useRef(0); - const MAX_SCROLL_RETRIES = 3; + // 6 Retries: bei weiten Spruengen (Suche auf Bubble #150 von Position 0) + // kann FlatList mehrere Iterationen brauchen bis die Items in der Naehe + // gemessen sind. Vorher 3 = vorzeitig aufgegeben. + const MAX_SCROLL_RETRIES = 6; const clearPendingScrollRetry = () => { if (pendingScrollRetry.current) { clearTimeout(pendingScrollRetry.current); @@ -1459,13 +1462,18 @@ const ChatScreen: React.FC = () => { // 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. + // onLayout) + dynamischer Fallback aus dem Mittel der bisher + // gemessenen Items. Beim Cold-Start gibt's nur 10 Messungen (die + // neuesten unten in der invertierten Liste) — der Mittel daraus ist + // immer noch besser als die Pauschal-150. + const measured = Array.from(itemHeights.current.values()); + const dynamicAvg = measured.length >= 5 + ? measured.reduce((a, b) => a + b, 0) / measured.length + : AVG_BUBBLE_HEIGHT; let preOffset = 0; const inv = invertedMessagesRef.current; for (let i = 0; i < idx; i++) { - preOffset += itemHeights.current.get(inv[i].id) || AVG_BUBBLE_HEIGHT; + preOffset += itemHeights.current.get(inv[i].id) || dynamicAvg; } try { flatListRef.current?.scrollToOffset({ @@ -1473,9 +1481,10 @@ const ChatScreen: React.FC = () => { animated: false, }); } catch {} - // Nach kurzer Render-Pause praezise nachsetzen. 200 ms statt 80 ms — - // bei Cold-Start braucht FlatList laenger fuer das Item-Layout, das - // war Stefans „erst beim zweiten Versuch klappt's"-Bug. + // Nach Render-Pause praezise nachsetzen. 350 ms — bei weiten Spruengen + // (Pre-Scroll 5000+ px) braucht FlatList Zeit die Items dort zu + // mounten und onLayout zu feuern. Zu kurz → averageItemLength im + // Failed-Handler basiert noch auf den falschen Items. requestAnimationFrame(() => { setTimeout(() => { try { @@ -1483,7 +1492,7 @@ const ChatScreen: React.FC = () => { } catch { // onScrollToIndexFailed-Handler uebernimmt den Fallback } - }, 200); + }, 350); }); }, [searchIndex, searchMatchIds]); @@ -2411,19 +2420,19 @@ const ChatScreen: React.FC = () => { transparent onRequestClose={() => setThoughtsVisible(false)} > - setThoughtsVisible(false)} - > - {/* View statt TouchableOpacity, sonst konsumiert das die Touch- - Events und die FlatList laesst sich nicht scrollen. - onStartShouldSetResponder={true} blockt aber die Propagation - an das aeussere TouchableOpacity (close-on-tap-outside). */} + + {/* Tap-Outside-Bereich oberhalb des Sheets — separater Touchable + damit das Sheet-View NICHT als Responder den FlatList-Scroll + blockiert. Frueher hatten wir den ganzen Hintergrund als + TouchableOpacity + inneren View mit onStartShouldSetResponder + = das hat alle Touch-Events kassiert. */} + setThoughtsVisible(false)} + /> true} - onResponderTerminationRequest={() => false} > {/* Drag-Indicator */} @@ -2504,7 +2513,7 @@ const ChatScreen: React.FC = () => { /> )} - + {/* Notizen-Inbox — Listet alle Memories aus dem aktuellen Chat (Special-Bubbles).