This update enhances the ThisWeekSection component by refining the layout and ensuring consistent styling for images and text elements. Additionally, the CalendarScreen component has been modified to adjust padding for Android devices and improve the overall visual structure. These changes aim to enhance user experience and maintain design coherence across the application.
699 lines
No EOL
23 KiB
TypeScript
699 lines
No EOL
23 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
FlatList,
|
|
TouchableOpacity,
|
|
ActivityIndicator,
|
|
RefreshControl,
|
|
SafeAreaView,
|
|
StatusBar,
|
|
Dimensions,
|
|
SectionList,
|
|
Platform
|
|
} from 'react-native';
|
|
import { useNavigation } from '@react-navigation/native';
|
|
import { NavigationProp } from '@react-navigation/native';
|
|
import { Image } from 'expo-image';
|
|
import { MaterialIcons } from '@expo/vector-icons';
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
import { useTheme } from '../contexts/ThemeContext';
|
|
import { RootStackParamList } from '../navigation/AppNavigator';
|
|
import { stremioService } from '../services/stremioService';
|
|
import { useLibrary } from '../hooks/useLibrary';
|
|
import { format, parseISO, isThisWeek, isAfter, startOfToday, addWeeks, isBefore, isSameDay } from 'date-fns';
|
|
import Animated, { FadeIn } from 'react-native-reanimated';
|
|
import { CalendarSection } from '../components/calendar/CalendarSection';
|
|
import { tmdbService } from '../services/tmdbService';
|
|
import { logger } from '../utils/logger';
|
|
|
|
const { width } = Dimensions.get('window');
|
|
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
|
|
|
interface CalendarEpisode {
|
|
id: string;
|
|
seriesId: string;
|
|
title: string;
|
|
seriesName: string;
|
|
poster: string;
|
|
releaseDate: string;
|
|
season: number;
|
|
episode: number;
|
|
overview: string;
|
|
vote_average: number;
|
|
still_path: string | null;
|
|
season_poster_path: string | null;
|
|
}
|
|
|
|
interface CalendarSection {
|
|
title: string;
|
|
data: CalendarEpisode[];
|
|
}
|
|
|
|
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);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
|
|
const [filteredEpisodes, setFilteredEpisodes] = useState<CalendarEpisode[]>([]);
|
|
|
|
const fetchCalendarData = useCallback(async () => {
|
|
logger.log("[Calendar] Starting to fetch calendar data");
|
|
setLoading(true);
|
|
|
|
try {
|
|
// Filter for only series in library
|
|
const seriesItems = libraryItems.filter(item => item.type === 'series');
|
|
logger.log(`[Calendar] Library items: ${libraryItems.length}, Series items: ${seriesItems.length}`);
|
|
|
|
let allEpisodes: CalendarEpisode[] = [];
|
|
let seriesWithoutEpisodes: CalendarEpisode[] = [];
|
|
|
|
// For each series, fetch upcoming episodes
|
|
for (const series of seriesItems) {
|
|
try {
|
|
logger.log(`[Calendar] Fetching episodes for series: ${series.name} (${series.id})`);
|
|
const metadata = await stremioService.getMetaDetails(series.type, series.id);
|
|
logger.log(`[Calendar] Metadata fetched:`, metadata ? 'success' : 'null');
|
|
|
|
if (metadata?.videos && metadata.videos.length > 0) {
|
|
logger.log(`[Calendar] Series ${series.name} has ${metadata.videos.length} videos`);
|
|
// Filter for upcoming episodes or recently released
|
|
const today = startOfToday();
|
|
const fourWeeksLater = addWeeks(today, 4);
|
|
const twoWeeksAgo = addWeeks(today, -2);
|
|
|
|
// Get TMDB ID for additional metadata
|
|
const tmdbId = await tmdbService.findTMDBIdByIMDB(series.id);
|
|
let tmdbEpisodes: { [key: string]: any } = {};
|
|
|
|
if (tmdbId) {
|
|
const allTMDBEpisodes = await tmdbService.getAllEpisodes(tmdbId);
|
|
// Flatten episodes into a map for easy lookup
|
|
Object.values(allTMDBEpisodes).forEach(seasonEpisodes => {
|
|
seasonEpisodes.forEach(episode => {
|
|
const key = `${episode.season_number}:${episode.episode_number}`;
|
|
tmdbEpisodes[key] = episode;
|
|
});
|
|
});
|
|
}
|
|
|
|
const upcomingEpisodes = metadata.videos
|
|
.filter(video => {
|
|
if (!video.released) return false;
|
|
const releaseDate = parseISO(video.released);
|
|
return isBefore(releaseDate, fourWeeksLater) && isAfter(releaseDate, twoWeeksAgo);
|
|
})
|
|
.map(video => {
|
|
const tmdbEpisode = tmdbEpisodes[`${video.season}:${video.episode}`] || {};
|
|
return {
|
|
id: video.id,
|
|
seriesId: series.id,
|
|
title: tmdbEpisode.name || video.title || `Episode ${video.episode}`,
|
|
seriesName: series.name || metadata.name,
|
|
poster: series.poster || metadata.poster || '',
|
|
releaseDate: video.released,
|
|
season: video.season || 0,
|
|
episode: video.episode || 0,
|
|
overview: tmdbEpisode.overview || '',
|
|
vote_average: tmdbEpisode.vote_average || 0,
|
|
still_path: tmdbEpisode.still_path || null,
|
|
season_poster_path: tmdbEpisode.season_poster_path || null
|
|
};
|
|
});
|
|
|
|
if (upcomingEpisodes.length > 0) {
|
|
allEpisodes = [...allEpisodes, ...upcomingEpisodes];
|
|
} else {
|
|
// Add to series without episode dates
|
|
seriesWithoutEpisodes.push({
|
|
id: series.id,
|
|
seriesId: series.id,
|
|
title: 'No upcoming episodes',
|
|
seriesName: series.name || (metadata?.name || ''),
|
|
poster: series.poster || (metadata?.poster || ''),
|
|
releaseDate: '',
|
|
season: 0,
|
|
episode: 0,
|
|
overview: '',
|
|
vote_average: 0,
|
|
still_path: null,
|
|
season_poster_path: null
|
|
});
|
|
}
|
|
} else {
|
|
// Add to series without episode dates
|
|
seriesWithoutEpisodes.push({
|
|
id: series.id,
|
|
seriesId: series.id,
|
|
title: 'No upcoming episodes',
|
|
seriesName: series.name || (metadata?.name || ''),
|
|
poster: series.poster || (metadata?.poster || ''),
|
|
releaseDate: '',
|
|
season: 0,
|
|
episode: 0,
|
|
overview: '',
|
|
vote_average: 0,
|
|
still_path: null,
|
|
season_poster_path: null
|
|
});
|
|
}
|
|
} catch (error) {
|
|
logger.error(`Error fetching episodes for ${series.name}:`, error);
|
|
}
|
|
}
|
|
|
|
// Sort episodes by release date
|
|
allEpisodes.sort((a, b) => {
|
|
return new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime();
|
|
});
|
|
|
|
// Group episodes into sections
|
|
const thisWeekEpisodes = allEpisodes.filter(
|
|
episode => isThisWeek(parseISO(episode.releaseDate))
|
|
);
|
|
|
|
const upcomingEpisodes = allEpisodes.filter(
|
|
episode => isAfter(parseISO(episode.releaseDate), new Date()) &&
|
|
!isThisWeek(parseISO(episode.releaseDate))
|
|
);
|
|
|
|
const recentEpisodes = allEpisodes.filter(
|
|
episode => isBefore(parseISO(episode.releaseDate), new Date()) &&
|
|
!isThisWeek(parseISO(episode.releaseDate))
|
|
);
|
|
|
|
logger.log(`[Calendar] Episodes summary: All episodes: ${allEpisodes.length}, This Week: ${thisWeekEpisodes.length}, Upcoming: ${upcomingEpisodes.length}, Recent: ${recentEpisodes.length}, No Schedule: ${seriesWithoutEpisodes.length}`);
|
|
|
|
const sections: CalendarSection[] = [];
|
|
|
|
if (thisWeekEpisodes.length > 0) {
|
|
sections.push({ title: 'This Week', data: thisWeekEpisodes });
|
|
}
|
|
|
|
if (upcomingEpisodes.length > 0) {
|
|
sections.push({ title: 'Upcoming', data: upcomingEpisodes });
|
|
}
|
|
|
|
if (recentEpisodes.length > 0) {
|
|
sections.push({ title: 'Recently Released', data: recentEpisodes });
|
|
}
|
|
|
|
if (seriesWithoutEpisodes.length > 0) {
|
|
sections.push({ title: 'Series with No Scheduled Episodes', data: seriesWithoutEpisodes });
|
|
}
|
|
|
|
setCalendarData(sections);
|
|
} catch (error) {
|
|
logger.error('Error fetching calendar data:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
setRefreshing(false);
|
|
}
|
|
}, [libraryItems]);
|
|
|
|
useEffect(() => {
|
|
if (libraryItems.length > 0 && !libraryLoading) {
|
|
logger.log(`[Calendar] Library loaded with ${libraryItems.length} items, fetching calendar data`);
|
|
fetchCalendarData();
|
|
} else if (!libraryLoading) {
|
|
logger.log(`[Calendar] Library loaded but empty (${libraryItems.length} items)`);
|
|
setLoading(false);
|
|
}
|
|
}, [libraryItems, libraryLoading, fetchCalendarData]);
|
|
|
|
const onRefresh = useCallback(() => {
|
|
setRefreshing(true);
|
|
fetchCalendarData();
|
|
}, [fetchCalendarData]);
|
|
|
|
const handleSeriesPress = useCallback((seriesId: string, episode?: CalendarEpisode) => {
|
|
navigation.navigate('Metadata', {
|
|
id: seriesId,
|
|
type: 'series',
|
|
episodeId: episode ? `${episode.seriesId}:${episode.season}:${episode.episode}` : undefined
|
|
});
|
|
}, [navigation]);
|
|
|
|
const handleEpisodePress = useCallback((episode: CalendarEpisode) => {
|
|
// For series without episode dates, just go to the series page
|
|
if (!episode.releaseDate) {
|
|
handleSeriesPress(episode.seriesId, episode);
|
|
return;
|
|
}
|
|
|
|
// For episodes with dates, go to the stream screen
|
|
const episodeId = `${episode.seriesId}:${episode.season}:${episode.episode}`;
|
|
navigation.navigate('Streams', {
|
|
id: episode.seriesId,
|
|
type: 'series',
|
|
episodeId
|
|
});
|
|
}, [navigation, handleSeriesPress]);
|
|
|
|
const renderEpisodeItem = ({ item }: { item: CalendarEpisode }) => {
|
|
const hasReleaseDate = !!item.releaseDate;
|
|
const releaseDate = hasReleaseDate ? parseISO(item.releaseDate) : null;
|
|
const formattedDate = releaseDate ? format(releaseDate, 'MMM d, yyyy') : '';
|
|
const isFuture = releaseDate ? isAfter(releaseDate, new Date()) : false;
|
|
|
|
// Use episode still image if available, fallback to series poster
|
|
const imageUrl = item.still_path ?
|
|
tmdbService.getImageUrl(item.still_path) :
|
|
(item.season_poster_path ?
|
|
tmdbService.getImageUrl(item.season_poster_path) :
|
|
item.poster);
|
|
|
|
return (
|
|
<Animated.View entering={FadeIn.duration(300).delay(100)}>
|
|
<TouchableOpacity
|
|
style={[styles.episodeItem, { borderBottomColor: currentTheme.colors.border + '20' }]}
|
|
onPress={() => handleEpisodePress(item)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<TouchableOpacity
|
|
onPress={() => handleSeriesPress(item.seriesId, item)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Image
|
|
source={{ uri: imageUrl }}
|
|
style={styles.poster}
|
|
contentFit="cover"
|
|
transition={300}
|
|
/>
|
|
</TouchableOpacity>
|
|
|
|
<View style={styles.episodeDetails}>
|
|
<Text style={[styles.seriesName, { color: currentTheme.colors.text }]} numberOfLines={1}>
|
|
{item.seriesName}
|
|
</Text>
|
|
|
|
{hasReleaseDate ? (
|
|
<>
|
|
<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, { color: currentTheme.colors.lightGray }]} numberOfLines={2}>
|
|
{item.overview}
|
|
</Text>
|
|
) : null}
|
|
|
|
<View style={styles.metadataContainer}>
|
|
<View style={styles.dateContainer}>
|
|
<MaterialIcons
|
|
name={isFuture ? "event" : "event-available"}
|
|
size={16}
|
|
color={currentTheme.colors.lightGray}
|
|
/>
|
|
<Text style={[styles.date, { color: currentTheme.colors.lightGray }]}>{formattedDate}</Text>
|
|
</View>
|
|
|
|
{item.vote_average > 0 && (
|
|
<View style={styles.ratingContainer}>
|
|
<MaterialIcons
|
|
name="star"
|
|
size={16}
|
|
color={currentTheme.colors.primary}
|
|
/>
|
|
<Text style={[styles.rating, { color: currentTheme.colors.primary }]}>
|
|
{item.vote_average.toFixed(1)}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Text style={[styles.noEpisodesText, { color: currentTheme.colors.text }]}>
|
|
No scheduled episodes
|
|
</Text>
|
|
<View style={styles.dateContainer}>
|
|
<MaterialIcons
|
|
name="event-busy"
|
|
size={16}
|
|
color={currentTheme.colors.lightGray}
|
|
/>
|
|
<Text style={[styles.date, { color: currentTheme.colors.lightGray }]}>Check back later</Text>
|
|
</View>
|
|
</>
|
|
)}
|
|
</View>
|
|
</TouchableOpacity>
|
|
</Animated.View>
|
|
);
|
|
};
|
|
|
|
const renderSectionHeader = ({ section }: { section: CalendarSection }) => (
|
|
<View style={[styles.sectionHeader, {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
borderBottomColor: currentTheme.colors.border
|
|
}]}>
|
|
<Text style={[styles.sectionTitle, { color: currentTheme.colors.text }]}>
|
|
{section.title}
|
|
</Text>
|
|
</View>
|
|
);
|
|
|
|
// Process all episodes once data is loaded
|
|
const allEpisodes = calendarData.reduce((acc, section) =>
|
|
[...acc, ...section.data], [] as CalendarEpisode[]);
|
|
|
|
// Log when rendering with relevant state info
|
|
logger.log(`[Calendar] Rendering: loading=${loading}, calendarData sections=${calendarData.length}, allEpisodes=${allEpisodes.length}`);
|
|
|
|
// Handle date selection from calendar
|
|
const handleDateSelect = useCallback((date: Date) => {
|
|
logger.log(`[Calendar] Date selected: ${format(date, 'yyyy-MM-dd')}`);
|
|
setSelectedDate(date);
|
|
|
|
// Filter episodes for the selected date
|
|
const filtered = allEpisodes.filter(episode => {
|
|
if (!episode.releaseDate) return false;
|
|
const episodeDate = parseISO(episode.releaseDate);
|
|
return isSameDay(episodeDate, date);
|
|
});
|
|
|
|
logger.log(`[Calendar] Filtered episodes for selected date: ${filtered.length}`);
|
|
setFilteredEpisodes(filtered);
|
|
}, [allEpisodes]);
|
|
|
|
// Reset date filter
|
|
const clearDateFilter = useCallback(() => {
|
|
logger.log(`[Calendar] Clearing date filter`);
|
|
setSelectedDate(null);
|
|
setFilteredEpisodes([]);
|
|
}, []);
|
|
|
|
if (libraryItems.length === 0 && !libraryLoading) {
|
|
return (
|
|
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
|
<StatusBar barStyle="light-content" />
|
|
|
|
<View style={[styles.header, { borderBottomColor: currentTheme.colors.border }]}>
|
|
<TouchableOpacity
|
|
style={styles.backButton}
|
|
onPress={() => navigation.goBack()}
|
|
>
|
|
<MaterialIcons name="arrow-back" size={24} color={currentTheme.colors.text} />
|
|
</TouchableOpacity>
|
|
<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={currentTheme.colors.lightGray} />
|
|
<Text style={styles.emptyText}>
|
|
Your library is empty
|
|
</Text>
|
|
<Text style={styles.emptySubtext}>
|
|
Add series to your library to see their upcoming episodes in the calendar
|
|
</Text>
|
|
<TouchableOpacity
|
|
style={styles.discoverButton}
|
|
onPress={() => navigation.navigate('MainTabs')}
|
|
>
|
|
<Text style={styles.discoverButtonText}>
|
|
Return to Home
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
if (loading && !refreshing) {
|
|
return (
|
|
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
|
<StatusBar barStyle="light-content" />
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color={currentTheme.colors.primary} />
|
|
<Text style={styles.loadingText}>Loading calendar...</Text>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
|
<StatusBar barStyle="light-content" />
|
|
|
|
<View style={[styles.header, { borderBottomColor: currentTheme.colors.border }]}>
|
|
<TouchableOpacity
|
|
style={styles.backButton}
|
|
onPress={() => navigation.goBack()}
|
|
>
|
|
<MaterialIcons name="arrow-back" size={24} color={currentTheme.colors.text} />
|
|
</TouchableOpacity>
|
|
<Text style={[styles.headerTitle, { color: currentTheme.colors.text }]}>Calendar</Text>
|
|
<View style={{ width: 40 }} />
|
|
</View>
|
|
|
|
{selectedDate && filteredEpisodes.length > 0 && (
|
|
<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={currentTheme.colors.text} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
)}
|
|
|
|
<CalendarSection
|
|
episodes={allEpisodes}
|
|
onSelectDate={handleDateSelect}
|
|
/>
|
|
|
|
{selectedDate && filteredEpisodes.length > 0 ? (
|
|
<FlatList
|
|
data={filteredEpisodes}
|
|
keyExtractor={(item) => item.id}
|
|
renderItem={renderEpisodeItem}
|
|
contentContainerStyle={styles.listContent}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={refreshing}
|
|
onRefresh={onRefresh}
|
|
tintColor={currentTheme.colors.primary}
|
|
colors={[currentTheme.colors.primary]}
|
|
/>
|
|
}
|
|
/>
|
|
) : selectedDate && filteredEpisodes.length === 0 ? (
|
|
<View style={styles.emptyFilterContainer}>
|
|
<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, { backgroundColor: currentTheme.colors.primary }]}
|
|
onPress={clearDateFilter}
|
|
>
|
|
<Text style={[styles.clearFilterButtonText, { color: currentTheme.colors.text }]}>
|
|
Show All Episodes
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
) : calendarData.length > 0 ? (
|
|
<SectionList
|
|
sections={calendarData}
|
|
keyExtractor={(item) => item.id}
|
|
renderItem={renderEpisodeItem}
|
|
renderSectionHeader={renderSectionHeader}
|
|
contentContainerStyle={styles.listContent}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={refreshing}
|
|
onRefresh={onRefresh}
|
|
tintColor={currentTheme.colors.primary}
|
|
colors={[currentTheme.colors.primary]}
|
|
/>
|
|
}
|
|
/>
|
|
) : (
|
|
<View style={styles.emptyContainer}>
|
|
<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, { color: currentTheme.colors.lightGray }]}>
|
|
Add series to your library to see their upcoming episodes here
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</SafeAreaView>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
},
|
|
listContent: {
|
|
paddingBottom: 20,
|
|
},
|
|
loadingContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
loadingText: {
|
|
marginTop: 10,
|
|
fontSize: 16,
|
|
},
|
|
sectionHeader: {
|
|
paddingVertical: 8,
|
|
paddingHorizontal: 16,
|
|
borderBottomWidth: 1,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 18,
|
|
fontWeight: 'bold',
|
|
},
|
|
episodeItem: {
|
|
flexDirection: 'row',
|
|
padding: 12,
|
|
borderBottomWidth: 1,
|
|
},
|
|
poster: {
|
|
width: 120,
|
|
height: 68,
|
|
borderRadius: 8,
|
|
},
|
|
episodeDetails: {
|
|
flex: 1,
|
|
marginLeft: 12,
|
|
justifyContent: 'space-between',
|
|
},
|
|
seriesName: {
|
|
fontSize: 16,
|
|
fontWeight: 'bold',
|
|
marginBottom: 4,
|
|
},
|
|
episodeTitle: {
|
|
fontSize: 14,
|
|
lineHeight: 20,
|
|
},
|
|
overview: {
|
|
fontSize: 12,
|
|
marginTop: 4,
|
|
lineHeight: 16,
|
|
},
|
|
metadataContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
marginTop: 8,
|
|
},
|
|
dateContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
date: {
|
|
fontSize: 14,
|
|
marginLeft: 4,
|
|
},
|
|
ratingContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
rating: {
|
|
fontSize: 14,
|
|
marginLeft: 4,
|
|
fontWeight: 'bold',
|
|
},
|
|
emptyContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: 20,
|
|
},
|
|
emptyText: {
|
|
fontSize: 18,
|
|
fontWeight: 'bold',
|
|
marginTop: 16,
|
|
textAlign: 'center',
|
|
},
|
|
emptySubtext: {
|
|
fontSize: 14,
|
|
marginTop: 8,
|
|
textAlign: 'center',
|
|
paddingHorizontal: 32,
|
|
},
|
|
filterInfoContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
padding: 12,
|
|
borderBottomWidth: 1,
|
|
},
|
|
filterInfoText: {
|
|
fontSize: 16,
|
|
fontWeight: 'bold',
|
|
},
|
|
clearFilterButton: {
|
|
padding: 8,
|
|
},
|
|
emptyFilterContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: 20,
|
|
},
|
|
emptyFilterText: {
|
|
fontSize: 18,
|
|
fontWeight: 'bold',
|
|
marginTop: 16,
|
|
textAlign: 'center',
|
|
},
|
|
clearFilterButtonLarge: {
|
|
marginTop: 20,
|
|
padding: 16,
|
|
borderRadius: 8,
|
|
},
|
|
clearFilterButtonText: {
|
|
fontSize: 16,
|
|
fontWeight: 'bold',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
padding: 12,
|
|
paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT + 12 : 12,
|
|
borderBottomWidth: 1,
|
|
},
|
|
backButton: {
|
|
padding: 8,
|
|
},
|
|
headerTitle: {
|
|
fontSize: 18,
|
|
fontWeight: 'bold',
|
|
marginLeft: 12,
|
|
},
|
|
emptyLibraryContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: 20,
|
|
},
|
|
discoverButton: {
|
|
padding: 16,
|
|
borderRadius: 8,
|
|
},
|
|
discoverButtonText: {
|
|
fontSize: 16,
|
|
fontWeight: 'bold',
|
|
},
|
|
noEpisodesText: {
|
|
fontSize: 14,
|
|
marginBottom: 4,
|
|
},
|
|
});
|
|
|
|
export default CalendarScreen;
|