fix(memory): Inbox-Crash auf Android — Modal-Stacking + Alert.prompt
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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<Props> = ({ restrictToIds, title, flatStyle }) => {
|
||||
export const MemoryBrowser: React.FC<Props> = ({ restrictToIds, title, flatStyle, onOpenMemory }) => {
|
||||
const [items, setItems] = useState<Memory[]>([]);
|
||||
const [filtered, setFiltered] = useState<Memory[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -82,38 +84,35 @@ export const MemoryBrowser: React.FC<Props> = ({ 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 (
|
||||
<TouchableOpacity style={s.row} onPress={() => setOpenId(item.id)}>
|
||||
<TouchableOpacity style={s.row} onPress={() => onOpenMemory ? onOpenMemory(item.id) : setOpenId(item.id)}>
|
||||
<View style={{flex:1}}>
|
||||
<Text style={s.rowTitle} numberOfLines={1}>
|
||||
{item.pinned ? '📌 ' : ''}{item.title || '(ohne Titel)'}
|
||||
@@ -202,12 +201,42 @@ export const MemoryBrowser: React.FC<Props> = ({ restrictToIds, title, flatStyle
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
|
||||
<MemoryDetailModal
|
||||
memoryId={openId}
|
||||
visible={!!openId}
|
||||
onClose={() => { setOpenId(null); load(); }}
|
||||
onDeleted={() => { setOpenId(null); load(); }}
|
||||
/>
|
||||
{/* Eigenes DetailModal nur wenn der Parent kein Callback uebergibt
|
||||
(vermeidet Modal-in-Modal-Stacking auf Android). */}
|
||||
{!onOpenMemory && (
|
||||
<MemoryDetailModal
|
||||
memoryId={openId}
|
||||
visible={!!openId}
|
||||
onClose={() => { setOpenId(null); load(); }}
|
||||
onDeleted={() => { setOpenId(null); load(); }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* "Neue Memory"-Dialog (Alert.prompt ist iOS-only, daher eigenes Modal) */}
|
||||
<Modal visible={showNewMemoryDialog} transparent animationType="fade" onRequestClose={() => setShowNewMemoryDialog(false)}>
|
||||
<View style={s.menuBack}>
|
||||
<View style={[s.menuBox, {padding:16, minWidth:280}]}>
|
||||
<Text style={{color:'#FFD60A', fontWeight:'bold', fontSize:14, marginBottom:10}}>Neue Memory anlegen</Text>
|
||||
<Text style={{color:'#8888AA', fontSize:11, marginBottom:6}}>Titel:</Text>
|
||||
<TextInput
|
||||
value={newMemoryTitle}
|
||||
onChangeText={setNewMemoryTitle}
|
||||
autoFocus
|
||||
placeholder="z.B. Stefans Auto"
|
||||
placeholderTextColor="#555570"
|
||||
style={{backgroundColor:'#1E1E2E', color:'#E0E0F0', padding:8, borderRadius:4, fontSize:13, marginBottom:12}}
|
||||
/>
|
||||
<View style={{flexDirection:'row', gap:8, justifyContent:'flex-end'}}>
|
||||
<TouchableOpacity onPress={() => setShowNewMemoryDialog(false)} style={{padding:8}}>
|
||||
<Text style={{color:'#8888AA'}}>Abbrechen</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={confirmAddNew} style={{backgroundColor:'#0096FF', paddingHorizontal:14, paddingVertical:8, borderRadius:4}}>
|
||||
<Text style={{color:'#fff', fontWeight:'600'}}>Anlegen</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1933,7 +1933,7 @@ const ChatScreen: React.FC = () => {
|
||||
<Text style={{color:'#8888AA', fontSize:11, paddingHorizontal:14, paddingTop:10, paddingBottom:4, textTransform:'uppercase', letterSpacing:0.5}}>
|
||||
Alle Memories aus der DB
|
||||
</Text>
|
||||
<MemoryBrowser />
|
||||
<MemoryBrowser onOpenMemory={(id) => { setInboxVisible(false); setMemoryDetailId(id); }} />
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user