From 0b764412b2f6390d4c52bc406b075f7d5ac3495a Mon Sep 17 00:00:00 2001 From: tapframe Date: Sun, 19 Oct 2025 20:07:21 +0530 Subject: [PATCH] homescreen imrpovements for tablet screens --- src/components/home/CatalogSection.tsx | 105 ++++++++++++++++++++----- src/components/home/ContentItem.tsx | 58 ++++++++++---- src/screens/SearchScreen.tsx | 78 ++++++++++++++---- 3 files changed, 194 insertions(+), 47 deletions(-) diff --git a/src/components/home/CatalogSection.tsx b/src/components/home/CatalogSection.tsx index 1f344fb3..ec8bd41c 100644 --- a/src/components/home/CatalogSection.tsx +++ b/src/components/home/CatalogSection.tsx @@ -16,6 +16,26 @@ interface CatalogSectionProps { const { width } = Dimensions.get('window'); +// Enhanced responsive breakpoints +const BREAKPOINTS = { + phone: 0, + tablet: 768, + largeTablet: 1024, + tv: 1440, +}; + +const getDeviceType = (deviceWidth: number) => { + if (deviceWidth >= BREAKPOINTS.tv) return 'tv'; + if (deviceWidth >= BREAKPOINTS.largeTablet) return 'largeTablet'; + if (deviceWidth >= BREAKPOINTS.tablet) return 'tablet'; + return 'phone'; +}; + +const deviceType = getDeviceType(width); +const isTablet = deviceType === 'tablet'; +const isLargeTablet = deviceType === 'largeTablet'; +const isTV = deviceType === 'tv'; + // Dynamic poster calculation based on screen width - show 1/4 of next poster const calculatePosterLayout = (screenWidth: number) => { const MIN_POSTER_WIDTH = 100; // Reduced minimum for more posters @@ -70,8 +90,9 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => { ); }, [handleContentPress]); - // Memoize the ItemSeparatorComponent to prevent re-creation - const ItemSeparator = useCallback(() => , []); + // Memoize the ItemSeparatorComponent to prevent re-creation (responsive spacing) + const separatorWidth = isTV ? 12 : isLargeTablet ? 10 : isTablet ? 8 : 8; + const ItemSeparator = useCallback(() => , [separatorWidth]); // Memoize the keyExtractor to prevent re-creation const keyExtractor = useCallback((item: StreamingContent) => `${item.id}-${item.type}`, []); @@ -81,10 +102,33 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => { style={styles.catalogContainer} entering={FadeIn.duration(400)} > - + - {catalog.name} - + + {catalog.name} + + @@ -94,10 +138,28 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => { addonId: catalog.addon }) } - style={styles.viewAllButton} + style={[ + styles.viewAllButton, + { + paddingVertical: isTV ? 10 : isLargeTablet ? 9 : isTablet ? 8 : 8, + paddingHorizontal: isTV ? 12 : isLargeTablet ? 11 : isTablet ? 10 : 10, + borderRadius: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 20 : 20, + } + ]} > - View All - + View All + @@ -107,7 +169,13 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => { keyExtractor={keyExtractor} horizontal showsHorizontalScrollIndicator={false} - contentContainerStyle={StyleSheet.flatten([styles.catalogList, { paddingRight: 16 - posterLayout.partialPosterWidth }])} + contentContainerStyle={StyleSheet.flatten([ + styles.catalogList, + { + paddingHorizontal: isTV ? 32 : isLargeTablet ? 28 : isTablet ? 24 : 16, + paddingRight: (isTV ? 32 : isLargeTablet ? 28 : isTablet ? 24 : 16) - posterLayout.partialPosterWidth, + } + ])} ItemSeparatorComponent={ItemSeparator} onEndReachedThreshold={0.7} onEndReached={() => {}} @@ -126,7 +194,6 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - paddingHorizontal: 16, marginBottom: 16, }, titleContainer: { @@ -135,7 +202,7 @@ const styles = StyleSheet.create({ marginRight: 16, }, catalogTitle: { - fontSize: 24, + fontSize: 24, // will be overridden responsively fontWeight: '800', letterSpacing: 0.5, marginBottom: 4, @@ -144,26 +211,26 @@ const styles = StyleSheet.create({ position: 'absolute', bottom: -2, left: 0, - width: 40, - height: 3, + width: 40, // overridden responsively + height: 3, // overridden responsively borderRadius: 2, opacity: 0.8, }, viewAllButton: { flexDirection: 'row', alignItems: 'center', - paddingVertical: 8, - paddingHorizontal: 10, - borderRadius: 20, + paddingVertical: 8, // overridden responsively + paddingHorizontal: 10, // overridden responsively + borderRadius: 20, // overridden responsively backgroundColor: 'rgba(255,255,255,0.1)', }, viewAllText: { - fontSize: 14, + fontSize: 14, // overridden responsively fontWeight: '600', - marginRight: 4, + marginRight: 4, // overridden responsively }, catalogList: { - paddingHorizontal: 16, + // padding will be applied responsively in JSX }, }); diff --git a/src/components/home/ContentItem.tsx b/src/components/home/ContentItem.tsx index e9e161bc..70390ed1 100644 --- a/src/components/home/ContentItem.tsx +++ b/src/components/home/ContentItem.tsx @@ -23,21 +23,39 @@ interface ContentItemProps { const { width } = Dimensions.get('window'); +// Enhanced responsive breakpoints +const BREAKPOINTS = { + phone: 0, + tablet: 768, + largeTablet: 1024, + tv: 1440, +}; + +const getDeviceType = (screenWidth: number) => { + if (screenWidth >= BREAKPOINTS.tv) return 'tv'; + if (screenWidth >= BREAKPOINTS.largeTablet) return 'largeTablet'; + if (screenWidth >= BREAKPOINTS.tablet) return 'tablet'; + return 'phone'; +}; + // Dynamic poster calculation based on screen width - show 1/4 of next poster const calculatePosterLayout = (screenWidth: number) => { - // Detect if device is a tablet (width >= 768px is common tablet breakpoint) - const isTablet = screenWidth >= 768; - - const MIN_POSTER_WIDTH = isTablet ? 140 : 100; // Bigger minimum for tablets - const MAX_POSTER_WIDTH = isTablet ? 180 : 130; // Bigger maximum for tablets - const LEFT_PADDING = 16; // Left padding - const SPACING = 8; // Space between posters + const deviceType = getDeviceType(screenWidth); + + // Responsive sizing based on device type + const MIN_POSTER_WIDTH = deviceType === 'tv' ? 180 : deviceType === 'largeTablet' ? 160 : deviceType === 'tablet' ? 140 : 100; + const MAX_POSTER_WIDTH = deviceType === 'tv' ? 220 : deviceType === 'largeTablet' ? 200 : deviceType === 'tablet' ? 180 : 130; + const LEFT_PADDING = deviceType === 'tv' ? 32 : deviceType === 'largeTablet' ? 28 : deviceType === 'tablet' ? 24 : 16; + const SPACING = deviceType === 'tv' ? 12 : deviceType === 'largeTablet' ? 10 : deviceType === 'tablet' ? 8 : 8; // Calculate available width for posters (reserve space for left padding) const availableWidth = screenWidth - LEFT_PADDING; // Try different numbers of full posters to find the best fit - let bestLayout = { numFullPosters: 3, posterWidth: isTablet ? 160 : 120 }; + let bestLayout = { + numFullPosters: 3, + posterWidth: deviceType === 'tv' ? 200 : deviceType === 'largeTablet' ? 180 : deviceType === 'tablet' ? 160 : 120 + }; for (let n = 3; n <= 6; n++) { // Calculate poster width needed for N full posters + 0.25 partial poster @@ -104,15 +122,18 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe const posterRadius = typeof settings.posterBorderRadius === 'number' ? settings.posterBorderRadius : 12; // Memoize poster width calculation to avoid recalculating on every render const posterWidth = React.useMemo(() => { + const deviceType = getDeviceType(width); + const sizeMultiplier = deviceType === 'tv' ? 1.2 : deviceType === 'largeTablet' ? 1.1 : deviceType === 'tablet' ? 1.0 : 0.9; + switch (settings.posterSize) { case 'small': - return Math.max(100, Math.min(POSTER_WIDTH - 10, POSTER_WIDTH)); + return Math.max(100, Math.min(POSTER_WIDTH - 10, POSTER_WIDTH)) * sizeMultiplier; case 'large': - return Math.min(POSTER_WIDTH + 20, POSTER_WIDTH + 30); + return Math.min(POSTER_WIDTH + 20, POSTER_WIDTH + 30) * sizeMultiplier; default: - return POSTER_WIDTH; + return POSTER_WIDTH * sizeMultiplier; } - }, [settings.posterSize]); + }, [settings.posterSize, width]); // Intersection observer simulation for lazy loading const itemRef = useRef(null); @@ -322,7 +343,16 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe {settings.showPosterTitles && ( - + {item.name} )} @@ -409,7 +439,7 @@ const styles = StyleSheet.create({ padding: 2, }, title: { - fontSize: 13, + fontSize: 13, // Will be overridden responsively fontWeight: '500', marginTop: 4, textAlign: 'center', diff --git a/src/screens/SearchScreen.tsx b/src/screens/SearchScreen.tsx index 22f15553..c680fe9e 100644 --- a/src/screens/SearchScreen.tsx +++ b/src/screens/SearchScreen.tsx @@ -45,14 +45,33 @@ import { useTheme } from '../contexts/ThemeContext'; import LoadingSpinner from '../components/common/LoadingSpinner'; const { width, height } = Dimensions.get('window'); -const isTablet = width >= 768; + +// Enhanced responsive breakpoints +const BREAKPOINTS = { + phone: 0, + tablet: 768, + largeTablet: 1024, + tv: 1440, +}; + +const getDeviceType = (deviceWidth: number) => { + if (deviceWidth >= BREAKPOINTS.tv) return 'tv'; + if (deviceWidth >= BREAKPOINTS.largeTablet) return 'largeTablet'; + if (deviceWidth >= BREAKPOINTS.tablet) return 'tablet'; + return 'phone'; +}; + +const deviceType = getDeviceType(width); +const isTablet = deviceType === 'tablet'; +const isLargeTablet = deviceType === 'largeTablet'; +const isTV = deviceType === 'tv'; const TAB_BAR_HEIGHT = 85; -// Tablet-optimized poster sizes -const HORIZONTAL_ITEM_WIDTH = isTablet ? width * 0.18 : width * 0.3; +// Responsive poster sizes +const HORIZONTAL_ITEM_WIDTH = isTV ? width * 0.14 : isLargeTablet ? width * 0.16 : isTablet ? width * 0.18 : width * 0.3; const HORIZONTAL_POSTER_HEIGHT = HORIZONTAL_ITEM_WIDTH * 1.5; -const POSTER_WIDTH = isTablet ? 70 : 90; -const POSTER_HEIGHT = isTablet ? 105 : 135; +const POSTER_WIDTH = isTV ? 90 : isLargeTablet ? 80 : isTablet ? 70 : 90; +const POSTER_HEIGHT = POSTER_WIDTH * 1.5; const RECENT_SEARCHES_KEY = 'recent_searches'; const MAX_RECENT_SEARCHES = 10; @@ -597,13 +616,20 @@ const SearchScreen = () => { )} {item.name} {item.year && ( - + {item.year} )} @@ -652,8 +678,16 @@ const SearchScreen = () => { {/* Movies */} {movieResults.length > 0 && ( - - + + Movies ({movieResults.length}) { {/* TV Shows */} {seriesResults.length > 0 && ( - - + + TV Shows ({seriesResults.length}) { {/* Other types */} {otherResults.length > 0 && ( - - + + {otherResults[0].type.charAt(0).toUpperCase() + otherResults[0].type.slice(1)} ({otherResults.length}) { const headerBaseHeight = Platform.OS === 'android' ? 80 : 60; // Keep header below floating top navigator on tablets by adding extra offset - const tabletNavOffset = isTablet ? 64 : 0; + const tabletNavOffset = (isTV || isLargeTablet || isTablet) ? 64 : 0; const topSpacing = (Platform.OS === 'android' ? (StatusBar.currentHeight || 0) : insets.top) + tabletNavOffset; const headerHeight = headerBaseHeight + topSpacing + 60;