diff --git a/src/components/discover/CatalogSection.tsx b/src/components/discover/CatalogSection.tsx deleted file mode 100644 index 72cb381e..00000000 --- a/src/components/discover/CatalogSection.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { View, Text, TouchableOpacity, StyleSheet, FlatList, Dimensions } from 'react-native'; -import { MaterialIcons } from '@expo/vector-icons'; -import { useNavigation } from '@react-navigation/native'; -import { NavigationProp } from '@react-navigation/native'; -import { useTheme } from '../../contexts/ThemeContext'; -import { GenreCatalog, Category } from '../../constants/discover'; -import { StreamingContent } from '../../services/catalogService'; -import { RootStackParamList } from '../../navigation/AppNavigator'; -import ContentItem from './ContentItem'; - -interface CatalogSectionProps { - catalog: GenreCatalog; - selectedCategory: Category; -} - -const CatalogSection = ({ catalog, selectedCategory }: CatalogSectionProps) => { - const navigation = useNavigation>(); - const { currentTheme } = useTheme(); - const { width } = Dimensions.get('window'); - const itemWidth = (width - 48) / 2.2; // 2 items per row with spacing - - // Only display first 3 items in each section - const displayItems = useMemo(() => - catalog.items.slice(0, 3), - [catalog.items] - ); - - const handleContentPress = useCallback((item: StreamingContent) => { - navigation.navigate('Metadata', { id: item.id, type: item.type }); - }, [navigation]); - - const handleSeeMorePress = useCallback(() => { - navigation.navigate('Catalog', { - id: catalog.genre, - type: selectedCategory.type, - name: `${catalog.genre} ${selectedCategory.name}`, - genreFilter: catalog.genre - }); - }, [navigation, selectedCategory, catalog.genre]); - - const renderItem = useCallback(({ item }: { item: StreamingContent }) => ( - handleContentPress(item)} - width={itemWidth} - /> - ), [handleContentPress, itemWidth]); - - const keyExtractor = useCallback((item: StreamingContent) => item.id, []); - - const ItemSeparator = useCallback(() => ( - - ), []); - - return ( - - - - - {catalog.genre} - - - - - View All - - - - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - marginBottom: 32, - }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingHorizontal: 20, - marginBottom: 16, - }, - titleContainer: { - flexDirection: 'column', - }, - titleBar: { - width: 32, - height: 3, - marginTop: 6, - borderRadius: 2, - }, - title: { - fontSize: 20, - fontWeight: '700', - }, - seeAllButton: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 8, - paddingHorizontal: 10, - borderRadius: 20, - marginRight: -10, - }, - seeAllText: { - fontSize: 14, - fontWeight: '600', - marginRight: 4, - }, -}); - -export default React.memo(CatalogSection); \ No newline at end of file diff --git a/src/components/discover/CatalogsList.tsx b/src/components/discover/CatalogsList.tsx deleted file mode 100644 index 6a8bcdfa..00000000 --- a/src/components/discover/CatalogsList.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { useCallback } from 'react'; -import { FlatList, StyleSheet, Platform } from 'react-native'; -import { GenreCatalog, Category } from '../../constants/discover'; -import CatalogSection from './CatalogSection'; - -interface CatalogsListProps { - catalogs: GenreCatalog[]; - selectedCategory: Category; -} - -const CatalogsList = ({ catalogs, selectedCategory }: CatalogsListProps) => { - const renderCatalogItem = useCallback(({ item }: { item: GenreCatalog }) => ( - - ), [selectedCategory]); - - // Memoize list key extractor - const catalogKeyExtractor = useCallback((item: GenreCatalog) => item.genre, []); - - return ( - - ); -}; - -const styles = StyleSheet.create({ - container: { - paddingVertical: 8, - paddingBottom: 90, - }, -}); - -export default React.memo(CatalogsList); \ No newline at end of file diff --git a/src/components/discover/CategorySelector.tsx b/src/components/discover/CategorySelector.tsx deleted file mode 100644 index c090e8a7..00000000 --- a/src/components/discover/CategorySelector.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { useCallback } from 'react'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; -import { MaterialIcons } from '@expo/vector-icons'; -import { useTheme } from '../../contexts/ThemeContext'; -import { Category } from '../../constants/discover'; - -interface CategorySelectorProps { - categories: Category[]; - selectedCategory: Category; - onSelectCategory: (category: Category) => void; -} - -const CategorySelector = ({ - categories, - selectedCategory, - onSelectCategory -}: CategorySelectorProps) => { - const { currentTheme } = useTheme(); - - const renderCategoryButton = useCallback((category: Category) => { - const isSelected = selectedCategory.id === category.id; - - return ( - onSelectCategory(category)} - activeOpacity={0.7} - > - - - {category.name} - - - ); - }, [selectedCategory, onSelectCategory, currentTheme]); - - return ( - - - {categories.map(renderCategoryButton)} - - - ); -}; - -const styles = StyleSheet.create({ - container: { - paddingVertical: 20, - borderBottomWidth: 1, - borderBottomColor: 'rgba(255,255,255,0.05)', - }, - content: { - flexDirection: 'row', - justifyContent: 'center', - paddingHorizontal: 20, - gap: 16, - }, - categoryButton: { - paddingHorizontal: 20, - paddingVertical: 14, - borderRadius: 24, - backgroundColor: 'rgba(255,255,255,0.05)', - flexDirection: 'row', - alignItems: 'center', - gap: 10, - flex: 1, - maxWidth: 160, - justifyContent: 'center', - shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.15, - shadowRadius: 8, - elevation: 4, - }, - categoryText: { - color: '#9e9e9e', // Default medium gray - fontWeight: '600', - fontSize: 16, - }, -}); - -export default React.memo(CategorySelector); \ No newline at end of file diff --git a/src/components/discover/ContentItem.tsx b/src/components/discover/ContentItem.tsx deleted file mode 100644 index 1f7e85d2..00000000 --- a/src/components/discover/ContentItem.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import { View, Text, TouchableOpacity, StyleSheet, Dimensions } from 'react-native'; -import { Image } from 'expo-image'; -import { LinearGradient } from 'expo-linear-gradient'; -import { useTheme } from '../../contexts/ThemeContext'; -import { StreamingContent } from '../../services/catalogService'; - -interface ContentItemProps { - item: StreamingContent; - onPress: () => void; - width?: number; -} - -const ContentItem = ({ item, onPress, width }: ContentItemProps) => { - const { width: screenWidth } = Dimensions.get('window'); - const { currentTheme } = useTheme(); - const itemWidth = width || (screenWidth - 48) / 2.2; // Default to 2 items per row with spacing - - return ( - - - - - - {item.name} - - {item.year && ( - {item.year} - )} - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - marginHorizontal: 0, - }, - posterContainer: { - borderRadius: 8, - overflow: 'hidden', - backgroundColor: 'rgba(255,255,255,0.03)', - elevation: 5, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.2, - shadowRadius: 8, - }, - poster: { - aspectRatio: 2/3, - width: '100%', - }, - gradient: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - padding: 16, - justifyContent: 'flex-end', - height: '45%', - }, - title: { - fontSize: 15, - fontWeight: '700', - marginBottom: 4, - textShadowColor: 'rgba(0, 0, 0, 0.75)', - textShadowOffset: { width: 0, height: 1 }, - textShadowRadius: 2, - letterSpacing: 0.3, - }, - year: { - fontSize: 12, - color: 'rgba(255,255,255,0.7)', - textShadowColor: 'rgba(0, 0, 0, 0.75)', - textShadowOffset: { width: 0, height: 1 }, - textShadowRadius: 2, - }, -}); - -export default React.memo(ContentItem); \ No newline at end of file diff --git a/src/components/discover/GenreSelector.tsx b/src/components/discover/GenreSelector.tsx deleted file mode 100644 index 7cc4df08..00000000 --- a/src/components/discover/GenreSelector.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useCallback } from 'react'; -import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native'; -import { useTheme } from '../../contexts/ThemeContext'; - -interface GenreSelectorProps { - genres: string[]; - selectedGenre: string; - onSelectGenre: (genre: string) => void; -} - -const GenreSelector = ({ - genres, - selectedGenre, - onSelectGenre -}: GenreSelectorProps) => { - const { currentTheme } = useTheme(); - - const renderGenreButton = useCallback((genre: string) => { - const isSelected = selectedGenre === genre; - - return ( - onSelectGenre(genre)} - activeOpacity={0.7} - > - - {genre} - - - ); - }, [selectedGenre, onSelectGenre, currentTheme]); - - return ( - - - {genres.map(renderGenreButton)} - - - ); -}; - -const styles = StyleSheet.create({ - container: { - paddingTop: 20, - paddingBottom: 12, - zIndex: 10, - }, - scrollViewContent: { - paddingHorizontal: 20, - paddingBottom: 8, - }, - genreButton: { - paddingHorizontal: 18, - paddingVertical: 10, - marginRight: 12, - borderRadius: 20, - backgroundColor: 'rgba(255,255,255,0.05)', - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 2, - overflow: 'hidden', - }, - genreText: { - color: '#9e9e9e', // Default medium gray - fontWeight: '500', - fontSize: 14, - }, -}); - -export default React.memo(GenreSelector); \ No newline at end of file diff --git a/src/components/home/CatalogSection.tsx b/src/components/home/CatalogSection.tsx index a0860b80..9365880c 100644 --- a/src/components/home/CatalogSection.tsx +++ b/src/components/home/CatalogSection.tsx @@ -176,4 +176,4 @@ const styles = StyleSheet.create({ }, }); -export default CatalogSection; \ No newline at end of file +export default React.memo(CatalogSection); \ No newline at end of file diff --git a/src/components/home/FeaturedContent.tsx b/src/components/home/FeaturedContent.tsx index 46ee9390..b8ac3b57 100644 --- a/src/components/home/FeaturedContent.tsx +++ b/src/components/home/FeaturedContent.tsx @@ -764,4 +764,4 @@ const styles = StyleSheet.create({ }, }); -export default FeaturedContent; \ No newline at end of file +export default React.memo(FeaturedContent); \ No newline at end of file diff --git a/src/components/home/ThisWeekSection.tsx b/src/components/home/ThisWeekSection.tsx index fbba9d72..aa78aa43 100644 --- a/src/components/home/ThisWeekSection.tsx +++ b/src/components/home/ThisWeekSection.tsx @@ -41,7 +41,7 @@ interface ThisWeekEpisode { season_poster_path: string | null; } -export const ThisWeekSection = () => { +export const ThisWeekSection = React.memo(() => { const navigation = useNavigation>(); const { libraryItems, loading: libraryLoading } = useLibrary(); const [episodes, setEpisodes] = useState([]); @@ -287,7 +287,7 @@ export const ThisWeekSection = () => { /> ); -}; +}); const styles = StyleSheet.create({ container: { diff --git a/src/constants/discover.ts b/src/constants/discover.ts deleted file mode 100644 index 1af9de27..00000000 --- a/src/constants/discover.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { MaterialIcons } from '@expo/vector-icons'; -import { StreamingContent } from '../services/catalogService'; - -export interface Category { - id: string; - name: string; - type: 'movie' | 'series' | 'channel' | 'tv'; - icon: keyof typeof MaterialIcons.glyphMap; -} - -export interface GenreCatalog { - genre: string; - items: StreamingContent[]; -} - -export const CATEGORIES: Category[] = [ - { id: 'movie', name: 'Movies', type: 'movie', icon: 'local-movies' }, - { id: 'series', name: 'TV Shows', type: 'series', icon: 'live-tv' } -]; - -// Common genres for movies and TV shows -export const COMMON_GENRES = [ - 'All', - 'Action', - 'Adventure', - 'Animation', - 'Comedy', - 'Crime', - 'Documentary', - 'Drama', - 'Family', - 'Fantasy', - 'History', - 'Horror', - 'Music', - 'Mystery', - 'Romance', - 'Science Fiction', - 'Thriller', - 'War', - 'Western' -]; \ No newline at end of file diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index d09acb9f..3c8723c0 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -17,7 +17,6 @@ import { useTheme } from '../contexts/ThemeContext'; // Import screens with their proper types import HomeScreen from '../screens/HomeScreen'; -import DiscoverScreen from '../screens/DiscoverScreen'; import LibraryScreen from '../screens/LibraryScreen'; import SettingsScreen from '../screens/SettingsScreen'; import MetadataScreen from '../screens/MetadataScreen'; @@ -46,7 +45,6 @@ export type RootStackParamList = { Onboarding: undefined; MainTabs: undefined; Home: undefined; - Discover: undefined; Library: undefined; Settings: undefined; Search: undefined; @@ -110,8 +108,8 @@ export type RootStackNavigationProp = NativeStackNavigationProp { case 'Home': iconName = 'home'; break; - case 'Discover': - iconName = 'compass'; - break; case 'Library': iconName = 'play-box-multiple'; break; + case 'Search': + iconName = 'feature-search'; + break; case 'Settings': iconName = 'cog'; break; @@ -546,12 +544,12 @@ const MainTabs = () => { case 'Home': iconName = 'home'; break; - case 'Discover': - iconName = 'compass'; - break; case 'Library': iconName = 'play-box-multiple'; break; + case 'Search': + iconName = 'feature-search'; + break; case 'Settings': iconName = 'cog'; break; @@ -634,14 +632,6 @@ const MainTabs = () => { tabBarLabel: 'Home', }} /> - { headerShown: false }} /> + { - const navigation = useNavigation>(); - const [selectedCategory, setSelectedCategory] = useState(CATEGORIES[0]); - const [selectedGenre, setSelectedGenre] = useState('All'); - const [catalogs, setCatalogs] = useState([]); - const [loading, setLoading] = useState(true); - const styles = useDiscoverStyles(); - const insets = useSafeAreaInsets(); - const { currentTheme } = useTheme(); - - // Force consistent status bar settings - useEffect(() => { - const applyStatusBarConfig = () => { - StatusBar.setBarStyle('light-content'); - if (Platform.OS === 'android') { - StatusBar.setTranslucent(true); - StatusBar.setBackgroundColor('transparent'); - } - }; - - applyStatusBarConfig(); - - // Re-apply on focus - const unsubscribe = navigation.addListener('focus', applyStatusBarConfig); - return unsubscribe; - }, [navigation]); - - // Load content when category or genre changes - useEffect(() => { - loadContent(selectedCategory, selectedGenre); - }, [selectedCategory, selectedGenre]); - - const loadContent = async (category: Category, genre: string) => { - setLoading(true); - try { - // If genre is 'All', don't apply genre filter - const genreFilter = genre === 'All' ? undefined : genre; - const fetchedCatalogs = await catalogService.getCatalogByType(category.type, genreFilter); - - // Collect all content items - const content: StreamingContent[] = []; - fetchedCatalogs.forEach(catalog => { - content.push(...catalog.items); - }); - - if (genre === 'All') { - // Group by genres when "All" is selected - const genreCatalogs: GenreCatalog[] = []; - - // Get all genres from content - const genresSet = new Set(); - content.forEach(item => { - if (item.genres && item.genres.length > 0) { - item.genres.forEach(g => genresSet.add(g)); - } - }); - - // Create catalogs for each genre - genresSet.forEach(g => { - const genreItems = content.filter(item => - item.genres && item.genres.includes(g) - ); - - if (genreItems.length > 0) { - genreCatalogs.push({ - genre: g, - items: genreItems - }); - } - }); - - // Sort by number of items - genreCatalogs.sort((a, b) => b.items.length - a.items.length); - - setCatalogs(genreCatalogs); - } else { - // When a specific genre is selected, show as a single catalog - setCatalogs([{ genre, items: content }]); - } - } catch (error) { - logger.error('Failed to load content:', error); - setCatalogs([]); - } finally { - setLoading(false); - } - }; - - const handleCategoryPress = useCallback((category: Category) => { - if (category.id !== selectedCategory.id) { - setSelectedCategory(category); - setSelectedGenre('All'); // Reset to All when changing category - } - }, [selectedCategory]); - - const handleGenrePress = useCallback((genre: string) => { - if (genre !== selectedGenre) { - setSelectedGenre(genre); - } - }, [selectedGenre]); - - const handleSearchPress = useCallback(() => { - navigation.navigate('Search'); - }, [navigation]); - - const headerBaseHeight = Platform.OS === 'android' ? 80 : 60; - const topSpacing = Platform.OS === 'android' ? (StatusBar.currentHeight || 0) : insets.top; - const headerHeight = headerBaseHeight + topSpacing; - - const renderEmptyState = () => ( - - - No content found for {selectedGenre !== 'All' ? selectedGenre : 'these filters'} - - - ); - - return ( - - {/* Fixed position header background */} - - - - {/* Header Section */} - - - Discover - - - - - - - {/* Content Container */} - - {/* Categories Section */} - - - {/* Genres Section */} - - - {/* Content Section */} - {loading ? ( - - - - ) : catalogs.length > 0 ? ( - - ) : renderEmptyState()} - - - - ); -}; - -export default React.memo(DiscoverScreen); \ No newline at end of file diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 68fbfa54..00011891 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -602,11 +602,10 @@ const HomeScreen = () => { ]} showsVerticalScrollIndicator={false} ListFooterComponent={ListFooterComponent} - initialNumToRender={3} - maxToRenderPerBatch={2} - windowSize={5} - removeClippedSubviews={Platform.OS === 'android'} - updateCellsBatchingPeriod={50} + initialNumToRender={5} + maxToRenderPerBatch={5} + windowSize={11} + removeClippedSubviews={false} onEndReachedThreshold={0.5} maintainVisibleContentPosition={{ minIndexForVisible: 0, diff --git a/src/styles/screens/discoverStyles.ts b/src/styles/screens/discoverStyles.ts deleted file mode 100644 index 0e00c21d..00000000 --- a/src/styles/screens/discoverStyles.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { StyleSheet, Dimensions } from 'react-native'; -import { useTheme } from '../../contexts/ThemeContext'; - -const useDiscoverStyles = () => { - const { width } = Dimensions.get('window'); - const { currentTheme } = useTheme(); - - return StyleSheet.create({ - container: { - flex: 1, - backgroundColor: currentTheme.colors.darkBackground, - }, - headerBackground: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - backgroundColor: currentTheme.colors.darkBackground, - zIndex: 1, - }, - contentContainer: { - flex: 1, - backgroundColor: currentTheme.colors.darkBackground, - }, - header: { - paddingHorizontal: 20, - justifyContent: 'flex-end', - paddingBottom: 8, - backgroundColor: 'transparent', - zIndex: 2, - }, - headerContent: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - headerTitle: { - fontSize: 32, - fontWeight: '800', - color: currentTheme.colors.white, - letterSpacing: 0.3, - }, - searchButton: { - padding: 10, - borderRadius: 24, - backgroundColor: 'rgba(255,255,255,0.08)', - }, - loadingContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - emptyContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - paddingTop: 80, - paddingBottom: 90, - }, - emptyText: { - color: currentTheme.colors.mediumGray, - fontSize: 16, - textAlign: 'center', - paddingHorizontal: 32, - }, - }); -}; - -export default useDiscoverStyles; \ No newline at end of file