diff --git a/src/components/metadata/CommentsSection.tsx b/src/components/metadata/CommentsSection.tsx index de9b31de..db1cf2ab 100644 --- a/src/components/metadata/CommentsSection.tsx +++ b/src/components/metadata/CommentsSection.tsx @@ -837,41 +837,66 @@ export const CommentsSection: React.FC = ({ const renderSkeletons = useCallback(() => { const placeholders = [0, 1, 2]; + // Responsive skeleton sizes to match CompactCommentCard + const skWidth = isTV ? 360 : isLargeTablet ? 320 : isTablet ? 300 : 280; + const skHeight = isTV ? 200 : isLargeTablet ? 185 : isTablet ? 175 : 170; + const skPad = isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 12; + const gap = isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 12; + const headLineWidth = isTV ? 160 : isLargeTablet ? 140 : isTablet ? 130 : 120; + const ratingWidth = isTV ? 100 : isLargeTablet ? 90 : isTablet ? 85 : 80; + const statWidth = isTV ? 44 : isLargeTablet ? 40 : isTablet ? 38 : 36; + const badgeW = isTV ? 60 : isLargeTablet ? 56 : isTablet ? 52 : 50; + const badgeH = isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 12; + return ( - + {placeholders.map((i) => ( - + - + - - - + + + - - + + - - - - + + + + - - - - - + + + + + ))} ); - }, [currentTheme]); + }, [currentTheme, isTV, isLargeTablet, isTablet]); // Don't show section if not authenticated, if comments are disabled in settings, or if still checking authentication // Only show when authentication is definitively true and settings allow it diff --git a/src/components/metadata/SeriesContent.tsx b/src/components/metadata/SeriesContent.tsx index 93a7e523..44e8e9d1 100644 --- a/src/components/metadata/SeriesContent.tsx +++ b/src/components/metadata/SeriesContent.tsx @@ -83,6 +83,46 @@ export const SeriesContent: React.FC = ({ return 16; // phone } }, [deviceType]); + + // Match ThisWeekSection card sizing for horizontal episode cards + const horizontalCardWidth = useMemo(() => { + switch (deviceType) { + case 'tv': + return Math.min(deviceWidth * 0.25, 400); + case 'largeTablet': + return Math.min(deviceWidth * 0.35, 350); + case 'tablet': + return Math.min(deviceWidth * 0.46, 300); + default: + return width * 0.75; + } + }, [deviceType, deviceWidth, width]); + + const horizontalCardHeight = useMemo(() => { + switch (deviceType) { + case 'tv': + return 280; + case 'largeTablet': + return 250; + case 'tablet': + return 220; + default: + return 180; + } + }, [deviceType]); + + const horizontalItemSpacing = useMemo(() => { + switch (deviceType) { + case 'tv': + return 20; + case 'largeTablet': + return 18; + case 'tablet': + return 16; + default: + return 16; + } + }, [deviceType]); // Enhanced season poster sizing const seasonPosterWidth = useMemo(() => { @@ -730,6 +770,20 @@ export const SeriesContent: React.FC = ({ )} + {(!progress || progressPercent === 0) && ( + + )} = ({ styles.episodeCardHorizontal, { borderRadius: isTV ? 20 : isLargeTablet ? 18 : isTablet ? 16 : 16, - height: isTV ? 280 : isLargeTablet ? 260 : isTablet ? 240 : 200, + height: horizontalCardHeight, elevation: isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 8, shadowOpacity: isTV ? 0.4 : isLargeTablet ? 0.35 : isTablet ? 0.3 : 0.3, shadowRadius: isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 8 @@ -882,7 +936,7 @@ export const SeriesContent: React.FC = ({ // Gradient border styling { borderWidth: 1, - borderColor: 'transparent', + borderColor: 'rgba(255,255,255,0.12)', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, } @@ -890,32 +944,7 @@ export const SeriesContent: React.FC = ({ onPress={() => onSelectEpisode(episode)} activeOpacity={0.85} > - {/* Gradient Border Container */} - - - + {/* Solid outline replaces gradient border */} {/* Background Image */} = ({ )} + {(!progress || progressPercent === 0) && ( + + )} @@ -1115,8 +1158,8 @@ export const SeriesContent: React.FC = ({ style={[ styles.episodeCardWrapperHorizontal, { - width: isTV ? width * 0.45 : isLargeTablet ? width * 0.4 : isTablet ? width * 0.4 : width * 0.75, - marginRight: isTV ? 24 : isLargeTablet ? 20 : isTablet ? 20 : 16 + width: horizontalCardWidth, + marginRight: horizontalItemSpacing } ]} > @@ -1138,11 +1181,10 @@ export const SeriesContent: React.FC = ({ maxToRenderPerBatch={5} windowSize={5} getItemLayout={(data, index) => { - const cardWidth = isTV ? width * 0.45 : isLargeTablet ? width * 0.4 : isTablet ? width * 0.4 : width * 0.75; - const margin = isTV ? 24 : isLargeTablet ? 20 : isTablet ? 20 : 16; + const length = horizontalCardWidth + horizontalItemSpacing; return { - length: cardWidth + margin, - offset: (cardWidth + margin) * index, + length, + offset: length * index, index, }; }} diff --git a/src/components/player/controls/PlayerControls.tsx b/src/components/player/controls/PlayerControls.tsx index 44a92e5c..30510306 100644 --- a/src/components/player/controls/PlayerControls.tsx +++ b/src/components/player/controls/PlayerControls.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, Text, TouchableOpacity, Animated, StyleSheet, Platform } from 'react-native'; +import { View, Text, TouchableOpacity, Animated, StyleSheet, Platform, Dimensions } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import Slider from '@react-native-community/slider'; @@ -82,6 +82,22 @@ export const PlayerControls: React.FC = ({ playerBackend, }) => { const { currentTheme } = useTheme(); + const deviceWidth = Dimensions.get('window').width; + const BREAKPOINTS = { phone: 0, tablet: 768, largeTablet: 1024, tv: 1440 } as const; + const getDeviceType = (w: number) => { + if (w >= BREAKPOINTS.tv) return 'tv'; + if (w >= BREAKPOINTS.largeTablet) return 'largeTablet'; + if (w >= BREAKPOINTS.tablet) return 'tablet'; + return 'phone'; + }; + const deviceType = getDeviceType(deviceWidth); + const isTablet = deviceType === 'tablet'; + const isLargeTablet = deviceType === 'largeTablet'; + const isTV = deviceType === 'tv'; + + const closeIconSize = isTV ? 28 : isLargeTablet ? 26 : isTablet ? 24 : 24; + const skipIconSize = isTV ? 28 : isLargeTablet ? 26 : isTablet ? 24 : 24; + const playIconSize = isTV ? 56 : isLargeTablet ? 48 : isTablet ? 44 : 40; return ( = ({ )} - + @@ -149,14 +165,14 @@ export const PlayerControls: React.FC = ({ {/* Center Controls (Play/Pause, Skip) */} skip(-10)} style={styles.skipButton}> - + 10 - + skip(10)} style={styles.skipButton}> - + 10 diff --git a/src/components/player/utils/playerStyles.ts b/src/components/player/utils/playerStyles.ts index 6b7b8ccf..78c71721 100644 --- a/src/components/player/utils/playerStyles.ts +++ b/src/components/player/utils/playerStyles.ts @@ -1,4 +1,38 @@ -import { StyleSheet } from 'react-native'; +import { StyleSheet, Dimensions } from 'react-native'; + +const deviceWidth = Dimensions.get('window').width; +const BREAKPOINTS = { phone: 0, tablet: 768, largeTablet: 1024, tv: 1440 } as const; +const getDeviceType = (w: number) => { + if (w >= BREAKPOINTS.tv) return 'tv'; + if (w >= BREAKPOINTS.largeTablet) return 'largeTablet'; + if (w >= BREAKPOINTS.tablet) return 'tablet'; + return 'phone'; +}; +const deviceType = getDeviceType(deviceWidth); +const isTablet = deviceType === 'tablet'; +const isLargeTablet = deviceType === 'largeTablet'; +const isTV = deviceType === 'tv'; + +// Scales for larger displays +const padH = isTV ? 28 : isLargeTablet ? 24 : isTablet ? 20 : 20; +const padV = isTV ? 24 : isLargeTablet ? 20 : isTablet ? 16 : 16; +const titleFont = isTV ? 28 : isLargeTablet ? 24 : isTablet ? 22 : 18; +const episodeInfoFont = isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 14; +const metadataFont = isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 12; +const qualityPadH = isTV ? 10 : isLargeTablet ? 9 : isTablet ? 8 : 8; +const qualityPadV = isTV ? 4 : isLargeTablet ? 3 : isTablet ? 3 : 2; +const qualityRadius = isTV ? 6 : isLargeTablet ? 5 : isTablet ? 4 : 4; +const qualityTextFont = isTV ? 13 : isLargeTablet ? 12 : isTablet ? 11 : 11; +const controlsGap = isTV ? 56 : isLargeTablet ? 48 : isTablet ? 44 : 40; +const controlsTranslateY = isTV ? -48 : isLargeTablet ? -42 : isTablet ? -36 : -30; +const skipTextFont = isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 12; +const sliderBottom = isTV ? 80 : isLargeTablet ? 70 : isTablet ? 65 : 55; +const progressTouchHeight = isTV ? 48 : isLargeTablet ? 44 : isTablet ? 40 : 40; +const progressBarHeight = isTV ? 6 : isLargeTablet ? 5 : isTablet ? 5 : 4; +const progressThumbSize = isTV ? 24 : isLargeTablet ? 20 : isTablet ? 18 : 16; +const progressThumbTop = isTV ? -10 : isLargeTablet ? -8 : isTablet ? -7 : -6; +const durationFont = isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 12; +const bottomButtonTextFont = isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 12; export const styles = StyleSheet.create({ container: { @@ -37,14 +71,14 @@ export const styles = StyleSheet.create({ padding: 0, }, topGradient: { - paddingTop: 20, - paddingHorizontal: 20, - paddingBottom: 10, + paddingTop: padV, + paddingHorizontal: padH, + paddingBottom: Math.max(10, Math.round(padV * 0.6)), }, bottomGradient: { - paddingBottom: 20, - paddingHorizontal: 20, - paddingTop: 20, + paddingBottom: padV, + paddingHorizontal: padH, + paddingTop: padV, }, header: { flexDirection: 'row', @@ -57,12 +91,12 @@ export const styles = StyleSheet.create({ }, title: { color: 'white', - fontSize: 18, + fontSize: titleFont, fontWeight: 'bold', }, episodeInfo: { color: 'rgba(255, 255, 255, 0.9)', - fontSize: 14, + fontSize: episodeInfoFont, marginTop: 3, }, metadataRow: { @@ -73,20 +107,20 @@ export const styles = StyleSheet.create({ }, metadataText: { color: 'rgba(255, 255, 255, 0.7)', - fontSize: 12, + fontSize: metadataFont, marginRight: 8, }, qualityBadge: { backgroundColor: 'rgba(229, 9, 20, 0.2)', - paddingHorizontal: 8, - paddingVertical: 2, - borderRadius: 4, + paddingHorizontal: qualityPadH, + paddingVertical: qualityPadV, + borderRadius: qualityRadius, marginRight: 8, marginBottom: 4, }, qualityText: { color: '#E50914', - fontSize: 11, + fontSize: qualityTextFont, fontWeight: 'bold', }, providerText: { @@ -102,11 +136,11 @@ export const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', - gap: 40, + gap: controlsGap, left: 0, right: 0, top: '50%', - transform: [{ translateY: -30 }], + transform: [{ translateY: controlsTranslateY }], zIndex: 1000, }, playButton: { @@ -120,7 +154,7 @@ export const styles = StyleSheet.create({ }, skipText: { color: 'white', - fontSize: 12, + fontSize: skipTextFont, marginTop: 2, }, bottomControls: { @@ -128,19 +162,19 @@ export const styles = StyleSheet.create({ }, sliderContainer: { position: 'absolute', - bottom: 55, + bottom: sliderBottom, left: 0, right: 0, - paddingHorizontal: 20, + paddingHorizontal: padH, zIndex: 1000, }, progressTouchArea: { - height: 40, // Increased from 30 to give more space for the thumb + height: progressTouchHeight, // Increased touch area for larger displays justifyContent: 'center', width: '100%', }, progressBarContainer: { - height: 4, + height: progressBarHeight, backgroundColor: 'rgba(255, 255, 255, 0.2)', borderRadius: 2, overflow: 'hidden', @@ -164,12 +198,12 @@ export const styles = StyleSheet.create({ }, progressThumb: { position: 'absolute', - width: 16, - height: 16, - borderRadius: 8, + width: progressThumbSize, + height: progressThumbSize, + borderRadius: progressThumbSize / 2, backgroundColor: '#E50914', - top: -6, // Position to center on the progress bar - marginLeft: -8, // Center the thumb horizontally + top: progressThumbTop, // Position to center on the progress bar + marginLeft: -(progressThumbSize / 2), // Center the thumb horizontally shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.3, @@ -187,7 +221,7 @@ export const styles = StyleSheet.create({ }, duration: { color: 'white', - fontSize: 12, + fontSize: durationFont, fontWeight: '500', }, bottomButtons: { @@ -202,7 +236,7 @@ export const styles = StyleSheet.create({ }, bottomButtonText: { color: 'white', - fontSize: 12, + fontSize: bottomButtonTextFont, }, modalOverlay: { flex: 1,