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',
},
});