Some fixes

This commit is contained in:
tapframe 2025-05-03 15:43:06 +05:30
parent 4f04ae874f
commit dbaadbe61b
6 changed files with 238 additions and 147 deletions

View file

@ -245,7 +245,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
const styles = StyleSheet.create({ const styles = StyleSheet.create({
featuredContainer: { featuredContainer: {
width: '100%', width: '100%',
height: height * 0.5, height: height * 0.48,
marginTop: 0, marginTop: 0,
marginBottom: 8, marginBottom: 8,
position: 'relative', position: 'relative',
@ -285,7 +285,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
justifyContent: 'flex-end', justifyContent: 'flex-end',
paddingHorizontal: 16, paddingHorizontal: 16,
paddingBottom: 12, paddingBottom: 4,
}, },
featuredLogo: { featuredLogo: {
width: width * 0.7, width: width * 0.7,
@ -308,7 +308,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginBottom: 8, marginBottom: 4,
flexWrap: 'wrap', flexWrap: 'wrap',
gap: 4, gap: 4,
}, },
@ -331,7 +331,7 @@ const styles = StyleSheet.create({
justifyContent: 'space-evenly', justifyContent: 'space-evenly',
width: '100%', width: '100%',
flex: 1, flex: 1,
maxHeight: 60, maxHeight: 55,
paddingTop: 0, paddingTop: 0,
}, },
playButton: { playButton: {

View file

@ -11,7 +11,13 @@ const persistentStore = {
featuredContent: null as StreamingContent | null, featuredContent: null as StreamingContent | null,
allFeaturedContent: [] as StreamingContent[], allFeaturedContent: [] as StreamingContent[],
lastFetchTime: 0, lastFetchTime: 0,
isFirstLoad: true isFirstLoad: true,
// Track last used settings to detect changes on app restart
lastSettings: {
showHeroSection: true,
featuredContentSource: 'tmdb' as 'tmdb' | 'catalogs',
selectedHeroCatalogs: [] as string[]
}
}; };
// Cache timeout in milliseconds (e.g., 5 minutes) // Cache timeout in milliseconds (e.g., 5 minutes)
@ -30,7 +36,7 @@ export function useFeaturedContent() {
const { genreMap, loadingGenres } = useGenres(); const { genreMap, loadingGenres } = useGenres();
// Update local state when settings change // Simple update for state variables
useEffect(() => { useEffect(() => {
setContentSource(settings.featuredContentSource); setContentSource(settings.featuredContentSource);
setSelectedCatalogs(settings.selectedHeroCatalogs || []); setSelectedCatalogs(settings.selectedHeroCatalogs || []);
@ -44,6 +50,14 @@ export function useFeaturedContent() {
}, []); }, []);
const loadFeaturedContent = useCallback(async (forceRefresh = false) => { const loadFeaturedContent = useCallback(async (forceRefresh = false) => {
// 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}`);
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
const now = Date.now(); const now = Date.now();
const cacheAge = now - persistentStore.lastFetchTime; const cacheAge = now - persistentStore.lastFetchTime;
@ -53,6 +67,7 @@ export function useFeaturedContent() {
persistentStore.allFeaturedContent.length > 0 && persistentStore.allFeaturedContent.length > 0 &&
cacheAge < CACHE_TIMEOUT) { cacheAge < CACHE_TIMEOUT) {
// Use cached data // Use cached data
console.log('Using cached featured content data');
setFeaturedContent(persistentStore.featuredContent); setFeaturedContent(persistentStore.featuredContent);
setAllFeaturedContent(persistentStore.allFeaturedContent); setAllFeaturedContent(persistentStore.allFeaturedContent);
setLoading(false); setLoading(false);
@ -60,6 +75,7 @@ export function useFeaturedContent() {
return; return;
} }
console.log(`Loading featured content from ${contentSource}`);
setLoading(true); setLoading(true);
cleanup(); cleanup();
abortControllerRef.current = new AbortController(); abortControllerRef.current = new AbortController();
@ -176,32 +192,75 @@ export function useFeaturedContent() {
} }
}, [cleanup, genreMap, loadingGenres, contentSource, selectedCatalogs]); }, [cleanup, genreMap, loadingGenres, contentSource, selectedCatalogs]);
// Check for settings changes, including during app restart
useEffect(() => {
// Check if settings changed while app was closed
const settingsChanged =
persistentStore.lastSettings.showHeroSection !== settings.showHeroSection ||
persistentStore.lastSettings.featuredContentSource !== settings.featuredContentSource ||
JSON.stringify(persistentStore.lastSettings.selectedHeroCatalogs) !== JSON.stringify(settings.selectedHeroCatalogs);
// Update our tracking of last used settings
persistentStore.lastSettings = {
showHeroSection: settings.showHeroSection,
featuredContentSource: settings.featuredContentSource,
selectedHeroCatalogs: [...settings.selectedHeroCatalogs]
};
// Force refresh if settings changed during app restart
if (settingsChanged) {
loadFeaturedContent(true);
}
}, [settings, loadFeaturedContent]);
// Subscribe directly to settings emitter for immediate updates // Subscribe directly to settings emitter for immediate updates
useEffect(() => { useEffect(() => {
const handleSettingsChange = () => { const handleSettingsChange = () => {
// Force refresh when settings change // Only refresh if current content source is different from settings
loadFeaturedContent(true); // 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);
// 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 (
contentSource === 'catalogs' &&
JSON.stringify(selectedCatalogs) !== JSON.stringify(settings.selectedHeroCatalogs)
) {
// Only refresh if using catalogs and selected catalogs changed
console.log('Selected catalogs changed, refreshing featured content');
loadFeaturedContent(true);
}
}; };
// Subscribe to settings changes // Subscribe to settings changes
const unsubscribe = settingsEmitter.addListener(handleSettingsChange); const unsubscribe = settingsEmitter.addListener(handleSettingsChange);
return unsubscribe; return unsubscribe;
}, [loadFeaturedContent]); }, [loadFeaturedContent, settings, contentSource, selectedCatalogs]);
// Load featured content initially and when content source changes // Load featured content initially and when content source changes
useEffect(() => { useEffect(() => {
const shouldForceRefresh = contentSource === 'tmdb' && // Force refresh when switching to catalogs or when catalog selection changes
contentSource !== persistentStore.featuredContent?.type; if (contentSource === 'catalogs') {
// Clear cache when switching to catalogs mode
if (shouldForceRefresh) {
setAllFeaturedContent([]); setAllFeaturedContent([]);
setFeaturedContent(null); setFeaturedContent(null);
persistentStore.allFeaturedContent = []; persistentStore.allFeaturedContent = [];
persistentStore.featuredContent = null; persistentStore.featuredContent = null;
loadFeaturedContent(true);
} else if (contentSource === 'tmdb' && contentSource !== persistentStore.featuredContent?.type) {
// Clear cache when switching to TMDB mode from catalogs
setAllFeaturedContent([]);
setFeaturedContent(null);
persistentStore.allFeaturedContent = [];
persistentStore.featuredContent = null;
loadFeaturedContent(true);
} else {
// Normal load (might use cache if available)
loadFeaturedContent(false);
} }
loadFeaturedContent(shouldForceRefresh);
}, [loadFeaturedContent, contentSource, selectedCatalogs]); }, [loadFeaturedContent, contentSource, selectedCatalogs]);
useEffect(() => { useEffect(() => {

View file

@ -84,8 +84,11 @@ export const useSettings = () => {
try { try {
await AsyncStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(newSettings)); await AsyncStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(newSettings));
setSettings(newSettings); setSettings(newSettings);
console.log(`Setting updated: ${key}`, value);
// Notify all subscribers that settings have changed (if requested) // Notify all subscribers that settings have changed (if requested)
if (emitEvent) { if (emitEvent) {
console.log('Emitting settings change event');
settingsEmitter.emit(); settingsEmitter.emit();
} }
} catch (error) { } catch (error) {

View file

@ -393,40 +393,27 @@ const HomeScreen = () => {
setShowHeroSection(settings.showHeroSection); setShowHeroSection(settings.showHeroSection);
setFeaturedContentSource(settings.featuredContentSource); setFeaturedContentSource(settings.featuredContentSource);
// If hero section is enabled, force a refresh of featured content // The featured content refresh is now handled by the useFeaturedContent hook
if (settings.showHeroSection) { // No need to call refreshFeatured() here to avoid duplicate refreshes
refreshFeatured();
}
}; };
// Subscribe to settings changes // Subscribe to settings changes
const unsubscribe = settingsEmitter.addListener(handleSettingsChange); const unsubscribe = settingsEmitter.addListener(handleSettingsChange);
return unsubscribe; return unsubscribe;
}, [refreshFeatured, settings]); }, [settings]);
// Update the featured content refresh logic to handle persistence // Update the featured content refresh logic to handle persistence
useEffect(() => { useEffect(() => {
// This effect was causing duplicate refreshes - it's now handled in useFeaturedContent
// We'll keep it just to sync the local state with settings
if (showHeroSection && featuredContentSource !== settings.featuredContentSource) { if (showHeroSection && featuredContentSource !== settings.featuredContentSource) {
// Clear any existing timeout // Just update the local state
if (refreshTimeoutRef.current) { setFeaturedContentSource(settings.featuredContentSource);
clearTimeout(refreshTimeoutRef.current);
}
// Set a new timeout to debounce the refresh - only when settings actually change
refreshTimeoutRef.current = setTimeout(() => {
refreshFeatured();
refreshTimeoutRef.current = null;
}, 300);
} }
// Cleanup the timeout on unmount // No timeout needed since we're not refreshing here
return () => { }, [settings.featuredContentSource, showHeroSection]);
if (refreshTimeoutRef.current) {
clearTimeout(refreshTimeoutRef.current);
}
};
}, [featuredContentSource, settings.featuredContentSource, showHeroSection, refreshFeatured]);
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {

View file

@ -269,7 +269,10 @@ const HomeScreenSettings: React.FC = () => {
<View style={styles.radioCardContainer}> <View style={styles.radioCardContainer}>
<RadioOption <RadioOption
selected={settings.featuredContentSource === 'tmdb'} selected={settings.featuredContentSource === 'tmdb'}
onPress={() => handleUpdateSetting('featuredContentSource', 'tmdb')} onPress={() => {
console.log('Selected TMDB source');
handleUpdateSetting('featuredContentSource', 'tmdb');
}}
label="TMDB Trending Movies" label="TMDB Trending Movies"
/> />
<View style={styles.radioDescription}> <View style={styles.radioDescription}>
@ -282,7 +285,10 @@ const HomeScreenSettings: React.FC = () => {
<View style={styles.radioCardContainer}> <View style={styles.radioCardContainer}>
<RadioOption <RadioOption
selected={settings.featuredContentSource === 'catalogs'} selected={settings.featuredContentSource === 'catalogs'}
onPress={() => handleUpdateSetting('featuredContentSource', 'catalogs')} onPress={() => {
console.log('Selected Catalogs source');
handleUpdateSetting('featuredContentSource', 'catalogs');
}}
label="Installed Catalogs" label="Installed Catalogs"
/> />
<View style={styles.radioDescription}> <View style={styles.radioDescription}>

View file

@ -147,12 +147,14 @@ class CatalogService {
async getHomeCatalogs(): Promise<CatalogContent[]> { async getHomeCatalogs(): Promise<CatalogContent[]> {
const addons = await this.getAllAddons(); const addons = await this.getAllAddons();
const catalogs: CatalogContent[] = [];
// Load enabled/disabled settings // Load enabled/disabled settings
const catalogSettingsJson = await AsyncStorage.getItem(CATALOG_SETTINGS_KEY); const catalogSettingsJson = await AsyncStorage.getItem(CATALOG_SETTINGS_KEY);
const catalogSettings = catalogSettingsJson ? JSON.parse(catalogSettingsJson) : {}; const catalogSettings = catalogSettingsJson ? JSON.parse(catalogSettingsJson) : {};
// Create an array of promises for all catalog fetches
const catalogPromises: Promise<CatalogContent | null>[] = [];
// Process addons in order (they're already returned in order from getAllAddons) // Process addons in order (they're already returned in order from getAllAddons)
for (const addon of addons) { for (const addon of addons) {
if (addon.catalogs) { if (addon.catalogs) {
@ -161,54 +163,65 @@ class CatalogService {
const isEnabled = catalogSettings[settingKey] ?? true; const isEnabled = catalogSettings[settingKey] ?? true;
if (isEnabled) { if (isEnabled) {
try { // Create a promise for each catalog fetch
const addonManifest = await stremioService.getInstalledAddonsAsync(); const catalogPromise = (async () => {
const manifest = addonManifest.find(a => a.id === addon.id); try {
if (!manifest) continue; const addonManifest = await stremioService.getInstalledAddonsAsync();
const manifest = addonManifest.find(a => a.id === addon.id);
if (!manifest) return null;
const metas = await stremioService.getCatalog(manifest, catalog.type, catalog.id, 1); const metas = await stremioService.getCatalog(manifest, catalog.type, catalog.id, 1);
if (metas && metas.length > 0) { if (metas && metas.length > 0) {
const items = metas.map(meta => this.convertMetaToStreamingContent(meta)); const items = metas.map(meta => this.convertMetaToStreamingContent(meta));
// Get potentially custom display name // Get potentially custom display name
let displayName = await getCatalogDisplayName(addon.id, catalog.type, catalog.id, catalog.name); let displayName = await getCatalogDisplayName(addon.id, catalog.type, catalog.id, catalog.name);
// Remove duplicate words and clean up the name (case-insensitive) // Remove duplicate words and clean up the name (case-insensitive)
const words = displayName.split(' '); const words = displayName.split(' ');
const uniqueWords = []; const uniqueWords = [];
const seenWords = new Set(); const seenWords = new Set();
for (const word of words) { for (const word of words) {
const lowerWord = word.toLowerCase(); const lowerWord = word.toLowerCase();
if (!seenWords.has(lowerWord)) { if (!seenWords.has(lowerWord)) {
uniqueWords.push(word); uniqueWords.push(word);
seenWords.add(lowerWord); seenWords.add(lowerWord);
}
} }
} displayName = uniqueWords.join(' ');
displayName = uniqueWords.join(' ');
// Add content type if not present // Add content type if not present
const contentType = catalog.type === 'movie' ? 'Movies' : 'TV Shows'; const contentType = catalog.type === 'movie' ? 'Movies' : 'TV Shows';
if (!displayName.toLowerCase().includes(contentType.toLowerCase())) { if (!displayName.toLowerCase().includes(contentType.toLowerCase())) {
displayName = `${displayName} ${contentType}`; displayName = `${displayName} ${contentType}`;
} }
catalogs.push({ return {
addon: addon.id, addon: addon.id,
type: catalog.type, type: catalog.type,
id: catalog.id, id: catalog.id,
name: displayName, name: displayName,
items items
}); };
}
return null;
} catch (error) {
logger.error(`Failed to load ${catalog.name} from ${addon.name}:`, error);
return null;
} }
} catch (error) { })();
logger.error(`Failed to load ${catalog.name} from ${addon.name}:`, error);
} catalogPromises.push(catalogPromise);
} }
} }
} }
} }
return catalogs; // Wait for all catalog fetch promises to resolve in parallel
const catalogResults = await Promise.all(catalogPromises);
// Filter out null results
return catalogResults.filter(catalog => catalog !== null) as CatalogContent[];
} }
async getCatalogByType(type: string, genreFilter?: string): Promise<CatalogContent[]> { async getCatalogByType(type: string, genreFilter?: string): Promise<CatalogContent[]> {
@ -222,46 +235,58 @@ class CatalogService {
// Otherwise use the original Stremio addons method // Otherwise use the original Stremio addons method
const addons = await this.getAllAddons(); const addons = await this.getAllAddons();
const catalogs: CatalogContent[] = [];
const typeAddons = addons.filter(addon => const typeAddons = addons.filter(addon =>
addon.catalogs && addon.catalogs.some(catalog => catalog.type === type) addon.catalogs && addon.catalogs.some(catalog => catalog.type === type)
); );
// Create an array of promises for all catalog fetches
const catalogPromises: Promise<CatalogContent | null>[] = [];
for (const addon of typeAddons) { for (const addon of typeAddons) {
const typeCatalogs = addon.catalogs.filter(catalog => catalog.type === type); const typeCatalogs = addon.catalogs.filter(catalog => catalog.type === type);
for (const catalog of typeCatalogs) { for (const catalog of typeCatalogs) {
try { const catalogPromise = (async () => {
const addonManifest = await stremioService.getInstalledAddonsAsync(); try {
const manifest = addonManifest.find(a => a.id === addon.id); const addonManifest = await stremioService.getInstalledAddonsAsync();
if (!manifest) continue; const manifest = addonManifest.find(a => a.id === addon.id);
if (!manifest) return null;
const filters = genreFilter ? [{ title: 'genre', value: genreFilter }] : []; const filters = genreFilter ? [{ title: 'genre', value: genreFilter }] : [];
const metas = await stremioService.getCatalog(manifest, type, catalog.id, 1, filters); const metas = await stremioService.getCatalog(manifest, type, catalog.id, 1, filters);
if (metas && metas.length > 0) { if (metas && metas.length > 0) {
const items = metas.map(meta => this.convertMetaToStreamingContent(meta)); const items = metas.map(meta => this.convertMetaToStreamingContent(meta));
// Get potentially custom display name // Get potentially custom display name
const displayName = await getCatalogDisplayName(addon.id, catalog.type, catalog.id, catalog.name); const displayName = await getCatalogDisplayName(addon.id, catalog.type, catalog.id, catalog.name);
catalogs.push({ return {
addon: addon.id, addon: addon.id,
type, type,
id: catalog.id, id: catalog.id,
name: displayName, name: displayName,
genre: genreFilter, genre: genreFilter,
items items
}); };
}
return null;
} catch (error) {
logger.error(`Failed to get catalog ${catalog.id} for addon ${addon.id}:`, error);
return null;
} }
} catch (error) { })();
logger.error(`Failed to get catalog ${catalog.id} for addon ${addon.id}:`, error);
} catalogPromises.push(catalogPromise);
} }
} }
return catalogs; // Wait for all catalog fetch promises to resolve in parallel
const catalogResults = await Promise.all(catalogPromises);
// Filter out null results
return catalogResults.filter(catalog => catalog !== null) as CatalogContent[];
} }
/** /**
@ -277,64 +302,75 @@ class CatalogService {
// If no genre filter or All is selected, get multiple catalogs // If no genre filter or All is selected, get multiple catalogs
if (!genreFilter || genreFilter === 'All') { if (!genreFilter || genreFilter === 'All') {
// Get trending // Create an array of promises for all catalog fetches
const trendingItems = await tmdbService.getTrending(tmdbType, 'week'); const catalogFetchPromises = [
const trendingItemsPromises = trendingItems.map(item => this.convertTMDBToStreamingContent(item, tmdbType)); // Trending catalog
const trendingStreamingItems = await Promise.all(trendingItemsPromises); (async () => {
const trendingItems = await tmdbService.getTrending(tmdbType, 'week');
const trendingItemsPromises = trendingItems.map(item => this.convertTMDBToStreamingContent(item, tmdbType));
const trendingStreamingItems = await Promise.all(trendingItemsPromises);
catalogs.push({ return {
addon: 'tmdb', addon: 'tmdb',
type, type,
id: 'trending', id: 'trending',
name: `Trending ${type === 'movie' ? 'Movies' : 'TV Shows'}`, name: `Trending ${type === 'movie' ? 'Movies' : 'TV Shows'}`,
items: trendingStreamingItems items: trendingStreamingItems
}); };
})(),
// Get popular // Popular catalog
const popularItems = await tmdbService.getPopular(tmdbType, 1); (async () => {
const popularItemsPromises = popularItems.map(item => this.convertTMDBToStreamingContent(item, tmdbType)); const popularItems = await tmdbService.getPopular(tmdbType, 1);
const popularStreamingItems = await Promise.all(popularItemsPromises); const popularItemsPromises = popularItems.map(item => this.convertTMDBToStreamingContent(item, tmdbType));
const popularStreamingItems = await Promise.all(popularItemsPromises);
catalogs.push({ return {
addon: 'tmdb', addon: 'tmdb',
type, type,
id: 'popular', id: 'popular',
name: `Popular ${type === 'movie' ? 'Movies' : 'TV Shows'}`, name: `Popular ${type === 'movie' ? 'Movies' : 'TV Shows'}`,
items: popularStreamingItems items: popularStreamingItems
}); };
})(),
// Get upcoming/on air // Upcoming/on air catalog
const upcomingItems = await tmdbService.getUpcoming(tmdbType, 1); (async () => {
const upcomingItemsPromises = upcomingItems.map(item => this.convertTMDBToStreamingContent(item, tmdbType)); const upcomingItems = await tmdbService.getUpcoming(tmdbType, 1);
const upcomingStreamingItems = await Promise.all(upcomingItemsPromises); const upcomingItemsPromises = upcomingItems.map(item => this.convertTMDBToStreamingContent(item, tmdbType));
const upcomingStreamingItems = await Promise.all(upcomingItemsPromises);
catalogs.push({ return {
addon: 'tmdb', addon: 'tmdb',
type, type,
id: 'upcoming', id: 'upcoming',
name: type === 'movie' ? 'Upcoming Movies' : 'On Air TV Shows', name: type === 'movie' ? 'Upcoming Movies' : 'On Air TV Shows',
items: upcomingStreamingItems items: upcomingStreamingItems
}); };
})()
];
// Wait for all catalog fetches to complete in parallel
return await Promise.all(catalogFetchPromises);
} else { } else {
// Get content by genre // Get content by genre
const genreItems = await tmdbService.discoverByGenre(tmdbType, genreFilter); const genreItems = await tmdbService.discoverByGenre(tmdbType, genreFilter);
const streamingItemsPromises = genreItems.map(item => this.convertTMDBToStreamingContent(item, tmdbType)); const streamingItemsPromises = genreItems.map(item => this.convertTMDBToStreamingContent(item, tmdbType));
const streamingItems = await Promise.all(streamingItemsPromises); const streamingItems = await Promise.all(streamingItemsPromises);
catalogs.push({ return [{
addon: 'tmdb', addon: 'tmdb',
type, type,
id: 'discover', id: 'discover',
name: `${genreFilter} ${type === 'movie' ? 'Movies' : 'TV Shows'}`, name: `${genreFilter} ${type === 'movie' ? 'Movies' : 'TV Shows'}`,
genre: genreFilter, genre: genreFilter,
items: streamingItems items: streamingItems
}); }];
} }
} catch (error) { } catch (error) {
logger.error(`Failed to get catalog from TMDB for type ${type}, genre ${genreFilter}:`, error); logger.error(`Failed to get catalog from TMDB for type ${type}, genre ${genreFilter}:`, error);
return [];
} }
return catalogs;
} }
/** /**