From 59c0b6ba1b16c7877d1251c64443c570e82332e7 Mon Sep 17 00:00:00 2001 From: tapframe Date: Wed, 17 Sep 2025 13:36:05 +0530 Subject: [PATCH] Homescreen layout and visual glithches optimization. --- src/components/home/ContentItem.tsx | 46 +++++++++++-- src/hooks/useSettings.ts | 6 ++ src/screens/LibraryScreen.tsx | 103 +++++++++++----------------- 3 files changed, 86 insertions(+), 69 deletions(-) diff --git a/src/components/home/ContentItem.tsx b/src/components/home/ContentItem.tsx index 3738c446..f4ff1bce 100644 --- a/src/components/home/ContentItem.tsx +++ b/src/components/home/ContentItem.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; -import { View, TouchableOpacity, ActivityIndicator, StyleSheet, Dimensions, Platform, Text } from 'react-native'; +import { View, TouchableOpacity, ActivityIndicator, StyleSheet, Dimensions, Platform, Text, Animated } from 'react-native'; import { Image as ExpoImage } from 'expo-image'; import { MaterialIcons } from '@expo/vector-icons'; import { useTheme } from '../../contexts/ThemeContext'; @@ -66,8 +66,9 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe const [shouldLoadImageState, setShouldLoadImageState] = useState(false); const [retryCount, setRetryCount] = useState(0); const { currentTheme } = useTheme(); - const { settings } = useSettings(); + const { settings, isLoaded } = useSettings(); const posterRadius = typeof settings.posterBorderRadius === 'number' ? settings.posterBorderRadius : 12; + const fadeInOpacity = React.useRef(new Animated.Value(0)).current; // Memoize poster width calculation to avoid recalculating on every render const posterWidth = React.useMemo(() => { switch (settings.posterSize) { @@ -157,9 +158,42 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe return item.poster; }, [item.poster, retryCount, item.id]); + // Smoothly fade in content when settings are ready + useEffect(() => { + if (isLoaded) { + fadeInOpacity.setValue(0); + Animated.timing(fadeInOpacity, { + toValue: 1, + duration: 180, + useNativeDriver: true, + }).start(); + } + }, [isLoaded, fadeInOpacity]); + + // While settings load, render a placeholder with reserved space (poster aspect + title) + if (!isLoaded) { + const placeholderRadius = 12; + return ( + + + {/* Reserve space for title to keep section spacing stable */} + + + ); + } + return ( <> - + { @@ -227,7 +259,7 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe {item.name} )} - + { const [settings, setSettings] = useState(DEFAULT_SETTINGS); + const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { loadSettings(); @@ -180,6 +181,10 @@ export const useSettings = () => { // Fallback to default settings on error setSettings(DEFAULT_SETTINGS); } + finally { + // Mark settings as loaded so UI can render with correct values without flicker + setIsLoaded(true); + } }; const updateSetting = useCallback(async ( @@ -217,6 +222,7 @@ export const useSettings = () => { return { settings, updateSetting, + isLoaded, }; }; diff --git a/src/screens/LibraryScreen.tsx b/src/screens/LibraryScreen.tsx index e4cea5f2..a40fbc80 100644 --- a/src/screens/LibraryScreen.tsx +++ b/src/screens/LibraryScreen.tsx @@ -209,7 +209,7 @@ const LibraryScreen = () => { const { numColumns, itemWidth } = useMemo(() => getGridLayout(width), [width]); const [loading, setLoading] = useState(true); const [libraryItems, setLibraryItems] = useState([]); - const [filter, setFilter] = useState<'all' | 'movies' | 'series'>('all'); + const [filter, setFilter] = useState<'trakt' | 'movies' | 'series'>('movies'); const [showTraktContent, setShowTraktContent] = useState(false); const [selectedTraktFolder, setSelectedTraktFolder] = useState(null); const insets = useSafeAreaInsets(); @@ -295,7 +295,6 @@ const LibraryScreen = () => { }, []); const filteredItems = libraryItems.filter(item => { - if (filter === 'all') return true; if (filter === 'movies') return item.type === 'movie'; if (filter === 'series') return item.type === 'series'; return true; @@ -776,7 +775,7 @@ const LibraryScreen = () => { ); }; - const renderFilter = (filterType: 'all' | 'movies' | 'series', label: string, iconName: keyof typeof MaterialIcons.glyphMap) => { + const renderFilter = (filterType: 'trakt' | 'movies' | 'series', label: string, iconName: keyof typeof MaterialIcons.glyphMap) => { const isActive = filter === filterType; return ( @@ -786,15 +785,33 @@ const LibraryScreen = () => { isActive && { backgroundColor: currentTheme.colors.primary }, { shadowColor: currentTheme.colors.black } ]} - onPress={() => setFilter(filterType)} + onPress={() => { + if (filterType === 'trakt') { + if (!traktAuthenticated) { + navigation.navigate('TraktSettings'); + } else { + setShowTraktContent(true); + setSelectedTraktFolder(null); + loadAllCollections(); + } + return; + } + setFilter(filterType); + }} activeOpacity={0.7} > - + {filterType === 'trakt' ? ( + + + + ) : ( + + )} { return ; } - // Combine regular library items with Trakt folder - const allItems = []; - - // Add Trakt folder if authenticated or as connection prompt - if (traktAuthenticated || !traktAuthenticated) { - allItems.push({ type: 'trakt-folder', id: 'trakt-folder' }); - } - - // Add filtered library items - allItems.push(...filteredItems); - - if (allItems.length === 0) { - return ( - - - Your library is empty - - Add content to your library to keep track of what you're watching - - navigation.navigate('MainTabs')} - activeOpacity={0.7} - > - Explore Content - - - ); + if (filteredItems.length === 0) { + // Intentionally render nothing to match the minimal empty state in the design + return ; } return ( - { - if (item.type === 'trakt-folder') { - return renderTraktFolder(); - } - return renderItem({ item: item as LibraryItem }); - }} - keyExtractor={item => item.id} - numColumns={numColumns} - contentContainerStyle={styles.listContainer} - showsVerticalScrollIndicator={false} - onEndReachedThreshold={0.7} - onEndReached={() => {}} - /> + renderItem({ item: item as LibraryItem })} + keyExtractor={item => item.id} + numColumns={numColumns} + contentContainerStyle={styles.listContainer} + showsVerticalScrollIndicator={false} + onEndReachedThreshold={0.7} + onEndReached={() => {}} + /> ); }; @@ -937,7 +916,7 @@ const LibraryScreen = () => { contentContainerStyle={styles.filtersContainer} style={styles.filtersScrollView} > - {renderFilter('all', 'All', 'apps')} + {renderFilter('trakt', 'Trakt', 'pan-tool')} {renderFilter('movies', 'Movies', 'movie')} {renderFilter('series', 'TV Shows', 'live-tv')}