From 29347ee028fae9491bd8bda78f30adc8d9cf0a0d Mon Sep 17 00:00:00 2001 From: tapframe Date: Sun, 4 May 2025 02:44:38 +0530 Subject: [PATCH] Refactor CalendarSection and CalendarScreen components to integrate theme context for improved UI consistency This update enhances the CalendarSection and CalendarScreen components by incorporating the ThemeContext, allowing for dynamic theming throughout the calendar interface. Styles have been adjusted to reflect the current theme colors, improving visual consistency and user experience. Key changes include updates to button styles, text colors, and background settings, ensuring a cohesive interface that adapts to different themes. Additionally, the CalendarSection's date handling logic has been optimized for better performance. --- src/components/calendar/CalendarSection.tsx | 329 ++++++++++---------- src/screens/CalendarScreen.tsx | 107 +++---- src/screens/NotificationSettingsScreen.tsx | 151 +++++---- 3 files changed, 276 insertions(+), 311 deletions(-) diff --git a/src/components/calendar/CalendarSection.tsx b/src/components/calendar/CalendarSection.tsx index 7bafa91..7050b90 100644 --- a/src/components/calendar/CalendarSection.tsx +++ b/src/components/calendar/CalendarSection.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useCallback } from 'react'; import { View, Text, @@ -8,24 +8,14 @@ import { Dimensions } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; -import { colors } from '../../styles/colors'; -import { - format, - addMonths, - subMonths, - startOfMonth, - endOfMonth, - isSameMonth, - isSameDay, - getDay, - isToday, - parseISO -} from 'date-fns'; +import { format, addMonths, subMonths, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isToday, isSameDay } from 'date-fns'; import Animated, { FadeIn } from 'react-native-reanimated'; +import { useTheme } from '../../contexts/ThemeContext'; const { width } = Dimensions.get('window'); const COLUMN_COUNT = 7; // 7 days in a week -const DAY_ITEM_SIZE = width / 9; // Slightly smaller than 1/7 to fit all days +const DAY_ITEM_SIZE = (width - 32 - 56) / 7; // Slightly smaller than 1/7 to fit all days +const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; interface CalendarEpisode { id: string; @@ -54,37 +44,40 @@ const DayItem = ({ isSelected, hasEvents, onPress -}: DayItemProps) => ( - onPress(date)} - > - - {date.getDate()} - - {hasEvents && ( - - )} - -); +}: DayItemProps) => { + const { currentTheme } = useTheme(); + return ( + onPress(date)} + > + + {date.getDate()} + + {hasEvents && ( + + )} + + ); +}; export const CalendarSection: React.FC = ({ episodes = [], onSelectDate }) => { - console.log(`[CalendarSection] Rendering with ${episodes.length} episodes`); + const { currentTheme } = useTheme(); const [currentDate, setCurrentDate] = useState(new Date()); - const [selectedDate, setSelectedDate] = useState(new Date()); + const [selectedDate, setSelectedDate] = useState(null); const scrollViewRef = useRef(null); // Map of dates with episodes @@ -97,7 +90,7 @@ export const CalendarSection: React.FC = ({ episodes.forEach(episode => { if (episode.releaseDate) { - const releaseDate = parseISO(episode.releaseDate); + const releaseDate = new Date(episode.releaseDate); const dateKey = format(releaseDate, 'yyyy-MM-dd'); dateMap[dateKey] = true; } @@ -107,201 +100,194 @@ export const CalendarSection: React.FC = ({ setDatesWithEpisodes(dateMap); }, [episodes]); - const goToPreviousMonth = () => { - setCurrentDate(prevDate => subMonths(prevDate, 1)); - }; + const goToPreviousMonth = useCallback(() => { + setCurrentDate(prev => subMonths(prev, 1)); + }, []); - const goToNextMonth = () => { - setCurrentDate(prevDate => addMonths(prevDate, 1)); - }; + const goToNextMonth = useCallback(() => { + setCurrentDate(prev => addMonths(prev, 1)); + }, []); - const handleDayPress = (date: Date) => { + const handleDateSelect = useCallback((date: Date) => { setSelectedDate(date); - if (onSelectDate) { - onSelectDate(date); + onSelectDate?.(date); + }, [onSelectDate]); + + const renderDays = () => { + const start = startOfMonth(currentDate); + const end = endOfMonth(currentDate); + const days = eachDayOfInterval({ start, end }); + + // Get the day of the week for the first day (0-6) + const firstDayOfWeek = start.getDay(); + + // Add empty days at the start + const emptyDays = Array(firstDayOfWeek).fill(null); + + // Calculate remaining days to fill the last row + const totalDays = emptyDays.length + days.length; + const remainingDays = 7 - (totalDays % 7); + const endEmptyDays = remainingDays === 7 ? [] : Array(remainingDays).fill(null); + + const allDays = [...emptyDays, ...days, ...endEmptyDays]; + const weeks = []; + + for (let i = 0; i < allDays.length; i += 7) { + weeks.push(allDays.slice(i, i + 7)); } + + return weeks.map((week, weekIndex) => ( + + {week.map((day, dayIndex) => { + if (!day) { + return ; + } + + const isCurrentMonth = isSameMonth(day, currentDate); + const isCurrentDay = isToday(day); + const isSelected = selectedDate && isSameDay(day, selectedDate); + const hasEvents = datesWithEpisodes[format(day, 'yyyy-MM-dd')] || false; + + return ( + handleDateSelect(day)} + > + + {format(day, 'd')} + + {hasEvents && ( + + )} + + ); + })} + + )); }; - // Generate days for the current month view - const generateDaysForMonth = () => { - const monthStart = startOfMonth(currentDate); - const monthEnd = endOfMonth(currentDate); - const startDate = new Date(monthStart); - - // Adjust the start date to the beginning of the week - const dayOfWeek = getDay(startDate); - startDate.setDate(startDate.getDate() - dayOfWeek); - - // Ensure we have 6 complete weeks in our view - const endDate = new Date(monthEnd); - const lastDayOfWeek = getDay(endDate); - if (lastDayOfWeek < 6) { - endDate.setDate(endDate.getDate() + (6 - lastDayOfWeek)); - } - - // Get dates for a complete 6-week calendar - const totalDaysNeeded = 42; // 6 weeks × 7 days - const daysInView = []; - - let currentDateInView = new Date(startDate); - for (let i = 0; i < totalDaysNeeded; i++) { - daysInView.push(new Date(currentDateInView)); - currentDateInView.setDate(currentDateInView.getDate() + 1); - } - - return daysInView; - }; - - const dayItems = generateDaysForMonth(); - - // Break days into rows (6 rows of 7 days each) - const rows = []; - for (let i = 0; i < dayItems.length; i += COLUMN_COUNT) { - rows.push(dayItems.slice(i, i + COLUMN_COUNT)); - } - - // Get weekday names for header - const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - return ( - - - - + + + + - + {format(currentDate, 'MMMM yyyy')} - - + + - - + + {weekDays.map((day, index) => ( - - {day} - + + {day} + ))} - - - {rows.map((row, rowIndex) => ( - - {row.map((date, cellIndex) => { - const isCurrentMonthDay = isSameMonth(date, currentDate); - const isSelectedToday = isToday(date); - const isDateSelected = isSameDay(date, selectedDate); - - // Check if this date has episodes - const dateKey = format(date, 'yyyy-MM-dd'); - const hasEvents = datesWithEpisodes[dateKey] || false; - - // Log every 7 days to avoid console spam - if (cellIndex === 0 && rowIndex === 0) { - console.log(`[CalendarSection] Sample date check - ${dateKey}: hasEvents=${hasEvents}`); - } - - return ( - - ); - })} - - ))} + + + {renderDays()} - + ); }; const styles = StyleSheet.create({ container: { - backgroundColor: colors.darkBackground, - marginBottom: 12, - borderRadius: 8, - overflow: 'hidden', - borderWidth: 1, - borderColor: colors.border, + width: '100%', }, header: { flexDirection: 'row', - alignItems: 'center', justifyContent: 'space-between', - paddingVertical: 12, - paddingHorizontal: 16, + alignItems: 'center', + padding: 16, borderBottomWidth: 1, - borderBottomColor: colors.border, }, headerButton: { padding: 8, }, - monthTitle: { + headerTitle: { fontSize: 18, fontWeight: 'bold', - color: colors.text, }, - weekHeader: { + weekDaysContainer: { flexDirection: 'row', + justifyContent: 'space-around', padding: 8, - borderBottomWidth: 1, - borderBottomColor: colors.border, - }, - weekHeaderItem: { - width: DAY_ITEM_SIZE, - alignItems: 'center', }, weekDayText: { fontSize: 12, - color: colors.lightGray, }, - calendarGrid: { + daysContainer: { padding: 8, }, - row: { + weekRow: { flexDirection: 'row', justifyContent: 'space-around', marginBottom: 8, }, - dayItem: { - width: DAY_ITEM_SIZE, - height: DAY_ITEM_SIZE, + dayButton: { + width: 36, + height: 36, justifyContent: 'center', alignItems: 'center', - borderRadius: DAY_ITEM_SIZE / 2, + borderRadius: 18, + borderWidth: 1, + borderColor: 'transparent', }, dayText: { fontSize: 14, - color: colors.text, }, - otherMonthDay: { - color: colors.lightGray + '80', // 50% opacity + emptyDay: { + width: 36, + height: 36, + }, + eventDot: { + width: 4, + height: 4, + borderRadius: 2, + position: 'absolute', + bottom: 6, }, todayItem: { - backgroundColor: colors.primary + '30', // 30% opacity borderWidth: 1, - borderColor: colors.primary, }, selectedItem: { - backgroundColor: colors.primary + '60', // 60% opacity borderWidth: 1, - borderColor: colors.primary, }, todayText: { fontWeight: 'bold', - color: colors.primary, }, selectedDayText: { fontWeight: 'bold', - color: colors.text, }, dayWithEvents: { position: 'relative', @@ -312,6 +298,5 @@ const styles = StyleSheet.create({ width: 4, height: 4, borderRadius: 2, - backgroundColor: colors.primary, }, }); \ No newline at end of file diff --git a/src/screens/CalendarScreen.tsx b/src/screens/CalendarScreen.tsx index 55a3050..dd7b0c2 100644 --- a/src/screens/CalendarScreen.tsx +++ b/src/screens/CalendarScreen.tsx @@ -9,7 +9,6 @@ import { RefreshControl, SafeAreaView, StatusBar, - useColorScheme, Dimensions, SectionList } from 'react-native'; @@ -18,7 +17,7 @@ import { NavigationProp } from '@react-navigation/native'; import { Image } from 'expo-image'; import { MaterialIcons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; -import { colors } from '../styles/colors'; +import { useTheme } from '../contexts/ThemeContext'; import { RootStackParamList } from '../navigation/AppNavigator'; import { stremioService } from '../services/stremioService'; import { useLibrary } from '../hooks/useLibrary'; @@ -53,6 +52,7 @@ interface CalendarSection { const CalendarScreen = () => { const navigation = useNavigation>(); const { libraryItems, loading: libraryLoading } = useLibrary(); + const { currentTheme } = useTheme(); logger.log(`[Calendar] Initial load - Library has ${libraryItems?.length || 0} items, loading: ${libraryLoading}`); const [calendarData, setCalendarData] = useState([]); const [loading, setLoading] = useState(true); @@ -270,7 +270,7 @@ const CalendarScreen = () => { return ( handleEpisodePress(item)} activeOpacity={0.7} > @@ -287,18 +287,18 @@ const CalendarScreen = () => { - + {item.seriesName} {hasReleaseDate ? ( <> - + S{item.season}:E{item.episode} - {item.title} {item.overview ? ( - + {item.overview} ) : null} @@ -308,9 +308,9 @@ const CalendarScreen = () => { - {formattedDate} + {formattedDate} {item.vote_average > 0 && ( @@ -318,9 +318,9 @@ const CalendarScreen = () => { - + {item.vote_average.toFixed(1)} @@ -329,16 +329,16 @@ const CalendarScreen = () => { ) : ( <> - + No scheduled episodes - Check back later + Check back later )} @@ -349,8 +349,13 @@ const CalendarScreen = () => { }; const renderSectionHeader = ({ section }: { section: CalendarSection }) => ( - - {section.title} + + + {section.title} + ); @@ -386,22 +391,22 @@ const CalendarScreen = () => { if (libraryItems.length === 0 && !libraryLoading) { return ( - + - + navigation.goBack()} > - + - Calendar + Calendar - + Your library is empty @@ -423,10 +428,10 @@ const CalendarScreen = () => { if (loading && !refreshing) { return ( - + - + Loading calendar... @@ -434,27 +439,27 @@ const CalendarScreen = () => { } return ( - + - + navigation.goBack()} > - + - Calendar + Calendar {selectedDate && filteredEpisodes.length > 0 && ( - - + + Showing episodes for {format(selectedDate, 'MMMM d, yyyy')} - + )} @@ -474,22 +479,22 @@ const CalendarScreen = () => { } /> ) : selectedDate && filteredEpisodes.length === 0 ? ( - - + + No episodes for {format(selectedDate, 'MMMM d, yyyy')} - + Show All Episodes @@ -505,18 +510,18 @@ const CalendarScreen = () => { } /> ) : ( - - + + No upcoming episodes found - + Add series to your library to see their upcoming episodes here @@ -528,7 +533,6 @@ const CalendarScreen = () => { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.darkBackground, }, listContent: { paddingBottom: 20, @@ -539,19 +543,15 @@ const styles = StyleSheet.create({ alignItems: 'center', }, loadingText: { - color: colors.text, marginTop: 10, fontSize: 16, }, sectionHeader: { - backgroundColor: colors.darkBackground, paddingVertical: 8, paddingHorizontal: 16, borderBottomWidth: 1, - borderBottomColor: colors.border, }, sectionTitle: { - color: colors.text, fontSize: 18, fontWeight: 'bold', }, @@ -559,7 +559,6 @@ const styles = StyleSheet.create({ flexDirection: 'row', padding: 12, borderBottomWidth: 1, - borderBottomColor: colors.border + '20', }, poster: { width: 120, @@ -572,18 +571,15 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', }, seriesName: { - color: colors.text, fontSize: 16, fontWeight: 'bold', marginBottom: 4, }, episodeTitle: { - color: colors.lightGray, fontSize: 14, lineHeight: 20, }, overview: { - color: colors.lightGray, fontSize: 12, marginTop: 4, lineHeight: 16, @@ -599,7 +595,6 @@ const styles = StyleSheet.create({ alignItems: 'center', }, date: { - color: colors.lightGray, fontSize: 14, marginLeft: 4, }, @@ -608,7 +603,6 @@ const styles = StyleSheet.create({ alignItems: 'center', }, rating: { - color: colors.primary, fontSize: 14, marginLeft: 4, fontWeight: 'bold', @@ -620,14 +614,12 @@ const styles = StyleSheet.create({ padding: 20, }, emptyText: { - color: colors.text, fontSize: 18, fontWeight: 'bold', marginTop: 16, textAlign: 'center', }, emptySubtext: { - color: colors.lightGray, fontSize: 14, marginTop: 8, textAlign: 'center', @@ -638,10 +630,8 @@ const styles = StyleSheet.create({ alignItems: 'center', padding: 12, borderBottomWidth: 1, - borderBottomColor: colors.border, }, filterInfoText: { - color: colors.text, fontSize: 16, fontWeight: 'bold', }, @@ -655,7 +645,6 @@ const styles = StyleSheet.create({ padding: 20, }, emptyFilterText: { - color: colors.text, fontSize: 18, fontWeight: 'bold', marginTop: 16, @@ -664,11 +653,9 @@ const styles = StyleSheet.create({ clearFilterButtonLarge: { marginTop: 20, padding: 16, - backgroundColor: colors.primary, borderRadius: 8, }, clearFilterButtonText: { - color: colors.text, fontSize: 16, fontWeight: 'bold', }, @@ -681,7 +668,6 @@ const styles = StyleSheet.create({ padding: 8, }, headerTitle: { - color: colors.text, fontSize: 18, fontWeight: 'bold', marginLeft: 12, @@ -694,16 +680,13 @@ const styles = StyleSheet.create({ }, discoverButton: { padding: 16, - backgroundColor: colors.primary, borderRadius: 8, }, discoverButtonText: { - color: colors.text, fontSize: 16, fontWeight: 'bold', }, noEpisodesText: { - color: colors.text, fontSize: 14, marginBottom: 4, }, diff --git a/src/screens/NotificationSettingsScreen.tsx b/src/screens/NotificationSettingsScreen.tsx index 68eb422..360dfb4 100644 --- a/src/screens/NotificationSettingsScreen.tsx +++ b/src/screens/NotificationSettingsScreen.tsx @@ -11,7 +11,7 @@ import { StatusBar, } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; -import { colors } from '../styles/colors'; +import { useTheme } from '../contexts/ThemeContext'; import { notificationService, NotificationSettings } from '../services/notificationService'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; import { useNavigation } from '@react-navigation/native'; @@ -19,6 +19,7 @@ import { logger } from '../utils/logger'; const NotificationSettingsScreen = () => { const navigation = useNavigation(); + const { currentTheme } = useTheme(); const [settings, setSettings] = useState({ enabled: true, newEpisodeNotifications: true, @@ -155,36 +156,36 @@ const NotificationSettingsScreen = () => { if (loading) { return ( - - + + navigation.goBack()} > - + - Notification Settings + Notification Settings - Loading settings... + Loading settings... ); } return ( - + - + navigation.goBack()} > - + - Notification Settings + Notification Settings @@ -193,72 +194,72 @@ const NotificationSettingsScreen = () => { entering={FadeIn.duration(300)} exiting={FadeOut.duration(200)} > - - General + + General - + - - Enable Notifications + + Enable Notifications updateSetting('enabled', value)} - trackColor={{ false: colors.border, true: colors.primary + '80' }} - thumbColor={settings.enabled ? colors.primary : colors.lightGray} + trackColor={{ false: currentTheme.colors.border, true: currentTheme.colors.primary + '80' }} + thumbColor={settings.enabled ? currentTheme.colors.primary : currentTheme.colors.lightGray} /> {settings.enabled && ( <> - - Notification Types + + Notification Types - + - - New Episodes + + New Episodes updateSetting('newEpisodeNotifications', value)} - trackColor={{ false: colors.border, true: colors.primary + '80' }} - thumbColor={settings.newEpisodeNotifications ? colors.primary : colors.lightGray} + trackColor={{ false: currentTheme.colors.border, true: currentTheme.colors.primary + '80' }} + thumbColor={settings.newEpisodeNotifications ? currentTheme.colors.primary : currentTheme.colors.lightGray} /> - + - - Upcoming Shows + + Upcoming Shows updateSetting('upcomingShowsNotifications', value)} - trackColor={{ false: colors.border, true: colors.primary + '80' }} - thumbColor={settings.upcomingShowsNotifications ? colors.primary : colors.lightGray} + trackColor={{ false: currentTheme.colors.border, true: currentTheme.colors.primary + '80' }} + thumbColor={settings.upcomingShowsNotifications ? currentTheme.colors.primary : currentTheme.colors.lightGray} /> - + - - Reminders + + Reminders updateSetting('reminderNotifications', value)} - trackColor={{ false: colors.border, true: colors.primary + '80' }} - thumbColor={settings.reminderNotifications ? colors.primary : colors.lightGray} + trackColor={{ false: currentTheme.colors.border, true: currentTheme.colors.primary + '80' }} + thumbColor={settings.reminderNotifications ? currentTheme.colors.primary : currentTheme.colors.lightGray} /> - - Notification Timing + + Notification Timing - + When should you be notified before an episode airs? @@ -268,13 +269,24 @@ const NotificationSettingsScreen = () => { key={hours} style={[ styles.timingOption, - settings.timeBeforeAiring === hours && styles.selectedTimingOption + { + backgroundColor: currentTheme.colors.elevation1, + borderColor: currentTheme.colors.border + }, + settings.timeBeforeAiring === hours && { + backgroundColor: currentTheme.colors.primary + '30', + borderColor: currentTheme.colors.primary, + } ]} onPress={() => setTimeBeforeAiring(hours)} > {hours === 1 ? '1 hour' : `${hours} hours`} @@ -283,27 +295,37 @@ const NotificationSettingsScreen = () => { - - Advanced + + Advanced - - Reset All Notifications + + Reset All Notifications - - + + {countdown !== null ? `Notification in ${countdown}s...` : 'Test Notification (1min)'} @@ -315,16 +337,16 @@ const NotificationSettingsScreen = () => { - + Notification will appear in {countdown} seconds )} - + This will cancel all scheduled notifications. You'll need to re-enable them manually. @@ -339,7 +361,6 @@ const NotificationSettingsScreen = () => { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.darkBackground, }, header: { flexDirection: 'row', @@ -348,7 +369,6 @@ const styles = StyleSheet.create({ paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: 1, - borderBottomColor: colors.border, }, backButton: { padding: 8, @@ -356,7 +376,6 @@ const styles = StyleSheet.create({ headerTitle: { fontSize: 18, fontWeight: 'bold', - color: colors.text, }, content: { flex: 1, @@ -367,18 +386,15 @@ const styles = StyleSheet.create({ alignItems: 'center', }, loadingText: { - color: colors.text, fontSize: 16, }, section: { padding: 16, borderBottomWidth: 1, - borderBottomColor: colors.border, }, sectionTitle: { fontSize: 16, fontWeight: 'bold', - color: colors.text, marginBottom: 16, }, settingItem: { @@ -387,7 +403,6 @@ const styles = StyleSheet.create({ alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, - borderBottomColor: colors.border + '50', }, settingInfo: { flexDirection: 'row', @@ -395,12 +410,10 @@ const styles = StyleSheet.create({ }, settingText: { fontSize: 16, - color: colors.text, marginLeft: 12, }, settingDescription: { fontSize: 14, - color: colors.lightGray, marginBottom: 16, }, timingOptions: { @@ -410,47 +423,32 @@ const styles = StyleSheet.create({ marginTop: 8, }, timingOption: { - backgroundColor: colors.elevation1, paddingVertical: 10, paddingHorizontal: 16, borderRadius: 8, borderWidth: 1, - borderColor: colors.border, marginBottom: 8, width: '48%', alignItems: 'center', }, - selectedTimingOption: { - backgroundColor: colors.primary + '30', - borderColor: colors.primary, - }, timingText: { - color: colors.text, fontSize: 14, }, - selectedTimingText: { - color: colors.primary, - fontWeight: 'bold', - }, resetButton: { flexDirection: 'row', alignItems: 'center', padding: 12, - backgroundColor: colors.error + '20', borderRadius: 8, borderWidth: 1, - borderColor: colors.error + '50', marginBottom: 8, }, resetButtonText: { - color: colors.error, fontSize: 16, fontWeight: 'bold', marginLeft: 8, }, resetDescription: { fontSize: 12, - color: colors.lightGray, fontStyle: 'italic', }, countdownContainer: { @@ -458,14 +456,13 @@ const styles = StyleSheet.create({ alignItems: 'center', marginTop: 8, padding: 8, - backgroundColor: colors.primary + '10', + backgroundColor: 'rgba(0, 0, 0, 0.1)', borderRadius: 4, }, countdownIcon: { marginRight: 8, }, countdownText: { - color: colors.primary, fontSize: 14, }, });