Files
FocusBuddy/lib/services/notification_service.dart
2025-11-24 11:25:33 +08:00

290 lines
8.0 KiB
Dart

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<void> 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<bool> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> cancelOngoingFocusNotification() async {
await cancel(2); // Cancel notification with ID 2
}
/// Check if notifications are supported on this platform
bool get isSupported => !kIsWeb;
}