import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter/foundation.dart'; /// Notification Service - Handles local notifications class NotificationService { static final NotificationService _instance = NotificationService._internal(); factory NotificationService() => _instance; NotificationService._internal(); final FlutterLocalNotificationsPlugin _notifications = FlutterLocalNotificationsPlugin(); bool _initialized = false; /// Initialize notification service Future initialize() async { if (_initialized) return; // Skip initialization on web platform if (kIsWeb) { if (kDebugMode) { print('Notifications not supported on web platform'); } return; } try { // Android initialization settings const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); // iOS initialization settings const iosSettings = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, ); const initSettings = InitializationSettings( android: androidSettings, iOS: iosSettings, ); await _notifications.initialize( initSettings, onDidReceiveNotificationResponse: _onNotificationTapped, ); _initialized = true; if (kDebugMode) { print('Notification service initialized successfully'); } } catch (e) { if (kDebugMode) { print('Failed to initialize notifications: $e'); } } } /// Handle notification tap void _onNotificationTapped(NotificationResponse response) { if (kDebugMode) { print('Notification tapped: ${response.payload}'); } // TODO: Navigate to appropriate screen if needed } /// Request notification permissions (iOS only, Android auto-grants) Future requestPermissions() async { if (kIsWeb) return false; try { final result = await _notifications .resolvePlatformSpecificImplementation< IOSFlutterLocalNotificationsPlugin>() ?.requestPermissions( alert: true, badge: true, sound: true, ); return result ?? true; // Android always returns true } catch (e) { if (kDebugMode) { print('Failed to request permissions: $e'); } return false; } } /// Show focus session completed notification Future showFocusCompletedNotification({ required int minutes, required int distractionCount, String? title, String? body, }) async { if (kIsWeb || !_initialized) return; try { const androidDetails = AndroidNotificationDetails( 'focus_completed', 'Focus Session Completed', channelDescription: 'Notifications for completed focus sessions', importance: Importance.high, priority: Priority.high, enableVibration: true, playSound: true, ); const iosDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ); const notificationDetails = NotificationDetails( android: androidDetails, iOS: iosDetails, ); // Use provided title/body or fall back to English final notificationTitle = title ?? '🎉 Focus session complete!'; final notificationBody = body ?? (distractionCount == 0 ? 'You focused for $minutes ${minutes == 1 ? 'minute' : 'minutes'} without distractions!' : 'You focused for $minutes ${minutes == 1 ? 'minute' : 'minutes'}. Great effort!'); await _notifications.show( 0, // Notification ID notificationTitle, notificationBody, notificationDetails, payload: 'focus_completed', ); if (kDebugMode) { print('Notification shown: $notificationTitle - $notificationBody'); } } catch (e) { if (kDebugMode) { print('Failed to show notification: $e'); } } } /// Show reminder notification (optional feature for future) Future showReminderNotification({ required String message, }) async { if (kIsWeb || !_initialized) return; try { const androidDetails = AndroidNotificationDetails( 'reminders', 'Focus Reminders', channelDescription: 'Gentle reminders to focus', importance: Importance.defaultImportance, priority: Priority.defaultPriority, ); const iosDetails = DarwinNotificationDetails(); const notificationDetails = NotificationDetails( android: androidDetails, iOS: iosDetails, ); await _notifications.show( 1, // Different ID from completion notifications '💚 FocusBuddy', message, notificationDetails, payload: 'reminder', ); } catch (e) { if (kDebugMode) { print('Failed to show reminder: $e'); } } } /// Cancel all notifications Future cancelAll() async { if (kIsWeb || !_initialized) return; try { await _notifications.cancelAll(); } catch (e) { if (kDebugMode) { print('Failed to cancel notifications: $e'); } } } /// Cancel a specific notification by ID Future cancel(int id) async { if (kIsWeb || !_initialized) return; try { await _notifications.cancel(id); } catch (e) { if (kDebugMode) { print('Failed to cancel notification $id: $e'); } } } /// Show ongoing focus session notification (for background) /// This notification stays visible while the timer is running Future showOngoingFocusNotification({ required int remainingMinutes, required int remainingSeconds, String? title, String? timeRemainingText, }) async { if (kIsWeb || !_initialized) return; try { // Format time display for fallback final timeStr = '${remainingMinutes.toString().padLeft(2, '0')}:${(remainingSeconds % 60).toString().padLeft(2, '0')}'; const androidDetails = AndroidNotificationDetails( 'focus_timer', 'Focus Timer', channelDescription: 'Shows ongoing focus session timer', importance: Importance.low, priority: Priority.low, ongoing: true, // Makes notification persistent autoCancel: false, showWhen: false, enableVibration: false, playSound: false, // Show in status bar showProgress: false, ); const iosDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: false, presentSound: false, ); const notificationDetails = NotificationDetails( android: androidDetails, iOS: iosDetails, ); // Use provided text or fall back to English final notificationTitle = title ?? '⏱️ Focus session in progress'; final notificationBody = timeRemainingText ?? '$timeStr remaining'; await _notifications.show( 2, // Use ID 2 for ongoing notifications notificationTitle, notificationBody, notificationDetails, payload: 'focus_ongoing', ); } catch (e) { if (kDebugMode) { print('Failed to show ongoing notification: $e'); } } } /// Update ongoing notification with new time Future updateOngoingFocusNotification({ required int remainingMinutes, required int remainingSeconds, String? title, String? timeRemainingText, }) async { // On Android, showing the same notification ID updates it await showOngoingFocusNotification( remainingMinutes: remainingMinutes, remainingSeconds: remainingSeconds, title: title, timeRemainingText: timeRemainingText, ); } /// Cancel ongoing focus notification Future cancelOngoingFocusNotification() async { await cancel(2); // Cancel notification with ID 2 } /// Check if notifications are supported on this platform bool get isSupported => !kIsWeb; }