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;
|
title?: string;
|
||||||
/** Style-Erweiterung fuer den Container. */
|
/** Style-Erweiterung fuer den Container. */
|
||||||
flatStyle?: boolean;
|
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 [items, setItems] = useState<Memory[]>([]);
|
||||||
const [filtered, setFiltered] = useState<Memory[]>([]);
|
const [filtered, setFiltered] = useState<Memory[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -82,38 +84,35 @@ export const MemoryBrowser: React.FC<Props> = ({ restrictToIds, title, flatStyle
|
|||||||
setFiltered(out);
|
setFiltered(out);
|
||||||
}, [items, q, typeFilter, pinnedFilter, restrictToIds]);
|
}, [items, q, typeFilter, pinnedFilter, restrictToIds]);
|
||||||
|
|
||||||
|
const [showNewMemoryDialog, setShowNewMemoryDialog] = useState(false);
|
||||||
|
const [newMemoryTitle, setNewMemoryTitle] = useState('');
|
||||||
|
|
||||||
const onAddNew = () => {
|
const onAddNew = () => {
|
||||||
Alert.prompt(
|
setNewMemoryTitle('');
|
||||||
'Neue Memory',
|
setShowNewMemoryDialog(true);
|
||||||
'Titel:',
|
};
|
||||||
[
|
|
||||||
{ text: 'Abbrechen', style: 'cancel' },
|
const confirmAddNew = async () => {
|
||||||
{
|
const t = newMemoryTitle.trim();
|
||||||
text: 'Anlegen',
|
if (!t) { setShowNewMemoryDialog(false); return; }
|
||||||
onPress: async (title?: string) => {
|
setShowNewMemoryDialog(false);
|
||||||
const t = (title || '').trim();
|
try {
|
||||||
if (!t) return;
|
const m = await brainApi.saveMemory({
|
||||||
try {
|
type: 'fact', title: t,
|
||||||
const m = await brainApi.saveMemory({
|
content: '(noch leer — bitte editieren)',
|
||||||
type: 'fact', title: t,
|
});
|
||||||
content: '(noch leer — bitte editieren)',
|
load();
|
||||||
});
|
if (onOpenMemory) onOpenMemory(m.id);
|
||||||
load();
|
else setOpenId(m.id);
|
||||||
setOpenId(m.id);
|
} catch (e: any) {
|
||||||
} catch (e: any) {
|
Alert.alert('Fehler', String(e?.message || e));
|
||||||
Alert.alert('Fehler', String(e?.message || e));
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'plain-text',
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: Memory }) => {
|
const renderItem = ({ item }: { item: Memory }) => {
|
||||||
const attCount = (item.attachments || []).length;
|
const attCount = (item.attachments || []).length;
|
||||||
return (
|
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}}>
|
<View style={{flex:1}}>
|
||||||
<Text style={s.rowTitle} numberOfLines={1}>
|
<Text style={s.rowTitle} numberOfLines={1}>
|
||||||
{item.pinned ? '📌 ' : ''}{item.title || '(ohne Titel)'}
|
{item.pinned ? '📌 ' : ''}{item.title || '(ohne Titel)'}
|
||||||
@@ -202,12 +201,42 @@ export const MemoryBrowser: React.FC<Props> = ({ restrictToIds, title, flatStyle
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<MemoryDetailModal
|
{/* Eigenes DetailModal nur wenn der Parent kein Callback uebergibt
|
||||||
memoryId={openId}
|
(vermeidet Modal-in-Modal-Stacking auf Android). */}
|
||||||
visible={!!openId}
|
{!onOpenMemory && (
|
||||||
onClose={() => { setOpenId(null); load(); }}
|
<MemoryDetailModal
|
||||||
onDeleted={() => { setOpenId(null); load(); }}
|
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>
|
</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}}>
|
<Text style={{color:'#8888AA', fontSize:11, paddingHorizontal:14, paddingTop:10, paddingBottom:4, textTransform:'uppercase', letterSpacing:0.5}}>
|
||||||
Alle Memories aus der DB
|
Alle Memories aus der DB
|
||||||
</Text>
|
</Text>
|
||||||
<MemoryBrowser />
|
<MemoryBrowser onOpenMemory={(id) => { setInboxVisible(false); setMemoryDetailId(id); }} />
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user