From d5cfe8a563e28cdfcf8c0c088c2f865c9399f91a Mon Sep 17 00:00:00 2001 From: tapframe Date: Wed, 18 Jun 2025 18:25:19 +0530 Subject: [PATCH] Implement dynamic poster layout calculation across multiple components for improved responsiveness This update introduces a new function to calculate poster layout dynamically based on screen width in several components, including CatalogSection, ContentItem, ContinueWatchingSection, MoreLikeThisSection, CatalogScreen, HomeScreen, and homeStyles. The function ensures that poster widths and the number of columns adapt to different screen sizes, enhancing the overall user experience and visual consistency across the application. --- src/components/home/CatalogSection.tsx | 27 +++++- src/components/home/ContentItem.tsx | 27 +++++- .../home/ContinueWatchingSection.tsx | 26 +++++- .../metadata/MoreLikeThisSection.tsx | 27 +++++- src/screens/CatalogScreen.tsx | 30 ++++++- src/screens/HomeScreen.tsx | 27 +++++- src/styles/homeStyles.ts | 27 +++++- src/utils/posterUtils.ts | 82 +++++++++++++++++++ 8 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 src/utils/posterUtils.ts diff --git a/src/components/home/CatalogSection.tsx b/src/components/home/CatalogSection.tsx index 8f4ed81f8..d4cdd9cd2 100644 --- a/src/components/home/CatalogSection.tsx +++ b/src/components/home/CatalogSection.tsx @@ -14,7 +14,32 @@ interface CatalogSectionProps { } const { width } = Dimensions.get('window'); -const POSTER_WIDTH = (width - 50) / 3; + +// Dynamic poster calculation based on screen width +const calculatePosterLayout = (screenWidth: number) => { + const MIN_POSTER_WIDTH = 110; // Minimum poster width for readability + const MAX_POSTER_WIDTH = 140; // Maximum poster width to prevent oversized posters + const HORIZONTAL_PADDING = 50; // Total horizontal padding/margins + + // Calculate how many posters can fit + const availableWidth = screenWidth - HORIZONTAL_PADDING; + const maxColumns = Math.floor(availableWidth / MIN_POSTER_WIDTH); + + // Limit to reasonable number of columns (3-6) + const numColumns = Math.min(Math.max(maxColumns, 3), 6); + + // Calculate actual poster width + const posterWidth = Math.min(availableWidth / numColumns, MAX_POSTER_WIDTH); + + return { + numColumns, + posterWidth, + spacing: 12 // Space between posters + }; +}; + +const posterLayout = calculatePosterLayout(width); +const POSTER_WIDTH = posterLayout.posterWidth; const CatalogSection = ({ catalog }: CatalogSectionProps) => { const navigation = useNavigation>(); diff --git a/src/components/home/ContentItem.tsx b/src/components/home/ContentItem.tsx index ac3b0dbea..f5061c54e 100644 --- a/src/components/home/ContentItem.tsx +++ b/src/components/home/ContentItem.tsx @@ -12,7 +12,32 @@ interface ContentItemProps { } const { width } = Dimensions.get('window'); -const POSTER_WIDTH = (width - 50) / 3; + +// Dynamic poster calculation based on screen width +const calculatePosterLayout = (screenWidth: number) => { + const MIN_POSTER_WIDTH = 110; // Minimum poster width for readability + const MAX_POSTER_WIDTH = 140; // Maximum poster width to prevent oversized posters + const HORIZONTAL_PADDING = 50; // Total horizontal padding/margins + + // Calculate how many posters can fit + const availableWidth = screenWidth - HORIZONTAL_PADDING; + const maxColumns = Math.floor(availableWidth / MIN_POSTER_WIDTH); + + // Limit to reasonable number of columns (3-6) + const numColumns = Math.min(Math.max(maxColumns, 3), 6); + + // Calculate actual poster width + const posterWidth = Math.min(availableWidth / numColumns, MAX_POSTER_WIDTH); + + return { + numColumns, + posterWidth, + spacing: 12 // Space between posters + }; +}; + +const posterLayout = calculatePosterLayout(width); +const POSTER_WIDTH = posterLayout.posterWidth; const ContentItem = ({ item: initialItem, onPress }: ContentItemProps) => { const [menuVisible, setMenuVisible] = useState(false); diff --git a/src/components/home/ContinueWatchingSection.tsx b/src/components/home/ContinueWatchingSection.tsx index 5ef50024d..17e64d2bd 100644 --- a/src/components/home/ContinueWatchingSection.tsx +++ b/src/components/home/ContinueWatchingSection.tsx @@ -33,8 +33,32 @@ interface ContinueWatchingRef { refresh: () => Promise; } +// Dynamic poster calculation based on screen width for Continue Watching section +const calculatePosterLayout = (screenWidth: number) => { + const MIN_POSTER_WIDTH = 120; // Slightly larger for continue watching items + const MAX_POSTER_WIDTH = 160; // Maximum poster width for this section + const HORIZONTAL_PADDING = 40; // Total horizontal padding/margins + + // Calculate how many posters can fit (fewer items for continue watching) + const availableWidth = screenWidth - HORIZONTAL_PADDING; + const maxColumns = Math.floor(availableWidth / MIN_POSTER_WIDTH); + + // Limit to reasonable number of columns (2-5 for continue watching) + const numColumns = Math.min(Math.max(maxColumns, 2), 5); + + // Calculate actual poster width + const posterWidth = Math.min(availableWidth / numColumns, MAX_POSTER_WIDTH); + + return { + numColumns, + posterWidth, + spacing: 12 // Space between posters + }; +}; + const { width } = Dimensions.get('window'); -const POSTER_WIDTH = (width - 40) / 2.7; +const posterLayout = calculatePosterLayout(width); +const POSTER_WIDTH = posterLayout.posterWidth; // Create a proper imperative handle with React.forwardRef and updated type const ContinueWatchingSection = React.forwardRef((props, ref) => { diff --git a/src/components/metadata/MoreLikeThisSection.tsx b/src/components/metadata/MoreLikeThisSection.tsx index f69cc6973..f2df2e4e4 100644 --- a/src/components/metadata/MoreLikeThisSection.tsx +++ b/src/components/metadata/MoreLikeThisSection.tsx @@ -19,7 +19,32 @@ import { TMDBService } from '../../services/tmdbService'; import { catalogService } from '../../services/catalogService'; const { width } = Dimensions.get('window'); -const POSTER_WIDTH = (width - 48) / 3.5; // Adjust number for desired items visible + +// Dynamic poster calculation based on screen width for More Like This section +const calculatePosterLayout = (screenWidth: number) => { + const MIN_POSTER_WIDTH = 100; // Slightly smaller for more items in this section + const MAX_POSTER_WIDTH = 130; // Maximum poster width + const HORIZONTAL_PADDING = 48; // Total horizontal padding/margins + + // Calculate how many posters can fit (aim for slightly more items than main sections) + const availableWidth = screenWidth - HORIZONTAL_PADDING; + const maxColumns = Math.floor(availableWidth / MIN_POSTER_WIDTH); + + // Limit to reasonable number of columns (3-7 for this section) + const numColumns = Math.min(Math.max(maxColumns, 3), 7); + + // Calculate actual poster width + const posterWidth = Math.min(availableWidth / numColumns, MAX_POSTER_WIDTH); + + return { + numColumns, + posterWidth, + spacing: 12 // Space between posters + }; +}; + +const posterLayout = calculatePosterLayout(width); +const POSTER_WIDTH = posterLayout.posterWidth; const POSTER_HEIGHT = POSTER_WIDTH * 1.5; interface MoreLikeThisSectionProps { diff --git a/src/screens/CatalogScreen.tsx b/src/screens/CatalogScreen.tsx index 59d5c3987..83de2ab2b 100644 --- a/src/screens/CatalogScreen.tsx +++ b/src/screens/CatalogScreen.tsx @@ -41,9 +41,35 @@ const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; // Screen dimensions and grid layout const { width } = Dimensions.get('window'); -const NUM_COLUMNS = 3; + +// Dynamic column calculation based on screen width +const calculateCatalogLayout = (screenWidth: number) => { + const MIN_ITEM_WIDTH = 110; // Minimum item width for readability + const MAX_ITEM_WIDTH = 150; // Maximum item width + const HORIZONTAL_PADDING = SPACING.lg * 2; // Total horizontal padding + const ITEM_MARGIN_TOTAL = SPACING.sm * 2; // Total margin per item + + // Calculate how many columns can fit + const availableWidth = screenWidth - HORIZONTAL_PADDING; + const maxColumns = Math.floor(availableWidth / (MIN_ITEM_WIDTH + ITEM_MARGIN_TOTAL)); + + // Limit to reasonable number of columns (2-6) + const numColumns = Math.min(Math.max(maxColumns, 2), 6); + + // Calculate actual item width + const totalMargins = ITEM_MARGIN_TOTAL * numColumns; + const itemWidth = Math.min((availableWidth - totalMargins) / numColumns, MAX_ITEM_WIDTH); + + return { + numColumns, + itemWidth + }; +}; + +const catalogLayout = calculateCatalogLayout(width); +const NUM_COLUMNS = catalogLayout.numColumns; const ITEM_MARGIN = SPACING.sm; -const ITEM_WIDTH = (width - (SPACING.lg * 2) - (ITEM_MARGIN * 2 * NUM_COLUMNS)) / NUM_COLUMNS; +const ITEM_WIDTH = catalogLayout.itemWidth; // Create a styles creator function that accepts the theme colors const createStyles = (colors: any) => StyleSheet.create({ diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index f781b0ddd..8da022b4f 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -679,7 +679,32 @@ const HomeScreen = () => { }; const { width, height } = Dimensions.get('window'); -const POSTER_WIDTH = (width - 50) / 3; + +// Dynamic poster calculation based on screen width +const calculatePosterLayout = (screenWidth: number) => { + const MIN_POSTER_WIDTH = 110; // Minimum poster width for readability + const MAX_POSTER_WIDTH = 140; // Maximum poster width to prevent oversized posters + const HORIZONTAL_PADDING = 50; // Total horizontal padding/margins + + // Calculate how many posters can fit + const availableWidth = screenWidth - HORIZONTAL_PADDING; + const maxColumns = Math.floor(availableWidth / MIN_POSTER_WIDTH); + + // Limit to reasonable number of columns (3-6) + const numColumns = Math.min(Math.max(maxColumns, 3), 6); + + // Calculate actual poster width + const posterWidth = Math.min(availableWidth / numColumns, MAX_POSTER_WIDTH); + + return { + numColumns, + posterWidth, + spacing: 12 // Space between posters + }; +}; + +const posterLayout = calculatePosterLayout(width); +const POSTER_WIDTH = posterLayout.posterWidth; const styles = StyleSheet.create({ container: { diff --git a/src/styles/homeStyles.ts b/src/styles/homeStyles.ts index dff5863ac..c74c122eb 100644 --- a/src/styles/homeStyles.ts +++ b/src/styles/homeStyles.ts @@ -1,7 +1,32 @@ import { StyleSheet, Dimensions, Platform } from 'react-native'; const { width, height } = Dimensions.get('window'); -export const POSTER_WIDTH = (width - 50) / 3; + +// Dynamic poster calculation based on screen width +const calculatePosterLayout = (screenWidth: number) => { + const MIN_POSTER_WIDTH = 110; // Minimum poster width for readability + const MAX_POSTER_WIDTH = 140; // Maximum poster width to prevent oversized posters + const HORIZONTAL_PADDING = 50; // Total horizontal padding/margins + + // Calculate how many posters can fit + const availableWidth = screenWidth - HORIZONTAL_PADDING; + const maxColumns = Math.floor(availableWidth / MIN_POSTER_WIDTH); + + // Limit to reasonable number of columns (3-6) + const numColumns = Math.min(Math.max(maxColumns, 3), 6); + + // Calculate actual poster width + const posterWidth = Math.min(availableWidth / numColumns, MAX_POSTER_WIDTH); + + return { + numColumns, + posterWidth, + spacing: 12 // Space between posters + }; +}; + +const posterLayout = calculatePosterLayout(width); +export const POSTER_WIDTH = posterLayout.posterWidth; export const POSTER_HEIGHT = POSTER_WIDTH * 1.5; export const HORIZONTAL_PADDING = 16; diff --git a/src/utils/posterUtils.ts b/src/utils/posterUtils.ts new file mode 100644 index 000000000..4fa4c1dc3 --- /dev/null +++ b/src/utils/posterUtils.ts @@ -0,0 +1,82 @@ +import { Dimensions } from 'react-native'; + +export interface PosterLayoutConfig { + minPosterWidth: number; + maxPosterWidth: number; + horizontalPadding: number; + minColumns: number; + maxColumns: number; + spacing: number; +} + +export interface PosterLayout { + numColumns: number; + posterWidth: number; + spacing: number; +} + +// Default configuration for main home sections +export const DEFAULT_POSTER_CONFIG: PosterLayoutConfig = { + minPosterWidth: 110, + maxPosterWidth: 140, + horizontalPadding: 50, + minColumns: 3, + maxColumns: 6, + spacing: 12 +}; + +// Configuration for More Like This section (smaller posters, more items) +export const MORE_LIKE_THIS_CONFIG: PosterLayoutConfig = { + minPosterWidth: 100, + maxPosterWidth: 130, + horizontalPadding: 48, + minColumns: 3, + maxColumns: 7, + spacing: 12 +}; + +// Configuration for Continue Watching section (larger posters, fewer items) +export const CONTINUE_WATCHING_CONFIG: PosterLayoutConfig = { + minPosterWidth: 120, + maxPosterWidth: 160, + horizontalPadding: 40, + minColumns: 2, + maxColumns: 5, + spacing: 12 +}; + +export const calculatePosterLayout = ( + screenWidth: number, + config: PosterLayoutConfig = DEFAULT_POSTER_CONFIG +): PosterLayout => { + const { + minPosterWidth, + maxPosterWidth, + horizontalPadding, + minColumns, + maxColumns, + spacing + } = config; + + // Calculate how many posters can fit + const availableWidth = screenWidth - horizontalPadding; + const maxColumnsBasedOnWidth = Math.floor(availableWidth / minPosterWidth); + + // Limit to reasonable number of columns + const numColumns = Math.min(Math.max(maxColumnsBasedOnWidth, minColumns), maxColumns); + + // Calculate actual poster width + const posterWidth = Math.min(availableWidth / numColumns, maxPosterWidth); + + return { + numColumns, + posterWidth, + spacing + }; +}; + +// Helper function to get current screen dimensions +export const getCurrentPosterLayout = (config?: PosterLayoutConfig): PosterLayout => { + const { width } = Dimensions.get('window'); + return calculatePosterLayout(width, config); +}; \ No newline at end of file