mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-28 13:28:48 +00:00
fixed herosection loading issue
This commit is contained in:
parent
9eea58ef98
commit
12c591216e
4 changed files with 126 additions and 130 deletions
|
|
@ -49,6 +49,10 @@ const { width, height } = Dimensions.get('window');
|
|||
// Utility to determine if device is tablet-sized
|
||||
const isTablet = width >= 768;
|
||||
|
||||
// Simple perf timer helper
|
||||
const nowMs = () => Date.now();
|
||||
const since = (start: number) => `${(nowMs() - start).toFixed(0)}ms`;
|
||||
|
||||
const NoFeaturedContent = () => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
|
|
@ -142,6 +146,19 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
const [logoLoadError, setLogoLoadError] = useState(false);
|
||||
// Add a ref to track logo fetch in progress
|
||||
const logoFetchInProgress = useRef<boolean>(false);
|
||||
const firstRenderTsRef = useRef<number>(nowMs());
|
||||
const lastContentChangeTsRef = useRef<number>(0);
|
||||
|
||||
// Initial diagnostics
|
||||
useEffect(() => {
|
||||
logger.info('[FeaturedContent] mounted', {
|
||||
isTablet,
|
||||
screen: { width, height },
|
||||
});
|
||||
return () => {
|
||||
logger.info('[FeaturedContent] unmounted');
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Enhanced poster transition animations
|
||||
const posterScale = useSharedValue(1);
|
||||
|
|
@ -185,6 +202,8 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
|
||||
// Preload the image
|
||||
const preloadImage = async (url: string): Promise<boolean> => {
|
||||
const t0 = nowMs();
|
||||
logger.debug('[FeaturedContent] preloadImage:start', { url });
|
||||
// Skip if already cached to prevent redundant prefetch
|
||||
if (imageCache[url]) return true;
|
||||
|
||||
|
|
@ -205,10 +224,12 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
timeout,
|
||||
]);
|
||||
imageCache[url] = true;
|
||||
logger.debug('[FeaturedContent] preloadImage:success', { url, duration: since(t0) });
|
||||
return true;
|
||||
} catch (error) {
|
||||
// Clear any partial cache entry on error
|
||||
delete imageCache[url];
|
||||
logger.warn('[FeaturedContent] preloadImage:error', { url, duration: since(t0), error: String(error) });
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
@ -224,6 +245,8 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
|
||||
const fetchLogo = async () => {
|
||||
logoFetchInProgress.current = true;
|
||||
const t0 = nowMs();
|
||||
logger.info('[FeaturedContent] fetchLogo:start', { id: featuredContent?.id, type: featuredContent?.type });
|
||||
|
||||
try {
|
||||
const contentId = featuredContent.id;
|
||||
|
|
@ -234,8 +257,10 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
const logoPreference = settings.logoSourcePreference || 'metahub';
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
|
||||
// Reset state for new fetch
|
||||
setLogoUrl(null);
|
||||
// Reset state for new fetch only if switching to a different item
|
||||
if (prevContentIdRef.current !== contentId) {
|
||||
setLogoUrl(null);
|
||||
}
|
||||
setLogoLoadError(false);
|
||||
|
||||
// Extract IDs
|
||||
|
|
@ -274,6 +299,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
let fallbackAttempted = false;
|
||||
|
||||
// --- Logo Fetching Logic ---
|
||||
logger.debug('[FeaturedContent] fetchLogo:ids', { imdbId, tmdbId, preference: logoPreference, lang: preferredLanguage });
|
||||
|
||||
if (logoPreference === 'metahub') {
|
||||
// Primary: Metahub (needs imdbId)
|
||||
|
|
@ -281,9 +307,11 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
primaryAttempted = true;
|
||||
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||
try {
|
||||
const tHead = nowMs();
|
||||
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
||||
if (response.ok) {
|
||||
finalLogoUrl = metahubUrl;
|
||||
logger.debug('[FeaturedContent] fetchLogo:metahub:ok', { url: metahubUrl, duration: since(tHead) });
|
||||
}
|
||||
} catch (error) { /* Log if needed */ }
|
||||
}
|
||||
|
|
@ -293,9 +321,11 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
fallbackAttempted = true;
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const tTmdb = nowMs();
|
||||
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
|
||||
if (logoUrl) {
|
||||
finalLogoUrl = logoUrl;
|
||||
logger.debug('[FeaturedContent] fetchLogo:tmdb:fallback:ok', { url: logoUrl, duration: since(tTmdb) });
|
||||
}
|
||||
} catch (error) { /* Log if needed */ }
|
||||
}
|
||||
|
|
@ -306,9 +336,11 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
primaryAttempted = true;
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const tTmdb = nowMs();
|
||||
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
|
||||
if (logoUrl) {
|
||||
finalLogoUrl = logoUrl;
|
||||
logger.debug('[FeaturedContent] fetchLogo:tmdb:ok', { url: logoUrl, duration: since(tTmdb) });
|
||||
}
|
||||
} catch (error) { /* Log if needed */ }
|
||||
}
|
||||
|
|
@ -318,9 +350,11 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
fallbackAttempted = true;
|
||||
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||
try {
|
||||
const tHead = nowMs();
|
||||
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
||||
if (response.ok) {
|
||||
finalLogoUrl = metahubUrl;
|
||||
logger.debug('[FeaturedContent] fetchLogo:metahub:fallback:ok', { url: metahubUrl, duration: since(tHead) });
|
||||
}
|
||||
} catch (error) { /* Log if needed */ }
|
||||
}
|
||||
|
|
@ -329,18 +363,22 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
// --- Set Final Logo ---
|
||||
if (finalLogoUrl) {
|
||||
setLogoUrl(finalLogoUrl);
|
||||
logger.info('[FeaturedContent] fetchLogo:done', { id: contentId, result: 'ok', duration: since(t0) });
|
||||
} else if (currentLogo) {
|
||||
// Use existing logo only if primary and fallback failed or weren't applicable
|
||||
setLogoUrl(currentLogo);
|
||||
logger.info('[FeaturedContent] fetchLogo:done', { id: contentId, result: 'existing', duration: since(t0) });
|
||||
} else {
|
||||
// No logo found from any source
|
||||
setLogoLoadError(true);
|
||||
logger.warn('[FeaturedContent] fetchLogo:none', { id: contentId, primaryAttempted, fallbackAttempted, duration: since(t0) });
|
||||
// logger.warn(`[FeaturedContent] No logo found for ${contentData.name} (${contentId}) with preference ${logoPreference}. Primary attempted: ${primaryAttempted}, Fallback attempted: ${fallbackAttempted}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// logger.error('[FeaturedContent] Error in fetchLogo:', error);
|
||||
setLogoLoadError(true);
|
||||
logger.error('[FeaturedContent] fetchLogo:error', { error: String(error), duration: since(t0) });
|
||||
} finally {
|
||||
logoFetchInProgress.current = false;
|
||||
}
|
||||
|
|
@ -357,6 +395,8 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
const posterUrl = featuredContent.banner || featuredContent.poster;
|
||||
const contentId = featuredContent.id;
|
||||
const isContentChange = contentId !== prevContentIdRef.current;
|
||||
const t0 = nowMs();
|
||||
logger.info('[FeaturedContent] content:update', { id: contentId, isContentChange, posterUrlExists: Boolean(posterUrl), sinceMount: since(firstRenderTsRef.current) });
|
||||
|
||||
// Enhanced content change detection and animations
|
||||
if (isContentChange) {
|
||||
|
|
@ -405,6 +445,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
|
||||
// Load poster with enhanced transition
|
||||
if (posterUrl) {
|
||||
const tPoster = nowMs();
|
||||
const posterSuccess = await preloadImage(posterUrl);
|
||||
if (posterSuccess) {
|
||||
// Animate in new poster with scale and fade
|
||||
|
|
@ -420,6 +461,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
duration: 600,
|
||||
easing: Easing.out(Easing.cubic)
|
||||
});
|
||||
logger.debug('[FeaturedContent] poster:ready', { id: contentId, duration: since(tPoster) });
|
||||
|
||||
// Animate content back in with delay
|
||||
contentOpacity.value = withDelay(200, withTiming(1, {
|
||||
|
|
@ -435,16 +477,20 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
|
||||
// Load logo if available with enhanced timing
|
||||
if (logoUrl) {
|
||||
const tLogo = nowMs();
|
||||
const logoSuccess = await preloadImage(logoUrl);
|
||||
if (logoSuccess) {
|
||||
logoOpacity.value = withDelay(500, withTiming(1, {
|
||||
duration: 600,
|
||||
easing: Easing.out(Easing.cubic)
|
||||
}));
|
||||
logger.debug('[FeaturedContent] logo:ready', { id: contentId, duration: since(tLogo) });
|
||||
} else {
|
||||
setLogoLoadError(true);
|
||||
logger.warn('[FeaturedContent] logo:failed', { id: contentId, duration: since(tLogo) });
|
||||
}
|
||||
}
|
||||
logger.info('[FeaturedContent] images:load:done', { id: contentId, total: since(t0) });
|
||||
};
|
||||
|
||||
loadImages();
|
||||
|
|
@ -453,6 +499,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
const onLogoLoadError = () => {
|
||||
setLogoLoaded(true); // Treat error as "loaded" to stop spinner
|
||||
setLogoError(true);
|
||||
logger.warn('[FeaturedContent] logo:onError', { id: featuredContent?.id, url: logoUrl });
|
||||
};
|
||||
|
||||
const handleInfoPress = () => {
|
||||
|
|
@ -464,13 +511,15 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
}
|
||||
};
|
||||
|
||||
// Show skeleton while loading to avoid empty state flash and sluggish feel
|
||||
if (loading) {
|
||||
// Show skeleton only if we're loading AND no content is available yet
|
||||
if (loading && !featuredContent) {
|
||||
logger.debug('[FeaturedContent] render:loading', { sinceMount: since(firstRenderTsRef.current) });
|
||||
return <SkeletonFeatured />;
|
||||
}
|
||||
|
||||
if (!featuredContent) {
|
||||
// Suppress empty state while loading to avoid flash on startup/hydration
|
||||
logger.debug('[FeaturedContent] render:no-featured-content', { sinceMount: since(firstRenderTsRef.current) });
|
||||
return <NoFeaturedContent />;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@ interface MetadataSourceSelectorProps {
|
|||
contentType: string;
|
||||
onSourceChange: (sourceId: string, sourceType: 'addon' | 'tmdb') => void;
|
||||
disabled?: boolean;
|
||||
enableComplementary?: boolean;
|
||||
onComplementaryToggle?: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
const MetadataSourceSelector: React.FC<MetadataSourceSelectorProps> = ({
|
||||
|
|
@ -41,8 +39,6 @@ const MetadataSourceSelector: React.FC<MetadataSourceSelectorProps> = ({
|
|||
contentType,
|
||||
onSourceChange,
|
||||
disabled = false,
|
||||
enableComplementary = false,
|
||||
onComplementaryToggle,
|
||||
}) => {
|
||||
const { currentTheme } = useTheme();
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
|
@ -292,46 +288,7 @@ const MetadataSourceSelector: React.FC<MetadataSourceSelectorProps> = ({
|
|||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Complementary Metadata Toggle */}
|
||||
<View style={styles.complementaryToggle}>
|
||||
<View style={styles.toggleContent}>
|
||||
<MaterialIcons
|
||||
name="merge-type"
|
||||
size={20}
|
||||
color={currentTheme.colors.primary}
|
||||
/>
|
||||
<View style={styles.toggleText}>
|
||||
<Text style={[styles.toggleTitle, { color: currentTheme.colors.text }]}>
|
||||
Complementary Metadata
|
||||
</Text>
|
||||
<Text style={[styles.toggleDescription, { color: currentTheme.colors.textMuted }]}>
|
||||
Fetch missing data from other sources
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.toggleSwitch,
|
||||
{
|
||||
backgroundColor: enableComplementary
|
||||
? currentTheme.colors.primary
|
||||
: currentTheme.colors.elevation2,
|
||||
}
|
||||
]}
|
||||
onPress={() => onComplementaryToggle?.(!enableComplementary)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.toggleThumb,
|
||||
{
|
||||
transform: [{ translateX: enableComplementary ? 20 : 2 }],
|
||||
backgroundColor: currentTheme.colors.white,
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
|
||||
{loading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
|
|
@ -561,51 +518,6 @@ const styles = StyleSheet.create({
|
|||
checkContainer: {
|
||||
padding: 4,
|
||||
},
|
||||
complementaryToggle: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: 'rgba(255, 255, 255, 0.08)',
|
||||
},
|
||||
toggleContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
toggleText: {
|
||||
marginLeft: 16,
|
||||
flex: 1,
|
||||
},
|
||||
toggleTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginBottom: 2,
|
||||
},
|
||||
toggleDescription: {
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
opacity: 0.8,
|
||||
},
|
||||
toggleSwitch: {
|
||||
width: 44,
|
||||
height: 24,
|
||||
borderRadius: 12,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
toggleThumb: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
borderRadius: 9,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 2,
|
||||
elevation: 2,
|
||||
},
|
||||
});
|
||||
|
||||
export default MetadataSourceSelector;
|
||||
|
|
@ -24,6 +24,7 @@ const persistentStore = {
|
|||
// Cache timeout in milliseconds (e.g., 5 minutes)
|
||||
const CACHE_TIMEOUT = 5 * 60 * 1000;
|
||||
const STORAGE_KEY = 'featured_content_cache_v1';
|
||||
const DISABLE_CACHE = true;
|
||||
|
||||
export function useFeaturedContent() {
|
||||
const [featuredContent, setFeaturedContent] = useState<StreamingContent | null>(persistentStore.featuredContent);
|
||||
|
|
@ -52,32 +53,42 @@ export function useFeaturedContent() {
|
|||
}, []);
|
||||
|
||||
const loadFeaturedContent = useCallback(async (forceRefresh = false) => {
|
||||
const t0 = Date.now();
|
||||
logger.info('[useFeaturedContent] load:start', { forceRefresh, contentSource, selectedCatalogsCount: (selectedCatalogs || []).length });
|
||||
// First, ensure contentSource matches current settings (could be outdated due to async updates)
|
||||
if (contentSource !== settings.featuredContentSource) {
|
||||
console.log(`Updating content source from ${contentSource} to ${settings.featuredContentSource}`);
|
||||
logger.debug('[useFeaturedContent] load:source-mismatch', { from: contentSource, to: settings.featuredContentSource });
|
||||
setContentSource(settings.featuredContentSource);
|
||||
// We return here and let the effect triggered by contentSource change handle the loading
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we should use cached data
|
||||
// Check if we should use cached data (disabled if DISABLE_CACHE)
|
||||
const now = Date.now();
|
||||
const cacheAge = now - persistentStore.lastFetchTime;
|
||||
|
||||
if (!forceRefresh &&
|
||||
persistentStore.featuredContent &&
|
||||
persistentStore.allFeaturedContent.length > 0 &&
|
||||
cacheAge < CACHE_TIMEOUT) {
|
||||
// Use cached data
|
||||
console.log('Using cached featured content data');
|
||||
setFeaturedContent(persistentStore.featuredContent);
|
||||
setAllFeaturedContent(persistentStore.allFeaturedContent);
|
||||
setLoading(false);
|
||||
persistentStore.isFirstLoad = false;
|
||||
return;
|
||||
logger.debug('[useFeaturedContent] cache:status', {
|
||||
disabled: DISABLE_CACHE,
|
||||
hasFeatured: Boolean(persistentStore.featuredContent),
|
||||
allCount: persistentStore.allFeaturedContent?.length || 0,
|
||||
cacheAgeMs: cacheAge,
|
||||
timeoutMs: CACHE_TIMEOUT,
|
||||
});
|
||||
if (!DISABLE_CACHE) {
|
||||
if (!forceRefresh &&
|
||||
persistentStore.featuredContent &&
|
||||
persistentStore.allFeaturedContent.length > 0 &&
|
||||
cacheAge < CACHE_TIMEOUT) {
|
||||
// Use cached data
|
||||
logger.info('[useFeaturedContent] cache:use', { duration: `${Date.now() - t0}ms` });
|
||||
setFeaturedContent(persistentStore.featuredContent);
|
||||
setAllFeaturedContent(persistentStore.allFeaturedContent);
|
||||
setLoading(false);
|
||||
persistentStore.isFirstLoad = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Loading featured content from ${contentSource}`);
|
||||
logger.info('[useFeaturedContent] fetch:start', { source: contentSource });
|
||||
setLoading(true);
|
||||
cleanup();
|
||||
abortControllerRef.current = new AbortController();
|
||||
|
|
@ -88,7 +99,9 @@ export function useFeaturedContent() {
|
|||
|
||||
if (contentSource === 'tmdb') {
|
||||
// Load from TMDB trending
|
||||
const tTmdb = Date.now();
|
||||
const trendingResults = await tmdbService.getTrending('movie', 'day');
|
||||
logger.info('[useFeaturedContent] tmdb:trending', { count: trendingResults?.length || 0, duration: `${Date.now() - tTmdb}ms` });
|
||||
|
||||
if (signal.aborted) return;
|
||||
|
||||
|
|
@ -115,6 +128,7 @@ export function useFeaturedContent() {
|
|||
});
|
||||
|
||||
// Then fetch logos for each item
|
||||
const tLogos = Date.now();
|
||||
formattedContent = await Promise.all(
|
||||
preFormattedContent.map(async (item) => {
|
||||
try {
|
||||
|
|
@ -135,10 +149,13 @@ export function useFeaturedContent() {
|
|||
}
|
||||
})
|
||||
);
|
||||
logger.info('[useFeaturedContent] tmdb:logos', { count: formattedContent.length, duration: `${Date.now() - tLogos}ms` });
|
||||
}
|
||||
} else {
|
||||
// Load from installed catalogs
|
||||
const tCats = Date.now();
|
||||
const catalogs = await catalogService.getHomeCatalogs();
|
||||
logger.info('[useFeaturedContent] catalogs:list', { count: catalogs?.length || 0, duration: `${Date.now() - tCats}ms` });
|
||||
|
||||
if (signal.aborted) return;
|
||||
|
||||
|
|
@ -153,14 +170,17 @@ export function useFeaturedContent() {
|
|||
return selectedCatalogs.includes(catalogId);
|
||||
})
|
||||
: catalogs; // Use all catalogs if none specifically selected
|
||||
logger.debug('[useFeaturedContent] catalogs:filtered', { filteredCount: filteredCatalogs.length, selectedCount: selectedCatalogs?.length || 0 });
|
||||
|
||||
// Flatten all catalog items into a single array, filter out items without posters
|
||||
const tFlat = Date.now();
|
||||
const allItems = filteredCatalogs.flatMap(catalog => catalog.items)
|
||||
.filter(item => item.poster)
|
||||
.filter((item, index, self) =>
|
||||
// Remove duplicates based on ID
|
||||
index === self.findIndex(t => t.id === item.id)
|
||||
);
|
||||
logger.info('[useFeaturedContent] catalogs:items', { total: allItems.length, duration: `${Date.now() - tFlat}ms` });
|
||||
|
||||
// Sort by popular, newest, etc. (possibly enhanced later)
|
||||
formattedContent = allItems.sort(() => Math.random() - 0.5).slice(0, 10);
|
||||
|
|
@ -171,6 +191,7 @@ export function useFeaturedContent() {
|
|||
|
||||
// Safety guard: if nothing came back within a reasonable time, stop loading
|
||||
if (!formattedContent || formattedContent.length === 0) {
|
||||
logger.warn('[useFeaturedContent] results:empty');
|
||||
// Fall back to any cached featured item so UI can render something
|
||||
const cachedJson = await AsyncStorage.getItem(STORAGE_KEY).catch(() => null);
|
||||
if (cachedJson) {
|
||||
|
|
@ -180,14 +201,17 @@ export function useFeaturedContent() {
|
|||
formattedContent = Array.isArray(parsed.allFeaturedContent) && parsed.allFeaturedContent.length > 0
|
||||
? parsed.allFeaturedContent
|
||||
: [parsed.featuredContent];
|
||||
logger.info('[useFeaturedContent] fallback:storage', { count: formattedContent.length });
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
// Update persistent store with the new data
|
||||
// Update persistent store with the new data (no lastFetchTime when cache disabled)
|
||||
persistentStore.allFeaturedContent = formattedContent;
|
||||
persistentStore.lastFetchTime = now;
|
||||
if (!DISABLE_CACHE) {
|
||||
persistentStore.lastFetchTime = now;
|
||||
}
|
||||
persistentStore.isFirstLoad = false;
|
||||
|
||||
setAllFeaturedContent(formattedContent);
|
||||
|
|
@ -196,40 +220,51 @@ export function useFeaturedContent() {
|
|||
persistentStore.featuredContent = formattedContent[0];
|
||||
setFeaturedContent(formattedContent[0]);
|
||||
currentIndexRef.current = 0;
|
||||
// Persist cache for fast startup
|
||||
try {
|
||||
await AsyncStorage.setItem(
|
||||
STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
ts: now,
|
||||
featuredContent: formattedContent[0],
|
||||
allFeaturedContent: formattedContent,
|
||||
})
|
||||
);
|
||||
} catch {}
|
||||
// Persist cache for fast startup (skipped when cache disabled)
|
||||
if (!DISABLE_CACHE) {
|
||||
try {
|
||||
await AsyncStorage.setItem(
|
||||
STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
ts: now,
|
||||
featuredContent: formattedContent[0],
|
||||
allFeaturedContent: formattedContent,
|
||||
})
|
||||
);
|
||||
logger.debug('[useFeaturedContent] cache:written', { firstId: formattedContent[0]?.id });
|
||||
} catch {}
|
||||
}
|
||||
} else {
|
||||
persistentStore.featuredContent = null;
|
||||
setFeaturedContent(null);
|
||||
// Clear persisted cache on empty
|
||||
try { await AsyncStorage.removeItem(STORAGE_KEY); } catch {}
|
||||
// Clear persisted cache on empty (skipped when cache disabled)
|
||||
if (!DISABLE_CACHE) {
|
||||
try { await AsyncStorage.removeItem(STORAGE_KEY); } catch {}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (signal.aborted) {
|
||||
logger.info('Featured content fetch aborted');
|
||||
logger.info('[useFeaturedContent] fetch:aborted');
|
||||
} else {
|
||||
logger.error('Failed to load featured content:', error);
|
||||
logger.error('[useFeaturedContent] fetch:error', { error: String(error) });
|
||||
}
|
||||
setFeaturedContent(null);
|
||||
setAllFeaturedContent([]);
|
||||
} finally {
|
||||
if (!signal.aborted) {
|
||||
setLoading(false);
|
||||
logger.info('[useFeaturedContent] load:done', { duration: `${Date.now() - t0}ms` });
|
||||
}
|
||||
}
|
||||
}, [cleanup, genreMap, loadingGenres, contentSource, selectedCatalogs]);
|
||||
|
||||
// Hydrate from persisted cache immediately for instant render
|
||||
useEffect(() => {
|
||||
if (DISABLE_CACHE) {
|
||||
// Skip hydration entirely
|
||||
logger.debug('[useFeaturedContent] hydrate:skipped');
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
try {
|
||||
|
|
@ -245,6 +280,7 @@ export function useFeaturedContent() {
|
|||
setFeaturedContent(parsed.featuredContent);
|
||||
setAllFeaturedContent(persistentStore.allFeaturedContent);
|
||||
setLoading(false);
|
||||
logger.info('[useFeaturedContent] hydrate:storage', { allCount: persistentStore.allFeaturedContent.length });
|
||||
}
|
||||
} catch {}
|
||||
})();
|
||||
|
|
@ -268,6 +304,7 @@ export function useFeaturedContent() {
|
|||
|
||||
// Force refresh if settings changed during app restart
|
||||
if (settingsChanged) {
|
||||
logger.info('[useFeaturedContent] settings:changed', { source: settings.featuredContentSource, selectedCount: settings.selectedHeroCatalogs?.length || 0 });
|
||||
loadFeaturedContent(true);
|
||||
}
|
||||
}, [settings, loadFeaturedContent]);
|
||||
|
|
@ -278,9 +315,7 @@ export function useFeaturedContent() {
|
|||
// Only refresh if current content source is different from settings
|
||||
// This prevents duplicate refreshes when HomeScreen also handles this event
|
||||
if (contentSource !== settings.featuredContentSource) {
|
||||
console.log('Content source changed, refreshing featured content');
|
||||
console.log('Current content source:', contentSource);
|
||||
console.log('New settings source:', settings.featuredContentSource);
|
||||
logger.info('[useFeaturedContent] event:content-source-changed', { from: contentSource, to: settings.featuredContentSource });
|
||||
// Content source will be updated in the next render cycle due to state updates
|
||||
// No need to call loadFeaturedContent here as it will be triggered by contentSource change
|
||||
} else if (
|
||||
|
|
@ -288,7 +323,7 @@ export function useFeaturedContent() {
|
|||
JSON.stringify(selectedCatalogs) !== JSON.stringify(settings.selectedHeroCatalogs)
|
||||
) {
|
||||
// Only refresh if using catalogs and selected catalogs changed
|
||||
console.log('Selected catalogs changed, refreshing featured content');
|
||||
logger.info('[useFeaturedContent] event:selected-catalogs-changed');
|
||||
loadFeaturedContent(true);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import axios from 'axios';
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
// TMDB API configuration
|
||||
const DEFAULT_API_KEY = '439c478a771f35c05022f9feabcca01c';
|
||||
const DEFAULT_API_KEY = 'd131017ccc6e5462a81c9304d21476de';
|
||||
const BASE_URL = 'https://api.themoviedb.org/3';
|
||||
const TMDB_API_KEY_STORAGE_KEY = 'tmdb_api_key';
|
||||
const USE_CUSTOM_TMDB_API_KEY = 'use_custom_tmdb_api_key';
|
||||
|
|
|
|||
Loading…
Reference in a new issue