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.
This commit is contained in:
tapframe 2025-05-04 02:44:38 +05:30
parent 9ab154f8b8
commit 29347ee028
3 changed files with 276 additions and 311 deletions

View file

@ -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) => (
<TouchableOpacity
style={[
styles.dayItem,
today && styles.todayItem,
isSelected && styles.selectedItem,
hasEvents && styles.dayWithEvents
]}
onPress={() => onPress(date)}
>
<Text style={[
styles.dayText,
!isCurrentMonth && styles.otherMonthDay,
today && styles.todayText,
isSelected && styles.selectedDayText
]}>
{date.getDate()}
</Text>
{hasEvents && (
<View style={styles.eventIndicator} />
)}
</TouchableOpacity>
);
}: DayItemProps) => {
const { currentTheme } = useTheme();
return (
<TouchableOpacity
style={[
styles.dayButton,
today && styles.todayItem,
isSelected && styles.selectedItem,
hasEvents && styles.dayWithEvents
]}
onPress={() => onPress(date)}
>
<Text style={[
styles.dayText,
!isCurrentMonth && { color: currentTheme.colors.lightGray + '80' },
today && styles.todayText,
isSelected && styles.selectedDayText
]}>
{date.getDate()}
</Text>
{hasEvents && (
<View style={[styles.eventIndicator, { backgroundColor: currentTheme.colors.primary }]} />
)}
</TouchableOpacity>
);
};
export const CalendarSection: React.FC<CalendarSectionProps> = ({
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<Date | null>(null);
const scrollViewRef = useRef<ScrollView>(null);
// Map of dates with episodes
@ -97,7 +90,7 @@ export const CalendarSection: React.FC<CalendarSectionProps> = ({
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<CalendarSectionProps> = ({
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) => (
<View key={weekIndex} style={styles.weekRow}>
{week.map((day, dayIndex) => {
if (!day) {
return <View key={`empty-${dayIndex}`} style={styles.emptyDay} />;
}
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 (
<TouchableOpacity
key={day.toISOString()}
style={[
styles.dayButton,
isCurrentDay && [styles.todayItem, { backgroundColor: currentTheme.colors.primary + '30', borderColor: currentTheme.colors.primary }],
isSelected && [styles.selectedItem, { backgroundColor: currentTheme.colors.primary + '60', borderColor: currentTheme.colors.primary }],
hasEvents && styles.dayWithEvents
]}
onPress={() => handleDateSelect(day)}
>
<Text
style={[
styles.dayText,
{ color: currentTheme.colors.text },
!isCurrentMonth && { color: currentTheme.colors.lightGray + '80' },
isCurrentDay && [styles.todayText, { color: currentTheme.colors.primary }],
isSelected && [styles.selectedDayText, { color: currentTheme.colors.text }]
]}
>
{format(day, 'd')}
</Text>
{hasEvents && (
<View style={[styles.eventDot, { backgroundColor: currentTheme.colors.primary }]} />
)}
</TouchableOpacity>
);
})}
</View>
));
};
// 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 (
<Animated.View entering={FadeIn.duration(300)} style={styles.container}>
<View style={styles.header}>
<TouchableOpacity onPress={goToPreviousMonth} style={styles.headerButton}>
<MaterialIcons name="chevron-left" size={24} color={colors.text} />
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
<View style={[styles.header, { borderBottomColor: currentTheme.colors.border }]}>
<TouchableOpacity
onPress={goToPreviousMonth}
style={styles.headerButton}
>
<MaterialIcons name="chevron-left" size={24} color={currentTheme.colors.text} />
</TouchableOpacity>
<Text style={styles.monthTitle}>
<Text style={[styles.headerTitle, { color: currentTheme.colors.text }]}>
{format(currentDate, 'MMMM yyyy')}
</Text>
<TouchableOpacity onPress={goToNextMonth} style={styles.headerButton}>
<MaterialIcons name="chevron-right" size={24} color={colors.text} />
<TouchableOpacity
onPress={goToNextMonth}
style={styles.headerButton}
>
<MaterialIcons name="chevron-right" size={24} color={currentTheme.colors.text} />
</TouchableOpacity>
</View>
<View style={styles.weekHeader}>
<View style={styles.weekDaysContainer}>
{weekDays.map((day, index) => (
<View key={index} style={styles.weekHeaderItem}>
<Text style={styles.weekDayText}>{day}</Text>
</View>
<Text
key={index}
style={[styles.weekDayText, { color: currentTheme.colors.lightGray }]}
>
{day}
</Text>
))}
</View>
<View style={styles.calendarGrid}>
{rows.map((row, rowIndex) => (
<View key={rowIndex} style={styles.row}>
{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 (
<DayItem
key={cellIndex}
date={date}
isCurrentMonth={isCurrentMonthDay}
isToday={isSelectedToday}
isSelected={isDateSelected}
hasEvents={hasEvents}
onPress={handleDayPress}
/>
);
})}
</View>
))}
<View style={styles.daysContainer}>
{renderDays()}
</View>
</Animated.View>
</View>
);
};
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,
},
});

