From e88b5f57bf25caa64b0ec962a54f2efedc563e28 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Thu, 14 May 2026 14:05:42 +0200 Subject: [PATCH] =?UTF-8?q?fix(memory):=20Inbox-Crash=20auf=20Android=20?= =?UTF-8?q?=E2=80=94=20Modal-Stacking=20+=20Alert.prompt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stefan: App crasht beim Tap auf Inbox-Button. Zwei Ursachen: 1. Modal-in-Modal-Stacking (Inbox-Modal enthielt MemoryBrowser, der wiederum ein MemoryDetailModal gerendered hat). Android Modal hat damit Probleme — der Native-Layer mag nur eine Modal-Instance gleichzeitig zuverlaessig. 2. MemoryBrowser nutzte Alert.prompt fuer "Neue Memory anlegen" — das ist iOS-only, Android wirft eine Warnung oder crasht. Fix: - MemoryBrowser bekommt optionalen onOpenMemory-Callback. Wenn der Parent diesen liefert, mounted MemoryBrowser KEIN eigenes DetailModal mehr. ChatScreen mountet das DetailModal nur einmal auf seiner Ebene; Inbox-Modal schliesst sich beim Tap und delegiert die ID an memoryDetailId-State. Damit ist immer maximal ein Modal aktiv. - Alert.prompt durch eigenes kleines Dialog-Modal ersetzt: TextInput fuer Titel, Anlegen/Abbrechen-Buttons. Cross-platform stabil. SettingsScreen-Nutzung von MemoryBrowser bleibt unveraendert (kein Callback → eingebautes DetailModal, aber dort kein Modal-Stacking weil Settings kein Modal ist). Co-Authored-By: Claude Opus 4.7 (1M context) --- android/src/components/MemoryBrowser.tsx | 95 ++++++++++++++++-------- android/src/screens/ChatScreen.tsx | 2 +- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/android/src/components/MemoryBrowser.tsx b/android/src/components/MemoryBrowser.tsx index f57ba1b..1cebfe0 100644 --- a/android/src/components/MemoryBrowser.tsx +++ b/android/src/components/MemoryBrowser.tsx @@ -37,9 +37,11 @@ interface Props { title?: string; /** Style-Erweiterung fuer den Container. */ flatStyle?: boolean; + /** Wenn gesetzt: kein eigenes DetailModal mounten — Parent kuemmert sich. */ + onOpenMemory?: (id: string) => void; } -export const MemoryBrowser: React.FC = ({ restrictToIds, title, flatStyle }) => { +export const MemoryBrowser: React.FC = ({ restrictToIds, title, flatStyle, onOpenMemory }) => { const [items, setItems] = useState([]); const [filtered, setFiltered] = useState([]); const [loading, setLoading] = useState(false); @@ -82,38 +84,35 @@ export const MemoryBrowser: React.FC = ({ restrictToIds, title, flatStyle setFiltered(out); }, [items, q, typeFilter, pinnedFilter, restrictToIds]); + const [showNewMemoryDialog, setShowNewMemoryDialog] = useState(false); + const [newMemoryTitle, setNewMemoryTitle] = useState(''); + const onAddNew = () => { - Alert.prompt( - 'Neue Memory', - 'Titel:', - [ - { text: 'Abbrechen', style: 'cancel' }, - { - text: 'Anlegen', - onPress: async (title?: string) => { - const t = (title || '').trim(); - if (!t) return; - try { - const m = await brainApi.saveMemory({ - type: 'fact', title: t, - content: '(noch leer — bitte editieren)', - }); - load(); - setOpenId(m.id); - } catch (e: any) { - Alert.alert('Fehler', String(e?.message || e)); - } - }, - }, - ], - 'plain-text', - ); + setNewMemoryTitle(''); + setShowNewMemoryDialog(true); + }; + + const confirmAddNew = async () => { + const t = newMemoryTitle.trim(); + if (!t) { setShowNewMemoryDialog(false); return; } + setShowNewMemoryDialog(false); + try { + const m = await brainApi.saveMemory({ + type: 'fact', title: t, + content: '(noch leer — bitte editieren)', + }); + load(); + if (onOpenMemory) onOpenMemory(m.id); + else setOpenId(m.id); + } catch (e: any) { + Alert.alert('Fehler', String(e?.message || e)); + } }; const renderItem = ({ item }: { item: Memory }) => { const attCount = (item.attachments || []).length; return ( - setOpenId(item.id)}> + onOpenMemory ? onOpenMemory(item.id) : setOpenId(item.id)}> {item.pinned ? '📌 ' : ''}{item.title || '(ohne Titel)'} @@ -202,12 +201,42 @@ export const MemoryBrowser: React.FC = ({ restrictToIds, title, flatStyle - { setOpenId(null); load(); }} - onDeleted={() => { setOpenId(null); load(); }} - /> + {/* Eigenes DetailModal nur wenn der Parent kein Callback uebergibt + (vermeidet Modal-in-Modal-Stacking auf Android). */} + {!onOpenMemory && ( + { setOpenId(null); load(); }} + onDeleted={() => { setOpenId(null); load(); }} + /> + )} + + {/* "Neue Memory"-Dialog (Alert.prompt ist iOS-only, daher eigenes Modal) */} + setShowNewMemoryDialog(false)}> + + + Neue Memory anlegen + Titel: + + + setShowNewMemoryDialog(false)} style={{padding:8}}> + Abbrechen + + + Anlegen + + + + + ); }; diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index 0b42cec..b72ff76 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -1933,7 +1933,7 @@ const ChatScreen: React.FC = () => { Alle Memories aus der DB - + { setInboxVisible(false); setMemoryDetailId(id); }} />