From c6a2c52365bc0386c1677cf6d672d369fcb629b3 Mon Sep 17 00:00:00 2001 From: CrissZollo Date: Sat, 20 Sep 2025 08:32:53 +0200 Subject: [PATCH 1/4] Fixed test notification and is now instant --- src/screens/NotificationSettingsScreen.tsx | 38 ++++++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/screens/NotificationSettingsScreen.tsx b/src/screens/NotificationSettingsScreen.tsx index 4b45e80..28d18a6 100644 --- a/src/screens/NotificationSettingsScreen.tsx +++ b/src/screens/NotificationSettingsScreen.tsx @@ -164,9 +164,24 @@ const NotificationSettingsScreen = () => { const handleTestNotification = async () => { try { - // Cancel previous test notification if exists - if (testNotificationId) { - await notificationService.cancelNotification(testNotificationId); + // Remove all previous test notifications before scheduling a new one + const scheduled = notificationService.getScheduledNotifications?.() || []; + const testNotifications = scheduled.filter(n => n.id.startsWith('test-notification-')); + if (testNotifications.length > 0 && typeof notificationService.cancelNotification === 'function') { + for (const n of testNotifications) { + await notificationService.cancelNotification(n.id); + } + } + + + // Temporarily override timeBeforeAiring to 0 for the test notification + let originalTimeBeforeAiring: number | undefined = undefined; + if (typeof notificationService.getSettings === 'function') { + const currentSettings = await notificationService.getSettings(); + originalTimeBeforeAiring = currentSettings.timeBeforeAiring; + if (typeof notificationService.updateSettings === 'function') { + await notificationService.updateSettings({ timeBeforeAiring: 0 }); + } } const testNotification = { @@ -176,15 +191,24 @@ const NotificationSettingsScreen = () => { episodeTitle: 'Test Episode', season: 1, episode: 1, - releaseDate: new Date(Date.now() + 60000).toISOString(), // 1 minute from now + releaseDate: new Date(Date.now() + 5000).toISOString(), // 5 seconds from now notified: false }; - + const notificationId = await notificationService.scheduleEpisodeNotification(testNotification); + + // Restore original timeBeforeAiring + if ( + typeof notificationService.updateSettings === 'function' && + originalTimeBeforeAiring !== undefined + ) { + await notificationService.updateSettings({ timeBeforeAiring: originalTimeBeforeAiring }); + } + if (notificationId) { setTestNotificationId(notificationId); - setCountdown(60); // Start 60 second countdown - Alert.alert('Success', 'Test notification scheduled for 1 minute from now'); + setCountdown(0); // No countdown for instant notification + Alert.alert('Success', 'Test notification scheduled to fire instantly'); } else { Alert.alert('Error', 'Failed to schedule test notification. Make sure notifications are enabled.'); } From 02ef82a8045792b4b3c9d422973c521f527bab57 Mon Sep 17 00:00:00 2001 From: CrissZollo Date: Sat, 20 Sep 2025 08:36:11 +0200 Subject: [PATCH 2/4] Updated test notification text --- src/screens/NotificationSettingsScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/NotificationSettingsScreen.tsx b/src/screens/NotificationSettingsScreen.tsx index 28d18a6..fecf782 100644 --- a/src/screens/NotificationSettingsScreen.tsx +++ b/src/screens/NotificationSettingsScreen.tsx @@ -449,7 +449,7 @@ const NotificationSettingsScreen = () => { {countdown !== null ? `Notification in ${countdown}s...` - : 'Test Notification (1min)'} + : 'Test Notification (5 sec)'} From 045e37a0d3f670a11fefa1b0fd3e3b99b6b5d65b Mon Sep 17 00:00:00 2001 From: CrissZollo Date: Sat, 20 Sep 2025 09:02:28 +0200 Subject: [PATCH 3/4] Updated so scheduled notifications does not cancel on test and are added and removed by saving to library --- src/screens/NotificationSettingsScreen.tsx | 12 +++--- src/services/catalogService.ts | 49 ++++++++++------------ 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/screens/NotificationSettingsScreen.tsx b/src/screens/NotificationSettingsScreen.tsx index fecf782..2708c55 100644 --- a/src/screens/NotificationSettingsScreen.tsx +++ b/src/screens/NotificationSettingsScreen.tsx @@ -116,7 +116,7 @@ const NotificationSettingsScreen = () => { const resetAllNotifications = async () => { Alert.alert( 'Reset Notifications', - 'This will cancel all scheduled notifications. Are you sure?', + 'This will cancel all scheduled notifications, but will not remove anything from your saved library. Are you sure?', [ { text: 'Cancel', @@ -127,7 +127,11 @@ const NotificationSettingsScreen = () => { style: 'destructive', onPress: async () => { try { - await notificationService.cancelAllNotifications(); + // Cancel all notifications for all series, but do not remove from saved + const scheduledNotifications = notificationService.getScheduledNotifications?.() || []; + for (const notification of scheduledNotifications) { + await notificationService.cancelNotification(notification.id); + } Alert.alert('Success', 'All notifications have been reset'); } catch (error) { logger.error('Error resetting notifications:', error); @@ -466,10 +470,6 @@ const NotificationSettingsScreen = () => { )} - - - This will cancel all scheduled notifications. You'll need to re-enable them manually. - )} diff --git a/src/services/catalogService.ts b/src/services/catalogService.ts index 6b5a81c..3988513 100644 --- a/src/services/catalogService.ts +++ b/src/services/catalogService.ts @@ -1,4 +1,5 @@ import { stremioService, Meta, Manifest } from './stremioService'; +import { notificationService } from './notificationService'; import AsyncStorage from '@react-native-async-storage/async-storage'; import axios from 'axios'; import { TMDBService } from './tmdbService'; @@ -690,15 +691,14 @@ class CatalogService { try { this.libraryAddListeners.forEach(l => l(content)); } catch {} // Auto-setup notifications for series when added to library - // if (content.type === 'series') { - // try { - // const { notificationService } = await import('./notificationService'); - // await notificationService.updateNotificationsForSeries(content.id); - // console.log(`[CatalogService] Auto-setup notifications for series: ${content.name}`); - // } catch (error) { - // console.error(`[CatalogService] Failed to setup notifications for ${content.name}:`, error); - // } - // } + if (content.type === 'series') { + try { + await notificationService.updateNotificationsForSeries(content.id); + console.log(`[CatalogService] Auto-setup notifications for series: ${content.name}`); + } catch (error) { + console.error(`[CatalogService] Failed to setup notifications for ${content.name}:`, error); + } + } } public async removeFromLibrary(type: string, id: string): Promise { @@ -707,24 +707,21 @@ class CatalogService { this.saveLibrary(); this.notifyLibrarySubscribers(); try { this.libraryRemoveListeners.forEach(l => l(type, id)); } catch {} - + // Cancel notifications for series when removed from library - // if (type === 'series') { - // try { - // const { notificationService } = await import('./notificationService'); - // // Cancel all notifications for this series - // const scheduledNotifications = await notificationService.getScheduledNotifications(); - // const seriesToCancel = scheduledNotifications.filter(notification => notification.seriesId === id); - // - // for (const notification of seriesToCancel) { - // await notificationService.cancelNotification(notification.id); - // } - // - // console.log(`[CatalogService] Cancelled ${seriesToCancel.length} notifications for removed series: ${id}`); - // } catch (error) { - // console.error(`[CatalogService] Failed to cancel notifications for removed series ${id}:`, error); - // } - // } + if (type === 'series') { + try { + // Cancel all notifications for this series + const scheduledNotifications = notificationService.getScheduledNotifications(); + const seriesToCancel = scheduledNotifications.filter(notification => notification.seriesId === id); + for (const notification of seriesToCancel) { + await notificationService.cancelNotification(notification.id); + } + console.log(`[CatalogService] Cancelled ${seriesToCancel.length} notifications for removed series: ${id}`); + } catch (error) { + console.error(`[CatalogService] Failed to cancel notifications for removed series ${id}:`, error); + } + } } private addToRecentContent(content: StreamingContent): void { From 3e55dff5423534f29a29409366c05beaf6222681 Mon Sep 17 00:00:00 2001 From: CrissZollo Date: Sat, 20 Sep 2025 09:21:04 +0200 Subject: [PATCH 4/4] Fixed mark/unmark as watched (long press) and adding/removing from library (long press) --- src/components/home/ContentItem.tsx | 21 +++++++++++++++++---- src/components/home/DropUpMenu.tsx | 13 +++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/components/home/ContentItem.tsx b/src/components/home/ContentItem.tsx index f4ff1bc..4ad0774 100644 --- a/src/components/home/ContentItem.tsx +++ b/src/components/home/ContentItem.tsx @@ -59,6 +59,17 @@ const POSTER_WIDTH = posterLayout.posterWidth; const PLACEHOLDER_BLURHASH = 'LEHV6nWB2yk8pyo0adR*.7kCMdnj'; const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, deferMs = 0 }: ContentItemProps) => { + // Track inLibrary status locally to force re-render + const [inLibrary, setInLibrary] = useState(!!item.inLibrary); + + useEffect(() => { + // Subscribe to library updates and update local state if this item's status changes + const unsubscribe = catalogService.subscribeToLibraryUpdates((items) => { + const found = items.find((libItem) => libItem.id === item.id && libItem.type === item.type); + setInLibrary(!!found); + }); + return () => unsubscribe(); + }, [item.id, item.type]); const [menuVisible, setMenuVisible] = useState(false); const [isWatched, setIsWatched] = useState(false); const [imageLoaded, setImageLoaded] = useState(false); @@ -95,7 +106,7 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe const handleOptionSelect = useCallback((option: string) => { switch (option) { case 'library': - if (item.inLibrary) { + if (inLibrary) { catalogService.removeFromLibrary(item.type, item.id); } else { catalogService.addToLibrary(item); @@ -109,7 +120,7 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe case 'share': break; } - }, [item]); + }, [item, inLibrary]); const handleMenuClose = useCallback(() => { setMenuVisible(false); @@ -238,7 +249,7 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe )} {imageError && ( - + )} @@ -247,7 +258,7 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe )} - {item.inLibrary && ( + {inLibrary && ( @@ -266,6 +277,8 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe onClose={handleMenuClose} item={item} onOptionSelect={handleOptionSelect} + isSaved={inLibrary} + isWatched={isWatched} /> ); diff --git a/src/components/home/DropUpMenu.tsx b/src/components/home/DropUpMenu.tsx index 7431655..d84ca62 100644 --- a/src/components/home/DropUpMenu.tsx +++ b/src/components/home/DropUpMenu.tsx @@ -33,9 +33,11 @@ interface DropUpMenuProps { onClose: () => void; item: StreamingContent; onOptionSelect: (option: string) => void; + isSaved?: boolean; // allow parent to pass saved status directly + isWatched?: boolean; // allow parent to pass watched status directly } -export const DropUpMenu = ({ visible, onClose, item, onOptionSelect }: DropUpMenuProps) => { +export const DropUpMenu = ({ visible, onClose, item, onOptionSelect, isSaved: isSavedProp, isWatched: isWatchedProp }: DropUpMenuProps) => { const translateY = useSharedValue(300); const opacity = useSharedValue(0); const isDarkMode = useColorScheme() === 'dark'; @@ -87,15 +89,18 @@ export const DropUpMenu = ({ visible, onClose, item, onOptionSelect }: DropUpMen borderTopRightRadius: 24, })); + // Robustly determine if the item is in the library (saved) + const isSaved = typeof isSavedProp === 'boolean' ? isSavedProp : !!item.inLibrary; + const isWatched = !!isWatchedProp; const menuOptions = [ { - icon: item.inLibrary ? 'bookmark' : 'bookmark-border', - label: item.inLibrary ? 'Remove from Library' : 'Add to Library', + icon: isSaved ? 'bookmark' : 'bookmark-border', + label: isSaved ? 'Remove from Library' : 'Add to Library', action: 'library' }, { icon: 'check-circle', - label: 'Mark as Watched', + label: isWatched ? 'Mark as Unwatched' : 'Mark as Watched', action: 'watched' }, {