import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator, Dimensions } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { Image } from 'expo-image'; import { LinearGradient } from 'expo-linear-gradient'; import { MaterialIcons } from '@expo/vector-icons'; import { useTheme } from '../../contexts/ThemeContext'; import { useTraktContext } from '../../contexts/TraktContext'; import { useLibrary } from '../../hooks/useLibrary'; import { RootStackParamList } from '../../navigation/AppNavigator'; import { parseISO, isThisWeek, format, isAfter, isBefore } from 'date-fns'; import Animated, { FadeIn } from 'react-native-reanimated'; import { useCalendarData } from '../../hooks/useCalendarData'; import { memoryManager } from '../../utils/memoryManager'; import { tmdbService } from '../../services/tmdbService'; // Compute base sizes; actual tablet sizes will be adjusted inside component for responsiveness const { width } = Dimensions.get('window'); const ITEM_WIDTH = width * 0.75; // phone default const ITEM_HEIGHT = 180; // phone default interface ThisWeekEpisode { id: string; seriesId: string; seriesName: string; title: string; poster: string; releaseDate: string; season: number; episode: number; isReleased: boolean; overview: string; vote_average: number; still_path: string | null; season_poster_path: string | null; } export const ThisWeekSection = React.memo(() => { const navigation = useNavigation>(); const { currentTheme } = useTheme(); const { calendarData, loading } = useCalendarData(); // Responsive sizing for tablets const deviceWidth = Dimensions.get('window').width; const isTablet = deviceWidth >= 768; const computedItemWidth = useMemo(() => (isTablet ? Math.min(deviceWidth * 0.46, 560) : ITEM_WIDTH), [isTablet, deviceWidth]); const computedItemHeight = useMemo(() => (isTablet ? 220 : ITEM_HEIGHT), [isTablet]); // Use the already memory-optimized calendar data instead of fetching separately const thisWeekEpisodes = useMemo(() => { const thisWeekSection = calendarData.find(section => section.title === 'This Week'); if (!thisWeekSection) return []; // Limit episodes to prevent memory issues and add release status const episodes = memoryManager.limitArraySize(thisWeekSection.data, 20); // Limit to 20 for home screen return episodes.map(episode => ({ ...episode, isReleased: episode.releaseDate ? isBefore(parseISO(episode.releaseDate), new Date()) : false, })); }, [calendarData]); const handleEpisodePress = (episode: ThisWeekEpisode) => { // For upcoming episodes, go to the metadata screen if (!episode.isReleased) { const episodeId = `${episode.seriesId}:${episode.season}:${episode.episode}`; navigation.navigate('Metadata', { id: episode.seriesId, type: 'series', episodeId }); return; } // For released episodes, go to the streams screen const episodeId = `${episode.seriesId}:${episode.season}:${episode.episode}`; navigation.navigate('Streams', { id: episode.seriesId, type: 'series', episodeId }); }; const handleViewAll = () => { navigation.navigate('Calendar' as any); }; if (thisWeekEpisodes.length === 0) { return null; } const renderEpisodeItem = ({ item, index }: { item: ThisWeekEpisode, index: number }) => { // Handle episodes without release dates gracefully const releaseDate = item.releaseDate ? parseISO(item.releaseDate) : null; const formattedDate = releaseDate ? format(releaseDate, 'E, MMM d') : 'TBA'; const isReleased = item.isReleased; // 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 ( handleEpisodePress(item)} activeOpacity={0.8} > {/* Enhanced gradient overlay */} {/* Content area */} {item.seriesName} {item.title} {item.overview && ( {item.overview} )} S{item.season}:E{item.episode} • {formattedDate} ); }; return ( This Week View All item.id} renderItem={renderEpisodeItem} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={[styles.listContent, { paddingLeft: isTablet ? 24 : 16, paddingRight: isTablet ? 24 : 16 }]} snapToInterval={computedItemWidth + 16} decelerationRate="fast" snapToAlignment="start" initialNumToRender={isTablet ? 4 : 3} windowSize={3} maxToRenderPerBatch={3} removeClippedSubviews getItemLayout={(data, index) => { const length = computedItemWidth + 16; const offset = length * index; return { length, offset, index }; }} ItemSeparatorComponent={() => } /> ); }); const styles = StyleSheet.create({ container: { marginVertical: 20, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, marginBottom: 16, }, titleContainer: { position: 'relative', }, title: { fontSize: 24, fontWeight: '800', letterSpacing: 0.5, marginBottom: 4, }, titleUnderline: { position: 'absolute', bottom: -2, left: 0, width: 40, height: 3, borderRadius: 2, opacity: 0.8, }, viewAllButton: { flexDirection: 'row', alignItems: 'center', paddingVertical: 8, paddingHorizontal: 10, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.1)', marginRight: -10, }, viewAllText: { fontSize: 14, fontWeight: '600', marginRight: 4, }, listContent: { paddingLeft: 16, paddingRight: 16, paddingBottom: 8, }, loadingContainer: { padding: 32, alignItems: 'center', }, loadingText: { marginTop: 12, fontSize: 16, fontWeight: '500', }, episodeItemContainer: { width: ITEM_WIDTH, height: ITEM_HEIGHT, }, episodeItem: { width: '100%', height: '100%', borderRadius: 16, overflow: 'hidden', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.3, shadowRadius: 12, elevation: 12, }, imageContainer: { width: '100%', height: '100%', position: 'relative', }, poster: { width: '100%', height: '100%', borderRadius: 16, }, gradient: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, justifyContent: 'flex-end', padding: 12, borderRadius: 16, }, contentArea: { width: '100%', }, seriesName: { fontSize: 16, fontWeight: '700', marginBottom: 6, }, episodeTitle: { fontSize: 14, fontWeight: '600', marginBottom: 4, lineHeight: 18, }, overview: { fontSize: 12, lineHeight: 16, marginBottom: 6, opacity: 0.9, }, dateContainer: { flexDirection: 'row', alignItems: 'center', marginTop: 4, }, episodeInfo: { fontSize: 12, fontWeight: '600', marginRight: 4, }, releaseDate: { fontSize: 13, fontWeight: '600', marginLeft: 6, letterSpacing: 0.3, }, });