nicer looking month view

This commit is contained in:
c-d-p
2025-04-20 23:52:29 +02:00
parent 6cee996fb3
commit 9e8e179a94
25 changed files with 329 additions and 125 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -24,9 +24,11 @@ const CalendarDayCell: React.FC<CalendarDayCellProps> = ({ date, events, isCurre
width: width,
height: height,
borderWidth: 0.5,
borderTopWidth: 0,
borderColor: theme.colors.outlineVariant,
padding: 2,
backgroundColor: isCurrentMonth ? theme.colors.surface : theme.colors.surfaceDisabled, // Dim non-month days
paddingTop: 0,
backgroundColor: theme.colors.background,
overflow: 'hidden', // Prevent events overflowing cell boundaries
},
dateNumberContainer: {
@@ -34,8 +36,9 @@ const CalendarDayCell: React.FC<CalendarDayCellProps> = ({ date, events, isCurre
marginBottom: 2,
},
dateNumber: {
fontSize: 10,
fontSize: 12,
fontWeight: today ? 'bold' : 'normal',
marginTop: 8,
color: today ? theme.colors.primary : (isCurrentMonth ? theme.colors.onSurface : theme.colors.onSurfaceDisabled),
},
eventsContainer: {

View File

@@ -2,19 +2,23 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Text, IconButton, useTheme } from 'react-native-paper';
import ViewSwitcher from './ViewSwitcher';
import { CalendarViewMode } from './CustomCalendarView';
interface CalendarHeaderProps {
currentRangeText: string;
onPrev: () => void;
onNext: () => void;
currentView: CalendarViewMode;
onViewChange: (view: CalendarViewMode) => void;
}
const CalendarHeader: React.FC<CalendarHeaderProps> = ({ currentRangeText, onPrev, onNext }) => {
const CalendarHeader: React.FC<CalendarHeaderProps> = ({ currentRangeText, onPrev, onNext, currentView, onViewChange }) => {
const theme = useTheme();
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
justifyContent: 'flex-start',
alignItems: 'center',
paddingVertical: 8,
paddingHorizontal: 12,
@@ -37,13 +41,16 @@ const CalendarHeader: React.FC<CalendarHeaderProps> = ({ currentRangeText, onPre
size={24}
iconColor={theme.colors.primary} // Use theme color
/>
<Text style={styles.title}>{currentRangeText}</Text>
<IconButton
icon="chevron-right"
onPress={onNext}
size={24}
iconColor={theme.colors.primary} // Use theme color
/>
<View style={{ width: 12 }} /> {/* Placeholder for alignment */}
<Text style={styles.title}>{currentRangeText}</Text>
<View style={{ flex: 1 }} /> {/* Spacer to push ViewSwitcher to the right */}
<ViewSwitcher currentView={currentView} onViewChange={onViewChange}></ViewSwitcher>
</View>
);
};

View File

@@ -7,6 +7,8 @@ import {
addWeeks, subWeeks, addDays, subDays, eachDayOfInterval, format, getMonth, getYear, isSameMonth,
parseISO, isValid, isSameDay, startOfDay, endOfDay
} from 'date-fns';
// Import useFocusEffect
import { useFocusEffect } from '@react-navigation/native';
import CalendarHeader from './CalendarHeader';
import ViewSwitcher from './ViewSwitcher';
@@ -69,9 +71,17 @@ const CustomCalendarView = () => {
}
}, [startDate, endDate]); // Depend on calculated start/end dates
useEffect(() => {
fetchEvents();
}, [fetchEvents]); // Re-run fetchEvents when it changes (due to date changes)
// Use useFocusEffect to fetch events when the screen is focused
useFocusEffect(
useCallback(() => {
console.log('[CustomCalendar] Screen focused, fetching events.');
fetchEvents();
// Optional: Return a cleanup function if needed, though not necessary for just fetching
return () => {
console.log('[CustomCalendar] Screen unfocused.');
};
}, [fetchEvents]) // Re-run effect if fetchEvents changes (due to date range change)
);
// Navigation handlers
const handlePrev = useCallback(() => {
@@ -147,9 +157,9 @@ const CustomCalendarView = () => {
currentRangeText={displayRangeText}
onPrev={handlePrev}
onNext={handleNext}
currentView={viewMode}
onViewChange={setViewMode}
/>
<ViewSwitcher currentView={viewMode} onViewChange={setViewMode} />
<View style={styles.contentArea}>
{isLoading ? (
<View style={styles.loadingContainer}>

View File

@@ -43,7 +43,7 @@ const EventItem: React.FC<EventItemProps> = ({ event, showTime = true }) => {
},
text: {
color: theme.colors.onPrimary, // Ensure text is readable on the background color
fontSize: 10,
fontSize: 12,
fontWeight: '500',
},
timeText: {

View File

@@ -45,7 +45,7 @@ const MonthView: React.FC<MonthViewProps> = ({ startDate, eventsByDate }) => {
}
});
const weekDays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const weekDays = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'];
const styles = StyleSheet.create({
container: { flex: 1 },
@@ -56,17 +56,20 @@ const MonthView: React.FC<MonthViewProps> = ({ startDate, eventsByDate }) => {
dayHeaderRow: {
flexDirection: 'row',
paddingVertical: 5,
borderBottomWidth: 1,
borderBottomColor: theme.colors.outlineVariant,
backgroundColor: theme.colors.surfaceVariant, // Slightly different background for header
paddingBottom: 0,
backgroundColor: theme.colors.background,
},
dayHeaderCell: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
borderLeftWidth: 0.5,
borderRightWidth: 0.5,
borderColor: theme.colors.outlineVariant,
backgroundColor: theme.colors.background,
},
dayHeaderText: {
fontSize: 10,
fontSize: 11,
fontWeight: 'bold',
color: theme.colors.onSurfaceVariant,
},

View File

@@ -16,7 +16,6 @@ const ViewSwitcher: React.FC<ViewSwitcherProps> = ({ currentView, onViewChange }
paddingVertical: 8,
paddingHorizontal: 16,
backgroundColor: theme.colors.surface, // Match background
borderBottomWidth: 1,
borderBottomColor: theme.colors.outlineVariant,
},
});
@@ -27,12 +26,11 @@ const ViewSwitcher: React.FC<ViewSwitcherProps> = ({ currentView, onViewChange }
value={currentView}
onValueChange={(value) => onViewChange(value as CalendarViewMode)} // Cast value
buttons={[
{ value: 'month', label: 'Month' },
{ value: 'week', label: 'Week' },
{ value: '3day', label: '3-Day' },
{ value: 'month', label: 'M', checkedColor: theme.colors.onPrimary },
{ value: 'week', label: 'W', checkedColor: theme.colors.onPrimary },
{ value: '3day', label: '3', checkedColor: theme.colors.onPrimary },
]}
// Optional: Add density for smaller buttons
// density="medium"
density="high"
/>
</View>
);

View File

@@ -1,11 +1,12 @@
// src/constants/colors.ts
export const colors = {
background: '#0a0a0a', // Dark blue-teal background
background: '#131314', // Dark blue-teal background
primary: '#4DB6AC', // Main teal color for text, icons, active elements
secondary: '#64FFDA', // Bright cyan accent for highlights, important actions
surface: '#252525', // Slightly lighter background for cards/modals (optional)
surface: '#1b1b1b', // Slightly lighter background for cards/modals (optional)
text: '#FFFFFF', // White text for high contrast on dark background
textSecondary: '#B0BEC5', // Lighter gray for less important text
error: '#FF5252', // Standard error color
disabled: '#78909C', // Color for disabled elements
outline: '#333537', // Outline color for borders
};

View File

@@ -1,44 +1,77 @@
// src/constants/theme.ts
import { MD3DarkTheme as DefaultTheme, configureFonts } from 'react-native-paper';
import { Platform } from 'react-native';
import { colors } from './colors';
// const fontConfig = {
// default: {
// regular: {
// fontFamily: 'Inter, sans-serif',
// fontWeight: 'normal',
// fontSize: 14,
// lineHeight: 20,
// letterSpacing: 0.25,
// },
// medium: {
// fontFamily: 'Inter, sans-serif',
// fontWeight: '500',
// fontSize: 16,
// lineHeight: 24,
// letterSpacing: 0.15,
// },
// light: {
// fontFamily: 'Inter, sans-serif',
// fontWeight: '300',
// fontSize: 12,
// lineHeight: 16,
// letterSpacing: 0.4,
// },
// thin: {
// fontFamily: 'Inter, sans-serif',
// fontWeight: '100',
// fontSize: 10,
// lineHeight: 14,
// letterSpacing: 0.5,
// },
// },
// };
// const fonts = configureFonts({ config: fontConfig });
// Define the font configuration using the names loaded in App.tsx
const fontConfig = {
// Default configuration for all platforms
regular: {
fontFamily: 'Inter-Regular',
fontWeight: 'normal' as 'normal', // Type assertion needed
},
medium: {
fontFamily: 'Inter-Medium',
fontWeight: '500' as '500', // Type assertion needed
},
light: {
fontFamily: 'Inter-Light',
fontWeight: '300' as '300', // Type assertion needed
},
thin: {
fontFamily: 'Inter-Thin',
fontWeight: '100' as '100', // Type assertion needed
},
// Add bold if needed and loaded
bold: {
fontFamily: 'Inter-Bold',
fontWeight: 'bold' as 'bold', // Type assertion needed
},
// Define specific font variants used by Paper components
// These map to the keys above
displayLarge: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 57, lineHeight: 64, letterSpacing: -0.25 },
displayMedium: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 45, lineHeight: 52, letterSpacing: 0 },
displaySmall: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 36, lineHeight: 44, letterSpacing: 0 },
headlineLarge: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 32, lineHeight: 40, letterSpacing: 0 },
headlineMedium: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 28, lineHeight: 36, letterSpacing: 0 },
headlineSmall: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 24, lineHeight: 32, letterSpacing: 0 },
titleLarge: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 22, lineHeight: 28, letterSpacing: 0 },
titleMedium: { fontFamily: 'Inter-Medium', fontWeight: '500' as '500', fontSize: 16, lineHeight: 24, letterSpacing: 0.15 },
titleSmall: { fontFamily: 'Inter-Medium', fontWeight: '500' as '500', fontSize: 14, lineHeight: 20, letterSpacing: 0.1 },
labelLarge: { fontFamily: 'Inter-Medium', fontWeight: '500' as '500', fontSize: 14, lineHeight: 20, letterSpacing: 0.1 },
labelMedium: { fontFamily: 'Inter-Medium', fontWeight: '500' as '500', fontSize: 12, lineHeight: 16, letterSpacing: 0.5 },
labelSmall: { fontFamily: 'Inter-Medium', fontWeight: '500' as '500', fontSize: 11, lineHeight: 16, letterSpacing: 0.5 },
bodyLarge: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 16, lineHeight: 24, letterSpacing: 0.5 },
bodyMedium: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 14, lineHeight: 20, letterSpacing: 0.25 },
bodySmall: { fontFamily: 'Inter-Regular', fontWeight: '400' as '400', fontSize: 12, lineHeight: 16, letterSpacing: 0.4 },
// Default is used as a fallback
default: {
fontFamily: 'Inter-Regular',
fontWeight: 'normal' as 'normal',
},
};
// Configure fonts for React Native Paper V5 (MD3)
// The structure differs slightly from V4
const fonts = configureFonts({
config: fontConfig,
// You might need specific web/ios/android configs if fonts differ
// web: fontConfig,
// ios: fontConfig,
// android: fontConfig,
});
const theme = {
...DefaultTheme, // Use MD3 dark theme as a base
// fonts: fonts,
fonts: fonts, // Apply the configured fonts
colors: {
...DefaultTheme.colors, // Keep default colors unless overridden
primary: colors.primary,
@@ -46,6 +79,8 @@ const theme = {
secondary: colors.secondary,
tertiary: colors.secondary, // Assign accent to tertiary as well if needed
background: colors.background,
outline: colors.outline,
outlineVariant: colors.outline,
surface: colors.surface || colors.background, // Use surface or fallback to background
text: colors.text,
onPrimary: colors.background, // Text color on primary background

View File

@@ -13,6 +13,11 @@ interface Message {
timestamp: Date;
}
// Define the expected structure for the API response
interface NlpResponse {
responses: string[]; // Expecting an array of response strings
}
const ChatScreen = () => {
const theme = useTheme();
const [messages, setMessages] = useState<Message[]>([]);
@@ -32,6 +37,7 @@ const ChatScreen = () => {
timestamp: new Date(),
};
// Add user message optimistically
setMessages(prevMessages => [...prevMessages, userMessage]);
setInputText('');
setIsLoading(true);
@@ -39,20 +45,37 @@ const ChatScreen = () => {
// Scroll to bottom after sending user message
setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100);
// --- Call Backend API ---
// --- 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 });
// Expect the backend to return an object with a 'responses' array
const response = await apiClient.post<NlpResponse>('/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]);
const aiResponses: Message[] = [];
if (response.data && Array.isArray(response.data.responses) && response.data.responses.length > 0) {
response.data.responses.forEach((responseText, index) => {
aiResponses.push({
id: `${Date.now()}-ai-${index}`, // Ensure unique IDs
text: responseText || "...", // Handle potential empty strings
sender: 'ai',
timestamp: new Date(),
});
});
} else {
// Handle cases where the response format is unexpected or empty
console.warn("[ChatScreen] Received invalid or empty responses array:", response.data);
aiResponses.push({
id: Date.now().toString() + '-ai-fallback',
text: "Sorry, I didn't get a valid response.",
sender: 'ai',
timestamp: new Date(),
});
}
// Add all AI responses to the state
setMessages(prevMessages => [...prevMessages, ...aiResponses]);
} catch (error: any) {
console.error("Failed to get AI response:", error.response?.data || error.message || error);
const errorResponse: Message = {
@@ -64,7 +87,7 @@ const ChatScreen = () => {
setMessages(prevMessages => [...prevMessages, errorResponse]);
} finally {
setIsLoading(false);
// Scroll to bottom after receiving AI message
// Scroll to bottom after receiving AI message(s)
setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100);
}
// --- End API Call ---