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

View file

@ -11,7 +11,13 @@ const persistentStore = {
featuredContent: null as StreamingContent | null,
allFeaturedContent: [] as StreamingContent[],
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)
@ -30,7 +36,7 @@ export function useFeaturedContent() {
const { genreMap, loadingGenres } = useGenres();
// Update local state when settings change
// Simple update for state variables
useEffect(() => {
setContentSource(settings.featuredContentSource);
setSelectedCatalogs(settings.selectedHeroCatalogs || []);
@ -44,6 +50,14 @@ export function useFeaturedContent() {
}, []);
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
const now = Date.now();
const cacheAge = now - persistentStore.lastFetchTime;
@ -53,6 +67,7 @@ export function useFeaturedContent() {
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);
@ -60,6 +75,7 @@ export function useFeaturedContent() {
return;
}
console.log(`Loading featured content from ${contentSource}`);
setLoading(true);
cleanup();
abortControllerRef.current = new AbortController();
@ -176,32 +192,75 @@ export function useFeaturedContent() {
}
}, [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
useEffect(() => {
const handleSettingsChange = () => {
// Force refresh when settings change
loadFeaturedContent(true);
// 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);
// 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
const unsubscribe = settingsEmitter.addListener(handleSettingsChange);
return unsubscribe;
}, [loadFeaturedContent]);
}, [loadFeaturedContent, settings, contentSource, selectedCatalogs]);
// Load featured content initially and when content source changes
useEffect(() => {
const shouldForceRefresh = contentSource === 'tmdb' &&
contentSource !== persistentStore.featuredContent?.type;
if (shouldForceRefresh) {
// Force refresh when switching to catalogs or when catalog selection changes
if (contentSource === 'catalogs') {
// Clear cache when switching to catalogs mode
setAllFeaturedContent([]);
setFeaturedContent(null);
persistentStore.allFeaturedContent = [];
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]);
useEffect(() => {

View file

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

View file

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

View file

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

View file

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