This commit is contained in:
tapframe 2025-08-03 01:07:30 +05:30
parent 8da78d1b0d
commit 11030c5601
3 changed files with 231 additions and 19 deletions

View file

@ -11,6 +11,7 @@ import { RootStackParamList } from '../../navigation/AppNavigator';
interface CatalogSectionProps {
catalog: CatalogContent;
onPosterPress?: (content: StreamingContent) => void;
}
const { width } = Dimensions.get('window');
@ -53,12 +54,17 @@ const calculatePosterLayout = (screenWidth: number) => {
const posterLayout = calculatePosterLayout(width);
const POSTER_WIDTH = posterLayout.posterWidth;
const CatalogSection = ({ catalog }: CatalogSectionProps) => {
const CatalogSection = ({ catalog, onPosterPress }: CatalogSectionProps) => {
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const { currentTheme } = useTheme();
const handleContentPress = (id: string, type: string) => {
navigation.navigate('Metadata', { id, type, addonId: catalog.addon });
const content = catalog.items.find(item => item.id === id && item.type === type);
if (content && onPosterPress) {
onPosterPress(content);
} else {
navigation.navigate('Metadata', { id, type, addonId: catalog.addon });
}
};
const renderContentItem = ({ item, index }: { item: StreamingContent, index: number }) => {

View file

@ -22,6 +22,8 @@ import { imageCacheService } from '../../services/imageCacheService';
interface FeaturedContentProps {
featuredContent: StreamingContent | null;
isSaved?: boolean;
handleSaveToLibrary?: () => Promise<void>;
}
// Cache to store preloaded images
@ -218,6 +220,15 @@ const FeaturedContent = ({ featuredContent }: FeaturedContentProps) => {
)}
</View>
{/* Description Section */}
{featuredContent.description && (
<View style={styles.descriptionContainer}>
<Text style={[styles.descriptionText, Platform.isTV && styles.descriptionTextTV]} numberOfLines={Platform.isTV ? 3 : 2}>
{featuredContent.description}
</Text>
</View>
)}
{/* Enhanced Metadata Section */}
<View style={styles.metadataContainer}>
{/* Genres */}
@ -241,6 +252,30 @@ const FeaturedContent = ({ featuredContent }: FeaturedContentProps) => {
)}
</View>
{/* Action Buttons */}
<View style={styles.actionButtonsContainer}>
<TouchableOpacity
style={styles.playButton}
onPress={() => navigation.navigate('Metadata', {
id: featuredContent.id,
type: featuredContent.type
})}
hasTVPreferredFocus={Platform.isTV}
tvParallaxProperties={Platform.isTV ? {
enabled: true,
shiftDistanceX: 4.0,
shiftDistanceY: 4.0,
tiltAngle: 0.05,
magnification: 1.1,
} : undefined}
>
<MaterialIcons name="play-arrow" size={Platform.isTV ? 28 : 24} color="#000" />
<Text style={styles.playButtonText}>Play</Text>
</TouchableOpacity>
</View>
</View>
</TVFocusGuideView>
@ -252,9 +287,9 @@ const FeaturedContent = ({ featuredContent }: FeaturedContentProps) => {
const styles = StyleSheet.create({
featuredContainer: {
width: '100%',
height: Platform.isTV ? height * 0.75 : height * 0.55,
height: Platform.isTV ? height * 0.5 : height * 0.4,
marginTop: 0,
marginBottom: Platform.isTV ? 24 : 12,
marginBottom: Platform.isTV ? 16 : 8,
position: 'relative',
borderRadius: Platform.isTV ? 0 : 12,
overflow: 'hidden',
@ -324,23 +359,25 @@ const styles = StyleSheet.create({
marginBottom: Platform.isTV ? 24 : 16,
paddingHorizontal: 0,
position: 'relative',
height: Platform.isTV ? 160 : 160,
height: Platform.isTV ? 120 : 100,
width: '100%',
marginLeft: Platform.isTV ? -200 : 0,
marginLeft: 0,
justifyContent: 'flex-start',
},
featuredLogo: {
width: width * 0.9,
height: 160,
width: Platform.isTV ? 300 : 250,
height: Platform.isTV ? 120 : 100,
marginBottom: 0,
alignSelf: 'flex-start',
position: Platform.isTV ? 'absolute' : 'relative',
left: Platform.isTV ? 0 : 'auto',
position: 'relative',
left: 0,
resizeMode: 'contain',
},
featuredLogoTV: {
width: width * 0.8,
height: 200,
maxWidth: 900,
position: 'absolute',
width: 300,
height: 120,
maxWidth: 300,
position: 'relative',
left: 0,
},
featuredTitleText: {
@ -389,6 +426,23 @@ const styles = StyleSheet.create({
yearContainer: {
marginTop: 8,
},
descriptionContainer: {
marginBottom: Platform.isTV ? 24 : 16,
maxWidth: Platform.isTV ? width * 0.5 : width * 0.8,
},
descriptionText: {
fontSize: 14,
color: '#FFFFFF',
opacity: 0.9,
lineHeight: 20,
textShadowColor: 'rgba(0,0,0,0.6)',
textShadowOffset: { width: 0, height: 1 },
textShadowRadius: 3,
},
descriptionTextTV: {
fontSize: 18,
lineHeight: 26,
},
yearText: {
fontSize: 16,
fontWeight: '500',
@ -417,6 +471,34 @@ const styles = StyleSheet.create({
fontSize: Platform.isTV ? 18 : 16,
fontWeight: '600',
},
actionButtonsContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: Platform.isTV ? 20 : 16,
marginTop: Platform.isTV ? 24 : 16,
},
playButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#FFFFFF',
paddingHorizontal: Platform.isTV ? 48 : 32,
paddingVertical: Platform.isTV ? 16 : 12,
borderRadius: Platform.isTV ? 12 : 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 4,
minWidth: Platform.isTV ? 200 : 140,
},
playButtonText: {
color: '#000',
fontSize: Platform.isTV ? 18 : 16,
fontWeight: '700',
marginLeft: 8,
},
});
export default React.memo(FeaturedContent);

View file

@ -106,6 +106,117 @@ const SkeletonCatalog = React.memo(() => {
});
const HomeScreen = () => {
const styles = StyleSheet.create<any>({
stickyHeroContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 10,
},
container: {
flex: 1,
},
scrollContent: {
paddingBottom: 90,
},
loadingMainContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingBottom: 90,
},
loadingText: {
marginTop: 12,
fontSize: 14,
},
loadingMoreCatalogs: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 16,
marginHorizontal: 16,
marginBottom: 16,
backgroundColor: 'rgba(0,0,0,0.2)',
borderRadius: 8,
},
loadingMoreText: {
marginLeft: 12,
fontSize: 14,
},
catalogPlaceholder: {
marginBottom: 24,
paddingHorizontal: 16,
},
placeholderHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
placeholderTitle: {
width: 150,
height: 20,
borderRadius: 4,
},
placeholderPosters: {
flexDirection: 'row',
paddingVertical: 8,
gap: 8,
},
placeholderPoster: {
width: POSTER_WIDTH,
aspectRatio: 2/3,
borderRadius: 4,
marginRight: 2,
},
emptyCatalog: {
padding: 32,
alignItems: 'center',
margin: 16,
borderRadius: 16,
},
addCatalogButton: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 30,
marginTop: 16,
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 3,
},
addCatalogButtonText: {
fontSize: 14,
fontWeight: '600',
marginLeft: 8,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
featuredContainer: {
width: '100%',
height: height * 0.6,
marginTop: Platform.OS === 'ios' ? 0 : 0,
marginBottom: 8,
position: 'relative',
},
featuredBanner: {
width: '100%',
height: '100%',
},
featuredGradient: {
width: '100%',
height: '100%',
justifyContent: 'space-between',
}
});
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const isDarkMode = useColorScheme() === 'dark';
const { currentTheme } = useTheme();
@ -513,7 +624,7 @@ const HomeScreen = () => {
case 'catalog':
return (
<Animated.View entering={FadeIn.duration(300)}>
<CatalogSection catalog={item.catalog} />
<CatalogSection catalog={item.catalog} onPosterPress={handlePosterPress} />
</Animated.View>
);
case 'placeholder':
@ -581,6 +692,12 @@ const HomeScreen = () => {
), [catalogsLoading, catalogs, loadedCatalogCount, totalCatalogsRef.current, navigation, currentTheme.colors]);
// Memoize the main content section
const [selectedContent, setSelectedContent] = useState<StreamingContent | null>(null);
const handlePosterPress = useCallback((content: StreamingContent) => {
setSelectedContent(content);
}, []);
const renderMainContent = useMemo(() => {
if (isLoading) return null;
@ -597,7 +714,7 @@ const HomeScreen = () => {
keyExtractor={item => item.key}
contentContainerStyle={[
styles.scrollContent,
{ paddingTop: Platform.OS === 'ios' ? 100 : 90 }
{ paddingTop: 0 }
]}
showsVerticalScrollIndicator={false}
ListFooterComponent={ListFooterComponent}
@ -612,8 +729,8 @@ const HomeScreen = () => {
autoscrollToTopThreshold: 10
}}
getItemLayout={(data, index) => ({
length: index === 0 ? 400 : 280, // Approximate heights for different item types
offset: index === 0 ? 0 : 400 + (index - 1) * 280,
length: 280,
offset: index * 280,
index,
})}
/>
@ -670,7 +787,14 @@ const calculatePosterLayout = (screenWidth: number) => {
const posterLayout = calculatePosterLayout(width);
const POSTER_WIDTH = posterLayout.posterWidth;
const styles = StyleSheet.create<any>({
const styles = StyleSheet.create<any>({
stickyHeroContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 10,
},
container: {
flex: 1,
},