fixed sst to milliseconds and autoscroll the the third, attachments added shared volume, addes attachments at chats, updateded readme

This commit is contained in:
2026-03-29 12:34:28 +02:00
parent 8c1dac86d5
commit db053c2dbd
16 changed files with 181 additions and 82 deletions
+91 -37
View File
@@ -15,6 +15,7 @@ import {
KeyboardAvoidingView,
Platform,
StyleSheet,
Image,
Modal,
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
@@ -33,6 +34,8 @@ interface Attachment {
type: 'image' | 'file' | 'audio';
name: string;
size?: number;
uri?: string; // Lokaler Pfad oder data URI fuer Anzeige
mimeType?: string;
}
interface ChatMessage {
@@ -87,10 +90,7 @@ const ChatScreen: React.FC = () => {
console.error('[Chat] Fehler beim Laden des Verlaufs:', err);
}
};
loadMessages().then(() => {
// Auto-Scroll nach Laden des Verlaufs
setTimeout(() => flatListRef.current?.scrollToEnd({ animated: false }), 200);
});
loadMessages();
}, []);
// RVS-Nachrichten abonnieren
@@ -222,19 +222,22 @@ const ChatScreen: React.FC = () => {
);
}, [messages]);
// Auto-Scroll bei neuen Nachrichten
useEffect(() => {
if (messages.length > 0) {
// Laengerer Delay damit FlatList fertig gerendert hat
setTimeout(() => {
flatListRef.current?.scrollToEnd({ animated: false });
}, 300);
// Nochmal animiert fuer den Fall dass sich die Hoehe geaendert hat
setTimeout(() => {
flatListRef.current?.scrollToEnd({ animated: true });
}, 600);
// Auto-Scroll wird ueber onContentSizeChange der FlatList gesteuert
const shouldAutoScroll = useRef(true);
const handleContentSizeChange = useCallback(() => {
if (shouldAutoScroll.current) {
flatListRef.current?.scrollToEnd({ animated: false });
}
}, [messages]);
}, []);
const handleScrollBeginDrag = useCallback(() => {
shouldAutoScroll.current = false;
}, []);
const handleScrollEndDrag = useCallback((e: any) => {
// Auto-Scroll wieder aktivieren wenn User ganz unten ist
const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
const isAtBottom = contentOffset.y + layoutMeasurement.height >= contentSize.height - 50;
shouldAutoScroll.current = isAtBottom;
}, []);
// GPS-Position holen (optional)
const getCurrentLocation = useCallback((): Promise<{ lat: number; lon: number } | null> => {
@@ -306,12 +309,19 @@ const ChatScreen: React.FC = () => {
setShowFileUpload(false);
const location = await getCurrentLocation();
const isImage = file.type.startsWith('image/');
const userMsg: ChatMessage = {
id: nextId(),
sender: 'user',
text: `[Datei: ${file.name}]`,
text: 'Anhang empfangen',
timestamp: Date.now(),
attachments: [{ type: 'file', name: file.name, size: file.size }],
attachments: [{
type: isImage ? 'image' : 'file',
name: file.name,
size: file.size,
uri: isImage && file.base64 ? `data:${file.type};base64,${file.base64}` : file.uri,
mimeType: file.type,
}],
};
setMessages(prev => [...prev, userMsg]);
@@ -332,9 +342,14 @@ const ChatScreen: React.FC = () => {
const userMsg: ChatMessage = {
id: nextId(),
sender: 'user',
text: `[Foto: ${photo.fileName}]`,
text: 'Anhang empfangen',
timestamp: Date.now(),
attachments: [{ type: 'image', name: photo.fileName }],
attachments: [{
type: 'image',
name: photo.fileName,
uri: photo.base64 ? `data:${photo.type};base64,${photo.base64}` : undefined,
mimeType: photo.type,
}],
};
setMessages(prev => [...prev, userMsg]);
@@ -359,16 +374,35 @@ const ChatScreen: React.FC = () => {
return (
<View style={[styles.messageBubble, isUser ? styles.userBubble : styles.ariaBubble]}>
<Text style={[styles.messageText, isUser ? styles.userText : styles.ariaText]}>
{item.text}
</Text>
{/* Anhang-Vorschau */}
{item.attachments?.map((att, idx) => (
<View key={idx} style={styles.attachmentBadge}>
<Text style={styles.attachmentText}>
{att.type === 'image' ? '\uD83D\uDDBC\uFE0F' : att.type === 'audio' ? '\uD83C\uDFA4' : '\uD83D\uDCC4'} {att.name}
</Text>
<View key={idx}>
{att.type === 'image' && att.uri ? (
<Image
source={{ uri: att.uri }}
style={styles.attachmentImage}
resizeMode="contain"
/>
) : (
<View style={styles.attachmentFile}>
<Text style={styles.attachmentFileIcon}>
{att.mimeType?.includes('pdf') ? '\uD83D\uDCC4' :
att.mimeType?.includes('word') || att.mimeType?.includes('document') ? '\uD83D\uDCC3' :
att.mimeType?.includes('sheet') || att.mimeType?.includes('excel') ? '\uD83D\uDCC8' :
'\uD83D\uDCC1'}
</Text>
<Text style={styles.attachmentFileName} numberOfLines={1}>{att.name}</Text>
{att.size ? <Text style={styles.attachmentFileSize}>{Math.round(att.size / 1024)}KB</Text> : null}
</View>
)}
</View>
))}
{/* Text (nicht anzeigen wenn nur "Anhang empfangen" und ein Bild da ist) */}
{!(item.text === 'Anhang empfangen' && item.attachments?.some(a => a.type === 'image' && a.uri)) && (
<Text style={[styles.messageText, isUser ? styles.userText : styles.ariaText]}>
{item.text}
</Text>
)}
<Text style={styles.timestamp}>{time}</Text>
</View>
);
@@ -401,6 +435,9 @@ const ChatScreen: React.FC = () => {
renderItem={renderMessage}
contentContainerStyle={styles.messageList}
showsVerticalScrollIndicator={false}
onContentSizeChange={handleContentSizeChange}
onScrollBeginDrag={handleScrollBeginDrag}
onScrollEndDrag={handleScrollEndDrag}
ListEmptyComponent={
<View style={styles.emptyContainer}>
<Text style={styles.emptyIcon}>{'\uD83E\uDD16'}</Text>
@@ -542,17 +579,34 @@ const styles = StyleSheet.create({
ariaText: {
color: '#E0E0F0',
},
attachmentBadge: {
backgroundColor: 'rgba(255,255,255,0.1)',
borderRadius: 6,
paddingHorizontal: 8,
paddingVertical: 4,
marginTop: 6,
alignSelf: 'flex-start',
attachmentImage: {
width: '100%',
height: 200,
borderRadius: 8,
marginBottom: 6,
backgroundColor: '#0D0D1A',
},
attachmentText: {
color: '#CCCCDD',
fontSize: 12,
attachmentFile: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(255,255,255,0.1)',
borderRadius: 8,
padding: 10,
marginBottom: 6,
},
attachmentFileIcon: {
fontSize: 24,
marginRight: 8,
},
attachmentFileName: {
flex: 1,
color: '#E0E0F0',
fontSize: 13,
},
attachmentFileSize: {
color: '#8888AA',
fontSize: 11,
marginLeft: 8,
},
timestamp: {
color: 'rgba(255,255,255,0.4)',