fix(chat): Gedanken-Stream scrollt jetzt + Suche praeziser
(1) Gedanken-Stream Modal: vorheriger Fix mit onStartShouldSetResponder
war falsch — der View wurde komplett zum Responder, die FlatList drin
bekam null Touch-Events. Jetzt: outer View ohne Touch-Handling, ein
separates TouchableOpacity-Element oberhalb des Sheets nur fuer den
Tap-Outside-Close. Sheet-View ist plain View → FlatList scrollt frei.
(2) Such-Sprung praeziser: drei Verbesserungen
- MAX_SCROLL_RETRIES 3 → 6: bei weiten Spruengen (Bubble #150 von
Position 0) braucht FlatList mehrere Iterationen bis die Items in
der Naehe gemessen sind
- Pre-Scroll-Offset: Fallback fuer unmeasured Items ist jetzt der
dynamische Mittel der bisher gemessenen Items (statt Pauschal-150).
Beim Cold-Start sind nur die untersten 10 gemessen, aber deren
Mittel ist immer noch eine bessere Schaetzung
- Render-Pause nach Pre-Scroll 200 → 350 ms: bei weiten Spruengen
braucht FlatList Zeit die Items zu mounten und onLayout zu feuern
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1415,7 +1415,10 @@ const ChatScreen: React.FC = () => {
|
|||||||
// verfuegbar wird (z.B. weil setMessages mitten in der Sequenz die
|
// verfuegbar wird (z.B. weil setMessages mitten in der Sequenz die
|
||||||
// FlatList re-rendert).
|
// FlatList re-rendert).
|
||||||
const scrollRetryCount = useRef<number>(0);
|
const scrollRetryCount = useRef<number>(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 = () => {
|
const clearPendingScrollRetry = () => {
|
||||||
if (pendingScrollRetry.current) {
|
if (pendingScrollRetry.current) {
|
||||||
clearTimeout(pendingScrollRetry.current);
|
clearTimeout(pendingScrollRetry.current);
|
||||||
@@ -1459,13 +1462,18 @@ const ChatScreen: React.FC = () => {
|
|||||||
// averageItemLength im Failed-Handler nur auf den ersten ~10 Items
|
// averageItemLength im Failed-Handler nur auf den ersten ~10 Items
|
||||||
// und liefert einen voellig falschen Sprung).
|
// und liefert einen voellig falschen Sprung).
|
||||||
// Offset = Summe echter Hoehen (aus itemHeights-Cache, gefuettert per
|
// Offset = Summe echter Hoehen (aus itemHeights-Cache, gefuettert per
|
||||||
// onLayout) + Fallback AVG fuer noch nicht gemessene. Bei „cold start"
|
// onLayout) + dynamischer Fallback aus dem Mittel der bisher
|
||||||
// ist der Cache leer → AVG fuer alle → grob. Beim zweiten Such-Versuch
|
// gemessenen Items. Beim Cold-Start gibt's nur 10 Messungen (die
|
||||||
// sind die Bubbles in der Naehe gemessen → genauer.
|
// 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;
|
let preOffset = 0;
|
||||||
const inv = invertedMessagesRef.current;
|
const inv = invertedMessagesRef.current;
|
||||||
for (let i = 0; i < idx; i++) {
|
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 {
|
try {
|
||||||
flatListRef.current?.scrollToOffset({
|
flatListRef.current?.scrollToOffset({
|
||||||
@@ -1473,9 +1481,10 @@ const ChatScreen: React.FC = () => {
|
|||||||
animated: false,
|
animated: false,
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
// Nach kurzer Render-Pause praezise nachsetzen. 200 ms statt 80 ms —
|
// Nach Render-Pause praezise nachsetzen. 350 ms — bei weiten Spruengen
|
||||||
// bei Cold-Start braucht FlatList laenger fuer das Item-Layout, das
|
// (Pre-Scroll 5000+ px) braucht FlatList Zeit die Items dort zu
|
||||||
// war Stefans „erst beim zweiten Versuch klappt's"-Bug.
|
// mounten und onLayout zu feuern. Zu kurz → averageItemLength im
|
||||||
|
// Failed-Handler basiert noch auf den falschen Items.
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
@@ -1483,7 +1492,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
} catch {
|
} catch {
|
||||||
// onScrollToIndexFailed-Handler uebernimmt den Fallback
|
// onScrollToIndexFailed-Handler uebernimmt den Fallback
|
||||||
}
|
}
|
||||||
}, 200);
|
}, 350);
|
||||||
});
|
});
|
||||||
}, [searchIndex, searchMatchIds]);
|
}, [searchIndex, searchMatchIds]);
|
||||||
|
|
||||||
@@ -2411,19 +2420,19 @@ const ChatScreen: React.FC = () => {
|
|||||||
transparent
|
transparent
|
||||||
onRequestClose={() => setThoughtsVisible(false)}
|
onRequestClose={() => setThoughtsVisible(false)}
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<View style={{flex:1, backgroundColor:'rgba(0,0,0,0.5)', justifyContent:'flex-end'}}>
|
||||||
style={{flex:1, backgroundColor:'rgba(0,0,0,0.5)', justifyContent:'flex-end'}}
|
{/* Tap-Outside-Bereich oberhalb des Sheets — separater Touchable
|
||||||
activeOpacity={1}
|
damit das Sheet-View NICHT als Responder den FlatList-Scroll
|
||||||
onPress={() => setThoughtsVisible(false)}
|
blockiert. Frueher hatten wir den ganzen Hintergrund als
|
||||||
>
|
TouchableOpacity + inneren View mit onStartShouldSetResponder
|
||||||
{/* View statt TouchableOpacity, sonst konsumiert das die Touch-
|
= das hat alle Touch-Events kassiert. */}
|
||||||
Events und die FlatList laesst sich nicht scrollen.
|
<TouchableOpacity
|
||||||
onStartShouldSetResponder={true} blockt aber die Propagation
|
style={{flex:1}}
|
||||||
an das aeussere TouchableOpacity (close-on-tap-outside). */}
|
activeOpacity={1}
|
||||||
|
onPress={() => setThoughtsVisible(false)}
|
||||||
|
/>
|
||||||
<View
|
<View
|
||||||
style={{height:'60%', backgroundColor:'#0D0D1A', borderTopLeftRadius:16, borderTopRightRadius:16}}
|
style={{height:'60%', backgroundColor:'#0D0D1A', borderTopLeftRadius:16, borderTopRightRadius:16}}
|
||||||
onStartShouldSetResponder={() => true}
|
|
||||||
onResponderTerminationRequest={() => false}
|
|
||||||
>
|
>
|
||||||
{/* Drag-Indicator */}
|
{/* Drag-Indicator */}
|
||||||
<View style={{alignItems:'center', paddingTop:8, paddingBottom:4}}>
|
<View style={{alignItems:'center', paddingTop:8, paddingBottom:4}}>
|
||||||
@@ -2504,7 +2513,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* Notizen-Inbox — Listet alle Memories aus dem aktuellen Chat (Special-Bubbles).
|
{/* Notizen-Inbox — Listet alle Memories aus dem aktuellen Chat (Special-Bubbles).
|
||||||
|
|||||||
Reference in New Issue
Block a user