From d6053837209c0135f66fac88408314f1713f833a Mon Sep 17 00:00:00 2001 From: tapframe Date: Mon, 28 Jul 2025 17:40:37 +0530 Subject: [PATCH] test --- src/screens/AddonsScreen.tsx | 62 +++++++++++++------------ src/screens/StreamsScreen.tsx | 45 +++++++++--------- src/services/notificationService.ts | 57 +++++++++++++++++++---- src/services/stremioService.ts | 71 ++++++++++++++++++++++++++++- 4 files changed, 172 insertions(+), 63 deletions(-) diff --git a/src/screens/AddonsScreen.tsx b/src/screens/AddonsScreen.tsx index 39804a0..e41c947 100644 --- a/src/screens/AddonsScreen.tsx +++ b/src/screens/AddonsScreen.tsx @@ -586,21 +586,7 @@ const createStyles = (colors: any) => StyleSheet.create({ }, }); -// Cinemeta addon details -const cinemetaAddon: CommunityAddon = { - transportUrl: 'https://v3-cinemeta.strem.io/manifest.json', - manifest: { - id: 'com.linvo.cinemeta', - version: '3.0.13', - name: 'Cinemeta', - description: 'Provides metadata for movies and series from TheTVDB, TheMovieDB, etc.', - logo: 'https://static.strem.io/addons/cinemeta.png', - types: ['movie', 'series'], - behaviorHints: { - configurable: false - } - } as ExtendedManifest, -}; + const AddonsScreen = () => { const navigation = useNavigation>(); @@ -671,16 +657,15 @@ const AddonsScreen = () => { // Filter out addons without a manifest or transportUrl (basic validation) let validAddons = response.data.filter(addon => addon.manifest && addon.transportUrl); - // Filter out Cinemeta if it's already in the community list to avoid duplication + // Filter out Cinemeta since it's now pre-installed validAddons = validAddons.filter(addon => addon.manifest.id !== 'com.linvo.cinemeta'); - // Add Cinemeta to the beginning of the list - setCommunityAddons([cinemetaAddon, ...validAddons]); + setCommunityAddons(validAddons); } catch (error) { logger.error('Failed to load community addons:', error); setCommunityError('Failed to load community addons. Please try again later.'); - // Still show Cinemeta if the community list fails to load - setCommunityAddons([cinemetaAddon]); + // Set empty array on error since Cinemeta is pre-installed + setCommunityAddons([]); } finally { setCommunityLoading(false); } @@ -746,6 +731,16 @@ const AddonsScreen = () => { }; const handleRemoveAddon = (addon: ExtendedManifest) => { + // Check if this is a pre-installed addon + if (stremioService.isPreInstalledAddon(addon.id)) { + Alert.alert( + 'Cannot Remove Addon', + `${addon.name} is a pre-installed addon and cannot be removed.`, + [{ text: 'OK', style: 'default' }] + ); + return; + } + Alert.alert( 'Uninstall Addon', `Are you sure you want to uninstall ${addon.name}?`, @@ -900,6 +895,8 @@ const AddonsScreen = () => { const logo = item.logo || null; // Check if addon is configurable const isConfigurable = item.behaviorHints?.configurable === true; + // Check if addon is pre-installed + const isPreInstalled = stremioService.isPreInstalledAddon(item.id); // Format the types into a simple category text const categoryText = types.length > 0 @@ -951,7 +948,14 @@ const AddonsScreen = () => { )} - {item.name} + + {item.name} + {isPreInstalled && ( + + PRE-INSTALLED + + )} + v{item.version || '1.0.0'} @@ -969,12 +973,14 @@ const AddonsScreen = () => { )} - handleRemoveAddon(item)} - > - - + {!stremioService.isPreInstalledAddon(item.id) && ( + handleRemoveAddon(item)} + > + + + )} ) : ( @@ -1410,4 +1416,4 @@ const AddonsScreen = () => { ); }; -export default AddonsScreen; \ No newline at end of file +export default AddonsScreen; \ No newline at end of file diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index a7da7a3..3939925 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -187,12 +187,12 @@ const PulsingChip = memo(({ text, delay }: { text: string; delay: number }) => { const { currentTheme } = useTheme(); const styles = React.useMemo(() => createStyles(currentTheme.colors), [currentTheme.colors]); - const pulseValue = useSharedValue(0.7); + const pulseValue = useSharedValue(0.6); useEffect(() => { const startPulse = () => { - pulseValue.value = withTiming(1, { duration: 800 }, () => { - pulseValue.value = withTiming(0.7, { duration: 800 }, () => { + pulseValue.value = withTiming(1, { duration: 1200 }, () => { + pulseValue.value = withTiming(0.6, { duration: 1200 }, () => { runOnJS(startPulse)(); }); }); @@ -207,8 +207,7 @@ const PulsingChip = memo(({ text, delay }: { text: string; delay: number }) => { const animatedStyle = useAnimatedStyle(() => { return { - opacity: pulseValue.value, - transform: [{ scale: interpolate(pulseValue.value, [0.7, 1], [0.95, 1], Extrapolate.CLAMP) }] + opacity: pulseValue.value }; }); @@ -1722,36 +1721,34 @@ const createStyles = (colors: any) => StyleSheet.create({ }, activeScrapersContainer: { paddingHorizontal: 16, - paddingBottom: 12, - backgroundColor: colors.elevation1, + paddingVertical: 8, + backgroundColor: 'transparent', marginHorizontal: 16, - marginBottom: 8, - borderRadius: 8, - paddingVertical: 12, + marginBottom: 4, }, activeScrapersTitle: { - color: colors.primary, - fontSize: 13, - fontWeight: '600', - marginBottom: 8, + color: colors.mediumEmphasis, + fontSize: 12, + fontWeight: '500', + marginBottom: 6, + opacity: 0.8, }, activeScrapersRow: { flexDirection: 'row', flexWrap: 'wrap', - gap: 6, + gap: 4, }, activeScraperChip: { - backgroundColor: colors.surfaceVariant, - paddingHorizontal: 10, - paddingVertical: 4, - borderRadius: 12, - borderWidth: 1, - borderColor: colors.primary + '40', + backgroundColor: colors.elevation2, + paddingHorizontal: 8, + paddingVertical: 3, + borderRadius: 6, + borderWidth: 0, }, activeScraperText: { - color: colors.highEmphasis, - fontSize: 12, - fontWeight: '500', + color: colors.mediumEmphasis, + fontSize: 11, + fontWeight: '400', }, }); diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts index 9c18fda..352d510 100644 --- a/src/services/notificationService.ts +++ b/src/services/notificationService.ts @@ -53,6 +53,8 @@ class NotificationService { private backgroundSyncInterval: NodeJS.Timeout | null = null; private librarySubscription: (() => void) | null = null; private appStateSubscription: any = null; + private lastSyncTime: number = 0; + private readonly MIN_SYNC_INTERVAL = 5 * 60 * 1000; // 5 minutes minimum between syncs private constructor() { // Initialize notifications @@ -149,6 +151,16 @@ class NotificationService { return null; } + // Check if notification already exists for this episode + const existingNotification = this.scheduledNotifications.find( + notification => notification.seriesId === item.seriesId && + notification.season === item.season && + notification.episode === item.episode + ); + if (existingNotification) { + return null; // Don't schedule duplicate notifications + } + const releaseDate = parseISO(item.releaseDate); const now = new Date(); @@ -162,9 +174,9 @@ class NotificationService { const notificationTime = new Date(releaseDate); notificationTime.setHours(notificationTime.getHours() - this.settings.timeBeforeAiring); - // If notification time has already passed, set to now + 1 minute + // If notification time has already passed, don't schedule the notification if (notificationTime < now) { - notificationTime.setTime(now.getTime() + 60000); + return null; } // Schedule the notification @@ -254,9 +266,17 @@ class NotificationService { this.librarySubscription = catalogService.subscribeToLibraryUpdates(async (libraryItems) => { if (!this.settings.enabled) return; - // Reduced logging verbosity - // logger.log('[NotificationService] Library updated, syncing notifications for', libraryItems.length, 'items'); - await this.syncNotificationsForLibrary(libraryItems); + const now = Date.now(); + const timeSinceLastSync = now - this.lastSyncTime; + + // Only sync if enough time has passed since last sync + if (timeSinceLastSync >= this.MIN_SYNC_INTERVAL) { + // Reduced logging verbosity + // logger.log('[NotificationService] Library updated, syncing notifications for', libraryItems.length, 'items'); + await this.syncNotificationsForLibrary(libraryItems); + } else { + // logger.log(`[NotificationService] Library updated, but skipping sync (last sync ${Math.round(timeSinceLastSync / 1000)}s ago)`); + } }); } catch (error) { logger.error('[NotificationService] Error setting up library integration:', error); @@ -284,10 +304,18 @@ class NotificationService { private handleAppStateChange = async (nextAppState: AppStateStatus) => { if (nextAppState === 'active' && this.settings.enabled) { - // App came to foreground, sync notifications - // Reduced logging verbosity - // logger.log('[NotificationService] App became active, syncing notifications'); - await this.performBackgroundSync(); + const now = Date.now(); + const timeSinceLastSync = now - this.lastSyncTime; + + // Only sync if enough time has passed since last sync + if (timeSinceLastSync >= this.MIN_SYNC_INTERVAL) { + // App came to foreground, sync notifications + // Reduced logging verbosity + // logger.log('[NotificationService] App became active, syncing notifications'); + await this.performBackgroundSync(); + } else { + // logger.log(`[NotificationService] App became active, but skipping sync (last sync ${Math.round(timeSinceLastSync / 1000)}s ago)`); + } } }; @@ -312,6 +340,9 @@ class NotificationService { // Perform comprehensive background sync including Trakt integration private async performBackgroundSync(): Promise { try { + // Update last sync time at the start + this.lastSyncTime = Date.now(); + // Reduced logging verbosity // logger.log('[NotificationService] Starting comprehensive background sync'); @@ -467,7 +498,13 @@ class NotificationService { if (!video.released) return false; const releaseDate = parseISO(video.released); return releaseDate > now && releaseDate < fourWeeksLater; - }); + }).map(video => ({ + id: video.id, + title: (video as any).title || (video as any).name || `Episode ${video.episode}`, + season: video.season || 0, + episode: video.episode || 0, + released: video.released, + })); } // If no upcoming episodes from Stremio, try TMDB diff --git a/src/services/stremioService.ts b/src/services/stremioService.ts index be5286f..6914a62 100644 --- a/src/services/stremioService.ts +++ b/src/services/stremioService.ts @@ -213,6 +213,51 @@ class StremioService { } } + // Ensure Cinemeta is always installed as a pre-installed addon + const cinemetaId = 'com.linvo.cinemeta'; + if (!this.installedAddons.has(cinemetaId)) { + const cinemetaManifest: Manifest = { + id: cinemetaId, + name: 'Cinemeta', + version: '3.0.13', + description: 'Provides metadata for movies and series from TheTVDB, TheMovieDB, etc.', + url: 'https://v3-cinemeta.strem.io', + originalUrl: 'https://v3-cinemeta.strem.io/manifest.json', + types: ['movie', 'series'], + catalogs: [ + { + type: 'movie', + id: 'top', + name: 'Top Movies', + extraSupported: ['search', 'genre', 'skip'] + }, + { + type: 'series', + id: 'top', + name: 'Top Series', + extraSupported: ['search', 'genre', 'skip'] + } + ], + resources: [ + { + name: 'catalog', + types: ['movie', 'series'], + idPrefixes: ['tt'] + }, + { + name: 'meta', + types: ['movie', 'series'], + idPrefixes: ['tt'] + } + ], + behaviorHints: { + configurable: false + } + }; + this.installedAddons.set(cinemetaId, cinemetaManifest); + logger.log('✅ Cinemeta pre-installed as default addon'); + } + // Load addon order if exists const storedOrder = await AsyncStorage.getItem(this.ADDON_ORDER_KEY); if (storedOrder) { @@ -221,13 +266,26 @@ class StremioService { this.addonOrder = this.addonOrder.filter(id => this.installedAddons.has(id)); } + // Ensure Cinemeta is first in the order + if (!this.addonOrder.includes(cinemetaId)) { + this.addonOrder.unshift(cinemetaId); + } else { + // Move Cinemeta to the front if it's not already there + const cinemetaIndex = this.addonOrder.indexOf(cinemetaId); + if (cinemetaIndex > 0) { + this.addonOrder.splice(cinemetaIndex, 1); + this.addonOrder.unshift(cinemetaId); + } + } + // Add any missing addons to the order const installedIds = Array.from(this.installedAddons.keys()); const missingIds = installedIds.filter(id => !this.addonOrder.includes(id)); this.addonOrder = [...this.addonOrder, ...missingIds]; - // Ensure order is saved + // Ensure order and addons are saved await this.saveAddonOrder(); + await this.saveInstalledAddons(); this.initialized = true; } catch (error) { @@ -336,6 +394,12 @@ class StremioService { } removeAddon(id: string): void { + // Prevent removal of Cinemeta as it's a pre-installed addon + if (id === 'com.linvo.cinemeta') { + logger.warn('❌ Cannot remove Cinemeta - it is a pre-installed addon'); + return; + } + if (this.installedAddons.has(id)) { this.installedAddons.delete(id); // Remove from order @@ -359,6 +423,11 @@ class StremioService { return this.getInstalledAddons(); } + // Check if an addon is pre-installed and cannot be removed + isPreInstalledAddon(id: string): boolean { + return id === 'com.linvo.cinemeta'; + } + private formatId(id: string): string { return id.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase(); }