diff --git a/src/components/metadata/CastDetailsModal.tsx b/src/components/metadata/CastDetailsModal.tsx index 074ff15b..a1c01040 100644 --- a/src/components/metadata/CastDetailsModal.tsx +++ b/src/components/metadata/CastDetailsModal.tsx @@ -17,6 +17,7 @@ import Animated, { useAnimatedStyle, useSharedValue, withTiming, + withSpring, runOnJS, } from 'react-native-reanimated'; import { LinearGradient } from 'expo-linear-gradient'; @@ -31,8 +32,8 @@ interface CastDetailsModalProps { } const { width, height } = Dimensions.get('window'); -const MODAL_WIDTH = Math.min(width - 32, 520); -const MODAL_MAX_HEIGHT = height * 0.85; +const MODAL_WIDTH = Math.min(width - 40, 400); +const MODAL_HEIGHT = height * 0.7; interface PersonDetails { id: number; @@ -45,57 +46,6 @@ interface PersonDetails { 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, @@ -104,24 +54,37 @@ export const CastDetailsModal: React.FC = ({ const { currentTheme } = useTheme(); const [personDetails, setPersonDetails] = useState(null); const [loading, setLoading] = useState(false); + const [hasFetched, setHasFetched] = useState(false); const modalOpacity = useSharedValue(0); + const modalScale = useSharedValue(0.9); useEffect(() => { if (visible && castMember) { - modalOpacity.value = withTiming(1, { duration: 200 }); - fetchPersonDetails(); + modalOpacity.value = withTiming(1, { duration: 250 }); + modalScale.value = withSpring(1, { damping: 20, stiffness: 200 }); + + if (!hasFetched || personDetails?.id !== castMember.id) { + fetchPersonDetails(); + } } else { - modalOpacity.value = withTiming(0, { duration: 150 }); + modalOpacity.value = withTiming(0, { duration: 200 }); + modalScale.value = withTiming(0.9, { duration: 200 }); + + if (!visible) { + setHasFetched(false); + setPersonDetails(null); + } } }, [visible, castMember]); const fetchPersonDetails = async () => { - if (!castMember) return; + if (!castMember || loading) return; setLoading(true); try { const details = await tmdbService.getPersonDetails(castMember.id); setPersonDetails(details); + setHasFetched(true); } catch (error) { console.error('Error fetching person details:', error); } finally { @@ -131,10 +94,12 @@ export const CastDetailsModal: React.FC = ({ const modalStyle = useAnimatedStyle(() => ({ opacity: modalOpacity.value, + transform: [{ scale: modalScale.value }], })); const handleClose = () => { - modalOpacity.value = withTiming(0, { duration: 150 }, () => { + modalOpacity.value = withTiming(0, { duration: 200 }); + modalScale.value = withTiming(0.9, { duration: 200 }, () => { runOnJS(onClose)(); }); }; @@ -144,7 +109,7 @@ export const CastDetailsModal: React.FC = ({ const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', - month: 'long', + month: 'short', day: 'numeric', }); }; @@ -167,19 +132,19 @@ export const CastDetailsModal: React.FC = ({ return ( = ({ style={[ { width: MODAL_WIDTH, - maxHeight: MODAL_MAX_HEIGHT, - minHeight: height * 0.4, + height: MODAL_HEIGHT, overflow: 'hidden', - elevation: 25, - shadowColor: '#000', - shadowOffset: { width: 0, height: 12 }, - shadowOpacity: 0.4, - shadowRadius: 25, - alignSelf: 'center', + borderRadius: 24, + backgroundColor: Platform.OS === 'android' + ? 'rgba(20, 20, 20, 0.95)' + : 'transparent', }, modalStyle, ]} > - - - - - {castMember.profile_path ? ( - - ) : ( - - - {castMember.name.split(' ').reduce((prev: string, current: string) => prev + current[0], '').substring(0, 2)} - - - )} - - - - - {castMember.name} - - {castMember.character && ( + {renderContent()} + + ) : ( + renderContent() + )} + + + ); + + function renderContent() { + return ( + <> + {/* Header */} + + + + {castMember.profile_path ? ( + + ) : ( + - as {castMember.character} + color: '#fff', + fontSize: 18, + fontWeight: '700', + }}> + {castMember.name.split(' ').reduce((prev: string, current: string) => prev + current[0], '').substring(0, 2)} - )} - + + )} + + + + + {castMember.name} + + {castMember.character && ( + + as {castMember.character} + + )} - + + - - {loading ? ( - + {loading ? ( + + + - - + + ) : ( + + {/* Quick Info */} + {(personDetails?.known_for_department || personDetails?.birthday || personDetails?.place_of_birth) && ( + - Loading details... - - - ) : ( - - {/* Personal Information */} - - - Personal Information - + {personDetails?.known_for_department && ( + + + + Department + + + {personDetails.known_for_department} + + + )} - - {personDetails?.known_for_department && ( - - )} - - {personDetails?.birthday && ( - - )} - - {personDetails?.place_of_birth && ( - - )} - + {personDetails?.birthday && ( + + + + Age + + + {calculateAge(personDetails.birthday)} years old + + + )} + + {personDetails?.place_of_birth && ( + + + + Born in + + + {personDetails.place_of_birth} + + + )} {personDetails?.birthday && ( - - - - Born - - - {formatDate(personDetails.birthday)} + Born on {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 - - - )} - - )} - - - - - ); + Biography + + + {personDetails.biography} + + + )} + + {/* Also Known As - Compact */} + {personDetails?.also_known_as && personDetails.also_known_as.length > 0 && ( + + + Also Known As + + + {personDetails.also_known_as.slice(0, 4).join(' • ')} + + + )} + + {/* 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