[V0.1 WORKING] Added chat, profile, & calendar screen implementations.

This commit is contained in:
c-d-p
2025-04-18 17:30:09 +02:00
parent bf7eb8275c
commit 8d884111fd
19 changed files with 613 additions and 181 deletions

View File

@@ -1,19 +1,192 @@
// src/screens/DashboardScreen.tsx
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Text, useTheme } from 'react-native-paper';
// src/screens/ChatScreen.tsx
import React, { useState, useCallback, useRef } from 'react';
import { View, StyleSheet, FlatList, KeyboardAvoidingView, Platform, TextInput as RNTextInput, NativeSyntheticEvent, TextInputKeyPressEventData } from 'react-native';
import { Text, useTheme, TextInput, Button, IconButton, PaperProvider } from 'react-native-paper';
import { SafeAreaView } from 'react-native-safe-area-context';
import apiClient from '../api/client'; // Import the apiClient
const DashboardScreen = () => {
// Define the structure for a message
interface Message {
id: string;
text: string;
sender: 'user' | 'ai';
timestamp: Date;
}
const ChatScreen = () => {
const theme = useTheme();
const [messages, setMessages] = useState<Message[]>([]);
const [inputText, setInputText] = useState('');
const [isLoading, setIsLoading] = useState(false); // To show activity indicator while AI responds
const flatListRef = useRef<FlatList>(null);
// Function to handle sending a message
const handleSend = useCallback(async () => {
const trimmedText = inputText.trim();
if (!trimmedText) return; // Don't send empty messages
const userMessage: Message = {
id: Date.now().toString() + '-user',
text: trimmedText,
sender: 'user',
timestamp: new Date(),
};
setMessages(prevMessages => [...prevMessages, userMessage]);
setInputText('');
setIsLoading(true);
// Scroll to bottom after sending user message
setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100);
// --- Call Backend API ---
try {
console.log(`[ChatScreen] Sending to /nlp/process-command: ${trimmedText}`);
const response = await apiClient.post<{ response: string }>('/nlp/process-command', { user_input: trimmedText });
console.log("[ChatScreen] Received response:", response.data);
const aiResponse: Message = {
id: Date.now().toString() + '-ai',
// Assuming the backend returns the response text in a 'response' field
text: response.data.response || "Sorry, I didn't get a valid response.",
sender: 'ai',
timestamp: new Date(),
};
setMessages(prevMessages => [...prevMessages, aiResponse]);
} catch (error: any) {
console.error("Failed to get AI response:", error.response?.data || error.message || error);
const errorResponse: Message = {
id: Date.now().toString() + '-error',
text: 'Sorry, I encountered an error trying to reach MAIA.',
sender: 'ai',
timestamp: new Date(),
};
setMessages(prevMessages => [...prevMessages, errorResponse]);
} finally {
setIsLoading(false);
// Scroll to bottom after receiving AI message
setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100);
}
// --- End API Call ---
}, [inputText]); // Keep inputText as dependency
const handleKeyPress = (e: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
if (e.nativeEvent.key === 'Enter' && !(e.nativeEvent as any).shiftKey) {
e.preventDefault(); // Prevent new line
handleSend();
}
};
// Render individual message item
const renderMessage = ({ item }: { item: Message }) => {
const isUser = item.sender === 'user';
return (
<View style={[styles.messageBubble, isUser ? styles.userBubble : styles.aiBubble]}>
<Text style={{ color: isUser ? theme.colors.onPrimary : theme.colors.onSurface }}>
{item.text}
</Text>
{/* Optional: Add timestamp */}
{/* <Text style={[styles.timestamp, { color: isUser ? theme.colors.onPrimary : theme.colors.onSurfaceVariant }]}>
{item.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</Text> */}
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 16, backgroundColor: theme.colors.background },
text: { fontSize: 20, color: theme.colors.text }
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
listContainer: {
flex: 1,
},
messageList: {
paddingHorizontal: 10,
paddingVertical: 10,
},
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
padding: 8,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: theme.colors.outlineVariant,
backgroundColor: theme.colors.elevation.level2, // Slightly elevated background
},
textInput: {
flex: 1,
marginRight: 8,
backgroundColor: theme.colors.surface, // Use surface color for input background
},
messageBubble: {
maxWidth: '80%',
padding: 10,
borderRadius: 15,
marginBottom: 10,
},
userBubble: {
alignSelf: 'flex-end',
backgroundColor: theme.colors.primary,
borderBottomRightRadius: 5,
},
aiBubble: {
alignSelf: 'flex-start',
backgroundColor: theme.colors.surfaceVariant,
borderBottomLeftRadius: 5,
},
timestamp: {
fontSize: 10,
marginTop: 4,
alignSelf: 'flex-end',
opacity: 0.7,
}
});
return (
<View style={styles.container}>
<Text style={styles.text}>Chat</Text>
</View>
<SafeAreaView style={styles.container} edges={['bottom', 'left', 'right']}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === "ios" ? 90 : 0} // Adjust as needed
>
<View style={styles.listContainer}>
<FlatList
ref={flatListRef}
data={messages}
renderItem={renderMessage}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.messageList}
onContentSizeChange={() => flatListRef.current?.scrollToEnd({ animated: false })} // Scroll on initial load/size change
onLayout={() => flatListRef.current?.scrollToEnd({ animated: false })} // Scroll on layout change
/>
</View>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
value={inputText}
onChangeText={setInputText}
placeholder="Type your message..."
mode="outlined" // Or "flat"
multiline
onKeyPress={handleKeyPress}
blurOnSubmit={false}
disabled={isLoading}
/>
<IconButton
icon="send"
size={24}
onPress={handleSend}
disabled={!inputText.trim() || isLoading}
mode="contained"
iconColor={theme.colors.onPrimary}
containerColor={theme.colors.primary}
/>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
export default DashboardScreen;
export default ChatScreen;