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 { 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 })} > {item.progress !== undefined && item.progress < 1 && ( )} {item.type === 'series' && ( Series )} {item.name} {item.lastWatched && ( {item.lastWatched} )} ); const renderFilter = (filterType: 'all' | 'movies' | 'series', label: string, iconName: keyof typeof MaterialIcons.glyphMap) => { const isActive = filter === filterType; return ( setFilter(filterType)} > {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 items to your library by marking them as favorites ) : ( item.id} numColumns={2} contentContainerStyle={styles.listContent} showsVerticalScrollIndicator={false} /> )} ); }; const styles = StyleSheet.create({ container: { flex: 1, }, header: { paddingHorizontal: 16, paddingVertical: 12, paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT + 12 : 4, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.1)', backgroundColor: colors.darkBackground, }, headerContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, headerTitle: { fontSize: 32, fontWeight: '800', color: colors.white, letterSpacing: 0.5, }, filtersContainer: { flexDirection: 'row', paddingHorizontal: 16, paddingVertical: 12, gap: 12, backgroundColor: colors.black, }, filterButton: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, borderWidth: 1, borderColor: colors.darkGray, backgroundColor: 'transparent', gap: 6, minWidth: 100, justifyContent: 'center', }, filterButtonActive: { backgroundColor: colors.primary + '20', borderColor: colors.primary, }, filterIcon: { marginRight: 2, }, filterText: { fontSize: 14, fontWeight: '500', }, filterTextActive: { color: colors.primary, fontWeight: '600', }, listContent: { paddingHorizontal: 8, paddingTop: 16, paddingBottom: 32, alignItems: 'flex-start', }, itemContainer: { marginHorizontal: 8, marginBottom: 24, }, posterContainer: { position: 'relative', borderRadius: 12, overflow: 'hidden', aspectRatio: 2/3, marginBottom: 8, backgroundColor: colors.darkBackground, elevation: 4, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, }, poster: { width: '100%', height: '100%', }, itemTitle: { fontSize: 14, fontWeight: '600', marginBottom: 4, lineHeight: 20, }, lastWatched: { fontSize: 12, lineHeight: 16, opacity: 0.7, }, progressBarContainer: { position: 'absolute', bottom: 0, left: 0, right: 0, height: 3, backgroundColor: 'rgba(0,0,0,0.5)', }, progressBar: { height: '100%', backgroundColor: colors.primary, }, badgeContainer: { position: 'absolute', top: 8, right: 8, backgroundColor: 'rgba(0,0,0,0.75)', borderRadius: 12, paddingHorizontal: 8, paddingVertical: 4, flexDirection: 'row', alignItems: 'center', }, badgeText: { color: colors.white, fontSize: 12, fontWeight: '600', }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32, }, emptyText: { fontSize: 18, fontWeight: 'bold', marginTop: 16, marginBottom: 8, textAlign: 'center', }, emptySubtext: { fontSize: 14, textAlign: 'center', lineHeight: 20, opacity: 0.7, }, skeletonContainer: { padding: 16, flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', }, skeletonTitle: { height: 20, borderRadius: 4, marginTop: 8, width: '80%', }, }); export default LibraryScreen;