NuvioStreaming/src/components/metadata/MetadataDetails.tsx
2025-09-15 20:38:37 +05:30

298 lines
No EOL
8.9 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ActivityIndicator,
} from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import { Image } from 'expo-image';
import Animated, {
FadeIn,
useAnimatedStyle,
useSharedValue,
withTiming,
runOnJS,
interpolate,
Extrapolate,
} from 'react-native-reanimated';
import { useTheme } from '../../contexts/ThemeContext';
import { isMDBListEnabled } from '../../screens/MDBListSettingsScreen';
// MetadataSourceSelector removed
interface MetadataDetailsProps {
metadata: any;
imdbId: string | null;
type: 'movie' | 'series';
renderRatings?: () => React.ReactNode;
contentId: string;
// Source switching removed
loadingMetadata?: boolean;
}
const MetadataDetails: React.FC<MetadataDetailsProps> = ({
metadata,
imdbId: _imdbId,
type,
renderRatings,
contentId,
loadingMetadata = false,
}) => {
const { currentTheme } = useTheme();
const [isFullDescriptionOpen, setIsFullDescriptionOpen] = useState(false);
const [isMDBEnabled, setIsMDBEnabled] = useState(false);
const [isTextTruncated, setIsTextTruncated] = useState(false);
// Animation values for smooth height transition
const animatedHeight = useSharedValue(0);
const [measuredHeights, setMeasuredHeights] = useState({ collapsed: 0, expanded: 0 });
useEffect(() => {
const checkMDBListEnabled = async () => {
try {
const enabled = await isMDBListEnabled();
setIsMDBEnabled(enabled);
} catch (error) {
setIsMDBEnabled(false); // Default to disabled if there's an error
}
};
checkMDBListEnabled();
}, []);
const handleTextLayout = (event: any) => {
const { lines } = event.nativeEvent;
// If we have 3 or more lines, it means the text was truncated
setIsTextTruncated(lines.length >= 3);
};
const handleCollapsedTextLayout = (event: any) => {
const { height } = event.nativeEvent.layout;
setMeasuredHeights(prev => ({ ...prev, collapsed: height }));
};
const handleExpandedTextLayout = (event: any) => {
const { height } = event.nativeEvent.layout;
setMeasuredHeights(prev => ({ ...prev, expanded: height }));
};
// Animate height changes
const toggleDescription = () => {
const targetHeight = isFullDescriptionOpen ? measuredHeights.collapsed : measuredHeights.expanded;
animatedHeight.value = withTiming(targetHeight, { duration: 300 });
setIsFullDescriptionOpen(!isFullDescriptionOpen);
};
// Initialize height when component mounts or text changes
useEffect(() => {
if (measuredHeights.collapsed > 0) {
animatedHeight.value = measuredHeights.collapsed;
}
}, [measuredHeights.collapsed]);
// Animated style for smooth height transition
const animatedDescriptionStyle = useAnimatedStyle(() => ({
height: animatedHeight.value,
overflow: 'hidden',
}));
return (
<>
{/* Metadata Source Selector removed */}
{/* Loading indicator when switching sources */}
{loadingMetadata && (
<View style={styles.loadingContainer}>
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
<Text style={[styles.loadingText, { color: currentTheme.colors.textMuted }]}>
Loading metadata...
</Text>
</View>
)}
{/* Meta Info */}
<View style={[styles.metaInfo, loadingMetadata && styles.dimmed]}>
{metadata.year && (
<Text style={[styles.metaText, { color: currentTheme.colors.text }]}>{metadata.year}</Text>
)}
{metadata.runtime && (
<Text style={[styles.metaText, { color: currentTheme.colors.text }]}>{metadata.runtime}</Text>
)}
{metadata.certification && (
<Text style={[styles.metaText, { color: currentTheme.colors.text }]}>{metadata.certification}</Text>
)}
{metadata.imdbRating && !isMDBEnabled && (
<View style={styles.ratingContainer}>
<Image
source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/69/IMDB_Logo_2016.svg/575px-IMDB_Logo_2016.svg.png' }}
style={styles.imdbLogo}
contentFit="contain"
/>
<Text style={[styles.ratingText, { color: currentTheme.colors.text }]}>{metadata.imdbRating}</Text>
</View>
)}
</View>
{/* Ratings Section */}
{renderRatings && renderRatings()}
{/* Creator/Director Info */}
<Animated.View
entering={FadeIn.duration(300).delay(100)}
style={[styles.creatorContainer, loadingMetadata && styles.dimmed]}
>
{metadata.directors && metadata.directors.length > 0 && (
<View style={styles.creatorSection}>
<Text style={[styles.creatorLabel, { color: currentTheme.colors.white }]}>Director{metadata.directors.length > 1 ? 's' : ''}:</Text>
<Text style={[styles.creatorText, { color: currentTheme.colors.mediumEmphasis }]}>{metadata.directors.join(', ')}</Text>
</View>
)}
{metadata.creators && metadata.creators.length > 0 && (
<View style={styles.creatorSection}>
<Text style={[styles.creatorLabel, { color: currentTheme.colors.white }]}>Creator{metadata.creators.length > 1 ? 's' : ''}:</Text>
<Text style={[styles.creatorText, { color: currentTheme.colors.mediumEmphasis }]}>{metadata.creators.join(', ')}</Text>
</View>
)}
</Animated.View>
{/* Description */}
{metadata.description && (
<Animated.View
style={[styles.descriptionContainer, loadingMetadata && styles.dimmed]}
entering={FadeIn.duration(300)}
>
{/* Hidden text elements to measure heights */}
<Text
style={[styles.description, { color: currentTheme.colors.mediumEmphasis, position: 'absolute', opacity: 0 }]}
numberOfLines={3}
onLayout={handleCollapsedTextLayout}
>
{metadata.description}
</Text>
<Text
style={[styles.description, { color: currentTheme.colors.mediumEmphasis, position: 'absolute', opacity: 0 }]}
onLayout={handleExpandedTextLayout}
>
{metadata.description}
</Text>
<TouchableOpacity
onPress={toggleDescription}
activeOpacity={0.7}
disabled={!isTextTruncated && !isFullDescriptionOpen}
>
<Animated.View style={animatedDescriptionStyle}>
<Text
style={[styles.description, { color: currentTheme.colors.mediumEmphasis }]}
numberOfLines={isFullDescriptionOpen ? undefined : 3}
onTextLayout={handleTextLayout}
>
{metadata.description}
</Text>
</Animated.View>
{(isTextTruncated || isFullDescriptionOpen) && (
<View style={styles.showMoreButton}>
<Text style={[styles.showMoreText, { color: currentTheme.colors.textMuted }]}>
{isFullDescriptionOpen ? 'Show Less' : 'Show More'}
</Text>
<MaterialIcons
name={isFullDescriptionOpen ? "keyboard-arrow-up" : "keyboard-arrow-down"}
size={18}
color={currentTheme.colors.textMuted}
/>
</View>
)}
</TouchableOpacity>
</Animated.View>
)}
</>
);
};
const styles = StyleSheet.create({
// Removed source selector styles
loadingContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 12,
marginBottom: 8,
},
loadingText: {
marginLeft: 8,
fontSize: 14,
},
dimmed: {
opacity: 0.6,
},
metaInfo: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
paddingHorizontal: 16,
marginBottom: 12,
},
metaText: {
fontSize: 15,
fontWeight: '700',
letterSpacing: 0.3,
textTransform: 'uppercase',
opacity: 0.9,
},
ratingContainer: {
flexDirection: 'row',
alignItems: 'center',
},
imdbLogo: {
width: 35,
height: 18,
marginRight: 4,
},
ratingText: {
fontWeight: '700',
fontSize: 15,
letterSpacing: 0.3,
},
creatorContainer: {
marginBottom: 2,
paddingHorizontal: 16,
},
creatorSection: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 4,
height: 20
},
creatorLabel: {
fontSize: 14,
fontWeight: '600',
marginRight: 8,
lineHeight: 20
},
creatorText: {
fontSize: 14,
flex: 1,
lineHeight: 20
},
descriptionContainer: {
marginBottom: 16,
paddingHorizontal: 16,
},
description: {
fontSize: 15,
lineHeight: 24,
},
showMoreButton: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 8,
paddingVertical: 4,
},
showMoreText: {
fontSize: 14,
marginRight: 4,
},
});
export default React.memo(MetadataDetails);