View file

@ -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<NavigationProp<RootStackParamList>>();
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<CalendarSection[]>([]);
const [loading, setLoading] = useState(true);
@ -270,7 +270,7 @@ const CalendarScreen = () => {
return (
<Animated.View entering={FadeIn.duration(300).delay(100)}>
<TouchableOpacity
style={styles.episodeItem}
style={[styles.episodeItem, { borderBottomColor: currentTheme.colors.border + '20' }]}
onPress={() => handleEpisodePress(item)}
activeOpacity={0.7}
>
@ -287,18 +287,18 @@ const CalendarScreen = () => {
</TouchableOpacity>
<View style={styles.episodeDetails}>
<Text style={styles.seriesName} numberOfLines={1}>
<Text style={[styles.seriesName, { color: currentTheme.colors.text }]} numberOfLines={1}>
{item.seriesName}
</Text>
{hasReleaseDate ? (
<>
<Text style={styles.episodeTitle} numberOfLines={2}>
<Text style={[styles.episodeTitle, { color: currentTheme.colors.lightGray }]} numberOfLines={2}>
S{item.season}:E{item.episode} - {item.title}
</Text>
{item.overview ? (
<Text style={styles.overview} numberOfLines={2}>
<Text style={[styles.overview, { color: currentTheme.colors.lightGray }]} numberOfLines={2}>
{item.overview}
</Text>
) : null}
@ -308,9 +308,9 @@ const CalendarScreen = () => {
<MaterialIcons
name={isFuture ? "event" : "event-available"}
size={16}
color={colors.lightGray}
color={currentTheme.colors.lightGray}
/>
<Text style={styles.date}>{formattedDate}</Text>
<Text style={[styles.date, { color: currentTheme.colors.lightGray }]}>{formattedDate}</Text>
</View>
{item.vote_average > 0 && (
@ -318,9 +318,9 @@ const CalendarScreen = () => {
<MaterialIcons
name="star"
size={16}
color={colors.primary}
color={currentTheme.colors.primary}
/>
<Text style={styles.rating}>
<Text style={[styles.rating, { color: currentTheme.colors.primary }]}>
{item.vote_average.toFixed(1)}
</Text>
</View>
@ -329,16 +329,16 @@ const CalendarScreen = () => {
</>
) : (
<>
<Text style={styles.noEpisodesText}>
<Text style={[styles.noEpisodesText, { color: currentTheme.colors.text }]}>
No scheduled episodes
</Text>
<View style={styles.dateContainer}>
<MaterialIcons
name="event-busy"
size={16}
color={colors.lightGray}
color={currentTheme.colors.lightGray}
/>
<Text style={styles.date}>Check back later</Text>
<Text style={[styles.date, { color: currentTheme.colors.lightGray }]}>Check back later</Text>
</View>
</>
)}
@ -349,8 +349,13 @@ const CalendarScreen = () => {
};
const renderSectionHeader = ({ section }: { section: CalendarSection }) => (
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>{section.title}</Text>
<View style={[styles.sectionHeader, {
backgroundColor: currentTheme.colors.darkBackground,
borderBottomColor: currentTheme.colors.border
}]}>
<Text style={[styles.sectionTitle, { color: currentTheme.colors.text }]}>
{section.title}
</Text>
</View>
);
@ -386,22 +391,22 @@ const CalendarScreen = () => {
if (libraryItems.length === 0 && !libraryLoading) {
return (
<SafeAreaView style={styles.container}>
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
<StatusBar barStyle="light-content" />
<View style={styles.header}>
<View style={[styles.header, { borderBottomColor: currentTheme.colors.border }]}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<MaterialIcons name="arrow-back" size={24} color={colors.text} />
<MaterialIcons name="arrow-back" size={24} color={currentTheme.colors.text} />
</TouchableOpacity>
<Text style={styles.headerTitle}>Calendar</Text>
<Text style={[styles.headerTitle, { color: currentTheme.colors.text }]}>Calendar</Text>
<View style={{ width: 40 }} />
</View>
<View style={styles.emptyLibraryContainer}>
<MaterialIcons name="video-library" size={64} color={colors.lightGray} />
<MaterialIcons name="video-library" size={64} color={currentTheme.colors.lightGray} />
<Text style={styles.emptyText}>
Your library is empty
</Text>
@ -423,10 +428,10 @@ const CalendarScreen = () => {
if (loading && !refreshing) {
return (
<SafeAreaView style={styles.container}>
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
<StatusBar barStyle="light-content" />
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={colors.primary} />
<ActivityIndicator size="large" color={currentTheme.colors.primary} />
<Text style={styles.loadingText}>Loading calendar...</Text>
</View>
</SafeAreaView>
@ -434,27 +439,27 @@ const CalendarScreen = () => {
}
return (
<SafeAreaView style={styles.container}>
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
<StatusBar barStyle="light-content" />
<View style={styles.header}>
<View style={[styles.header, { borderBottomColor: currentTheme.colors.border }]}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<MaterialIcons name="arrow-back" size={24} color={colors.text} />
<MaterialIcons name="arrow-back" size={24} color={currentTheme.colors.text} />
</TouchableOpacity>
<Text style={styles.headerTitle}>Calendar</Text>
<Text style={[styles.headerTitle, { color: currentTheme.colors.text }]}>Calendar</Text>
<View style={{ width: 40 }} />
</View>
{selectedDate && filteredEpisodes.length > 0 && (
<View style={styles.filterInfoContainer}>
<Text style={styles.filterInfoText}>
<View style={[styles.filterInfoContainer, { borderBottomColor: currentTheme.colors.border }]}>
<Text style={[styles.filterInfoText, { color: currentTheme.colors.text }]}>
Showing episodes for {format(selectedDate, 'MMMM d, yyyy')}
</Text>
<TouchableOpacity onPress={clearDateFilter} style={styles.clearFilterButton}>
<MaterialIcons name="close" size={18} color={colors.text} />
<MaterialIcons name="close" size={18} color={currentTheme.colors.text} />
</TouchableOpacity>
</View>
)}
@ -474,22 +479,22 @@ const CalendarScreen = () => {
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={colors.primary}
colors={[colors.primary]}
tintColor={currentTheme.colors.primary}
colors={[currentTheme.colors.primary]}
/>
}
/>
) : selectedDate && filteredEpisodes.length === 0 ? (
<View style={styles.emptyFilterContainer}>
<MaterialIcons name="event-busy" size={48} color={colors.lightGray} />
<Text style={styles.emptyFilterText}>
<MaterialIcons name="event-busy" size={48} color={currentTheme.colors.lightGray} />
<Text style={[styles.emptyFilterText, { color: currentTheme.colors.text }]}>
No episodes for {format(selectedDate, 'MMMM d, yyyy')}
</Text>
<TouchableOpacity
style={styles.clearFilterButtonLarge}
style={[styles.clearFilterButtonLarge, { backgroundColor: currentTheme.colors.primary }]}
onPress={clearDateFilter}
>
<Text style={styles.clearFilterButtonText}>
<Text style={[styles.clearFilterButtonText, { color: currentTheme.colors.text }]}>
Show All Episodes
</Text>
</TouchableOpacity>
@ -505,18 +510,18 @@ const CalendarScreen = () => {
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={colors.primary}
colors={[colors.primary]}
tintColor={currentTheme.colors.primary}
colors={[currentTheme.colors.primary]}
/>
}
/>
) : (
<View style={styles.emptyContainer}>
<MaterialIcons name="calendar-today" size={64} color={colors.lightGray} />
<Text style={styles.emptyText}>
<MaterialIcons name="calendar-today" size={64} color={currentTheme.colors.lightGray} />
<Text style={[styles.emptyText, { color: currentTheme.colors.text }]}>
No upcoming episodes found
</Text>
<Text style={styles.emptySubtext}>
<Text style={[styles.emptySubtext, { color: currentTheme.colors.lightGray }]}>
Add series to your library to see their upcoming episodes here
</Text>
</View>
@ -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,
},

View file

@ -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<NotificationSettings>({
enabled: true,
newEpisodeNotifications: true,
@ -155,36 +156,36 @@ const NotificationSettingsScreen = () => {
if (loading) {
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
<View style={[styles.header, { borderBottomColor: currentTheme.colors.border }]}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<MaterialIcons name="arrow-back" size={24} color={colors.text} />
<MaterialIcons name="arrow-back" size={24} color={currentTheme.colors.text} />
</TouchableOpacity>
<Text style={styles.headerTitle}>Notification Settings</Text>
<Text style={[styles.headerTitle, { color: currentTheme.colors.text }]}>Notification Settings</Text>
<View style={{ width: 40 }} />
</View>
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading settings...</Text>
<Text style={[styles.loadingText, { color: currentTheme.colors.text }]}>Loading settings...</Text>
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
<StatusBar barStyle="light-content" />
<View style={styles.header}>
<View style={[styles.header, { borderBottomColor: currentTheme.colors.border }]}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<MaterialIcons name="arrow-back" size={24} color={colors.text} />
<MaterialIcons name="arrow-back" size={24} color={currentTheme.colors.text} />
</TouchableOpacity>
<Text style={styles.headerTitle}>Notification Settings</Text>
<Text style={[styles.headerTitle, { color: currentTheme.colors.text }]}>Notification Settings</Text>
<View style={{ width: 40 }} />
</View>
@ -193,72 +194,72 @@ const NotificationSettingsScreen = () => {
entering={FadeIn.duration(300)}
exiting={FadeOut.duration(200)}
>
<View style={styles.section}>
<Text style={styles.sectionTitle}>General</Text>
<View style={[styles.section, { borderBottomColor: currentTheme.colors.border }]}>
<Text style={[styles.sectionTitle, { color: currentTheme.colors.text }]}>General</Text>
<View style={styles.settingItem}>
<View style={[styles.settingItem, { borderBottomColor: currentTheme.colors.border + '50' }]}>
<View style={styles.settingInfo}>
<MaterialIcons name="notifications" size={24} color={colors.text} />
<Text style={styles.settingText}>Enable Notifications</Text>
<MaterialIcons name="notifications" size={24} color={currentTheme.colors.text} />
<Text style={[styles.settingText, { color: currentTheme.colors.text }]}>Enable Notifications</Text>
</View>
<Switch
value={settings.enabled}
onValueChange={(value) => 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}
/>
</View>
</View>
{settings.enabled && (
<>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Notification Types</Text>
<View style={[styles.section, { borderBottomColor: currentTheme.colors.border }]}>
<Text style={[styles.sectionTitle, { color: currentTheme.colors.text }]}>Notification Types</Text>
<View style={styles.settingItem}>
<View style={[styles.settingItem, { borderBottomColor: currentTheme.colors.border + '50' }]}>
<View style={styles.settingInfo}>
<MaterialIcons name="new-releases" size={24} color={colors.text} />
<Text style={styles.settingText}>New Episodes</Text>
<MaterialIcons name="new-releases" size={24} color={currentTheme.colors.text} />
<Text style={[styles.settingText, { color: currentTheme.colors.text }]}>New Episodes</Text>
</View>
<Switch
value={settings.newEpisodeNotifications}
onValueChange={(value) => 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}
/>
</View>
<View style={styles.settingItem}>
<View style={[styles.settingItem, { borderBottomColor: currentTheme.colors.border + '50' }]}>
<View style={styles.settingInfo}>
<MaterialIcons name="event" size={24} color={colors.text} />
<Text style={styles.settingText}>Upcoming Shows</Text>
<MaterialIcons name="event" size={24} color={currentTheme.colors.text} />
<Text style={[styles.settingText, { color: currentTheme.colors.text }]}>Upcoming Shows</Text>
</View>
<Switch
value={settings.upcomingShowsNotifications}
onValueChange={(value) => 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}
/>
</View>
<View style={styles.settingItem}>
<View style={[styles.settingItem, { borderBottomColor: currentTheme.colors.border + '50' }]}>
<View style={styles.settingInfo}>
<MaterialIcons name="alarm" size={24} color={colors.text} />
<Text style={styles.settingText}>Reminders</Text>
<MaterialIcons name="alarm" size={24} color={currentTheme.colors.text} />
<Text style={[styles.settingText, { color: currentTheme.colors.text }]}>Reminders</Text>
</View>
<Switch
value={settings.reminderNotifications}
onValueChange={(value) => 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}
/>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Notification Timing</Text>
<View style={[styles.section, { borderBottomColor: currentTheme.colors.border }]}>
<Text style={[styles.sectionTitle, { color: currentTheme.colors.text }]}>Notification Timing</Text>
<Text style={styles.settingDescription}>
<Text style={[styles.settingDescription, { color: currentTheme.colors.lightGray }]}>
When should you be notified before an episode airs?
</Text>
@ -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)}
>
<Text style={[
styles.timingText,
settings.timeBeforeAiring === hours && styles.selectedTimingText
{ color: currentTheme.colors.text },
settings.timeBeforeAiring === hours && {
color: currentTheme.colors.primary,
fontWeight: 'bold',
}
]}>
{hours === 1 ? '1 hour' : `${hours} hours`}
</Text>
@ -283,27 +295,37 @@ const NotificationSettingsScreen = () => {
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Advanced</Text>
<View style={[styles.section, { borderBottomColor: currentTheme.colors.border }]}>
<Text style={[styles.sectionTitle, { color: currentTheme.colors.text }]}>Advanced</Text>
<TouchableOpacity
style={styles.resetButton}
style={[
styles.resetButton,
{
backgroundColor: currentTheme.colors.error + '20',
borderColor: currentTheme.colors.error + '50'
}
]}
onPress={resetAllNotifications}
>
<MaterialIcons name="refresh" size={24} color={colors.error} />
<Text style={styles.resetButtonText}>Reset All Notifications</Text>
<MaterialIcons name="refresh" size={24} color={currentTheme.colors.error} />
<Text style={[styles.resetButtonText, { color: currentTheme.colors.error }]}>Reset All Notifications</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.resetButton,
{ marginTop: 12, backgroundColor: colors.primary + '20', borderColor: colors.primary + '50' }
styles.resetButton,
{
marginTop: 12,
backgroundColor: currentTheme.colors.primary + '20',
borderColor: currentTheme.colors.primary + '50'
}
]}
onPress={handleTestNotification}
disabled={countdown !== null}
>
<MaterialIcons name="bug-report" size={24} color={colors.primary} />
<Text style={[styles.resetButtonText, { color: colors.primary }]}>
<MaterialIcons name="bug-report" size={24} color={currentTheme.colors.primary} />
<Text style={[styles.resetButtonText, { color: currentTheme.colors.primary }]}>
{countdown !== null
? `Notification in ${countdown}s...`
: 'Test Notification (1min)'}
@ -315,16 +337,16 @@ const NotificationSettingsScreen = () => {
<MaterialIcons
name="timer"
size={16}
color={colors.primary}
color={currentTheme.colors.primary}
style={styles.countdownIcon}
/>
<Text style={styles.countdownText}>
<Text style={[styles.countdownText, { color: currentTheme.colors.primary }]}>
Notification will appear in {countdown} seconds
</Text>
</View>
)}
<Text style={styles.resetDescription}>
<Text style={[styles.resetDescription, { color: currentTheme.colors.lightGray }]}>
This will cancel all scheduled notifications. You'll need to re-enable them manually.
</Text>
</View>
@ -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,
},
});