import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, Switch, TouchableOpacity, SafeAreaView, StatusBar, Platform, } from 'react-native'; import CustomAlert from '../components/CustomAlert'; import { MaterialIcons } from '@expo/vector-icons'; import { useTheme } from '../contexts/ThemeContext'; import { notificationService, NotificationSettings } from '../services/notificationService'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; import { useNavigation } from '@react-navigation/native'; import { logger } from '../utils/logger'; const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; const NotificationSettingsScreen = () => { const navigation = useNavigation(); const { currentTheme } = useTheme(); const [settings, setSettings] = useState({ enabled: true, newEpisodeNotifications: true, reminderNotifications: true, upcomingShowsNotifications: true, timeBeforeAiring: 24, }); const [loading, setLoading] = useState(true); const [countdown, setCountdown] = useState(null); const [testNotificationId, setTestNotificationId] = useState(null); const [isSyncing, setIsSyncing] = useState(false); const [notificationStats, setNotificationStats] = useState({ total: 0, upcoming: 0, thisWeek: 0 }); // Custom alert state const [alertVisible, setAlertVisible] = useState(false); const [alertTitle, setAlertTitle] = useState(''); const [alertMessage, setAlertMessage] = useState(''); const [alertActions, setAlertActions] = useState([]); // Load settings and stats on mount useEffect(() => { const loadSettings = async () => { try { const savedSettings = await notificationService.getSettings(); setSettings(savedSettings); // Load notification stats const stats = notificationService.getNotificationStats(); setNotificationStats(stats); } catch (error) { logger.error('Error loading notification settings:', error); } finally { setLoading(false); } }; loadSettings(); }, []); // Refresh stats when settings change useEffect(() => { if (!loading) { const stats = notificationService.getNotificationStats(); setNotificationStats(stats); } }, [settings, loading]); // Add countdown effect useEffect(() => { let intervalId: NodeJS.Timeout; if (countdown !== null && countdown > 0) { intervalId = setInterval(() => { setCountdown(prev => prev !== null ? prev - 1 : null); }, 1000); } else if (countdown === 0) { setCountdown(null); setTestNotificationId(null); } return () => { if (intervalId) { clearInterval(intervalId); } }; }, [countdown]); // Update a setting const updateSetting = async (key: keyof NotificationSettings, value: boolean | number) => { try { const updatedSettings = { ...settings, [key]: value, }; // Special case: if enabling notifications, make sure permissions are granted if (key === 'enabled' && value === true) { // Permissions are handled in the service } // Update settings in the service await notificationService.updateSettings({ [key]: value }); // Update local state setSettings(updatedSettings); } catch (error) { logger.error('Error updating notification settings:', error); setAlertTitle('Error'); setAlertMessage('Failed to update notification settings'); setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); setAlertVisible(true); } }; // Set time before airing const setTimeBeforeAiring = (hours: number) => { updateSetting('timeBeforeAiring', hours); }; const resetAllNotifications = async () => { setAlertTitle('Reset Notifications'); setAlertMessage('This will cancel all scheduled notifications, but will not remove anything from your saved library. Are you sure?'); setAlertActions([ { label: 'Cancel', onPress: () => setAlertVisible(false), style: { color: currentTheme.colors.mediumGray } }, { label: 'Reset', onPress: async () => { try { const scheduledNotifications = notificationService.getScheduledNotifications?.() || []; for (const notification of scheduledNotifications) { await notificationService.cancelNotification(notification.id); } setAlertTitle('Success'); setAlertMessage('All notifications have been reset'); setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); setAlertVisible(true); } catch (error) { logger.error('Error resetting notifications:', error); setAlertTitle('Error'); setAlertMessage('Failed to reset notifications'); setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); setAlertVisible(true); } }, style: { color: currentTheme.colors.error } }, ]); setAlertVisible(true); }; const handleSyncNotifications = async () => { if (isSyncing) return; setIsSyncing(true); try { await notificationService.syncAllNotifications(); // Refresh stats after sync const stats = notificationService.getNotificationStats(); setNotificationStats(stats); setAlertTitle('Sync Complete'); setAlertMessage(`Successfully synced notifications for your library and Trakt items.\n\nScheduled: ${stats.upcoming} upcoming episodes\nThis week: ${stats.thisWeek} episodes`); setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); setAlertVisible(true); } catch (error) { logger.error('Error syncing notifications:', error); setAlertTitle('Error'); setAlertMessage('Failed to sync notifications. Please try again.'); setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); setAlertVisible(true); } finally { setIsSyncing(false); } }; const handleTestNotification = async () => { try { // 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 = { id: 'test-notification-' + Date.now(), seriesId: 'test-series', seriesName: 'Test Show', episodeTitle: 'Test Episode', season: 1, episode: 1, 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(0); // No countdown for instant notification setAlertTitle('Success'); setAlertMessage('Test notification scheduled to fire instantly'); setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); setAlertVisible(true); } else { setAlertTitle('Error'); setAlertMessage('Failed to schedule test notification. Make sure notifications are enabled.'); setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); setAlertVisible(true); } } catch (error) { logger.error('Error scheduling test notification:', error); setAlertTitle('Error'); setAlertMessage('Failed to schedule test notification'); setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); setAlertVisible(true); } }; if (loading) { return ( navigation.goBack()} > Notification Settings Loading settings... ); } return ( navigation.goBack()} > Settings {/* Empty for now, but ready for future actions */} Notification Settings General Enable Notifications updateSetting('enabled', value)} trackColor={{ false: currentTheme.colors.border, true: currentTheme.colors.primary + '80' }} thumbColor={settings.enabled ? currentTheme.colors.primary : currentTheme.colors.lightGray} /> {settings.enabled && ( <> Notification Types New Episodes updateSetting('newEpisodeNotifications', value)} trackColor={{ false: currentTheme.colors.border, true: currentTheme.colors.primary + '80' }} thumbColor={settings.newEpisodeNotifications ? currentTheme.colors.primary : currentTheme.colors.lightGray} /> Upcoming Shows updateSetting('upcomingShowsNotifications', value)} trackColor={{ false: currentTheme.colors.border, true: currentTheme.colors.primary + '80' }} thumbColor={settings.upcomingShowsNotifications ? currentTheme.colors.primary : currentTheme.colors.lightGray} /> Reminders updateSetting('reminderNotifications', value)} trackColor={{ false: currentTheme.colors.border, true: currentTheme.colors.primary + '80' }} thumbColor={settings.reminderNotifications ? currentTheme.colors.primary : currentTheme.colors.lightGray} /> Notification Timing When should you be notified before an episode airs? {[1, 6, 12, 24].map((hours) => ( setTimeBeforeAiring(hours)} > {hours === 1 ? '1 hour' : `${hours} hours`} ))} Notification Status Upcoming {notificationStats.upcoming} This Week {notificationStats.thisWeek} Total {notificationStats.total} {isSyncing ? 'Syncing...' : 'Sync Library & Trakt'} Automatically syncs notifications for all shows in your library and Trakt watchlist/collection. Advanced Reset All Notifications {countdown !== null ? `Notification in ${countdown}s...` : 'Test Notification (5 sec)'} {countdown !== null && ( Notification will appear in {countdown} seconds )} )} setAlertVisible(false)} actions={alertActions} /> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT + 8 : 8, }, backButton: { flexDirection: 'row', alignItems: 'center', padding: 8, }, backText: { fontSize: 17, marginLeft: 8, }, headerActions: { flexDirection: 'row', alignItems: 'center', }, headerButton: { padding: 8, marginLeft: 8, }, headerTitle: { fontSize: 34, fontWeight: 'bold', paddingHorizontal: 16, marginBottom: 24, }, content: { flex: 1, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, loadingText: { fontSize: 16, }, section: { padding: 16, borderBottomWidth: 1, }, sectionTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 16, }, settingItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, }, settingInfo: { flexDirection: 'row', alignItems: 'center', }, settingText: { fontSize: 16, marginLeft: 12, }, settingDescription: { fontSize: 14, marginBottom: 16, }, timingOptions: { flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap', marginTop: 8, }, timingOption: { paddingVertical: 10, paddingHorizontal: 16, borderRadius: 8, borderWidth: 1, marginBottom: 8, width: '48%', alignItems: 'center', }, timingText: { fontSize: 14, }, resetButton: { flexDirection: 'row', alignItems: 'center', padding: 12, borderRadius: 8, borderWidth: 1, marginBottom: 8, }, resetButtonText: { fontSize: 16, fontWeight: 'bold', marginLeft: 8, }, resetDescription: { fontSize: 12, fontStyle: 'italic', }, countdownContainer: { flexDirection: 'row', alignItems: 'center', marginTop: 8, padding: 8, backgroundColor: 'rgba(0, 0, 0, 0.1)', borderRadius: 4, }, countdownIcon: { marginRight: 8, }, countdownText: { fontSize: 14, }, statsContainer: { flexDirection: 'row', justifyContent: 'space-around', padding: 16, borderRadius: 8, marginBottom: 16, }, statItem: { alignItems: 'center', flex: 1, }, statLabel: { fontSize: 12, marginTop: 4, textAlign: 'center', }, statValue: { fontSize: 18, fontWeight: 'bold', marginTop: 2, }, }); export default NotificationSettingsScreen;