From 7a22474efdfe0d9637d539610ff9bc960973f865 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Thu, 14 May 2026 22:10:26 +0200 Subject: [PATCH] feat(chat): Jump-down-Button + Sprung-an-Text-Anfang + Vision-Issue raus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drei kleine UX-Fixes im Chat: 1. Jump-Down-Button (↓): Bei inverted FlatList erscheint rechts ueber der Eingabe ein blauer FAB, sobald man mehr als 250px von der neuesten Nachricht weg gescrollt ist. Tap → scrollToOffset(0) animated → wieder unten. Auto-hide wenn man unten ist. 2. Such-Sprung landet jetzt am TEXT-ANFANG der Treffer-Bubble: viewPosition 0.5 (Mitte) → 0 (Item-Top am Viewport-Top). Plus Retry-Folge (180/420/800ms) gegen Layout-Race bei langen Listen. Vorher musste man oft nochmal hoch scrollen um den Anfang zu sehen. onScrollToIndexFailed-Fallback genauso mit viewPosition 0. 3. issue.md: "Bilder: Claude Vision direkt nutzen" raus aus den offenen Punkten — ist durch Stufe E (Memory-Anhaenge, Read-Tool multi-modal) längst geloest. ARIA sieht Bilder echt. Folge-Etappen: Such-Sprung-Resilienz war Teil davon (mehrere Retries abgedeckt). Naechste Brocken: Doppel-Send-Haenger, AsyncStorage-Race, Offline-Queue mit Idempotenz. Co-Authored-By: Claude Opus 4.7 (1M context) --- android/src/screens/ChatScreen.tsx | 72 +++++++++++++++++++++++++----- issue.md | 1 - 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index b1cb1e2..f99600f 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -236,6 +236,7 @@ const ChatScreen: React.FC = () => { const [fullscreenImage, setFullscreenImage] = useState(null); const [memoryDetailId, setMemoryDetailId] = useState(null); const [inboxVisible, setInboxVisible] = useState(false); + const [showJumpDown, setShowJumpDown] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [searchVisible, setSearchVisible] = useState(false); const [searchIndex, setSearchIndex] = useState(0); // welcher Treffer aktiv ist @@ -1052,9 +1053,10 @@ const ChatScreen: React.FC = () => { }, [searchQuery]); // Bei Index-Wechsel zu der entsprechenden Bubble scrollen. - // FlatList ist `inverted` → viewPosition 0.5 (mitte) ist beim inverted-Render - // tatsaechlich die Mitte des sichtbaren Bereichs. Wir verzoegern minimal - // damit Layout sicher fertig ist. + // FlatList ist `inverted`. viewPosition 0 = Item-Top oben am Viewport → + // Treffer-Bubble liegt mit dem Anfang direkt oben sichtbar, kein + // weiteres Hochscrollen noetig. Plus mehrere Retries da Layout bei + // langen Listen zeitversetzt fertig wird. useEffect(() => { if (!searchMatchIds.length) return; const id = searchMatchIds[searchIndex]; @@ -1063,13 +1065,16 @@ const ChatScreen: React.FC = () => { if (idx < 0 || !flatListRef.current) return; const tryScroll = () => { try { - flatListRef.current?.scrollToIndex({ index: idx, animated: true, viewPosition: 0.5 }); + flatListRef.current?.scrollToIndex({ index: idx, animated: true, viewPosition: 0 }); } catch { // wird von onScrollToIndexFailed nochmal versucht } }; - // requestAnimationFrame statt setTimeout 0 — wartet auf naechsten Layout-Frame + // requestAnimationFrame fuer den ersten Versuch, dann setTimeout-Folge + // damit auch bei tiefen Indizes (viel ungelayoutete Items dazwischen) + // der Sprung am Ende sitzt. requestAnimationFrame(tryScroll); + [180, 420, 800].forEach(d => setTimeout(tryScroll, d)); }, [searchIndex, searchMatchIds, invertedMessages]); const activeSearchId = searchMatchIds[searchIndex] || ''; @@ -1726,15 +1731,27 @@ const ChatScreen: React.FC = () => { ref={flatListRef} inverted data={invertedMessages} + onScroll={(e) => { + // Bei inverted FlatList: contentOffset.y > 0 = weg von "unten" + // (= aelter scrollen). Wir zeigen den Jump-Down-Button ab ~250px. + const y = e.nativeEvent.contentOffset.y; + setShowJumpDown(y > 250); + }} + scrollEventThrottle={120} onScrollToIndexFailed={(info) => { // FlatList kennt das Item-Layout noch nicht. Zuerst grob in die - // Naehe scrollen (Average-Item-Hoehe-Schaetzung), dann nach 250ms - // praezise nochmal versuchen. + // Naehe scrollen (Average-Item-Hoehe-Schaetzung), dann mehrfach + // praezise nachsetzen — bei langem Chat braucht's manchmal mehrere + // Runden bis die Layouts gemessen sind. const offset = info.averageItemLength * info.index; try { flatListRef.current?.scrollToOffset({ offset, animated: false }); } catch {} - setTimeout(() => { - try { flatListRef.current?.scrollToIndex({ index: info.index, animated: true, viewPosition: 0.5 }); } catch {} - }, 250); + // viewPosition 0 = Item-Top oben am Viewport → Stefan landet am + // Text-Anfang der Bubble, nicht in der Mitte oder am Ende. + [120, 320, 600].forEach(delay => { + setTimeout(() => { + try { flatListRef.current?.scrollToIndex({ index: info.index, animated: true, viewPosition: 0 }); } catch {} + }, delay); + }); }} keyExtractor={item => item.id} renderItem={renderMessage} @@ -1801,6 +1818,24 @@ const ChatScreen: React.FC = () => { )} + {/* Jump-to-Bottom-Button — erscheint wenn man weg von der neuesten + Nachricht gescrollt hat. Bei inverted FlatList ist scrollToOffset + 0 == neueste Nachricht visuell unten. */} + {showJumpDown && ( + { + try { + flatListRef.current?.scrollToOffset({ offset: 0, animated: true }); + } catch {} + setShowJumpDown(false); + }} + > + {'↓'} + + )} + {/* Eingabebereich */} {/* Datei-Buttons */} @@ -2341,6 +2376,23 @@ const styles = StyleSheet.create({ color: '#555570', fontSize: 10, }, + jumpDownBtn: { + position: 'absolute', + right: 16, + bottom: 80, + width: 44, + height: 44, + borderRadius: 22, + backgroundColor: '#0096FF', + alignItems: 'center', + justifyContent: 'center', + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.4, + shadowRadius: 4, + elevation: 5, + zIndex: 100, + }, bubbleTrash: { position: 'absolute', top: 4, diff --git a/issue.md b/issue.md index c3da567..9ca3d02 100644 --- a/issue.md +++ b/issue.md @@ -348,7 +348,6 @@ Skills mit Tool-Use. - [ ] Custom-Wake-Word-Upload via Diagnostic (eigene .onnx-Files ohne App-Rebuild) ### Architektur -- [ ] Bilder: Claude Vision direkt nutzen (aktuell nur Dateipfad an ARIA) - [ ] Diagnostic: System-Info Tab (Container-Status, Disk, RAM, CPU) - [ ] RVS Zombie-Connections endgueltig loesen - [ ] Gamebox: kleine Web-Oberflaeche fuer Credentials/Server-Config oder zentral aus Diagnostic per RVS push