import 'package:flutter/foundation.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:relationship_saver/features/local/local_models.dart'; import 'package:relationship_saver/features/reminders/scheduling/reminder_scheduler.dart'; import 'package:timezone/timezone.dart' as tz; const String _channelId = 'relationship_saver_reminders'; const String _channelName = 'Relationship Reminders'; const String _channelDescription = 'Reminders for relationship plans and follow-ups'; /// Schedules reminders with local notifications where platform support exists. class LocalNotificationsReminderScheduler implements ReminderScheduler { LocalNotificationsReminderScheduler({ ReminderNotificationsDriver? driver, DateTime Function()? now, }) : _driver = driver ?? FlutterReminderNotificationsDriver(), _now = now ?? DateTime.now; final ReminderNotificationsDriver _driver; final DateTime Function() _now; @override Future clearAll() async { if (!_supportsSchedulingPlatform()) { return; } await _driver.ensureInitialized(); await _driver.cancelAll(); } @override Future reconcile(List reminders) async { if (!_supportsSchedulingPlatform()) { return; } await _driver.ensureInitialized(); await _driver.cancelAll(); final DateTime now = _now(); final List enabled = reminders .where((ReminderRule reminder) => reminder.enabled) .toList(growable: false); for (final ReminderRule reminder in enabled) { final DateTimeComponents? repeating = _repeatComponents(reminder.cadence); if (repeating == null && reminder.nextAt.isBefore(now)) { continue; } await _driver.schedule( id: _notificationId(reminder.id), title: reminder.title, body: _buildBody(reminder), scheduledAt: reminder.nextAt, repeatComponents: repeating, payload: reminder.id, ); } } DateTimeComponents? _repeatComponents(ReminderCadence cadence) { return switch (cadence) { ReminderCadence.daily => DateTimeComponents.time, ReminderCadence.weekly => DateTimeComponents.dayOfWeekAndTime, ReminderCadence.monthly => DateTimeComponents.dayOfMonthAndTime, }; } String _buildBody(ReminderRule reminder) { final String frequency = switch (reminder.cadence) { ReminderCadence.daily => 'daily', ReminderCadence.weekly => 'weekly', ReminderCadence.monthly => 'monthly', }; return 'Your $frequency reminder is due now.'; } bool _supportsSchedulingPlatform() { if (kIsWeb) { return false; } return defaultTargetPlatform != TargetPlatform.linux; } int _notificationId(String raw) { int hash = 0; for (final int unit in raw.codeUnits) { hash = ((hash * 31) + unit) & 0x7fffffff; } return hash; } } /// Driver wrapper around the notification plugin for easier testing. abstract class ReminderNotificationsDriver { Future ensureInitialized(); Future cancelAll(); Future schedule({ required int id, required String title, required String body, required DateTime scheduledAt, required DateTimeComponents? repeatComponents, required String payload, }); } class FlutterReminderNotificationsDriver implements ReminderNotificationsDriver { FlutterReminderNotificationsDriver({FlutterLocalNotificationsPlugin? plugin}) : _plugin = plugin ?? FlutterLocalNotificationsPlugin(); final FlutterLocalNotificationsPlugin _plugin; Future? _initializeFuture; @override Future ensureInitialized() { return _initializeFuture ??= _initialize(); } @override Future cancelAll() async { await _plugin.cancelAll(); } @override Future schedule({ required int id, required String title, required String body, required DateTime scheduledAt, required DateTimeComponents? repeatComponents, required String payload, }) async { await _plugin.zonedSchedule( id: id, title: title, body: body, scheduledDate: tz.TZDateTime.from(scheduledAt.toUtc(), tz.UTC), notificationDetails: _notificationDetails, androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle, matchDateTimeComponents: repeatComponents, payload: payload, ); } Future _initialize() async { await _plugin.initialize( settings: const InitializationSettings( android: AndroidInitializationSettings('@mipmap/ic_launcher'), iOS: DarwinInitializationSettings(), macOS: DarwinInitializationSettings(), linux: LinuxInitializationSettings(defaultActionName: 'Open reminders'), windows: WindowsInitializationSettings( appName: 'Relationship Saver', appUserModelId: 'com.relationshipsaver.app', guid: 'ee23c0af-5898-4d96-b599-cf2d7d0d37d7', ), ), ); } NotificationDetails get _notificationDetails { return const NotificationDetails( android: AndroidNotificationDetails( _channelId, _channelName, channelDescription: _channelDescription, importance: Importance.high, priority: Priority.high, ), iOS: DarwinNotificationDetails(), macOS: DarwinNotificationDetails(), linux: LinuxNotificationDetails(), windows: WindowsNotificationDetails(), ); } }