mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-12 13:00:53 +00:00
Integrate theme context across metadata components for enhanced UI consistency
This update refactors multiple metadata components, including CastSection, FloatingHeader, HeroSection, and RatingsSection, to utilize the new ThemeContext for dynamic theming. Styles have been adjusted to reflect the current theme colors, improving visual consistency throughout the application. Additionally, loading indicators and text colors have been updated to align with the theme, enhancing the overall user experience. These changes streamline the components and ensure a cohesive interface across different themes.
This commit is contained in:
parent
190c1a7371
commit
188c6e37f1
8 changed files with 310 additions and 234 deletions
|
|
@ -3,20 +3,21 @@ import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
FlatList,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
ScrollView,
|
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
import Animated, {
|
||||||
import { colors } from '../../styles/colors';
|
FadeIn,
|
||||||
import { Cast } from '../../types/metadata';
|
Layout,
|
||||||
import { tmdbService } from '../../services/tmdbService';
|
} from 'react-native-reanimated';
|
||||||
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
|
|
||||||
interface CastSectionProps {
|
interface CastSectionProps {
|
||||||
cast: Cast[];
|
cast: any[];
|
||||||
loadingCast: boolean;
|
loadingCast: boolean;
|
||||||
onSelectCastMember: (member: Cast) => void;
|
onSelectCastMember: (castMember: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CastSection: React.FC<CastSectionProps> = ({
|
export const CastSection: React.FC<CastSectionProps> = ({
|
||||||
|
|
@ -24,123 +25,137 @@ export const CastSection: React.FC<CastSectionProps> = ({
|
||||||
loadingCast,
|
loadingCast,
|
||||||
onSelectCastMember,
|
onSelectCastMember,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
|
|
||||||
if (loadingCast) {
|
if (loadingCast) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
<ActivityIndicator size="small" color={colors.primary} />
|
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cast.length) {
|
if (!cast || cast.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.castSection}>
|
<Animated.View
|
||||||
<Text style={styles.sectionTitle}>Cast</Text>
|
style={styles.castSection}
|
||||||
<ScrollView
|
entering={FadeIn.duration(500).delay(300)}
|
||||||
horizontal
|
layout={Layout}
|
||||||
showsHorizontalScrollIndicator={false}
|
>
|
||||||
style={styles.castScrollContainer}
|
<View style={styles.sectionHeader}>
|
||||||
contentContainerStyle={styles.castContainer}
|
<Text style={[styles.sectionTitle, { color: currentTheme.colors.highEmphasis }]}>Cast</Text>
|
||||||
snapToAlignment="start"
|
</View>
|
||||||
|
<FlatList
|
||||||
|
horizontal
|
||||||
|
data={cast}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
contentContainerStyle={styles.castList}
|
||||||
|
keyExtractor={(item) => item.id.toString()}
|
||||||
|
renderItem={({ item, index }) => (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeIn.duration(500).delay(100 + index * 50)}
|
||||||
|
layout={Layout}
|
||||||
>
|
>
|
||||||
{cast.map((member) => (
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={member.id}
|
style={styles.castCard}
|
||||||
style={styles.castMember}
|
onPress={() => onSelectCastMember(item)}
|
||||||
onPress={() => onSelectCastMember(member)}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<View style={styles.castImageContainer}>
|
<View style={styles.castImageContainer}>
|
||||||
{member.profile_path ? (
|
{item.profile_path ? (
|
||||||
<Image
|
<Image
|
||||||
source={{
|
source={{
|
||||||
uri: `https://image.tmdb.org/t/p/w185${member.profile_path}`
|
uri: `https://image.tmdb.org/t/p/w185${item.profile_path}`,
|
||||||
}}
|
}}
|
||||||
style={styles.castImage}
|
style={styles.castImage}
|
||||||
contentFit="cover"
|
contentFit="cover"
|
||||||
|
transition={200}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<MaterialIcons
|
<View style={[styles.castImagePlaceholder, { backgroundColor: currentTheme.colors.cardBackground }]}>
|
||||||
name="person"
|
<Text style={[styles.placeholderText, { color: currentTheme.colors.textMuted }]}>
|
||||||
size={32}
|
{item.name.split(' ').reduce((prev: string, current: string) => prev + current[0], '').substring(0, 2)}
|
||||||
color={colors.textMuted}
|
</Text>
|
||||||
/>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.castTextContainer}>
|
<Text style={[styles.castName, { color: currentTheme.colors.text }]} numberOfLines={1}>{item.name}</Text>
|
||||||
<Text style={styles.castName} numberOfLines={1}>{member.name}</Text>
|
{item.character && (
|
||||||
<Text style={styles.castCharacter} numberOfLines={1}>{member.character}</Text>
|
<Text style={[styles.characterName, { color: currentTheme.colors.textMuted }]} numberOfLines={1}>{item.character}</Text>
|
||||||
</View>
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
</Animated.View>
|
||||||
</ScrollView>
|
)}
|
||||||
</View>
|
/>
|
||||||
|
</Animated.View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
castSection: {
|
||||||
|
marginBottom: 24,
|
||||||
|
paddingHorizontal: 0,
|
||||||
|
},
|
||||||
loadingContainer: {
|
loadingContainer: {
|
||||||
|
paddingVertical: 20,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
padding: 12,
|
|
||||||
},
|
},
|
||||||
castSection: {
|
sectionHeader: {
|
||||||
marginTop: 0,
|
flexDirection: 'row',
|
||||||
paddingLeft: 0,
|
alignItems: 'center',
|
||||||
},
|
justifyContent: 'space-between',
|
||||||
sectionTitle: {
|
marginBottom: 12,
|
||||||
color: colors.highEmphasis,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: '700',
|
|
||||||
marginBottom: 10,
|
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
},
|
},
|
||||||
castScrollContainer: {
|
sectionTitle: {
|
||||||
marginTop: 4,
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
castContainer: {
|
castList: {
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 4,
|
paddingBottom: 4,
|
||||||
},
|
},
|
||||||
castMember: {
|
castCard: {
|
||||||
width: 80,
|
marginRight: 16,
|
||||||
marginRight: 12,
|
width: 90,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
castImageContainer: {
|
castImageContainer: {
|
||||||
width: 64,
|
width: 80,
|
||||||
height: 64,
|
height: 80,
|
||||||
borderRadius: 32,
|
borderRadius: 40,
|
||||||
backgroundColor: colors.elevation2,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginBottom: 6,
|
marginBottom: 8,
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(255,255,255,0.1)',
|
|
||||||
},
|
},
|
||||||
castImage: {
|
castImage: {
|
||||||
width: 64,
|
|
||||||
height: 64,
|
|
||||||
borderRadius: 32,
|
|
||||||
},
|
|
||||||
castTextContainer: {
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
castImagePlaceholder: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: 40,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
placeholderText: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
castName: {
|
castName: {
|
||||||
color: colors.highEmphasis,
|
fontSize: 14,
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
width: 90,
|
||||||
},
|
},
|
||||||
castCharacter: {
|
characterName: {
|
||||||
color: colors.mediumEmphasis,
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
width: 90,
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
opacity: 0.8,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -16,7 +16,7 @@ import Animated, {
|
||||||
interpolate,
|
interpolate,
|
||||||
Extrapolate,
|
Extrapolate,
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { colors } from '../../styles/colors';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
const { width } = Dimensions.get('window');
|
||||||
|
|
@ -46,6 +46,8 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
|
||||||
safeAreaTop,
|
safeAreaTop,
|
||||||
setLogoLoadError,
|
setLogoLoadError,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
|
|
||||||
// Animated styles for the header
|
// Animated styles for the header
|
||||||
const headerAnimatedStyle = useAnimatedStyle(() => ({
|
const headerAnimatedStyle = useAnimatedStyle(() => ({
|
||||||
opacity: headerOpacity.value,
|
opacity: headerOpacity.value,
|
||||||
|
|
@ -74,7 +76,11 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
|
||||||
onPress={handleBack}
|
onPress={handleBack}
|
||||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="arrow-back" size={24} color={colors.highEmphasis} />
|
<MaterialIcons
|
||||||
|
name="arrow-back"
|
||||||
|
size={24}
|
||||||
|
color={currentTheme.colors.highEmphasis}
|
||||||
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<View style={styles.headerTitleContainer}>
|
<View style={styles.headerTitleContainer}>
|
||||||
|
|
@ -90,7 +96,7 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.floatingHeaderTitle} numberOfLines={1}>{metadata.name}</Text>
|
<Text style={[styles.floatingHeaderTitle, { color: currentTheme.colors.highEmphasis }]} numberOfLines={1}>{metadata.name}</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
@ -102,7 +108,7 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name={inLibrary ? 'bookmark' : 'bookmark-border'}
|
name={inLibrary ? 'bookmark' : 'bookmark-border'}
|
||||||
size={22}
|
size={22}
|
||||||
color={colors.highEmphasis}
|
color={currentTheme.colors.highEmphasis}
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
@ -121,7 +127,11 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
|
||||||
onPress={handleBack}
|
onPress={handleBack}
|
||||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="arrow-back" size={24} color={colors.highEmphasis} />
|
<MaterialIcons
|
||||||
|
name="arrow-back"
|
||||||
|
size={24}
|
||||||
|
color={currentTheme.colors.highEmphasis}
|
||||||
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<View style={styles.headerTitleContainer}>
|
<View style={styles.headerTitleContainer}>
|
||||||
|
|
@ -137,7 +147,7 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.floatingHeaderTitle} numberOfLines={1}>{metadata.name}</Text>
|
<Text style={[styles.floatingHeaderTitle, { color: currentTheme.colors.highEmphasis }]} numberOfLines={1}>{metadata.name}</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
@ -149,13 +159,13 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name={inLibrary ? 'bookmark' : 'bookmark-border'}
|
name={inLibrary ? 'bookmark' : 'bookmark-border'}
|
||||||
size={22}
|
size={22}
|
||||||
color={colors.highEmphasis}
|
color={currentTheme.colors.highEmphasis}
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{Platform.OS === 'ios' && <View style={styles.headerBottomBorder} />}
|
{Platform.OS === 'ios' && <View style={[styles.headerBottomBorder, { backgroundColor: 'rgba(255,255,255,0.15)' }]} />}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -190,7 +200,6 @@ const styles = StyleSheet.create({
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
height: 0.5,
|
height: 0.5,
|
||||||
backgroundColor: 'rgba(255,255,255,0.15)',
|
|
||||||
},
|
},
|
||||||
headerTitleContainer: {
|
headerTitleContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -218,7 +227,6 @@ const styles = StyleSheet.create({
|
||||||
maxWidth: 240,
|
maxWidth: 240,
|
||||||
},
|
},
|
||||||
floatingHeaderTitle: {
|
floatingHeaderTitle: {
|
||||||
color: colors.highEmphasis,
|
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import Animated, {
|
||||||
interpolate,
|
interpolate,
|
||||||
Extrapolate,
|
Extrapolate,
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { colors } from '../../styles/colors';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import { TMDBService } from '../../services/tmdbService';
|
import { TMDBService } from '../../services/tmdbService';
|
||||||
|
|
||||||
|
|
@ -77,6 +77,7 @@ const ActionButtons = React.memo(({
|
||||||
playButtonText: string;
|
playButtonText: string;
|
||||||
animatedStyle: any;
|
animatedStyle: any;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.actionButtons, animatedStyle]}>
|
<Animated.View style={[styles.actionButtons, animatedStyle]}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|
@ -100,7 +101,7 @@ const ActionButtons = React.memo(({
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name={inLibrary ? 'bookmark' : 'bookmark-border'}
|
name={inLibrary ? 'bookmark' : 'bookmark-border'}
|
||||||
size={24}
|
size={24}
|
||||||
color="#fff"
|
color={currentTheme.colors.white}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.infoButtonText}>
|
<Text style={styles.infoButtonText}>
|
||||||
{inLibrary ? 'Saved' : 'Save'}
|
{inLibrary ? 'Saved' : 'Save'}
|
||||||
|
|
@ -155,7 +156,11 @@ const ActionButtons = React.memo(({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="assessment" size={24} color="#fff" />
|
<MaterialIcons
|
||||||
|
name="assessment"
|
||||||
|
size={24}
|
||||||
|
color={currentTheme.colors.white}
|
||||||
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
@ -174,6 +179,7 @@ const WatchProgressDisplay = React.memo(({
|
||||||
getEpisodeDetails: (episodeId: string) => { seasonNumber: string; episodeNumber: string; episodeName: string } | null;
|
getEpisodeDetails: (episodeId: string) => { seasonNumber: string; episodeNumber: string; episodeName: string } | null;
|
||||||
animatedStyle: any;
|
animatedStyle: any;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
if (!watchProgress || watchProgress.duration === 0) {
|
if (!watchProgress || watchProgress.duration === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -195,11 +201,14 @@ const WatchProgressDisplay = React.memo(({
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.watchProgressFill,
|
styles.watchProgressFill,
|
||||||
{ width: `${progressPercent}%` }
|
{
|
||||||
|
width: `${progressPercent}%`,
|
||||||
|
backgroundColor: currentTheme.colors.primary
|
||||||
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.watchProgressText}>
|
<Text style={[styles.watchProgressText, { color: currentTheme.colors.textMuted }]}>
|
||||||
{progressPercent >= 95 ? 'Watched' : `${Math.round(progressPercent)}% watched`}{episodeInfo} • Last watched on {formattedTime}
|
{progressPercent >= 95 ? 'Watched' : `${Math.round(progressPercent)}% watched`}{episodeInfo} • Last watched on {formattedTime}
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
@ -236,11 +245,12 @@ const HeroSection: React.FC<HeroSectionProps> = ({
|
||||||
setBannerImage,
|
setBannerImage,
|
||||||
setLogoLoadError,
|
setLogoLoadError,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
// Animated styles
|
// Animated styles
|
||||||
const heroAnimatedStyle = useAnimatedStyle(() => ({
|
const heroAnimatedStyle = useAnimatedStyle(() => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: heroHeight.value,
|
height: heroHeight.value,
|
||||||
backgroundColor: colors.black,
|
backgroundColor: currentTheme.colors.black,
|
||||||
transform: [{ scale: heroScale.value }],
|
transform: [{ scale: heroScale.value }],
|
||||||
opacity: heroOpacity.value,
|
opacity: heroOpacity.value,
|
||||||
}));
|
}));
|
||||||
|
|
@ -309,9 +319,13 @@ const HeroSection: React.FC<HeroSectionProps> = ({
|
||||||
|
|
||||||
return genresToDisplay.slice(0, 4).map((genreName, index, array) => (
|
return genresToDisplay.slice(0, 4).map((genreName, index, array) => (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
<Text style={styles.genreText}>{genreName}</Text>
|
<Text style={[styles.genreText, { color: currentTheme.colors.text }]}>
|
||||||
|
{genreName}
|
||||||
|
</Text>
|
||||||
{index < array.length - 1 && (
|
{index < array.length - 1 && (
|
||||||
<Text style={styles.genreDot}>•</Text>
|
<Text style={[styles.genreDot, { color: currentTheme.colors.text, opacity: 0.6 }]}>
|
||||||
|
•
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
));
|
));
|
||||||
|
|
@ -321,7 +335,7 @@ const HeroSection: React.FC<HeroSectionProps> = ({
|
||||||
<Animated.View style={heroAnimatedStyle}>
|
<Animated.View style={heroAnimatedStyle}>
|
||||||
<View style={styles.heroSection}>
|
<View style={styles.heroSection}>
|
||||||
{loadingBanner ? (
|
{loadingBanner ? (
|
||||||
<View style={[styles.absoluteFill, { backgroundColor: colors.black }]} />
|
<View style={[styles.absoluteFill, { backgroundColor: currentTheme.colors.black }]} />
|
||||||
) : (
|
) : (
|
||||||
<Animated.Image
|
<Animated.Image
|
||||||
source={{ uri: bannerImage || metadata.banner || metadata.poster }}
|
source={{ uri: bannerImage || metadata.banner || metadata.poster }}
|
||||||
|
|
@ -337,12 +351,12 @@ const HeroSection: React.FC<HeroSectionProps> = ({
|
||||||
)}
|
)}
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[
|
colors={[
|
||||||
`${colors.darkBackground}00`,
|
`${currentTheme.colors.darkBackground}00`,
|
||||||
`${colors.darkBackground}20`,
|
`${currentTheme.colors.darkBackground}20`,
|
||||||
`${colors.darkBackground}50`,
|
`${currentTheme.colors.darkBackground}50`,
|
||||||
`${colors.darkBackground}C0`,
|
`${currentTheme.colors.darkBackground}C0`,
|
||||||
`${colors.darkBackground}F8`,
|
`${currentTheme.colors.darkBackground}F8`,
|
||||||
colors.darkBackground
|
currentTheme.colors.darkBackground
|
||||||
]}
|
]}
|
||||||
locations={[0, 0.4, 0.65, 0.8, 0.9, 1]}
|
locations={[0, 0.4, 0.65, 0.8, 0.9, 1]}
|
||||||
style={styles.heroGradient}
|
style={styles.heroGradient}
|
||||||
|
|
@ -363,7 +377,7 @@ const HeroSection: React.FC<HeroSectionProps> = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.heroTitle}>{metadata.name}</Text>
|
<Text style={[styles.heroTitle, { color: currentTheme.colors.highEmphasis }]}>{metadata.name}</Text>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -405,7 +419,7 @@ const styles = StyleSheet.create({
|
||||||
heroSection: {
|
heroSection: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: height * 0.5,
|
height: height * 0.5,
|
||||||
backgroundColor: colors.black,
|
backgroundColor: '#000',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
absoluteFill: {
|
absoluteFill: {
|
||||||
|
|
@ -442,7 +456,6 @@ const styles = StyleSheet.create({
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
},
|
},
|
||||||
heroTitle: {
|
heroTitle: {
|
||||||
color: colors.highEmphasis,
|
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
fontWeight: '900',
|
fontWeight: '900',
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
|
|
@ -461,15 +474,12 @@ const styles = StyleSheet.create({
|
||||||
gap: 4,
|
gap: 4,
|
||||||
},
|
},
|
||||||
genreText: {
|
genreText: {
|
||||||
color: colors.text,
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
genreDot: {
|
genreDot: {
|
||||||
color: colors.text,
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
opacity: 0.6,
|
|
||||||
marginHorizontal: 4,
|
marginHorizontal: 4,
|
||||||
},
|
},
|
||||||
actionButtons: {
|
actionButtons: {
|
||||||
|
|
@ -494,7 +504,7 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
playButton: {
|
playButton: {
|
||||||
backgroundColor: colors.white,
|
backgroundColor: '#fff',
|
||||||
},
|
},
|
||||||
infoButton: {
|
infoButton: {
|
||||||
backgroundColor: 'rgba(255,255,255,0.2)',
|
backgroundColor: 'rgba(255,255,255,0.2)',
|
||||||
|
|
@ -546,11 +556,9 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
watchProgressFill: {
|
watchProgressFill: {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
backgroundColor: colors.primary,
|
|
||||||
borderRadius: 1.5,
|
borderRadius: 1.5,
|
||||||
},
|
},
|
||||||
watchProgressText: {
|
watchProgressText: {
|
||||||
color: colors.textMuted,
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import Animated, {
|
||||||
Easing,
|
Easing,
|
||||||
FadeIn,
|
FadeIn,
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { colors } from '../../styles/colors';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
|
|
||||||
interface MetadataDetailsProps {
|
interface MetadataDetailsProps {
|
||||||
metadata: any;
|
metadata: any;
|
||||||
|
|
@ -25,6 +25,7 @@ const MetadataDetails: React.FC<MetadataDetailsProps> = ({
|
||||||
imdbId,
|
imdbId,
|
||||||
type,
|
type,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
const [isFullDescriptionOpen, setIsFullDescriptionOpen] = useState(false);
|
const [isFullDescriptionOpen, setIsFullDescriptionOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -32,13 +33,13 @@ const MetadataDetails: React.FC<MetadataDetailsProps> = ({
|
||||||
{/* Meta Info */}
|
{/* Meta Info */}
|
||||||
<View style={styles.metaInfo}>
|
<View style={styles.metaInfo}>
|
||||||
{metadata.year && (
|
{metadata.year && (
|
||||||
<Text style={styles.metaText}>{metadata.year}</Text>
|
<Text style={[styles.metaText, { color: currentTheme.colors.text }]}>{metadata.year}</Text>
|
||||||
)}
|
)}
|
||||||
{metadata.runtime && (
|
{metadata.runtime && (
|
||||||
<Text style={styles.metaText}>{metadata.runtime}</Text>
|
<Text style={[styles.metaText, { color: currentTheme.colors.text }]}>{metadata.runtime}</Text>
|
||||||
)}
|
)}
|
||||||
{metadata.certification && (
|
{metadata.certification && (
|
||||||
<Text style={styles.metaText}>{metadata.certification}</Text>
|
<Text style={[styles.metaText, { color: currentTheme.colors.text }]}>{metadata.certification}</Text>
|
||||||
)}
|
)}
|
||||||
{metadata.imdbRating && (
|
{metadata.imdbRating && (
|
||||||
<View style={styles.ratingContainer}>
|
<View style={styles.ratingContainer}>
|
||||||
|
|
@ -47,7 +48,7 @@ const MetadataDetails: React.FC<MetadataDetailsProps> = ({
|
||||||
style={styles.imdbLogo}
|
style={styles.imdbLogo}
|
||||||
contentFit="contain"
|
contentFit="contain"
|
||||||
/>
|
/>
|
||||||
<Text style={styles.ratingText}>{metadata.imdbRating}</Text>
|
<Text style={[styles.ratingText, { color: currentTheme.colors.text }]}>{metadata.imdbRating}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -59,14 +60,14 @@ const MetadataDetails: React.FC<MetadataDetailsProps> = ({
|
||||||
>
|
>
|
||||||
{metadata.directors && metadata.directors.length > 0 && (
|
{metadata.directors && metadata.directors.length > 0 && (
|
||||||
<View style={styles.creatorSection}>
|
<View style={styles.creatorSection}>
|
||||||
<Text style={styles.creatorLabel}>Director{metadata.directors.length > 1 ? 's' : ''}:</Text>
|
<Text style={[styles.creatorLabel, { color: currentTheme.colors.white }]}>Director{metadata.directors.length > 1 ? 's' : ''}:</Text>
|
||||||
<Text style={styles.creatorText}>{metadata.directors.join(', ')}</Text>
|
<Text style={[styles.creatorText, { color: currentTheme.colors.mediumEmphasis }]}>{metadata.directors.join(', ')}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{metadata.creators && metadata.creators.length > 0 && (
|
{metadata.creators && metadata.creators.length > 0 && (
|
||||||
<View style={styles.creatorSection}>
|
<View style={styles.creatorSection}>
|
||||||
<Text style={styles.creatorLabel}>Creator{metadata.creators.length > 1 ? 's' : ''}:</Text>
|
<Text style={[styles.creatorLabel, { color: currentTheme.colors.white }]}>Creator{metadata.creators.length > 1 ? 's' : ''}:</Text>
|
||||||
<Text style={styles.creatorText}>{metadata.creators.join(', ')}</Text>
|
<Text style={[styles.creatorText, { color: currentTheme.colors.mediumEmphasis }]}>{metadata.creators.join(', ')}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
@ -81,17 +82,17 @@ const MetadataDetails: React.FC<MetadataDetailsProps> = ({
|
||||||
onPress={() => setIsFullDescriptionOpen(!isFullDescriptionOpen)}
|
onPress={() => setIsFullDescriptionOpen(!isFullDescriptionOpen)}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<Text style={styles.description} numberOfLines={isFullDescriptionOpen ? undefined : 3}>
|
<Text style={[styles.description, { color: currentTheme.colors.mediumEmphasis }]} numberOfLines={isFullDescriptionOpen ? undefined : 3}>
|
||||||
{metadata.description}
|
{metadata.description}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.showMoreButton}>
|
<View style={styles.showMoreButton}>
|
||||||
<Text style={styles.showMoreText}>
|
<Text style={[styles.showMoreText, { color: currentTheme.colors.textMuted }]}>
|
||||||
{isFullDescriptionOpen ? 'Show Less' : 'Show More'}
|
{isFullDescriptionOpen ? 'Show Less' : 'Show More'}
|
||||||
</Text>
|
</Text>
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name={isFullDescriptionOpen ? "keyboard-arrow-up" : "keyboard-arrow-down"}
|
name={isFullDescriptionOpen ? "keyboard-arrow-up" : "keyboard-arrow-down"}
|
||||||
size={18}
|
size={18}
|
||||||
color={colors.textMuted}
|
color={currentTheme.colors.textMuted}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
@ -110,7 +111,6 @@ const styles = StyleSheet.create({
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
metaText: {
|
metaText: {
|
||||||
color: colors.text,
|
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
|
|
@ -127,7 +127,6 @@ const styles = StyleSheet.create({
|
||||||
marginRight: 4,
|
marginRight: 4,
|
||||||
},
|
},
|
||||||
ratingText: {
|
ratingText: {
|
||||||
color: colors.text,
|
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
|
|
@ -143,14 +142,12 @@ const styles = StyleSheet.create({
|
||||||
height: 20
|
height: 20
|
||||||
},
|
},
|
||||||
creatorLabel: {
|
creatorLabel: {
|
||||||
color: colors.white,
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginRight: 8,
|
marginRight: 8,
|
||||||
lineHeight: 20
|
lineHeight: 20
|
||||||
},
|
},
|
||||||
creatorText: {
|
creatorText: {
|
||||||
color: colors.lightGray,
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
lineHeight: 20
|
lineHeight: 20
|
||||||
|
|
@ -160,7 +157,6 @@ const styles = StyleSheet.create({
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
color: colors.mediumEmphasis,
|
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
lineHeight: 24,
|
lineHeight: 24,
|
||||||
},
|
},
|
||||||
|
|
@ -171,7 +167,6 @@ const styles = StyleSheet.create({
|
||||||
paddingVertical: 4,
|
paddingVertical: 4,
|
||||||
},
|
},
|
||||||
showMoreText: {
|
showMoreText: {
|
||||||
color: colors.textMuted,
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
marginRight: 4,
|
marginRight: 4,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { useNavigation, StackActions } from '@react-navigation/native';
|
||||||
import { NavigationProp } from '@react-navigation/native';
|
import { NavigationProp } from '@react-navigation/native';
|
||||||
import { RootStackParamList } from '../../navigation/AppNavigator';
|
import { RootStackParamList } from '../../navigation/AppNavigator';
|
||||||
import { StreamingContent } from '../../types/metadata';
|
import { StreamingContent } from '../../types/metadata';
|
||||||
import { colors } from '../../styles/colors';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
import { TMDBService } from '../../services/tmdbService';
|
import { TMDBService } from '../../services/tmdbService';
|
||||||
import { catalogService } from '../../services/catalogService';
|
import { catalogService } from '../../services/catalogService';
|
||||||
|
|
||||||
|
|
@ -31,6 +31,7 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
|
||||||
recommendations,
|
recommendations,
|
||||||
loadingRecommendations
|
loadingRecommendations
|
||||||
}) => {
|
}) => {
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||||
|
|
||||||
const handleItemPress = async (item: StreamingContent) => {
|
const handleItemPress = async (item: StreamingContent) => {
|
||||||
|
|
@ -69,11 +70,11 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: item.poster }}
|
source={{ uri: item.poster }}
|
||||||
style={styles.poster}
|
style={[styles.poster, { backgroundColor: currentTheme.colors.elevation1 }]}
|
||||||
contentFit="cover"
|
contentFit="cover"
|
||||||
transition={200}
|
transition={200}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.title} numberOfLines={2}>
|
<Text style={[styles.title, { color: currentTheme.colors.mediumEmphasis }]} numberOfLines={2}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
@ -82,7 +83,7 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
|
||||||
if (loadingRecommendations) {
|
if (loadingRecommendations) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
<ActivityIndicator size="small" color={colors.primary} />
|
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +94,7 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.sectionTitle}>More Like This</Text>
|
<Text style={[styles.sectionTitle, { color: currentTheme.colors.highEmphasis }]}>More Like This</Text>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={recommendations}
|
data={recommendations}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
|
|
@ -115,7 +116,6 @@ const styles = StyleSheet.create({
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
color: colors.highEmphasis,
|
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
|
|
@ -132,12 +132,10 @@ const styles = StyleSheet.create({
|
||||||
width: POSTER_WIDTH,
|
width: POSTER_WIDTH,
|
||||||
height: POSTER_HEIGHT,
|
height: POSTER_HEIGHT,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
backgroundColor: colors.elevation1,
|
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: colors.mediumEmphasis,
|
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
lineHeight: 18,
|
lineHeight: 18,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
import { colors } from '../../styles/colors';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
import { StreamingContent } from '../../types/metadata';
|
import { StreamingContent } from '../../types/metadata';
|
||||||
|
|
||||||
interface MovieContentProps {
|
interface MovieContentProps {
|
||||||
|
|
@ -8,6 +8,7 @@ interface MovieContentProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MovieContent: React.FC<MovieContentProps> = ({ metadata }) => {
|
export const MovieContent: React.FC<MovieContentProps> = ({ metadata }) => {
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
const hasCast = Array.isArray(metadata.cast) && metadata.cast.length > 0;
|
const hasCast = Array.isArray(metadata.cast) && metadata.cast.length > 0;
|
||||||
const castDisplay = hasCast ? (metadata.cast as string[]).slice(0, 5).join(', ') : '';
|
const castDisplay = hasCast ? (metadata.cast as string[]).slice(0, 5).join(', ') : '';
|
||||||
|
|
||||||
|
|
@ -17,22 +18,22 @@ export const MovieContent: React.FC<MovieContentProps> = ({ metadata }) => {
|
||||||
<View style={styles.additionalInfo}>
|
<View style={styles.additionalInfo}>
|
||||||
{metadata.director && (
|
{metadata.director && (
|
||||||
<View style={styles.metadataRow}>
|
<View style={styles.metadataRow}>
|
||||||
<Text style={styles.metadataLabel}>Director:</Text>
|
<Text style={[styles.metadataLabel, { color: currentTheme.colors.textMuted }]}>Director:</Text>
|
||||||
<Text style={styles.metadataValue}>{metadata.director}</Text>
|
<Text style={[styles.metadataValue, { color: currentTheme.colors.text }]}>{metadata.director}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{metadata.writer && (
|
{metadata.writer && (
|
||||||
<View style={styles.metadataRow}>
|
<View style={styles.metadataRow}>
|
||||||
<Text style={styles.metadataLabel}>Writer:</Text>
|
<Text style={[styles.metadataLabel, { color: currentTheme.colors.textMuted }]}>Writer:</Text>
|
||||||
<Text style={styles.metadataValue}>{metadata.writer}</Text>
|
<Text style={[styles.metadataValue, { color: currentTheme.colors.text }]}>{metadata.writer}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasCast && (
|
{hasCast && (
|
||||||
<View style={styles.metadataRow}>
|
<View style={styles.metadataRow}>
|
||||||
<Text style={styles.metadataLabel}>Cast:</Text>
|
<Text style={[styles.metadataLabel, { color: currentTheme.colors.textMuted }]}>Cast:</Text>
|
||||||
<Text style={styles.metadataValue}>{castDisplay}</Text>
|
<Text style={[styles.metadataValue, { color: currentTheme.colors.text }]}>{castDisplay}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -53,12 +54,10 @@ const styles = StyleSheet.create({
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
},
|
},
|
||||||
metadataLabel: {
|
metadataLabel: {
|
||||||
color: colors.textMuted,
|
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
width: 70,
|
width: 70,
|
||||||
},
|
},
|
||||||
metadataValue: {
|
metadataValue: {
|
||||||
color: colors.text,
|
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
lineHeight: 24,
|
lineHeight: 24,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { View, Text, StyleSheet, ActivityIndicator, Image, Animated } from 'react-native';
|
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 { useMDBListRatings } from '../../hooks/useMDBListRatings';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { isMDBListEnabled, RATING_PROVIDERS_STORAGE_KEY } from '../../screens/MDBListSettingsScreen';
|
import { isMDBListEnabled, RATING_PROVIDERS_STORAGE_KEY } from '../../screens/MDBListSettingsScreen';
|
||||||
|
|
@ -54,6 +54,7 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
|
||||||
const [enabledProviders, setEnabledProviders] = useState<Record<string, boolean>>({});
|
const [enabledProviders, setEnabledProviders] = useState<Record<string, boolean>>({});
|
||||||
const [isMDBEnabled, setIsMDBEnabled] = useState(true);
|
const [isMDBEnabled, setIsMDBEnabled] = useState(true);
|
||||||
const fadeAnim = useRef(new Animated.Value(0)).current;
|
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadProviderSettings();
|
loadProviderSettings();
|
||||||
|
|
@ -120,7 +121,7 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
<ActivityIndicator size="small" color={colors.primary} />
|
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -214,10 +215,13 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={[styles.title, { color: currentTheme.colors.highEmphasis }]}>Ratings</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.ratingsContainer}>
|
||||||
{displayRatings.map(([source, value]) => {
|
{displayRatings.map(([source, value]) => {
|
||||||
const config = ratingConfig[source as keyof typeof ratingConfig];
|
const config = ratingConfig[source as keyof typeof ratingConfig];
|
||||||
const numericValue = typeof value === 'string' ? parseFloat(value) : value;
|
const displayValue = config.transform(parseFloat(value as string));
|
||||||
const displayValue = config.transform(numericValue);
|
|
||||||
|
|
||||||
// Get a short display name for the rating source
|
// Get a short display name for the rating source
|
||||||
const getSourceLabel = (src: string): string => {
|
const getSourceLabel = (src: string): string => {
|
||||||
|
|
@ -235,65 +239,104 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View key={source} style={styles.ratingItem}>
|
<View key={source} style={styles.ratingItem}>
|
||||||
|
<View style={styles.ratingIconContainer}>
|
||||||
{config.isImage ? (
|
{config.isImage ? (
|
||||||
<Image
|
<Image
|
||||||
source={config.icon}
|
source={config.icon as any}
|
||||||
style={styles.ratingIcon}
|
style={styles.ratingIconImage}
|
||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<config.icon
|
<View style={styles.svgContainer}>
|
||||||
width={16}
|
{React.createElement(config.icon as any, {
|
||||||
height={16}
|
width: 24,
|
||||||
style={styles.ratingIcon}
|
height: 24,
|
||||||
/>
|
})}
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
<Text style={[styles.ratingValue, {color: config.color}]}>
|
</View>
|
||||||
{displayValue}{config.suffix}
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.ratingValue,
|
||||||
|
{ color: config.color }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{config.prefix}{displayValue}{config.suffix}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text style={[styles.ratingSource, { color: currentTheme.colors.mediumEmphasis }]}>{getSourceLabel(source)}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flexDirection: 'row',
|
marginBottom: 20,
|
||||||
alignItems: 'center',
|
paddingHorizontal: 16,
|
||||||
justifyContent: 'center',
|
|
||||||
marginTop: 8,
|
|
||||||
marginBottom: 16,
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
gap: 4,
|
|
||||||
},
|
},
|
||||||
loadingContainer: {
|
loadingContainer: {
|
||||||
alignItems: 'center',
|
height: 80,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
height: 40,
|
alignItems: 'center',
|
||||||
marginVertical: 16,
|
|
||||||
},
|
},
|
||||||
ratingItem: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
justifyContent: 'space-between',
|
||||||
paddingVertical: 3,
|
marginBottom: 12,
|
||||||
paddingHorizontal: 4,
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
},
|
||||||
ratingIcon: {
|
title: {
|
||||||
width: 16,
|
fontSize: 18,
|
||||||
height: 16,
|
fontWeight: '700',
|
||||||
marginRight: 3,
|
},
|
||||||
alignSelf: 'center',
|
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: {
|
ratingValue: {
|
||||||
fontSize: 13,
|
fontSize: 16,
|
||||||
fontWeight: 'bold',
|
fontWeight: '700',
|
||||||
|
marginVertical: 2,
|
||||||
},
|
},
|
||||||
ratingLabel: {
|
ratingSource: {
|
||||||
fontSize: 11,
|
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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -12,7 +12,7 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import { useRoute, useNavigation } from '@react-navigation/native';
|
import { useRoute, useNavigation } from '@react-navigation/native';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
import { colors } from '../styles/colors';
|
import { useTheme } from '../contexts/ThemeContext';
|
||||||
import { useMetadata } from '../hooks/useMetadata';
|
import { useMetadata } from '../hooks/useMetadata';
|
||||||
import { CastSection } from '../components/metadata/CastSection';
|
import { CastSection } from '../components/metadata/CastSection';
|
||||||
import { SeriesContent } from '../components/metadata/SeriesContent';
|
import { SeriesContent } from '../components/metadata/SeriesContent';
|
||||||
|
|
@ -48,6 +48,9 @@ const MetadataScreen = () => {
|
||||||
// Add settings hook
|
// Add settings hook
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
|
|
||||||
|
// Get theme context
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
|
|
||||||
// Get safe area insets
|
// Get safe area insets
|
||||||
const { top: safeAreaTop } = useSafeAreaInsets();
|
const { top: safeAreaTop } = useSafeAreaInsets();
|
||||||
|
|
||||||
|
|
@ -182,7 +185,9 @@ const MetadataScreen = () => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={[styles.container, { backgroundColor: colors.darkBackground }]}
|
style={[styles.container, {
|
||||||
|
backgroundColor: currentTheme.colors.darkBackground
|
||||||
|
}]}
|
||||||
edges={['bottom']}
|
edges={['bottom']}
|
||||||
>
|
>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
|
|
@ -191,8 +196,10 @@ const MetadataScreen = () => {
|
||||||
barStyle="light-content"
|
barStyle="light-content"
|
||||||
/>
|
/>
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
<ActivityIndicator size="large" color={colors.primary} />
|
<ActivityIndicator size="large" color={currentTheme.colors.primary} />
|
||||||
<Text style={[styles.loadingText, { color: colors.lightGray }]}>
|
<Text style={[styles.loadingText, {
|
||||||
|
color: currentTheme.colors.mediumEmphasis
|
||||||
|
}]}>
|
||||||
Loading content...
|
Loading content...
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -203,7 +210,9 @@ const MetadataScreen = () => {
|
||||||
if (metadataError || !metadata) {
|
if (metadataError || !metadata) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={[styles.container, { backgroundColor: colors.darkBackground }]}
|
style={[styles.container, {
|
||||||
|
backgroundColor: currentTheme.colors.darkBackground
|
||||||
|
}]}
|
||||||
edges={['bottom']}
|
edges={['bottom']}
|
||||||
>
|
>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
|
|
@ -215,22 +224,24 @@ const MetadataScreen = () => {
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name="error-outline"
|
name="error-outline"
|
||||||
size={64}
|
size={64}
|
||||||
color={colors.textMuted}
|
color={currentTheme.colors.textMuted}
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.errorText, { color: colors.text }]}>
|
<Text style={[styles.errorText, {
|
||||||
|
color: currentTheme.colors.highEmphasis
|
||||||
|
}]}>
|
||||||
{metadataError || 'Content not found'}
|
{metadataError || 'Content not found'}
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.retryButton,
|
styles.retryButton,
|
||||||
{ backgroundColor: colors.primary }
|
{ backgroundColor: currentTheme.colors.primary }
|
||||||
]}
|
]}
|
||||||
onPress={loadMetadata}
|
onPress={loadMetadata}
|
||||||
>
|
>
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name="refresh"
|
name="refresh"
|
||||||
size={20}
|
size={20}
|
||||||
color={colors.white}
|
color={currentTheme.colors.white}
|
||||||
style={{ marginRight: 8 }}
|
style={{ marginRight: 8 }}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.retryButtonText}>Try Again</Text>
|
<Text style={styles.retryButtonText}>Try Again</Text>
|
||||||
|
|
@ -238,11 +249,11 @@ const MetadataScreen = () => {
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.backButton,
|
styles.backButton,
|
||||||
{ borderColor: colors.primary }
|
{ borderColor: currentTheme.colors.primary }
|
||||||
]}
|
]}
|
||||||
onPress={handleBack}
|
onPress={handleBack}
|
||||||
>
|
>
|
||||||
<Text style={[styles.backButtonText, { color: colors.primary }]}>
|
<Text style={[styles.backButtonText, { color: currentTheme.colors.primary }]}>
|
||||||
Go Back
|
Go Back
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
@ -253,7 +264,9 @@ const MetadataScreen = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={[styles.container, { backgroundColor: colors.darkBackground }]}
|
style={[containerAnimatedStyle, styles.container, {
|
||||||
|
backgroundColor: currentTheme.colors.darkBackground
|
||||||
|
}]}
|
||||||
edges={['bottom']}
|
edges={['bottom']}
|
||||||
>
|
>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
|
|
@ -386,7 +399,6 @@ const styles = StyleSheet.create({
|
||||||
loadingText: {
|
loadingText: {
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
textAlign: 'center',
|
|
||||||
},
|
},
|
||||||
errorContainer: {
|
errorContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -395,11 +407,10 @@ const styles = StyleSheet.create({
|
||||||
padding: 32,
|
padding: 32,
|
||||||
},
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
fontSize: 16,
|
fontSize: 18,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
marginBottom: 24,
|
marginBottom: 24,
|
||||||
lineHeight: 24,
|
|
||||||
},
|
},
|
||||||
retryButton: {
|
retryButton: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
@ -411,7 +422,6 @@ const styles = StyleSheet.create({
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
},
|
},
|
||||||
retryButtonText: {
|
retryButtonText: {
|
||||||
color: colors.white,
|
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue