import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, FlatList, TouchableOpacity, useColorScheme, useWindowDimensions, SafeAreaView, StatusBar, Animated as RNAnimated, ActivityIndicator, Platform, } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; import { colors } from '../styles'; import { Image } from 'expo-image'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; import { LinearGradient } from 'expo-linear-gradient'; import { catalogService } from '../services/catalogService'; import type { StreamingContent } from '../services/catalogService'; import { RootStackParamList } from '../navigation/AppNavigator'; import { logger } from '../utils/logger'; // Types interface LibraryItem extends StreamingContent { progress?: number; lastWatched?: string; } const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; const SkeletonLoader = () => { const pulseAnim = React.useRef(new RNAnimated.Value(0)).current; const { width } = useWindowDimensions(); const itemWidth = (width - 48) / 2; React.useEffect(() => { const pulse = RNAnimated.loop( RNAnimated.sequence([ RNAnimated.timing(pulseAnim, { toValue: 1, duration: 1000, useNativeDriver: true, }), RNAnimated.timing(pulseAnim, { toValue: 0, duration: 1000, useNativeDriver: true, }), ]) ); pulse.start(); return () => pulse.stop(); }, [pulseAnim]); const opacity = pulseAnim.interpolate({ inputRange: [0, 1], outputRange: [0.3, 0.7], }); const renderSkeletonItem = () => ( ); return ( {[...Array(6)].map((_, index) => ( {renderSkeletonItem()} ))} ); }; const LibraryScreen = () => { const navigation = useNavigation>(); const isDarkMode = useColorScheme() === 'dark'; const { width } = useWindowDimensions(); const [loading, setLoading] = useState(true); const [libraryItems, setLibraryItems] = useState([]); const [filter, setFilter] = useState<'all' | 'movies' | 'series'>('all'); useEffect(() => { const loadLibrary = async () => { setLoading(true); try { const items = await catalogService.getLibraryItems(); setLibraryItems(items); } catch (error) { logger.error('Failed to load library:', error); } finally { setLoading(false); } }; loadLibrary(); // Subscribe to library updates const unsubscribe = catalogService.subscribeToLibraryUpdates((items) => { setLibraryItems(items); }); return () => { unsubscribe(); }; }, []); const filteredItems = libraryItems.filter(item => { if (filter === 'all') return true; if (filter === 'movies') return item.type === 'movie'; if (filter === 'series') return item.type === 'series'; return true; }); const itemWidth = (width - 48) / 2; // 2 items per row with padding const renderItem = ({ item }: { item: LibraryItem }) => ( navigation.navigate('Metadata', { id: item.id, type: item.type })} activeOpacity={0.7} > {item.name} {item.lastWatched && ( {item.lastWatched} )} {item.progress !== undefined && item.progress < 1 && ( )} {item.type === 'series' && ( Series )} ); const renderFilter = (filterType: 'all' | 'movies' | 'series', label: string, iconName: keyof typeof MaterialIcons.glyphMap) => { const isActive = filter === filterType; return ( setFilter(filterType)} activeOpacity={0.7} > {label} ); }; return ( Library {renderFilter('all', 'All', 'apps')} {renderFilter('movies', 'Movies', 'movie')} {renderFilter('series', 'TV Shows', 'live-tv')} {loading ? ( ) : filteredItems.length === 0 ? ( Your library is empty Add content to your library to keep track of what you're watching navigation.navigate('Discover')} activeOpacity={0.7} > Explore Content ) : ( item.id} numColumns={2} contentContainerStyle={styles.listContainer} showsVerticalScrollIndicator={false} columnWrapperStyle={styles.columnWrapper} initialNumToRender={6} maxToRenderPerBatch={6} windowSize={5} removeClippedSubviews={Platform.OS === 'android'} /> )} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.darkBackground, }, header: { paddingHorizontal: 20, paddingVertical: 16, paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT + 16 : 16, }, headerContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, headerTitle: { fontSize: 32, fontWeight: '800', color: colors.white, letterSpacing: 0.3, }, filtersContainer: { flexDirection: 'row', paddingHorizontal: 16, paddingBottom: 16, paddingTop: 8, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.05)', zIndex: 10, }, filterButton: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10, paddingHorizontal: 16, marginHorizontal: 4, borderRadius: 24, backgroundColor: 'rgba(255,255,255,0.05)', shadowColor: colors.black, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, filterButtonActive: { backgroundColor: colors.primary, }, filterIcon: { marginRight: 8, }, filterText: { fontSize: 15, fontWeight: '500', color: colors.mediumGray, }, filterTextActive: { fontWeight: '600', color: colors.white, }, listContainer: { paddingHorizontal: 12, paddingVertical: 16, }, columnWrapper: { justifyContent: 'space-between', marginBottom: 16, }, skeletonContainer: { flexDirection: 'row', flexWrap: 'wrap', paddingHorizontal: 12, paddingTop: 16, justifyContent: 'space-between', }, itemContainer: { marginBottom: 16, }, posterContainer: { borderRadius: 16, overflow: 'hidden', backgroundColor: 'rgba(255,255,255,0.03)', aspectRatio: 2/3, elevation: 5, shadowColor: colors.black, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 8, }, poster: { width: '100%', height: '100%', }, posterGradient: { position: 'absolute', bottom: 0, left: 0, right: 0, padding: 16, justifyContent: 'flex-end', height: '45%', }, progressBarContainer: { position: 'absolute', bottom: 0, left: 0, right: 0, height: 4, backgroundColor: 'rgba(0,0,0,0.5)', }, progressBar: { height: '100%', backgroundColor: colors.primary, }, badgeContainer: { position: 'absolute', top: 10, right: 10, backgroundColor: 'rgba(0,0,0,0.7)', borderRadius: 12, paddingHorizontal: 8, paddingVertical: 4, flexDirection: 'row', alignItems: 'center', }, badgeText: { color: colors.white, fontSize: 10, fontWeight: '600', }, itemTitle: { fontSize: 15, fontWeight: '700', color: colors.white, marginBottom: 4, textShadowColor: 'rgba(0, 0, 0, 0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, letterSpacing: 0.3, }, lastWatched: { fontSize: 12, color: 'rgba(255,255,255,0.7)', textShadowColor: 'rgba(0, 0, 0, 0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, skeletonTitle: { height: 14, marginTop: 8, borderRadius: 4, }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32, }, emptyText: { fontSize: 20, fontWeight: '700', color: colors.white, marginTop: 16, marginBottom: 8, }, emptySubtext: { fontSize: 15, color: colors.mediumGray, textAlign: 'center', marginBottom: 24, }, exploreButton: { backgroundColor: colors.primary, paddingVertical: 12, paddingHorizontal: 24, borderRadius: 24, elevation: 3, shadowColor: colors.black, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, }, exploreButtonText: { color: colors.white, fontSize: 16, fontWeight: '600', } }); export default LibraryScreen;