diff --git a/src/components/metadata/CastDetailsModal.tsx b/src/components/metadata/CastDetailsModal.tsx
new file mode 100644
index 00000000..074ff15b
--- /dev/null
+++ b/src/components/metadata/CastDetailsModal.tsx
@@ -0,0 +1,548 @@
+import React, { useState, useEffect } from 'react';
+import {
+ View,
+ Text,
+ TouchableOpacity,
+ ScrollView,
+ ActivityIndicator,
+ Dimensions,
+ Platform,
+} from 'react-native';
+import { MaterialIcons } from '@expo/vector-icons';
+import { BlurView } from 'expo-blur';
+import { Image } from 'expo-image';
+import Animated, {
+ FadeIn,
+ FadeOut,
+ useAnimatedStyle,
+ useSharedValue,
+ withTiming,
+ runOnJS,
+} from 'react-native-reanimated';
+import { LinearGradient } from 'expo-linear-gradient';
+import { useTheme } from '../../contexts/ThemeContext';
+import { Cast } from '../../types/cast';
+import { tmdbService } from '../../services/tmdbService';
+
+interface CastDetailsModalProps {
+ visible: boolean;
+ onClose: () => void;
+ castMember: Cast | null;
+}
+
+const { width, height } = Dimensions.get('window');
+const MODAL_WIDTH = Math.min(width - 32, 520);
+const MODAL_MAX_HEIGHT = height * 0.85;
+
+interface PersonDetails {
+ id: number;
+ name: string;
+ biography: string;
+ birthday: string | null;
+ place_of_birth: string | null;
+ known_for_department: string;
+ profile_path: string | null;
+ also_known_as: string[];
+}
+
+const InfoBadge = ({
+ label,
+ value,
+ icon,
+ color = '#6B7280',
+ bgColor = 'rgba(107, 114, 128, 0.15)'
+}: {
+ label: string;
+ value: string;
+ icon?: string;
+ color?: string;
+ bgColor?: string;
+}) => (
+
+ {icon && (
+
+ )}
+
+
+ {label}
+
+
+ {value}
+
+
+
+);
+
+export const CastDetailsModal: React.FC = ({
+ visible,
+ onClose,
+ castMember,
+}) => {
+ const { currentTheme } = useTheme();
+ const [personDetails, setPersonDetails] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const modalOpacity = useSharedValue(0);
+
+ useEffect(() => {
+ if (visible && castMember) {
+ modalOpacity.value = withTiming(1, { duration: 200 });
+ fetchPersonDetails();
+ } else {
+ modalOpacity.value = withTiming(0, { duration: 150 });
+ }
+ }, [visible, castMember]);
+
+ const fetchPersonDetails = async () => {
+ if (!castMember) return;
+
+ setLoading(true);
+ try {
+ const details = await tmdbService.getPersonDetails(castMember.id);
+ setPersonDetails(details);
+ } catch (error) {
+ console.error('Error fetching person details:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const modalStyle = useAnimatedStyle(() => ({
+ opacity: modalOpacity.value,
+ }));
+
+ const handleClose = () => {
+ modalOpacity.value = withTiming(0, { duration: 150 }, () => {
+ runOnJS(onClose)();
+ });
+ };
+
+ const formatDate = (dateString: string | null) => {
+ if (!dateString) return null;
+ const date = new Date(dateString);
+ return date.toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+ };
+
+ const calculateAge = (birthday: string | null) => {
+ if (!birthday) return null;
+ const today = new Date();
+ const birthDate = new Date(birthday);
+ let age = today.getFullYear() - birthDate.getFullYear();
+ const monthDiff = today.getMonth() - birthDate.getMonth();
+
+ if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
+ age--;
+ }
+
+ return age;
+ };
+
+ if (!visible || !castMember) return null;
+
+ return (
+
+
+
+
+
+
+
+
+ {castMember.profile_path ? (
+
+ ) : (
+
+
+ {castMember.name.split(' ').reduce((prev: string, current: string) => prev + current[0], '').substring(0, 2)}
+
+
+ )}
+
+
+
+
+ {castMember.name}
+
+ {castMember.character && (
+
+ as {castMember.character}
+
+ )}
+
+
+
+
+
+
+
+
+
+ {loading ? (
+
+
+
+ Loading details...
+
+
+ ) : (
+
+ {/* Personal Information */}
+
+
+ Personal Information
+
+
+
+ {personDetails?.known_for_department && (
+
+ )}
+
+ {personDetails?.birthday && (
+
+ )}
+
+ {personDetails?.place_of_birth && (
+
+ )}
+
+
+ {personDetails?.birthday && (
+
+
+
+
+ Born
+
+
+
+ {formatDate(personDetails.birthday)}
+
+
+ )}
+
+
+ {/* Biography */}
+ {personDetails?.biography && (
+
+
+ Biography
+
+
+
+
+ {personDetails.biography}
+
+
+
+ )}
+
+ {/* Also Known As */}
+ {personDetails?.also_known_as && personDetails.also_known_as.length > 0 && (
+
+
+ Also Known As
+
+
+
+ {personDetails.also_known_as.slice(0, 6).map((alias, index) => (
+
+
+ {alias}
+
+
+ ))}
+
+
+ )}
+
+ {/* No details available */}
+ {!loading && !personDetails?.biography && !personDetails?.birthday && !personDetails?.place_of_birth && (
+
+
+
+ No additional details available
+
+
+ )}
+
+ )}
+
+
+
+
+ );
+};
+
+export default CastDetailsModal;
\ No newline at end of file
diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx
index a2e00cb0..e1093708 100644
--- a/src/components/metadata/HeroSection.tsx
+++ b/src/components/metadata/HeroSection.tsx
@@ -210,13 +210,6 @@ const ActionButtons = React.memo(({
color={isWatched ? "#fff" : "#000"}
/>
{finalPlayButtonText}
-
- {/* Subtle watched indicator in play button */}
- {isWatched && (
-
-
-
- )}
{
// Optimized state management - reduced state variables
const [isContentReady, setIsContentReady] = useState(false);
+ const [showCastModal, setShowCastModal] = useState(false);
+ const [selectedCastMember, setSelectedCastMember] = useState(null);
const transitionOpacity = useSharedValue(1);
const {
@@ -310,7 +313,10 @@ const MetadataScreen: React.FC = () => {
}, [navigation, id, type]);
const handleBack = useCallback(() => navigation.goBack(), [navigation]);
- const handleSelectCastMember = useCallback(() => {}, []); // Simplified for performance
+ const handleSelectCastMember = useCallback((castMember: any) => {
+ setSelectedCastMember(castMember);
+ setShowCastModal(true);
+ }, []);
// Ultra-optimized animated styles - minimal calculations
const containerStyle = useAnimatedStyle(() => ({
@@ -472,6 +478,13 @@ const MetadataScreen: React.FC = () => {
>
)}
+
+ {/* Cast Details Modal */}
+ setShowCastModal(false)}
+ castMember={selectedCastMember}
+ />
);
};