diff --git a/src/components/metadata/CastSection.tsx b/src/components/metadata/CastSection.tsx index 8c9e3e6..f904eb7 100644 --- a/src/components/metadata/CastSection.tsx +++ b/src/components/metadata/CastSection.tsx @@ -3,20 +3,21 @@ import { View, Text, StyleSheet, + FlatList, TouchableOpacity, ActivityIndicator, - ScrollView, } from 'react-native'; import { Image } from 'expo-image'; -import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; -import { colors } from '../../styles/colors'; -import { Cast } from '../../types/metadata'; -import { tmdbService } from '../../services/tmdbService'; +import Animated, { + FadeIn, + Layout, +} from 'react-native-reanimated'; +import { useTheme } from '../../contexts/ThemeContext'; interface CastSectionProps { - cast: Cast[]; + cast: any[]; loadingCast: boolean; - onSelectCastMember: (member: Cast) => void; + onSelectCastMember: (castMember: any) => void; } export const CastSection: React.FC = ({ @@ -24,123 +25,137 @@ export const CastSection: React.FC = ({ loadingCast, onSelectCastMember, }) => { + const { currentTheme } = useTheme(); + if (loadingCast) { return ( - + ); } - if (!cast.length) { + if (!cast || cast.length === 0) { return null; } return ( - - Cast - + + Cast + + - {cast.map((member) => ( - onSelectCastMember(member)} + contentContainerStyle={styles.castList} + keyExtractor={(item) => item.id.toString()} + renderItem={({ item, index }) => ( + - - {member.profile_path ? ( - - ) : ( - + onSelectCastMember(item)} + activeOpacity={0.7} + > + + {item.profile_path ? ( + + ) : ( + + + {item.name.split(' ').reduce((prev: string, current: string) => prev + current[0], '').substring(0, 2)} + + + )} + + {item.name} + {item.character && ( + {item.character} )} - - - {member.name} - {member.character} - - - ))} - - + + + )} + /> + ); }; const styles = StyleSheet.create({ + castSection: { + marginBottom: 24, + paddingHorizontal: 0, + }, loadingContainer: { + paddingVertical: 20, alignItems: 'center', justifyContent: 'center', - padding: 12, }, - castSection: { - marginTop: 0, - paddingLeft: 0, - }, - sectionTitle: { - color: colors.highEmphasis, - fontSize: 18, - fontWeight: '700', - marginBottom: 10, + sectionHeader: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: 12, paddingHorizontal: 16, }, - castScrollContainer: { - marginTop: 4, + sectionTitle: { + fontSize: 18, + fontWeight: '700', }, - castContainer: { - paddingHorizontal: 12, - paddingVertical: 4, + castList: { + paddingHorizontal: 16, + paddingBottom: 4, }, - castMember: { - width: 80, - marginRight: 12, + castCard: { + marginRight: 16, + width: 90, alignItems: 'center', }, castImageContainer: { - width: 64, - height: 64, - borderRadius: 32, - backgroundColor: colors.elevation2, - justifyContent: 'center', - alignItems: 'center', + width: 80, + height: 80, + borderRadius: 40, overflow: 'hidden', - marginBottom: 6, - borderWidth: 1, - borderColor: 'rgba(255,255,255,0.1)', + marginBottom: 8, }, castImage: { - width: 64, - height: 64, - borderRadius: 32, - }, - castTextContainer: { width: '100%', + height: '100%', + }, + castImagePlaceholder: { + width: '100%', + height: '100%', + borderRadius: 40, alignItems: 'center', + justifyContent: 'center', + }, + placeholderText: { + fontSize: 24, + fontWeight: '600', }, castName: { - color: colors.highEmphasis, - fontSize: 13, + fontSize: 14, fontWeight: '600', textAlign: 'center', + width: 90, }, - castCharacter: { - color: colors.mediumEmphasis, + characterName: { fontSize: 12, textAlign: 'center', + width: 90, marginTop: 2, - opacity: 0.8, }, }); \ No newline at end of file diff --git a/src/components/metadata/FloatingHeader.tsx b/src/components/metadata/FloatingHeader.tsx index 619723f..30bdfbb 100644 --- a/src/components/metadata/FloatingHeader.tsx +++ b/src/components/metadata/FloatingHeader.tsx @@ -16,7 +16,7 @@ import Animated, { interpolate, Extrapolate, } from 'react-native-reanimated'; -import { colors } from '../../styles/colors'; +import { useTheme } from '../../contexts/ThemeContext'; import { logger } from '../../utils/logger'; const { width } = Dimensions.get('window'); @@ -46,6 +46,8 @@ const FloatingHeader: React.FC = ({ safeAreaTop, setLogoLoadError, }) => { + const { currentTheme } = useTheme(); + // Animated styles for the header const headerAnimatedStyle = useAnimatedStyle(() => ({ opacity: headerOpacity.value, @@ -74,7 +76,11 @@ const FloatingHeader: React.FC = ({ onPress={handleBack} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > - + @@ -90,7 +96,7 @@ const FloatingHeader: React.FC = ({ }} /> ) : ( - {metadata.name} + {metadata.name} )} @@ -102,7 +108,7 @@ const FloatingHeader: React.FC = ({ @@ -121,7 +127,11 @@ const FloatingHeader: React.FC = ({ onPress={handleBack} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > - + @@ -137,7 +147,7 @@ const FloatingHeader: React.FC = ({ }} /> ) : ( - {metadata.name} + {metadata.name} )} @@ -149,13 +159,13 @@ const FloatingHeader: React.FC = ({ )} - {Platform.OS === 'ios' && } + {Platform.OS === 'ios' && } ); }; @@ -190,7 +200,6 @@ const styles = StyleSheet.create({ left: 0, right: 0, height: 0.5, - backgroundColor: 'rgba(255,255,255,0.15)', }, headerTitleContainer: { flex: 1, @@ -218,7 +227,6 @@ const styles = StyleSheet.create({ maxWidth: 240, }, floatingHeaderTitle: { - color: colors.highEmphasis, fontSize: 18, fontWeight: '700', textAlign: 'center', diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index bd1c1cd..cef9ec4 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -14,7 +14,7 @@ import Animated, { interpolate, Extrapolate, } from 'react-native-reanimated'; -import { colors } from '../../styles/colors'; +import { useTheme } from '../../contexts/ThemeContext'; import { logger } from '../../utils/logger'; import { TMDBService } from '../../services/tmdbService'; @@ -77,6 +77,7 @@ const ActionButtons = React.memo(({ playButtonText: string; animatedStyle: any; }) => { + const { currentTheme } = useTheme(); return ( {inLibrary ? 'Saved' : 'Save'} @@ -155,7 +156,11 @@ const ActionButtons = React.memo(({ } }} > - + )} @@ -174,6 +179,7 @@ const WatchProgressDisplay = React.memo(({ getEpisodeDetails: (episodeId: string) => { seasonNumber: string; episodeNumber: string; episodeName: string } | null; animatedStyle: any; }) => { + const { currentTheme } = useTheme(); if (!watchProgress || watchProgress.duration === 0) { return null; } @@ -195,11 +201,14 @@ const WatchProgressDisplay = React.memo(({ - + {progressPercent >= 95 ? 'Watched' : `${Math.round(progressPercent)}% watched`}{episodeInfo} • Last watched on {formattedTime} @@ -236,11 +245,12 @@ const HeroSection: React.FC = ({ setBannerImage, setLogoLoadError, }) => { + const { currentTheme } = useTheme(); // Animated styles const heroAnimatedStyle = useAnimatedStyle(() => ({ width: '100%', height: heroHeight.value, - backgroundColor: colors.black, + backgroundColor: currentTheme.colors.black, transform: [{ scale: heroScale.value }], opacity: heroOpacity.value, })); @@ -309,9 +319,13 @@ const HeroSection: React.FC = ({ return genresToDisplay.slice(0, 4).map((genreName, index, array) => ( - {genreName} + + {genreName} + {index < array.length - 1 && ( - + + • + )} )); @@ -321,7 +335,7 @@ const HeroSection: React.FC = ({ {loadingBanner ? ( - + ) : ( = ({ )} = ({ }} /> ) : ( - {metadata.name} + {metadata.name} )} @@ -405,7 +419,7 @@ const styles = StyleSheet.create({ heroSection: { width: '100%', height: height * 0.5, - backgroundColor: colors.black, + backgroundColor: '#000', overflow: 'hidden', }, absoluteFill: { @@ -442,7 +456,6 @@ const styles = StyleSheet.create({ alignSelf: 'center', }, heroTitle: { - color: colors.highEmphasis, fontSize: 28, fontWeight: '900', marginBottom: 12, @@ -461,15 +474,12 @@ const styles = StyleSheet.create({ gap: 4, }, genreText: { - color: colors.text, fontSize: 12, fontWeight: '500', }, genreDot: { - color: colors.text, fontSize: 12, fontWeight: '500', - opacity: 0.6, marginHorizontal: 4, }, actionButtons: { @@ -494,7 +504,7 @@ const styles = StyleSheet.create({ flex: 1, }, playButton: { - backgroundColor: colors.white, + backgroundColor: '#fff', }, infoButton: { backgroundColor: 'rgba(255,255,255,0.2)', @@ -546,11 +556,9 @@ const styles = StyleSheet.create({ }, watchProgressFill: { height: '100%', - backgroundColor: colors.primary, borderRadius: 1.5, }, watchProgressText: { - color: colors.textMuted, fontSize: 12, textAlign: 'center', opacity: 0.9, diff --git a/src/components/metadata/MetadataDetails.tsx b/src/components/metadata/MetadataDetails.tsx index a32b67b..905dc5a 100644 --- a/src/components/metadata/MetadataDetails.tsx +++ b/src/components/metadata/MetadataDetails.tsx @@ -12,7 +12,7 @@ import Animated, { Easing, FadeIn, } from 'react-native-reanimated'; -import { colors } from '../../styles/colors'; +import { useTheme } from '../../contexts/ThemeContext'; interface MetadataDetailsProps { metadata: any; @@ -25,6 +25,7 @@ const MetadataDetails: React.FC = ({ imdbId, type, }) => { + const { currentTheme } = useTheme(); const [isFullDescriptionOpen, setIsFullDescriptionOpen] = useState(false); return ( @@ -32,13 +33,13 @@ const MetadataDetails: React.FC = ({ {/* Meta Info */} {metadata.year && ( - {metadata.year} + {metadata.year} )} {metadata.runtime && ( - {metadata.runtime} + {metadata.runtime} )} {metadata.certification && ( - {metadata.certification} + {metadata.certification} )} {metadata.imdbRating && ( @@ -47,7 +48,7 @@ const MetadataDetails: React.FC = ({ style={styles.imdbLogo} contentFit="contain" /> - {metadata.imdbRating} + {metadata.imdbRating} )} @@ -59,14 +60,14 @@ const MetadataDetails: React.FC = ({ > {metadata.directors && metadata.directors.length > 0 && ( - Director{metadata.directors.length > 1 ? 's' : ''}: - {metadata.directors.join(', ')} + Director{metadata.directors.length > 1 ? 's' : ''}: + {metadata.directors.join(', ')} )} {metadata.creators && metadata.creators.length > 0 && ( - Creator{metadata.creators.length > 1 ? 's' : ''}: - {metadata.creators.join(', ')} + Creator{metadata.creators.length > 1 ? 's' : ''}: + {metadata.creators.join(', ')} )} @@ -81,17 +82,17 @@ const MetadataDetails: React.FC = ({ onPress={() => setIsFullDescriptionOpen(!isFullDescriptionOpen)} activeOpacity={0.7} > - + {metadata.description} - + {isFullDescriptionOpen ? 'Show Less' : 'Show More'} @@ -110,7 +111,6 @@ const styles = StyleSheet.create({ marginBottom: 12, }, metaText: { - color: colors.text, fontSize: 15, fontWeight: '700', letterSpacing: 0.3, @@ -127,7 +127,6 @@ const styles = StyleSheet.create({ marginRight: 4, }, ratingText: { - color: colors.text, fontWeight: '700', fontSize: 15, letterSpacing: 0.3, @@ -143,14 +142,12 @@ const styles = StyleSheet.create({ height: 20 }, creatorLabel: { - color: colors.white, fontSize: 14, fontWeight: '600', marginRight: 8, lineHeight: 20 }, creatorText: { - color: colors.lightGray, fontSize: 14, flex: 1, lineHeight: 20 @@ -160,7 +157,6 @@ const styles = StyleSheet.create({ paddingHorizontal: 16, }, description: { - color: colors.mediumEmphasis, fontSize: 15, lineHeight: 24, }, @@ -171,7 +167,6 @@ const styles = StyleSheet.create({ paddingVertical: 4, }, showMoreText: { - color: colors.textMuted, fontSize: 14, marginRight: 4, }, diff --git a/src/components/metadata/MoreLikeThisSection.tsx b/src/components/metadata/MoreLikeThisSection.tsx index f9c8563..f69cc69 100644 --- a/src/components/metadata/MoreLikeThisSection.tsx +++ b/src/components/metadata/MoreLikeThisSection.tsx @@ -14,7 +14,7 @@ import { useNavigation, StackActions } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { RootStackParamList } from '../../navigation/AppNavigator'; import { StreamingContent } from '../../types/metadata'; -import { colors } from '../../styles/colors'; +import { useTheme } from '../../contexts/ThemeContext'; import { TMDBService } from '../../services/tmdbService'; import { catalogService } from '../../services/catalogService'; @@ -31,6 +31,7 @@ export const MoreLikeThisSection: React.FC = ({ recommendations, loadingRecommendations }) => { + const { currentTheme } = useTheme(); const navigation = useNavigation>(); const handleItemPress = async (item: StreamingContent) => { @@ -69,11 +70,11 @@ export const MoreLikeThisSection: React.FC = ({ > - + {item.name} @@ -82,7 +83,7 @@ export const MoreLikeThisSection: React.FC = ({ if (loadingRecommendations) { return ( - + ); } @@ -93,7 +94,7 @@ export const MoreLikeThisSection: React.FC = ({ return ( - More Like This + More Like This = ({ metadata }) => { + const { currentTheme } = useTheme(); const hasCast = Array.isArray(metadata.cast) && metadata.cast.length > 0; const castDisplay = hasCast ? (metadata.cast as string[]).slice(0, 5).join(', ') : ''; @@ -17,22 +18,22 @@ export const MovieContent: React.FC = ({ metadata }) => { {metadata.director && ( - Director: - {metadata.director} + Director: + {metadata.director} )} {metadata.writer && ( - Writer: - {metadata.writer} + Writer: + {metadata.writer} )} {hasCast && ( - Cast: - {castDisplay} + Cast: + {castDisplay} )} @@ -53,12 +54,10 @@ const styles = StyleSheet.create({ alignItems: 'flex-start', }, metadataLabel: { - color: colors.textMuted, fontSize: 15, width: 70, }, metadataValue: { - color: colors.text, fontSize: 15, flex: 1, lineHeight: 24, diff --git a/src/components/metadata/RatingsSection.tsx b/src/components/metadata/RatingsSection.tsx index a52076e..f09ea70 100644 --- a/src/components/metadata/RatingsSection.tsx +++ b/src/components/metadata/RatingsSection.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useRef } from 'react'; import { View, Text, StyleSheet, ActivityIndicator, Image, Animated } from 'react-native'; -import { colors } from '../../styles/colors'; +import { useTheme } from '../../contexts/ThemeContext'; import { useMDBListRatings } from '../../hooks/useMDBListRatings'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { isMDBListEnabled, RATING_PROVIDERS_STORAGE_KEY } from '../../screens/MDBListSettingsScreen'; @@ -54,6 +54,7 @@ export const RatingsSection: React.FC = ({ imdbId, type }) const [enabledProviders, setEnabledProviders] = useState>({}); const [isMDBEnabled, setIsMDBEnabled] = useState(true); const fadeAnim = useRef(new Animated.Value(0)).current; + const { currentTheme } = useTheme(); useEffect(() => { loadProviderSettings(); @@ -120,7 +121,7 @@ export const RatingsSection: React.FC = ({ imdbId, type }) if (loading) { return ( - + ); } @@ -214,86 +215,128 @@ export const RatingsSection: React.FC = ({ imdbId, type }) }, ]} > - {displayRatings.map(([source, value]) => { - const config = ratingConfig[source as keyof typeof ratingConfig]; - const numericValue = typeof value === 'string' ? parseFloat(value) : value; - const displayValue = config.transform(numericValue); - - // Get a short display name for the rating source - const getSourceLabel = (src: string): string => { - switch(src) { - case 'imdb': return 'IMDb'; - case 'tmdb': return 'TMDB'; - case 'tomatoes': return 'RT'; - case 'audience': return 'Aud'; - case 'metacritic': return 'Meta'; - case 'letterboxd': return 'LBXD'; - case 'trakt': return 'Trakt'; - default: return src; - } - }; - - return ( - - {config.isImage ? ( - - ) : ( - - )} - - {displayValue}{config.suffix} - - - ); - })} + + Ratings + + + {displayRatings.map(([source, value]) => { + const config = ratingConfig[source as keyof typeof ratingConfig]; + const displayValue = config.transform(parseFloat(value as string)); + + // Get a short display name for the rating source + const getSourceLabel = (src: string): string => { + switch(src) { + case 'imdb': return 'IMDb'; + case 'tmdb': return 'TMDB'; + case 'tomatoes': return 'RT'; + case 'audience': return 'Aud'; + case 'metacritic': return 'Meta'; + case 'letterboxd': return 'LBXD'; + case 'trakt': return 'Trakt'; + default: return src; + } + }; + + return ( + + + {config.isImage ? ( + + ) : ( + + {React.createElement(config.icon as any, { + width: 24, + height: 24, + })} + + )} + + + {config.prefix}{displayValue}{config.suffix} + + {getSourceLabel(source)} + + ); + })} + ); }; const styles = StyleSheet.create({ container: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - marginTop: 8, - marginBottom: 16, - paddingHorizontal: 12, - gap: 4, + marginBottom: 20, + paddingHorizontal: 16, }, loadingContainer: { - alignItems: 'center', + height: 80, justifyContent: 'center', - height: 40, - marginVertical: 16, + alignItems: 'center', }, - ratingItem: { + header: { flexDirection: 'row', alignItems: 'center', - backgroundColor: 'rgba(0, 0, 0, 0.4)', - paddingVertical: 3, - paddingHorizontal: 4, - borderRadius: 4, + justifyContent: 'space-between', + marginBottom: 12, }, - ratingIcon: { - width: 16, - height: 16, - marginRight: 3, - alignSelf: 'center', + title: { + fontSize: 18, + fontWeight: '700', + }, + ratingsContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 10, + }, + ratingItem: { + flexDirection: 'column', + alignItems: 'center', + width: 55, + }, + ratingIconContainer: { + width: 32, + height: 32, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 4, + }, + ratingIconImage: { + width: 32, + height: 32, + }, + svgContainer: { + alignItems: 'center', + justifyContent: 'center', }, ratingValue: { - fontSize: 13, - fontWeight: 'bold', + fontSize: 16, + fontWeight: '700', + marginVertical: 2, }, - ratingLabel: { + ratingSource: { fontSize: 11, - opacity: 0.9, + textAlign: 'center', + }, + noRatingsText: { + fontSize: 14, + color: 'gray', + fontStyle: 'italic', + textAlign: 'center', + marginVertical: 16, + }, + errorText: { + fontSize: 12, + color: '#ff0000', + textAlign: 'center', + marginVertical: 8, }, }); \ No newline at end of file diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index 881bdd0..7648477 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -12,7 +12,7 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' import { useRoute, useNavigation } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; import * as Haptics from 'expo-haptics'; -import { colors } from '../styles/colors'; +import { useTheme } from '../contexts/ThemeContext'; import { useMetadata } from '../hooks/useMetadata'; import { CastSection } from '../components/metadata/CastSection'; import { SeriesContent } from '../components/metadata/SeriesContent'; @@ -48,6 +48,9 @@ const MetadataScreen = () => { // Add settings hook const { settings } = useSettings(); + // Get theme context + const { currentTheme } = useTheme(); + // Get safe area insets const { top: safeAreaTop } = useSafeAreaInsets(); @@ -182,7 +185,9 @@ const MetadataScreen = () => { if (loading) { return ( { barStyle="light-content" /> - - + + Loading content... @@ -203,7 +210,9 @@ const MetadataScreen = () => { if (metadataError || !metadata) { return ( { - + {metadataError || 'Content not found'} Try Again @@ -238,11 +249,11 @@ const MetadataScreen = () => { - + Go Back @@ -253,7 +264,9 @@ const MetadataScreen = () => { return (