diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index db72a70..eed023a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -968,19 +968,19 @@ pub fn run() { }); let show_item = - MenuItem::with_id(app, "show", "Mostra Presto", true, None::<&str>)?; + MenuItem::with_id(app, "show", "Show Presto", true, None::<&str>)?; let start_session_item = MenuItem::with_id( app, "start_session", - "Inizia sessione", + "Start Session", false, None::<&str>, )?; - let pause_item = MenuItem::with_id(app, "pause", "Pausa", false, None::<&str>)?; + let pause_item = MenuItem::with_id(app, "pause", "Pause", false, None::<&str>)?; let skip_item = - MenuItem::with_id(app, "skip", "Salta sessione", false, None::<&str>)?; - let cancel_item = MenuItem::with_id(app, "cancel", "Annulla", false, None::<&str>)?; - let quit_item = MenuItem::with_id(app, "quit", "Esci", true, None::<&str>)?; + MenuItem::with_id(app, "skip", "Skip Session", false, None::<&str>)?; + let cancel_item = MenuItem::with_id(app, "cancel", "Cancel", false, None::<&str>)?; + let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?; let menu = Menu::with_items( app, &[ @@ -1285,43 +1285,43 @@ async fn update_tray_menu( let tray = app.tray_by_id("main"); if let Some(tray) = tray { - let show_item = MenuItem::with_id(&app, "show", "Mostra Presto", true, None::<&str>) + let show_item = MenuItem::with_id(&app, "show", "Show Presto", true, None::<&str>) .map_err(|e| format!("Failed to create show item: {}", e))?; - // Inizia sessione: abilitato solo se non è in esecuzione + // Start Session: enabled only if not running let start_session_item = MenuItem::with_id( &app, "start_session", - "Inizia sessione", + "Start Session", !is_running, None::<&str>, ) .map_err(|e| format!("Failed to create start session item: {}", e))?; - // Pausa: abilitata solo se è in esecuzione e non in pausa + // Pause: enabled only if running and not paused let pause_item = MenuItem::with_id( &app, "pause", - "Pausa", + "Pause", is_running && !is_paused, None::<&str>, ) .map_err(|e| format!("Failed to create pause item: {}", e))?; - // Skip: abilitato solo se è in esecuzione - let skip_item = MenuItem::with_id(&app, "skip", "Salta sessione", is_running, None::<&str>) + // Skip: enabled only if running + let skip_item = MenuItem::with_id(&app, "skip", "Skip Session", is_running, None::<&str>) .map_err(|e| format!("Failed to create skip item: {}", e))?; - // Annulla: abilitato se è in modalità focus, disabilitato in break/longBreak (undo) + // Cancel: enabled if in focus mode, disabled in break/longBreak (undo) let cancel_text = if current_mode == "focus" { - "Annulla" + "Cancel" } else { - "Annulla ultima" + "Cancel Last" }; let cancel_item = MenuItem::with_id(&app, "cancel", cancel_text, true, None::<&str>) .map_err(|e| format!("Failed to create cancel item: {}", e))?; - let quit_item = MenuItem::with_id(&app, "quit", "Esci", true, None::<&str>) + let quit_item = MenuItem::with_id(&app, "quit", "Quit", true, None::<&str>) .map_err(|e| format!("Failed to create quit item: {}", e))?; let new_menu = Menu::with_items( diff --git a/src/core/pomodoro-timer.js b/src/core/pomodoro-timer.js index b73f19e..a724fbd 100644 --- a/src/core/pomodoro-timer.js +++ b/src/core/pomodoro-timer.js @@ -778,10 +778,12 @@ export class PomodoroTimer { ); // Show desktop notification if enabled - NotificationUtils.showDesktopNotification( - 'Session Time Limit Reached', - `Your session has been automatically paused after ${maxTimeInMinutes} minutes. Consider taking a break!` - ); + if (this.enableDesktopNotifications) { + NotificationUtils.showDesktopNotification( + 'Session Time Limit Reached', + `Your session has been automatically paused after ${maxTimeInMinutes} minutes. Consider taking a break!` + ); + } } } @@ -2297,56 +2299,222 @@ export class PomodoroTimer { } } - // Simple notification system + // Enhanced notification system with better error handling and debugging async showNotification() { + // Only show desktop notifications if the setting is enabled + if (!this.enableDesktopNotifications) { + console.log('🔔 Desktop notifications are disabled in settings'); + return; + } + + const messages = { + focus: 'Break time! Take a rest 😌', + break: 'Break over! Time to focus 🍅', + longBreak: 'Long break over! Ready for more focus? 🚀' + }; + + const notificationTitle = 'Presto - Pomodoro Timer'; + const notificationBody = messages[this.currentMode]; + + console.log(`🔔 Attempting to show desktop notification: "${notificationBody}"`); + try { // Check if we're in a Tauri context and use Tauri notifications if (window.__TAURI__ && window.__TAURI__.notification) { + console.log('🔔 Using Tauri notification system'); const { isPermissionGranted, requestPermission, sendNotification } = window.__TAURI__.notification; // Check if permission is granted let permissionGranted = await isPermissionGranted(); + console.log(`🔔 Tauri notification permission status: ${permissionGranted}`); // If not granted, request permission if (!permissionGranted) { + console.log('🔔 Requesting Tauri notification permission...'); const permission = await requestPermission(); permissionGranted = permission === 'granted'; + console.log(`🔔 Permission request result: ${permission} (granted: ${permissionGranted})`); + + if (!permissionGranted) { + console.warn('❌ Tauri notification permission was denied'); + NotificationUtils.showNotificationPing('Desktop notifications are disabled. Enable them in system settings to get timer alerts! 🔔', 'warning', this.currentMode); + return; + } } // Send notification if permission is granted if (permissionGranted) { - const messages = { - focus: 'Break time! Take a rest 😌', - break: 'Break over! Time to focus 🍅', - longBreak: 'Long break over! Ready for more focus? 🚀' - }; - + console.log('🔔 Sending Tauri notification...'); await sendNotification({ - title: 'Presto - Pomodoro Timer', - body: messages[this.currentMode], + title: notificationTitle, + body: notificationBody, icon: '/assets/tauri.svg' }); + console.log('✅ Tauri notification sent successfully'); + } else { + console.warn('❌ Tauri notification permission not available'); + this.fallbackToWebNotifications(notificationTitle, notificationBody); } } else { - // Fallback to Web Notification API - if ('Notification' in window && Notification.permission === 'granted') { - const messages = { - focus: 'Break time! Take a rest 😌', - break: 'Break over! Time to focus 🍅', - longBreak: 'Long break over! Ready for more focus? 🚀' - }; - - NotificationUtils.showDesktopNotification('Presto - Pomodoro Timer', messages[this.currentMode]); + console.log('🔔 Tauri not available, falling back to Web Notification API'); + this.fallbackToWebNotifications(notificationTitle, notificationBody); + } + } catch (error) { + console.error('❌ Failed to show Tauri notification:', error); + console.log('🔄 Attempting fallback to Web Notification API...'); + this.fallbackToWebNotifications(notificationTitle, notificationBody); + } + } + + // Fallback to Web Notification API with improved error handling + async fallbackToWebNotifications(title, body) { + try { + if ('Notification' in window) { + console.log(`🔔 Web Notification API available, permission: ${Notification.permission}`); + + if (Notification.permission === 'granted') { + console.log('🔔 Sending Web notification...'); + NotificationUtils.showDesktopNotification(title, body); + console.log('✅ Web notification sent successfully'); + } else if (Notification.permission === 'default') { + console.log('🔔 Requesting Web notification permission...'); + const permission = await Notification.requestPermission(); + console.log(`🔔 Web permission request result: ${permission}`); + + if (permission === 'granted') { + NotificationUtils.showDesktopNotification(title, body); + console.log('✅ Web notification sent after permission granted'); + } else { + console.warn('❌ Web notification permission was denied'); + NotificationUtils.showNotificationPing('Desktop notifications are disabled. Enable them in your browser to get timer alerts! 🔔', 'warning', this.currentMode); + } + } else { + console.warn('❌ Web notification permission was previously denied'); + NotificationUtils.showNotificationPing('Desktop notifications are disabled. Enable them in your browser settings to get timer alerts! 🔔', 'warning', this.currentMode); } + } else { + console.warn('❌ Web Notification API not supported'); + this.fallbackToInAppNotification(body); } } catch (error) { - console.error('Failed to show notification:', error); - // Fallback to in-app notification - this.showNotificationPing( - this.currentMode === 'focus' ? 'Break time! Take a rest 😌' : - this.currentMode === 'break' ? 'Break over! Time to focus 🍅' : - 'Long break over! Ready for more focus? 🚀' - ); + console.error('❌ Failed to show Web notification:', error); + this.fallbackToInAppNotification(body); + } + } + + // Final fallback to in-app notification + fallbackToInAppNotification(message) { + console.log('🔔 Using in-app notification as final fallback'); + NotificationUtils.showNotificationPing(message, 'info', this.currentMode); + } + + // Test notification function for debugging + // Usage: Open browser console and type: window.pomodoroTimer.testNotification() + async testNotification() { + console.log('🧪 Testing notification system...'); + console.log('📝 Instructions: This will test the notification system and show debug info in the console'); + console.log(`🔧 Current settings: desktop notifications = ${this.enableDesktopNotifications}`); + + // Detect if we're in development mode + const isDevMode = window.location.protocol === 'tauri:' ? false : true; + const bundleId = 'com.presto.app'; + + console.log(`🔧 Environment: ${isDevMode ? 'Development (tauri dev)' : 'Production (built app)'}`); + console.log(`🔧 Bundle ID: ${bundleId}`); + + if (isDevMode) { + console.log('⚠️ IMPORTANT: You\'re running in development mode (tauri dev)'); + console.log('⚠️ On macOS, Tauri notifications often don\'t work in dev mode due to:'); + console.log(' 1. Tauri uses Terminal.app for dev mode, which may not have notification permissions'); + console.log(' 2. Bundle identifier is handled differently in dev vs production'); + console.log(' 3. macOS requires proper app bundle registration for notifications'); + console.log(''); + console.log('🔧 To test notifications properly:'); + console.log(' 1. Run: npm run tauri build'); + console.log(' 2. Install the built app from src-tauri/target/release/bundle/'); + console.log(' 3. Test notifications in the installed production app'); + console.log(''); + console.log('🔧 For dev mode, check Terminal.app permissions:'); + console.log(' - System Preferences > Notifications & Focus > Terminal'); + console.log(' - Make sure "Allow Notifications" is enabled'); + console.log(''); + } + + // Show in-app notification first + NotificationUtils.showNotificationPing('Testing notification system... 🧪', 'info', this.currentMode); + + // Test desktop notification + const originalSetting = this.enableDesktopNotifications; + this.enableDesktopNotifications = true; // Temporarily enable for testing + + try { + await this.showNotification(); + console.log('✅ Test notification API call completed - check console logs above for detailed debug info'); + console.log('🔍 Look for messages starting with 🔔 for notification flow details'); + + if (isDevMode) { + console.log(''); + console.log('⚠️ If you see "✅ Tauri notification sent successfully" but no notification appeared:'); + console.log(' - This is NORMAL in development mode on macOS'); + console.log(' - Test with a production build to verify notifications work'); + console.log(''); + console.log('🔄 Trying Web Notification API as fallback...'); + await this.testWebNotificationFallback(); + } + } catch (error) { + console.error('❌ Test notification failed:', error); + console.log('💡 Troubleshooting steps:'); + if (isDevMode) { + console.log(' 1. This is likely due to dev mode limitations on macOS'); + console.log(' 2. Check Terminal.app notification permissions in System Preferences'); + console.log(' 3. Test with a production build: npm run tauri build'); + } else { + console.log(' 1. Check if notifications are enabled in System Preferences > Notifications'); + console.log(' 2. Look for "presto" or "com.presto.app" in the notifications list'); + console.log(' 3. Ensure "Allow Notifications" is enabled for the app'); + } + } finally { + // Restore original setting + this.enableDesktopNotifications = originalSetting; + } + } + + // Test Web Notification API fallback + async testWebNotificationFallback() { + try { + if ('Notification' in window) { + console.log('🌐 Web Notification API available'); + console.log(`🌐 Current permission: ${Notification.permission}`); + + if (Notification.permission === 'default') { + console.log('🌐 Requesting Web notification permission...'); + const permission = await Notification.requestPermission(); + console.log(`🌐 Permission result: ${permission}`); + } + + if (Notification.permission === 'granted') { + console.log('🌐 Sending Web notification...'); + const notification = new Notification('Presto - Test Web Notification', { + body: 'This is a fallback Web notification test', + icon: '/assets/tauri.svg' + }); + + notification.onshow = () => console.log('✅ Web notification displayed'); + notification.onerror = (error) => console.error('❌ Web notification error:', error); + + // Auto-close after 5 seconds + setTimeout(() => { + notification.close(); + console.log('🌐 Web notification closed automatically'); + }, 5000); + } else { + console.log('❌ Web notification permission denied'); + } + } else { + console.log('❌ Web Notification API not available'); + } + } catch (error) { + console.error('❌ Web notification test failed:', error); } } diff --git a/src/index.html b/src/index.html index db70a4b..83c0128 100644 --- a/src/index.html +++ b/src/index.html @@ -606,8 +606,12 @@

Notification Types

Desktop Notifications

Show system notifications when timer completes. Browser permission will - be - requested when enabled.

+ be requested when enabled.
+ Note: On macOS, notifications may not work in development mode. Test with a production build for full functionality.

+
diff --git a/src/managers/settings-manager.js b/src/managers/settings-manager.js index b1adcba..3c97876 100644 --- a/src/managers/settings-manager.js +++ b/src/managers/settings-manager.js @@ -174,11 +174,8 @@ export class SettingsManager { this.initializeTimerThemeSelector(); // Populate notification settings - // Check current notification permission and adjust desktop notifications setting - const hasNotificationPermission = NotificationUtils.getNotificationPermission() === 'granted'; - const desktopNotificationsEnabled = this.settings.notifications.desktop_notifications && hasNotificationPermission; - - document.getElementById('desktop-notifications').checked = desktopNotificationsEnabled; + // Always show the user's setting preference, regardless of system permission + document.getElementById('desktop-notifications').checked = this.settings.notifications.desktop_notifications; document.getElementById('sound-notifications').checked = this.settings.notifications.sound_notifications; document.getElementById('auto-start-timer').checked = this.settings.notifications.auto_start_timer; @@ -599,7 +596,10 @@ export class SettingsManager { if (e.target.checked) { try { // Request notification permission when enabling + console.log('🔔 Desktop notifications enabled, requesting permission...'); const permission = await NotificationUtils.requestNotificationPermission(); + console.log('🔔 Notification permission result:', permission); + if (permission !== 'granted') { // Show warning but don't prevent saving the setting const message = permission === 'unsupported' @@ -607,6 +607,9 @@ export class SettingsManager { : 'Notification permission denied. Settings saved, but notifications won\'t work until permission is granted.'; NotificationUtils.showNotificationPing(message, 'warning'); // Don't uncheck the box - let the user's choice be saved + } else { + // Permission granted, show success message + NotificationUtils.showNotificationPing('✓ Desktop notifications enabled!', 'success'); } } catch (error) { console.warn('Failed to request notification permission, but allowing setting to be saved:', error); @@ -614,12 +617,18 @@ export class SettingsManager { // This allows the setting to work when Tauri notifications are properly configured NotificationUtils.showNotificationPing('Settings saved. Notifications will work when properly configured.', 'info'); } + } else { + console.log('🔔 Desktop notifications disabled'); + NotificationUtils.showNotificationPing('Desktop notifications disabled', 'info'); } // Always save the setting regardless of permission status this.scheduleAutoSave(); }); } + // Initialize notification status display and test button + this.setupNotificationStatusDisplay(); + // Other notification checkboxes const checkboxFields = [ 'sound-notifications', @@ -997,6 +1006,115 @@ export class SettingsManager { } } + // Notification status display and debugging functions + async setupNotificationStatusDisplay() { + const statusDiv = document.getElementById('notification-status'); + const statusText = document.getElementById('notification-status-text'); + const testBtn = document.getElementById('test-notifications-btn'); + + if (!statusDiv || !statusText || !testBtn) { + console.warn('Notification status elements not found in DOM'); + return; + } + + // Show the status div + statusDiv.style.display = 'block'; + + // Update status on load + await this.updateNotificationStatus(); + + // Set up test button + testBtn.addEventListener('click', async () => { + if (window.pomodoroTimer && typeof window.pomodoroTimer.testNotification === 'function') { + await window.pomodoroTimer.testNotification(); + // Update status after test + setTimeout(() => this.updateNotificationStatus(), 1000); + } else { + console.warn('Test notification function not available'); + NotificationUtils.showNotificationPing('Test function not available. Try again after the timer is fully loaded.', 'warning'); + } + }); + + // Update status when desktop notifications setting changes + const desktopNotificationsCheckbox = document.getElementById('desktop-notifications'); + if (desktopNotificationsCheckbox) { + desktopNotificationsCheckbox.addEventListener('change', () => { + setTimeout(() => this.updateNotificationStatus(), 500); + }); + } + } + + async updateNotificationStatus() { + const statusDiv = document.getElementById('notification-status'); + const statusText = document.getElementById('notification-status-text'); + + if (!statusDiv || !statusText) return; + + let status = ''; + let className = ''; + + try { + // Detect if we're in development mode + const isDevMode = window.location.protocol === 'tauri:' ? false : true; + + // Check if desktop notifications are enabled in settings + const isEnabledInSettings = document.getElementById('desktop-notifications')?.checked || false; + + if (!isEnabledInSettings) { + status = '🔕 Disabled in settings'; + className = 'status-disabled'; + } else { + // Check Tauri notifications first + if (window.__TAURI__ && window.__TAURI__.notification) { + try { + const { isPermissionGranted } = window.__TAURI__.notification; + const granted = await isPermissionGranted(); + if (granted) { + if (isDevMode) { + status = '⚠️ Dev mode - may not work on macOS'; + className = 'status-warning'; + } else { + status = '✅ Tauri notifications ready'; + className = 'status-ready'; + } + } else { + status = '⚠️ Tauri permission needed'; + className = 'status-warning'; + } + } catch (error) { + status = '❌ Tauri error: ' + error.message; + className = 'status-error'; + } + } else { + // Check Web Notification API + if ('Notification' in window) { + const permission = Notification.permission; + if (permission === 'granted') { + status = '✅ Web notifications ready'; + className = 'status-ready'; + } else if (permission === 'denied') { + status = '❌ Web notifications blocked'; + className = 'status-error'; + } else { + status = '⚠️ Web permission needed'; + className = 'status-warning'; + } + } else { + status = '❌ Notifications not supported'; + className = 'status-error'; + } + } + } + } catch (error) { + status = '❌ Status check failed'; + className = 'status-error'; + console.error('Failed to check notification status:', error); + } + + statusText.textContent = status; + statusDiv.className = `notification-status ${className}`; + } + // Theme management functions async applyTheme(theme) { const html = document.documentElement; diff --git a/src/styles/notifications.css b/src/styles/notifications.css index f5b16af..ae22da6 100644 --- a/src/styles/notifications.css +++ b/src/styles/notifications.css @@ -188,4 +188,73 @@ border-radius: 14px; box-shadow: 0 3px 14px rgba(0, 0, 0, 0.09); } +} + +/* Notification status indicator styles */ +.notification-status { + border-left: 3px solid #ccc; + background-color: var(--bg-secondary); + color: var(--text-secondary); + transition: all 0.3s ease; +} + +.notification-status.status-ready { + border-left-color: #4CAF50; + background-color: rgba(76, 175, 80, 0.1); + color: #2E7D32; +} + +.notification-status.status-warning { + border-left-color: #FF9800; + background-color: rgba(255, 152, 0, 0.1); + color: #E65100; +} + +.notification-status.status-error { + border-left-color: #F44336; + background-color: rgba(244, 67, 54, 0.1); + color: #C62828; +} + +.notification-status.status-disabled { + border-left-color: #9E9E9E; + background-color: rgba(158, 158, 158, 0.1); + color: #616161; +} + +#test-notifications-btn { + background: var(--button-primary); + color: var(--button-text); + border: 1px solid var(--border); + transition: background-color 0.2s ease, transform 0.1s ease; +} + +#test-notifications-btn:hover { + background: var(--button-primary-hover); + transform: translateY(-1px); +} + +#test-notifications-btn:active { + transform: translateY(0); +} + +/* Dark theme adjustments for notification status */ +[data-theme="dark"] .notification-status.status-ready { + background-color: rgba(76, 175, 80, 0.15); + color: #81C784; +} + +[data-theme="dark"] .notification-status.status-warning { + background-color: rgba(255, 152, 0, 0.15); + color: #FFB74D; +} + +[data-theme="dark"] .notification-status.status-error { + background-color: rgba(244, 67, 54, 0.15); + color: #E57373; +} + +[data-theme="dark"] .notification-status.status-disabled { + background-color: rgba(158, 158, 158, 0.15); + color: #BDBDBD; } \ No newline at end of file diff --git a/src/utils/theme-loader.js b/src/utils/theme-loader.js index a2f9c22..ced74bb 100644 --- a/src/utils/theme-loader.js +++ b/src/utils/theme-loader.js @@ -39,7 +39,7 @@ class ThemeLoader { // that gets updated by the build process or manually maintained // This could be enhanced to use a build-time script that generates this list - const knownThemes = [ + const knownThemes = [ 'espresso.css', 'pipboy.css', 'pommodore64.css'