diff --git a/src/components/home/CatalogSection.tsx b/src/components/home/CatalogSection.tsx index 424ba73..fa14ecd 100644 --- a/src/components/home/CatalogSection.tsx +++ b/src/components/home/CatalogSection.tsx @@ -93,19 +93,6 @@ const CatalogSection = ({ catalog, onPosterPress, onPosterFocus }: CatalogSectio {catalog.name} - - navigation.navigate('Catalog', { - id: catalog.id, - type: catalog.type, - addonId: catalog.addon - }) - } - style={styles.viewAllButton} - > - View All - - + {playButtonText} {Platform.OS === 'ios' ? ( @@ -104,7 +128,7 @@ const ActionButtons = React.memo(({ )} @@ -238,18 +262,22 @@ const HeroSection: React.FC = ({ }; return ( - + {/* Background Image */} - {bannerImage && !imageLoadError && ( - - )} + {(() => { + const fallback = (metadata && (metadata.banner || metadata.poster)) || null; + const uriToUse = !imageLoadError && bannerImage ? bannerImage : fallback; + return uriToUse ? ( + + ) : null; + })()} {/* Gradient Overlay */} = ({ imdbId, type }) ) : ( {React.createElement(config.icon as any, { - width: 16, - height: 16, + width: Platform.isTV ? 22 : 16, + height: Platform.isTV ? 22 : 16, })} )} @@ -209,8 +209,8 @@ export const RatingsSection: React.FC = ({ imdbId, type }) const styles = StyleSheet.create({ container: { marginTop: 2, - marginBottom: 8, - paddingHorizontal: 16, + marginBottom: 10, + paddingHorizontal: Platform.isTV ? 24 : 16, }, loadingContainer: { height: 40, @@ -225,18 +225,18 @@ const styles = StyleSheet.create({ compactRatingItem: { flexDirection: 'row', alignItems: 'center', - marginRight: 12, + marginRight: Platform.isTV ? 16 : 12, }, compactRatingIcon: { - width: 16, - height: 16, - marginRight: 4, + width: Platform.isTV ? 22 : 16, + height: Platform.isTV ? 22 : 16, + marginRight: Platform.isTV ? 6 : 4, }, compactSvgContainer: { - marginRight: 4, + marginRight: Platform.isTV ? 6 : 4, }, compactRatingValue: { - fontSize: 14, - fontWeight: '600', + fontSize: Platform.isTV ? 18 : 14, + fontWeight: '700', }, }); \ No newline at end of file diff --git a/src/components/metadata/SeriesContent.tsx b/src/components/metadata/SeriesContent.tsx index b423dd5..dc4a940 100644 --- a/src/components/metadata/SeriesContent.tsx +++ b/src/components/metadata/SeriesContent.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useRef } from 'react'; -import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions, useWindowDimensions, useColorScheme, FlatList } from 'react-native'; +import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions, useWindowDimensions, useColorScheme, FlatList, Platform } from 'react-native'; import { Image } from 'expo-image'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import { LinearGradient } from 'expo-linear-gradient'; @@ -231,7 +231,10 @@ export const SeriesContent: React.FC = ({ horizontal showsHorizontalScrollIndicator={false} style={styles.seasonSelectorContainer} - contentContainerStyle={styles.seasonSelectorContent} + contentContainerStyle={[ + styles.seasonSelectorContent, + Platform.isTV && { paddingVertical: 10, paddingHorizontal: 24 } + ]} initialNumToRender={5} maxToRenderPerBatch={5} windowSize={3} @@ -250,18 +253,31 @@ export const SeriesContent: React.FC = ({ key={season} style={[ styles.seasonButton, + Platform.isTV && { width: 140, marginRight: 24 }, selectedSeason === season && [styles.selectedSeasonButton, { borderColor: currentTheme.colors.primary }] ]} onPress={() => onSeasonChange(season)} + hasTVPreferredFocus={Platform.isTV && selectedSeason === season} + activeOpacity={0.85} + tvParallaxProperties={Platform.isTV ? { + enabled: true, + shiftDistanceX: 2.0, + shiftDistanceY: 2.0, + tiltAngle: 0.05, + magnification: 1.08, + } : undefined} > - + {selectedSeason === season && ( - + )} {/* Show episode count badge, including when there are no episodes */} @@ -274,6 +290,7 @@ export const SeriesContent: React.FC = ({ style={[ styles.seasonButtonText, { color: currentTheme.colors.mediumEmphasis }, + Platform.isTV && { fontSize: 18, marginTop: 6 }, selectedSeason === season && [styles.selectedSeasonButtonText, { color: currentTheme.colors.primary }] ]} > @@ -340,14 +357,24 @@ export const SeriesContent: React.FC = ({ style={[ styles.episodeCardVertical, isTablet && styles.episodeCardVerticalTablet, - { backgroundColor: currentTheme.colors.elevation2 } + { backgroundColor: currentTheme.colors.elevation2 }, + Platform.isTV && { height: 150 } ]} onPress={() => onSelectEpisode(episode)} activeOpacity={0.7} + hasTVPreferredFocus={Platform.isTV && episode === episodes[0]} + tvParallaxProperties={Platform.isTV ? { + enabled: true, + shiftDistanceX: 2.0, + shiftDistanceY: 2.0, + tiltAngle: 0.05, + magnification: 1.05, + } : undefined} > = ({ ]} onPress={() => onSelectEpisode(episode)} activeOpacity={0.85} + hasTVPreferredFocus={Platform.isTV && episode === episodes[0]} + tvParallaxProperties={Platform.isTV ? { + enabled: true, + shiftDistanceX: 2.0, + shiftDistanceY: 2.0, + tiltAngle: 0.05, + magnification: 1.06, + } : undefined} > {/* Gradient Border Container */} = ({ entering={FadeIn.duration(300).delay(100 + index * 30)} style={[ styles.episodeCardWrapperHorizontal, - isTablet && styles.episodeCardWrapperHorizontalTablet + isTablet && styles.episodeCardWrapperHorizontalTablet, + Platform.isTV && { width: width * 0.3, marginRight: 24 } ]} > {renderHorizontalEpisodeCard(episode)} @@ -653,9 +689,12 @@ export const SeriesContent: React.FC = ({ keyExtractor={episode => episode.id.toString()} horizontal showsHorizontalScrollIndicator={false} - contentContainerStyle={styles.episodeListContentHorizontal} + contentContainerStyle={[ + styles.episodeListContentHorizontal, + Platform.isTV && { paddingLeft: 24, paddingRight: 24 } + ]} decelerationRate="fast" - snapToInterval={isTablet ? width * 0.4 + 16 : width * 0.85 + 16} + snapToInterval={Platform.isTV ? width * 0.3 + 24 : (isTablet ? width * 0.4 + 16 : width * 0.85 + 16)} snapToAlignment="start" initialNumToRender={3} maxToRenderPerBatch={3} @@ -721,7 +760,7 @@ const styles = StyleSheet.create({ opacity: 0.8, }, sectionTitle: { - fontSize: 20, + fontSize: 22, fontWeight: '700', marginBottom: 16, paddingHorizontal: 16, @@ -756,22 +795,22 @@ const styles = StyleSheet.create({ shadowRadius: 8, borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)', - height: 120, + height: 130, }, episodeCardVerticalTablet: { width: '47%', flexDirection: 'row', - height: 140, + height: 160, marginBottom: 0, }, episodeImageContainer: { position: 'relative', - width: 120, - height: 120, + width: 130, + height: 130, }, episodeImageContainerTablet: { - width: 140, - height: 140, + width: 160, + height: 160, }, episodeImage: { width: '100%', @@ -811,14 +850,14 @@ const styles = StyleSheet.create({ marginBottom: 6, }, episodeTitle: { - fontSize: 15, + fontSize: 16, fontWeight: '700', letterSpacing: 0.3, marginBottom: 2, }, episodeTitleTablet: { - fontSize: 16, - marginBottom: 4, + fontSize: 18, + marginBottom: 6, }, episodeMetadata: { flexDirection: 'row', @@ -843,7 +882,7 @@ const styles = StyleSheet.create({ }, ratingText: { color: '#01b4e4', - fontSize: 13, + fontSize: 14, fontWeight: '700', marginLeft: 4, }, @@ -856,22 +895,22 @@ const styles = StyleSheet.create({ borderRadius: 4, }, runtimeText: { - fontSize: 13, + fontSize: 14, fontWeight: '600', marginLeft: 4, }, airDateText: { - fontSize: 12, + fontSize: 13, opacity: 0.8, }, episodeOverview: { - fontSize: 13, - lineHeight: 18, - }, - episodeOverviewTablet: { fontSize: 14, lineHeight: 20, }, + episodeOverviewTablet: { + fontSize: 15, + lineHeight: 22, + }, progressBarContainer: { position: 'absolute', bottom: 0, @@ -919,13 +958,13 @@ const styles = StyleSheet.create({ shadowRadius: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.05)', - height: 200, + height: Platform.isTV ? 200 : 220, position: 'relative', width: '100%', backgroundColor: 'transparent', }, episodeCardHorizontalTablet: { - height: 180, + height: 250, }, episodeBackgroundImage: { width: '100%', @@ -963,7 +1002,7 @@ const styles = StyleSheet.create({ }, episodeTitleHorizontal: { color: '#fff', - fontSize: 15, + fontSize: 16, fontWeight: '700', letterSpacing: -0.3, marginBottom: 4, @@ -971,8 +1010,8 @@ const styles = StyleSheet.create({ }, episodeDescriptionHorizontal: { color: 'rgba(255,255,255,0.85)', - fontSize: 12, - lineHeight: 16, + fontSize: Platform.isTV ? 14 : 13, + lineHeight: 18, marginBottom: 8, opacity: 0.9, }, @@ -991,7 +1030,7 @@ const styles = StyleSheet.create({ }, runtimeTextHorizontal: { color: 'rgba(255,255,255,0.8)', - fontSize: 11, + fontSize: 12, fontWeight: '500', }, ratingContainerHorizontal: { @@ -1005,7 +1044,7 @@ const styles = StyleSheet.create({ }, ratingTextHorizontal: { color: '#FFD700', - fontSize: 11, + fontSize: 12, fontWeight: '600', }, progressBarContainerHorizontal: { diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index 1b7042f..9f726f2 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -363,17 +363,6 @@ const MetadataScreen: React.FC = () => { {metadata && ( <> - {/* Floating Header - Optimized */} - - @@ -1865,9 +1866,9 @@ export const StreamsScreen = () => { removeClippedSubviews={false} getItemLayout={undefined} contentContainerStyle={{ - paddingHorizontal: Platform.isTV ? 0 : 16, - paddingVertical: Platform.isTV ? 0 : 16, - paddingBottom: Platform.isTV ? 120 : 16, + paddingHorizontal: Platform.isTV ? 24 : 16, + paddingVertical: 0, + paddingBottom: 0, width: '100%', }} style={{ @@ -1948,7 +1949,7 @@ const createStyles = (colors: any) => StyleSheet.create({ paddingTop: Platform.OS === 'android' ? 10 : 15, }, filterContainer: { - paddingHorizontal: Platform.isTV ? 0 : 16, + paddingHorizontal: Platform.isTV ? 24 : 16, paddingBottom: 12, }, filterScroll: { @@ -1956,11 +1957,11 @@ const createStyles = (colors: any) => StyleSheet.create({ }, filterChip: { backgroundColor: 'transparent', - paddingHorizontal: 16, - paddingVertical: 8, - borderRadius: 20, - marginRight: 8, - borderWidth: 1, + paddingHorizontal: Platform.isTV ? 22 : 16, + paddingVertical: Platform.isTV ? 12 : 8, + borderRadius: Platform.isTV ? 28 : 20, + marginRight: Platform.isTV ? 12 : 8, + borderWidth: Platform.isTV ? 2 : 1, borderColor: colors.border, }, filterChipSelected: { @@ -1969,11 +1970,12 @@ const createStyles = (colors: any) => StyleSheet.create({ }, filterChipText: { color: colors.mediumEmphasis, - fontWeight: '500', + fontWeight: '600', + fontSize: Platform.isTV ? 18 : undefined, }, filterChipTextSelected: { color: colors.white, - fontWeight: '600', + fontWeight: '700', }, streamsContent: { flex: 1, @@ -2061,17 +2063,17 @@ const createStyles = (colors: any) => StyleSheet.create({ alignItems: 'center', }, chip: { - paddingHorizontal: 10, - paddingVertical: 4, - borderRadius: 4, - marginRight: 4, - marginBottom: 4, + paddingHorizontal: Platform.isTV ? 14 : 10, + paddingVertical: Platform.isTV ? 6 : 4, + borderRadius: Platform.isTV ? 8 : 4, + marginRight: Platform.isTV ? 6 : 4, + marginBottom: Platform.isTV ? 6 : 4, backgroundColor: colors.surfaceVariant, }, chipText: { color: colors.highEmphasis, - fontSize: 12, - fontWeight: '600', + fontSize: Platform.isTV ? 14 : 12, + fontWeight: '700', }, progressContainer: { height: 20, @@ -2141,7 +2143,7 @@ const createStyles = (colors: any) => StyleSheet.create({ }, streamsHeroContainer: { width: '100%', - height: 220, + height: Platform.isTV ? Math.round(height * 0.45) : 220, marginBottom: 0, position: 'relative', backgroundColor: colors.black, @@ -2155,7 +2157,7 @@ const createStyles = (colors: any) => StyleSheet.create({ streamsHeroGradient: { flex: 1, justifyContent: 'flex-end', - padding: 16, + padding: Platform.isTV ? 24 : 16, paddingBottom: 0, }, streamsHeroContent: { @@ -2166,27 +2168,27 @@ const createStyles = (colors: any) => StyleSheet.create({ }, streamsHeroEpisodeNumber: { color: colors.primary, - fontSize: 14, + fontSize: Platform.isTV ? 50 : 24, fontWeight: 'bold', - marginBottom: 2, + marginBottom: Platform.isTV ? 8 : 2, textShadowColor: 'rgba(0,0,0,0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, streamsHeroTitle: { color: colors.highEmphasis, - fontSize: 24, + fontSize: Platform.isTV ? 60 : 24, fontWeight: 'bold', - marginBottom: 4, + marginBottom: Platform.isTV ? 12 : 4, textShadowColor: 'rgba(0,0,0,0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 3, }, streamsHeroOverview: { color: colors.mediumEmphasis, - fontSize: 14, - lineHeight: 20, - marginBottom: 2, + fontSize: Platform.isTV ? 30 : 14, + lineHeight: Platform.isTV ? 28 : 20, + marginBottom: Platform.isTV ? 10 : 2, textShadowColor: 'rgba(0,0,0,0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, @@ -2194,12 +2196,12 @@ const createStyles = (colors: any) => StyleSheet.create({ streamsHeroMeta: { flexDirection: 'row', alignItems: 'center', - gap: 12, + gap: Platform.isTV ? 20 : 12, marginTop: 0, }, streamsHeroReleased: { color: colors.mediumEmphasis, - fontSize: 14, + fontSize: Platform.isTV ? 25 : 14, textShadowColor: 'rgba(0,0,0,0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, @@ -2208,18 +2210,18 @@ const createStyles = (colors: any) => StyleSheet.create({ flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.7)', - paddingHorizontal: 6, - paddingVertical: 3, + paddingHorizontal: Platform.isTV ? 8 : 6, + paddingVertical: Platform.isTV ? 4 : 3, borderRadius: 4, marginTop: 0, }, tmdbLogo: { - width: 20, - height: 14, + width: Platform.isTV ? 28 : 20, + height: Platform.isTV ? 18 : 14, }, streamsHeroRatingText: { color: colors.accent, - fontSize: 13, + fontSize: Platform.isTV ? 18 : 13, fontWeight: '700', marginLeft: 4, }, @@ -2270,11 +2272,11 @@ const createStyles = (colors: any) => StyleSheet.create({ }, movieTitleContainer: { width: '100%', - height: 140, + height: Platform.isTV ? 200 : 140, backgroundColor: colors.darkBackground, pointerEvents: 'box-none', justifyContent: 'center', - paddingTop: Platform.OS === 'android' ? 65 : 35, + paddingTop: Platform.isTV ? 40 : (Platform.OS === 'android' ? 65 : 35), }, movieTitleContent: { width: '100%', @@ -2285,11 +2287,11 @@ const createStyles = (colors: any) => StyleSheet.create({ movieLogo: { width: '100%', height: '100%', - maxWidth: width * 0.85, + maxWidth: Platform.isTV ? width * 0.9 : width * 0.85, }, movieTitle: { color: colors.highEmphasis, - fontSize: 28, + fontSize: Platform.isTV ? 36 : 28, fontWeight: '900', textAlign: 'center', letterSpacing: -0.5, @@ -2378,7 +2380,7 @@ const createStyles = (colors: any) => StyleSheet.create({ fontWeight: '600', }, activeScrapersContainer: { - paddingHorizontal: Platform.isTV ? 0 : 16, + paddingHorizontal: Platform.isTV ? 24 : 16, paddingVertical: 8, backgroundColor: 'transparent', marginHorizontal: Platform.isTV ? 0 : 16, @@ -2386,27 +2388,27 @@ const createStyles = (colors: any) => StyleSheet.create({ }, activeScrapersTitle: { color: colors.mediumEmphasis, - fontSize: 12, - fontWeight: '500', + fontSize: Platform.isTV ? 14 : 12, + fontWeight: '600', marginBottom: 6, opacity: 0.8, }, activeScrapersRow: { flexDirection: 'row', flexWrap: 'wrap', - gap: 4, + gap: Platform.isTV ? 6 : 4, }, activeScraperChip: { backgroundColor: colors.elevation2, - paddingHorizontal: 8, - paddingVertical: 3, - borderRadius: 6, + paddingHorizontal: Platform.isTV ? 12 : 8, + paddingVertical: Platform.isTV ? 6 : 3, + borderRadius: Platform.isTV ? 10 : 6, borderWidth: 0, }, activeScraperText: { color: colors.mediumEmphasis, - fontSize: 11, - fontWeight: '400', + fontSize: Platform.isTV ? 13 : 11, + fontWeight: '500', }, });