From 5cf8cab5bd17d6334efd533619235f784e9c7ee9 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Mon, 11 May 2026 23:35:02 +0200 Subject: [PATCH] feat: App-Chat-Suche mit Next/Prev + Diagnostic Sprachausgabe-Layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App Chat-Suche umgebaut von Filter zu Highlight+Navigation Vorher: searchQuery filtert die FlatList, zeigt nur Treffer. Jetzt: Suche filtert NICHT mehr, alle Nachrichten bleiben sichtbar. Treffer wird gelb (FFD60A) umrandet, FlatList scrollt automatisch dorthin. - Suchleiste: Input + Counter "N/M" + ▲ + ▼ + ✕ - ▲ / ▼ navigieren chronologisch durch alle Matches (zyklisch) - searchMatchIds via useMemo, searchIndex separates State - scrollToIndex mit viewPosition: 0.4 (Treffer landet im oberen Drittel) - onScrollToIndexFailed Fallback nach 200ms (Layout noch nicht fertig) Diagnostic Sprachausgabe-Layout Export/Import-Buttons wandern aus dem Section-Header in den Details-Block neben "Anwenden" (Stefan's Wunsch). Header zeigt nur noch den Titel. File-Input bleibt versteckt im Section-Top, wird vom neuen Button-Block unten ueber click() getriggert. Co-Authored-By: Claude Opus 4.7 (1M context) --- android/src/screens/ChatScreen.tsx | 78 ++++++++++++++++++++++++++++-- diagnostic/index.html | 25 +++++----- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index be480cd..b254f64 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -201,6 +201,7 @@ const ChatScreen: React.FC = () => { const [fullscreenImage, setFullscreenImage] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [searchVisible, setSearchVisible] = useState(false); + const [searchIndex, setSearchIndex] = useState(0); // welcher Treffer aktiv ist const [pendingAttachments, setPendingAttachments] = useState<{file: any, isPhoto: boolean}[]>([]); const [agentActivity, setAgentActivity] = useState<{activity: string, tool: string}>({activity: 'idle', tool: ''}); // Service-Status (Gamebox: F5-TTS / Whisper Lade-Status) + Banner-Sichtbarkeit @@ -892,6 +893,43 @@ const ChatScreen: React.FC = () => { // Inverted FlatList: neueste Nachrichten unten, kein manuelles Scrollen noetig const invertedMessages = useMemo(() => [...messages].reverse(), [messages]); + // Such-Treffer: alle Message-IDs die zur Query passen, in chronologischer + // Reihenfolge (aelteste zuerst). Bei Query-Change resetten wir den Index. + const searchMatchIds = useMemo(() => { + const q = searchQuery.trim().toLowerCase(); + if (!q) return [] as string[]; + return messages + .filter(m => (m.text || '').toLowerCase().includes(q)) + .map(m => m.id); + }, [messages, searchQuery]); + + useEffect(() => { + setSearchIndex(0); + }, [searchQuery]); + + // Bei Index-Wechsel zu der entsprechenden Bubble scrollen + useEffect(() => { + if (!searchMatchIds.length) return; + const id = searchMatchIds[searchIndex]; + if (!id) return; + // invertedMessages → index in der angezeigten Liste finden + const idx = invertedMessages.findIndex(m => m.id === id); + if (idx < 0 || !flatListRef.current) return; + try { + flatListRef.current.scrollToIndex({ index: idx, animated: true, viewPosition: 0.4 }); + } catch {} + }, [searchIndex, searchMatchIds, invertedMessages]); + + const activeSearchId = searchMatchIds[searchIndex] || ''; + const gotoSearchPrev = () => { + if (!searchMatchIds.length) return; + setSearchIndex(i => (i - 1 + searchMatchIds.length) % searchMatchIds.length); + }; + const gotoSearchNext = () => { + if (!searchMatchIds.length) return; + setSearchIndex(i => (i + 1) % searchMatchIds.length); + }; + // GPS-Position holen (optional) const getCurrentLocation = useCallback((): Promise<{ lat: number; lon: number } | null> => { if (!gpsEnabled) { @@ -1143,12 +1181,16 @@ const ChatScreen: React.FC = () => { hour: '2-digit', minute: '2-digit', }); + const isSearchHit = activeSearchId === item.id; + const searchHighlightStyle = isSearchHit + ? { borderWidth: 2, borderColor: '#FFD60A' } + : null; // Spezial-Bubble: ARIA hat einen Skill erstellt if (item.skillCreated) { const s = item.skillCreated; return ( - + {'🛠 ARIA hat einen neuen Skill erstellt'} @@ -1168,7 +1210,7 @@ const ChatScreen: React.FC = () => { } return ( - + {/* Anhang-Vorschau */} {item.attachments?.map((att, idx) => ( @@ -1342,7 +1384,7 @@ const ChatScreen: React.FC = () => { ); })()} - {/* Suchleiste */} + {/* Suchleiste mit Treffer-Navigation */} {searchVisible && ( { placeholderTextColor="#555570" autoFocus /> + {searchQuery ? ( + + {searchMatchIds.length ? `${searchIndex + 1}/${searchMatchIds.length}` : '0/0'} + + ) : null} + + {'▲'} + + + {'▼'} + { setSearchVisible(false); setSearchQuery(''); }}> X )} - {/* Nachrichtenliste */} + {/* Nachrichtenliste — Suche FILTERT NICHT mehr, sondern hebt aktiven + Treffer hervor (siehe renderMessage: activeSearchId-Border). */} m.text.toLowerCase().includes(searchQuery.toLowerCase())).reverse() : invertedMessages} + data={invertedMessages} + onScrollToIndexFailed={(info) => { + // Bei zu schnellem Aufruf vor Layout: einmal nachfassen + setTimeout(() => { + try { flatListRef.current?.scrollToIndex({ index: info.index, animated: true, viewPosition: 0.4 }); } catch {} + }, 200); + }} keyExtractor={item => item.id} renderItem={renderMessage} contentContainerStyle={styles.messageList} diff --git a/diagnostic/index.html b/diagnostic/index.html index 55f31a2..16bfc57 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -470,14 +470,9 @@
-
-

Sprachausgabe

-
- - - -
-
+

Sprachausgabe

+ +
@@ -546,9 +541,17 @@
- +
+ + + +