From cf03a44fab0ef608b0ad780f2d8beba845a8f79b Mon Sep 17 00:00:00 2001 From: tapframe Date: Sat, 3 May 2025 15:56:00 +0530 Subject: [PATCH] Refactor DiscoverScreen component by removing unused imports and optimizing structure. Introduce CategorySelector and GenreSelector components for better organization. Update loading state handling and improve empty state rendering. --- src/components/discover/CatalogSection.tsx | 132 +++++ src/components/discover/CatalogsList.tsx | 43 ++ src/components/discover/CategorySelector.tsx | 101 ++++ src/components/discover/ContentItem.tsx | 94 +++ src/components/discover/GenreSelector.tsx | 94 +++ src/constants/discover.ts | 42 ++ src/screens/DiscoverScreen.tsx | 578 ++----------------- src/styles/screens/discoverStyles.ts | 67 +++ 8 files changed, 609 insertions(+), 542 deletions(-) create mode 100644 src/components/discover/CatalogSection.tsx create mode 100644 src/components/discover/CatalogsList.tsx create mode 100644 src/components/discover/CategorySelector.tsx create mode 100644 src/components/discover/ContentItem.tsx create mode 100644 src/components/discover/GenreSelector.tsx create mode 100644 src/constants/discover.ts create mode 100644 src/styles/screens/discoverStyles.ts diff --git a/src/components/discover/CatalogSection.tsx b/src/components/discover/CatalogSection.tsx new file mode 100644 index 00000000..44bffdef --- /dev/null +++ b/src/components/discover/CatalogSection.tsx @@ -0,0 +1,132 @@ +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 { colors } from '../../styles'; +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 { 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} + + + + See 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, + backgroundColor: colors.primary, + marginTop: 6, + borderRadius: 2, + }, + title: { + fontSize: 20, + fontWeight: '700', + color: colors.white, + }, + seeAllButton: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + paddingVertical: 6, + paddingHorizontal: 4, + }, + seeAllText: { + color: colors.primary, + fontWeight: '600', + fontSize: 14, + }, +}); + +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 new file mode 100644 index 00000000..5b074958 --- /dev/null +++ b/src/components/discover/CatalogsList.tsx @@ -0,0 +1,43 @@ +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, + }, +}); + +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 new file mode 100644 index 00000000..a5db821b --- /dev/null +++ b/src/components/discover/CategorySelector.tsx @@ -0,0 +1,101 @@ +import React, { useCallback } from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { MaterialIcons } from '@expo/vector-icons'; +import { colors } from '../../styles'; +import { Category } from '../../constants/discover'; + +interface CategorySelectorProps { + categories: Category[]; + selectedCategory: Category; + onSelectCategory: (category: Category) => void; +} + +const CategorySelector = ({ + categories, + selectedCategory, + onSelectCategory +}: CategorySelectorProps) => { + + const renderCategoryButton = useCallback((category: Category) => { + const isSelected = selectedCategory.id === category.id; + + return ( + onSelectCategory(category)} + activeOpacity={0.7} + > + + + {category.name} + + + ); + }, [selectedCategory, onSelectCategory]); + + 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: colors.black, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4, + }, + selectedCategoryButton: { + backgroundColor: colors.primary, + }, + categoryText: { + color: colors.mediumGray, + fontWeight: '600', + fontSize: 16, + }, + selectedCategoryText: { + color: colors.white, + fontWeight: '700', + }, +}); + +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 new file mode 100644 index 00000000..263dad9c --- /dev/null +++ b/src/components/discover/ContentItem.tsx @@ -0,0 +1,94 @@ +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 { colors } from '../../styles'; +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 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: 16, + overflow: 'hidden', + backgroundColor: 'rgba(255,255,255,0.03)', + elevation: 5, + shadowColor: colors.black, + 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', + color: colors.white, + 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 new file mode 100644 index 00000000..7cd41191 --- /dev/null +++ b/src/components/discover/GenreSelector.tsx @@ -0,0 +1,94 @@ +import React, { useCallback } from 'react'; +import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native'; +import { colors } from '../../styles'; + +interface GenreSelectorProps { + genres: string[]; + selectedGenre: string; + onSelectGenre: (genre: string) => void; +} + +const GenreSelector = ({ + genres, + selectedGenre, + onSelectGenre +}: GenreSelectorProps) => { + + const renderGenreButton = useCallback((genre: string) => { + const isSelected = selectedGenre === genre; + + return ( + onSelectGenre(genre)} + activeOpacity={0.7} + > + + {genre} + + + ); + }, [selectedGenre, onSelectGenre]); + + 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: colors.black, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + overflow: 'hidden', + }, + selectedGenreButton: { + backgroundColor: colors.primary, + }, + genreText: { + color: colors.mediumGray, + fontWeight: '500', + fontSize: 14, + }, + selectedGenreText: { + color: colors.white, + fontWeight: '600', + }, +}); + +export default React.memo(GenreSelector); \ No newline at end of file diff --git a/src/constants/discover.ts b/src/constants/discover.ts new file mode 100644 index 00000000..1af9de27 --- /dev/null +++ b/src/constants/discover.ts @@ -0,0 +1,42 @@ +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/screens/DiscoverScreen.tsx b/src/screens/DiscoverScreen.tsx index 008d01a9..ee6de4f4 100644 --- a/src/screens/DiscoverScreen.tsx +++ b/src/screens/DiscoverScreen.tsx @@ -1,507 +1,41 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, StyleSheet, - FlatList, TouchableOpacity, ActivityIndicator, - SafeAreaView, StatusBar, Dimensions, - ScrollView, Platform, - Animated, } 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 { catalogService, StreamingContent, CatalogContent } from '../services/catalogService'; -import { Image } from 'expo-image'; -import { FadeIn, FadeOut, SlideInRight, Layout } from 'react-native-reanimated'; -import { LinearGradient } from 'expo-linear-gradient'; +import { catalogService, StreamingContent } from '../services/catalogService'; import { RootStackParamList } from '../navigation/AppNavigator'; import { logger } from '../utils/logger'; -import { BlurView } from 'expo-blur'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -interface Category { - id: string; - name: string; - type: 'movie' | 'series' | 'channel' | 'tv'; - icon: keyof typeof MaterialIcons.glyphMap; -} +// Components +import CategorySelector from '../components/discover/CategorySelector'; +import GenreSelector from '../components/discover/GenreSelector'; +import CatalogsList from '../components/discover/CatalogsList'; -interface GenreCatalog { - genre: string; - items: StreamingContent[]; -} +// Constants and types +import { CATEGORIES, COMMON_GENRES, Category, GenreCatalog } from '../constants/discover'; -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 -const COMMON_GENRES = [ - 'All', - 'Action', - 'Adventure', - 'Animation', - 'Comedy', - 'Crime', - 'Documentary', - 'Drama', - 'Family', - 'Fantasy', - 'History', - 'Horror', - 'Music', - 'Mystery', - 'Romance', - 'Science Fiction', - 'Thriller', - 'War', - 'Western' -]; - -const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; - -// Memoized child components -const CategoryButton = React.memo(({ - category, - isSelected, - onPress -}: { - category: Category; - isSelected: boolean; - onPress: () => void; -}) => { - const styles = useStyles(); - return ( - - - - {category.name} - - - ); -}); - -const GenreButton = React.memo(({ - genre, - isSelected, - onPress -}: { - genre: string; - isSelected: boolean; - onPress: () => void; -}) => { - const styles = useStyles(); - return ( - - - {genre} - - - ); -}); - -const ContentItem = React.memo(({ - item, - onPress -}: { - item: StreamingContent; - onPress: () => void; -}) => { - const styles = useStyles(); - const { width } = Dimensions.get('window'); - const itemWidth = (width - 48) / 2.2; // 2 items per row with spacing - - return ( - - - - - - {item.name} - - {item.year && ( - {item.year} - )} - - - - ); -}); - -const CatalogSection = React.memo(({ - catalog, - selectedCategory, - navigation -}: { - catalog: GenreCatalog; - selectedCategory: Category; - navigation: NavigationProp; -}) => { - const styles = useStyles(); - const { width } = Dimensions.get('window'); - const itemWidth = (width - 48) / 2.2; // 2 items per row with spacing - - 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 renderItem = useCallback(({ item }: { item: StreamingContent }) => ( - handleContentPress(item)} - /> - ), [handleContentPress]); - - const handleSeeMorePress = useCallback(() => { - // Get addon/catalog info from the first item (assuming homogeneity) - const firstItem = catalog.items[0]; - if (!firstItem) return; // Should not happen if section exists - - // We need addonId and catalogId. These aren't directly on StreamingContent. - // We might need to fetch this or adjust the GenreCatalog structure. - // FOR NOW: Assuming CatalogScreen can handle potentially missing addonId/catalogId - // OR: We could pass the *genre* as the name and let CatalogScreen figure it out? - // Let's pass the necessary info if available, assuming StreamingContent might have it - // (Requires checking StreamingContent interface or how it's populated) - - // --- TEMPORARY/PLACEHOLDER --- - // Ideally, GenreCatalog should contain addonId/catalogId for the group. - // If not, CatalogScreen needs modification or we fetch IDs here. - // Let's stick to passing genre and type for now, CatalogScreen logic might suffice? - navigation.navigate('Catalog', { - // Don't pass an addonId since we want to filter by genre across all addons - id: catalog.genre, - type: selectedCategory.type, - name: `${catalog.genre} ${selectedCategory.name}`, - genreFilter: catalog.genre // This will trigger the genre-based filtering logic in CatalogScreen - }); - // --- END TEMPORARY --- - - }, [navigation, selectedCategory, catalog.genre, catalog.items]); - - const keyExtractor = useCallback((item: StreamingContent) => item.id, []); - const ItemSeparator = useCallback(() => , []); - - return ( - - - - {catalog.genre} - - - - See All - - - - - - - ); -}); - -// Extract styles into a hook for better performance with dimensions -const useStyles = () => { - const { width } = Dimensions.get('window'); - - return StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.darkBackground, - }, - headerBackground: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - backgroundColor: colors.darkBackground, - zIndex: 1, - }, - contentContainer: { - flex: 1, - backgroundColor: 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: colors.white, - letterSpacing: 0.3, - }, - searchButton: { - padding: 10, - borderRadius: 24, - backgroundColor: 'rgba(255,255,255,0.08)', - }, - categoryContainer: { - paddingVertical: 20, - borderBottomWidth: 1, - borderBottomColor: 'rgba(255,255,255,0.05)', - }, - categoriesContent: { - 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: colors.black, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.15, - shadowRadius: 8, - elevation: 4, - }, - selectedCategoryButton: { - backgroundColor: colors.primary, - }, - categoryText: { - color: colors.mediumGray, - fontWeight: '600', - fontSize: 16, - }, - selectedCategoryText: { - color: colors.white, - fontWeight: '700', - }, - genreContainer: { - paddingTop: 20, - paddingBottom: 12, - zIndex: 10, - }, - genresScrollView: { - paddingHorizontal: 20, - paddingBottom: 8, - }, - genreButton: { - paddingHorizontal: 18, - paddingVertical: 10, - marginRight: 12, - borderRadius: 20, - backgroundColor: 'rgba(255,255,255,0.05)', - shadowColor: colors.black, - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 2, - overflow: 'hidden', - }, - selectedGenreButton: { - backgroundColor: colors.primary, - }, - genreText: { - color: colors.mediumGray, - fontWeight: '500', - fontSize: 14, - }, - selectedGenreText: { - color: colors.white, - fontWeight: '600', - }, - loadingContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - catalogsContainer: { - paddingVertical: 8, - }, - catalogContainer: { - marginBottom: 32, - }, - catalogHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingHorizontal: 20, - marginBottom: 16, - }, - catalogTitleContainer: { - flexDirection: 'column', - }, - catalogTitleBar: { - width: 32, - height: 3, - backgroundColor: colors.primary, - marginTop: 6, - borderRadius: 2, - }, - catalogTitle: { - fontSize: 20, - fontWeight: '700', - color: colors.white, - }, - seeAllButton: { - flexDirection: 'row', - alignItems: 'center', - gap: 4, - paddingVertical: 6, - paddingHorizontal: 4, - }, - seeAllText: { - color: colors.primary, - fontWeight: '600', - fontSize: 14, - }, - contentItem: { - marginHorizontal: 0, - }, - posterContainer: { - borderRadius: 16, - overflow: 'hidden', - backgroundColor: 'rgba(255,255,255,0.03)', - elevation: 5, - shadowColor: colors.black, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.2, - shadowRadius: 8, - }, - poster: { - aspectRatio: 2/3, - width: '100%', - }, - posterGradient: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - padding: 16, - justifyContent: 'flex-end', - height: '45%', - }, - contentTitle: { - 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, - }, - contentYear: { - fontSize: 12, - color: 'rgba(255,255,255,0.7)', - textShadowColor: 'rgba(0, 0, 0, 0.75)', - textShadowOffset: { width: 0, height: 1 }, - textShadowRadius: 2, - }, - emptyContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - paddingTop: 80, - }, - emptyText: { - color: colors.mediumGray, - fontSize: 16, - textAlign: 'center', - paddingHorizontal: 32, - }, - }); -}; +// Styles +import useDiscoverStyles from '../styles/screens/discoverStyles'; const DiscoverScreen = () => { const navigation = useNavigation>(); const [selectedCategory, setSelectedCategory] = useState(CATEGORIES[0]); const [selectedGenre, setSelectedGenre] = useState('All'); const [catalogs, setCatalogs] = useState([]); - const [allContent, setAllContent] = useState([]); const [loading, setLoading] = useState(true); - const styles = useStyles(); + const styles = useDiscoverStyles(); const insets = useSafeAreaInsets(); // Force consistent status bar settings @@ -539,8 +73,6 @@ const DiscoverScreen = () => { content.push(...catalog.items); }); - setAllContent(content); - if (genre === 'All') { // Group by genres when "All" is selected const genreCatalogs: GenreCatalog[] = []; @@ -578,7 +110,6 @@ const DiscoverScreen = () => { } catch (error) { logger.error('Failed to load content:', error); setCatalogs([]); - setAllContent([]); } finally { setLoading(false); } @@ -601,29 +132,25 @@ const DiscoverScreen = () => { navigation.navigate('Search'); }, [navigation]); - // Memoize rendering functions - const renderCatalogItem = useCallback(({ item }: { item: GenreCatalog }) => ( - - ), [selectedCategory, navigation]); - - // Memoize list key extractor - const catalogKeyExtractor = useCallback((item: GenreCatalog) => item.genre, []); - 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 to prevent shifts */} + {/* Fixed position header background */} - {/* Header Section with proper top spacing */} + {/* Header Section */} Discover @@ -641,41 +168,21 @@ const DiscoverScreen = () => { - {/* Rest of the content */} + {/* Content Container */} {/* Categories Section */} - - - {CATEGORIES.map((category) => ( - handleCategoryPress(category)} - /> - ))} - - + {/* Genres Section */} - - - {COMMON_GENRES.map(genre => ( - handleGenrePress(genre)} - /> - ))} - - + {/* Content Section */} {loading ? ( @@ -683,24 +190,11 @@ const DiscoverScreen = () => { ) : catalogs.length > 0 ? ( - - ) : ( - - - No content found for {selectedGenre !== 'All' ? selectedGenre : 'these filters'} - - - )} + ) : renderEmptyState()} diff --git a/src/styles/screens/discoverStyles.ts b/src/styles/screens/discoverStyles.ts new file mode 100644 index 00000000..1010d2af --- /dev/null +++ b/src/styles/screens/discoverStyles.ts @@ -0,0 +1,67 @@ +import { StyleSheet, Dimensions } from 'react-native'; +import { colors } from '../index'; + +const useDiscoverStyles = () => { + const { width } = Dimensions.get('window'); + + return StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.darkBackground, + }, + headerBackground: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + backgroundColor: colors.darkBackground, + zIndex: 1, + }, + contentContainer: { + flex: 1, + backgroundColor: 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: 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, + }, + emptyText: { + color: colors.mediumGray, + fontSize: 16, + textAlign: 'center', + paddingHorizontal: 32, + }, + }); +}; + +export default useDiscoverStyles; \ No newline at end of file