Integrate local notifications scheduler for reminders
This commit is contained in:
+19
-7
@@ -46,16 +46,28 @@ flutter run --dart-define=ENABLE_BACKGROUND_SYNC=false
|
|||||||
|
|
||||||
## Reminder Delivery Scaffold
|
## Reminder Delivery Scaffold
|
||||||
|
|
||||||
Reminder scheduling is now wired through `ReminderScheduler` and reconciled from
|
Reminder scheduling is now wired through `ReminderScheduler` and reconciled
|
||||||
`LocalRepository` on startup/state changes.
|
from `LocalRepository` on startup/state changes.
|
||||||
|
|
||||||
Current default implementation is no-op:
|
Default implementation now uses local notifications runtime where supported.
|
||||||
|
|
||||||
- `NoopReminderScheduler` in
|
Control flag:
|
||||||
`lib/features/reminders/scheduling/reminder_scheduler.dart`
|
|
||||||
|
|
||||||
This keeps behavior stable across all platforms until real local notification
|
```bash
|
||||||
integration is added.
|
flutter run --dart-define=ENABLE_LOCAL_NOTIFICATIONS=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Disable notifications explicitly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flutter run --dart-define=ENABLE_LOCAL_NOTIFICATIONS=false
|
||||||
|
```
|
||||||
|
|
||||||
|
Behavior notes:
|
||||||
|
|
||||||
|
- web: no-op fallback
|
||||||
|
- linux: schedule fallback (no-op) due platform scheduling limitations in plugin
|
||||||
|
- android/iOS/macOS/windows: scheduled local notifications
|
||||||
|
|
||||||
## Local Persistence Backend
|
## Local Persistence Backend
|
||||||
|
|
||||||
|
|||||||
+32
-2
@@ -2,6 +2,36 @@
|
|||||||
|
|
||||||
Updated: 2026-02-15
|
Updated: 2026-02-15
|
||||||
|
|
||||||
|
## Latest Milestone (2026-02-15): Local Notifications Reminder Runtime
|
||||||
|
|
||||||
|
Implemented reminder delivery with local notifications runtime integration:
|
||||||
|
|
||||||
|
- Added dependency:
|
||||||
|
- `flutter_local_notifications`
|
||||||
|
- Added concrete scheduler:
|
||||||
|
- `lib/features/reminders/scheduling/reminder_scheduler_local_notifications.dart`
|
||||||
|
- `LocalNotificationsReminderScheduler`
|
||||||
|
- reconciles local reminders into platform notification schedule
|
||||||
|
- supports recurring cadence mapping:
|
||||||
|
- daily -> `DateTimeComponents.time`
|
||||||
|
- weekly -> `DateTimeComponents.dayOfWeekAndTime`
|
||||||
|
- monthly -> `DateTimeComponents.dayOfMonthAndTime`
|
||||||
|
- web/linux gracefully no-op (scheduling fallback)
|
||||||
|
- Provider wiring now defaults to local notifications scheduler:
|
||||||
|
- `lib/features/reminders/scheduling/reminder_scheduler_provider.dart`
|
||||||
|
- gated by `ENABLE_LOCAL_NOTIFICATIONS`
|
||||||
|
- Added config flag:
|
||||||
|
- `lib/core/config/app_config.dart` -> `enableLocalNotifications`
|
||||||
|
- Added tests:
|
||||||
|
- `test/features/reminders/scheduling/reminder_scheduler_local_notifications_test.dart`
|
||||||
|
- Updated setup docs:
|
||||||
|
- `docs/SETUP.md`
|
||||||
|
|
||||||
|
Validation for this milestone:
|
||||||
|
|
||||||
|
- `flutter analyze` -> pass
|
||||||
|
- `flutter test` -> pass
|
||||||
|
|
||||||
## Latest Milestone (2026-02-15): Sync Rejection Payload Inspection
|
## Latest Milestone (2026-02-15): Sync Rejection Payload Inspection
|
||||||
|
|
||||||
Improved conflict handling UX in Sync view:
|
Improved conflict handling UX in Sync view:
|
||||||
@@ -169,8 +199,8 @@ Validation for this milestone:
|
|||||||
### Open Phases (Current)
|
### Open Phases (Current)
|
||||||
|
|
||||||
1. Reminder delivery runtime integration:
|
1. Reminder delivery runtime integration:
|
||||||
- replace no-op scheduler with platform local notifications implementation
|
- implemented for Android/iOS/macOS/windows with web/linux safe fallback.
|
||||||
for Android/iOS/macOS/Windows/Linux/Web-safe fallback.
|
- future enhancement: permission UX and direct notification tap routing.
|
||||||
2. Conflict resolution UX:
|
2. Conflict resolution UX:
|
||||||
- reject/requeue + payload inspect are implemented; future enhancement is
|
- reject/requeue + payload inspect are implemented; future enhancement is
|
||||||
direct deep-link navigation to entity edit screens from Sync view.
|
direct deep-link navigation to entity edit screens from Sync view.
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ class AppConfig {
|
|||||||
static bool get enableBackgroundSync =>
|
static bool get enableBackgroundSync =>
|
||||||
const bool.fromEnvironment('ENABLE_BACKGROUND_SYNC', defaultValue: true);
|
const bool.fromEnvironment('ENABLE_BACKGROUND_SYNC', defaultValue: true);
|
||||||
|
|
||||||
|
/// Enables local notifications runtime for reminder delivery.
|
||||||
|
static bool get enableLocalNotifications => const bool.fromEnvironment(
|
||||||
|
'ENABLE_LOCAL_NOTIFICATIONS',
|
||||||
|
defaultValue: true,
|
||||||
|
);
|
||||||
|
|
||||||
/// Interval used for periodic background sync ticks.
|
/// Interval used for periodic background sync ticks.
|
||||||
static int get backgroundSyncIntervalSeconds => const int.fromEnvironment(
|
static int get backgroundSyncIntervalSeconds => const int.fromEnvironment(
|
||||||
'BACKGROUND_SYNC_INTERVAL_SECONDS',
|
'BACKGROUND_SYNC_INTERVAL_SECONDS',
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
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<void> clearAll() async {
|
||||||
|
if (!_supportsSchedulingPlatform()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _driver.ensureInitialized();
|
||||||
|
await _driver.cancelAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reconcile(List<ReminderRule> reminders) async {
|
||||||
|
if (!_supportsSchedulingPlatform()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _driver.ensureInitialized();
|
||||||
|
await _driver.cancelAll();
|
||||||
|
|
||||||
|
final DateTime now = _now();
|
||||||
|
final List<ReminderRule> 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<void> ensureInitialized();
|
||||||
|
|
||||||
|
Future<void> cancelAll();
|
||||||
|
|
||||||
|
Future<void> 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<void>? _initializeFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> ensureInitialized() {
|
||||||
|
return _initializeFuture ??= _initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> cancelAll() async {
|
||||||
|
await _plugin.cancelAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> 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<void> _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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:relationship_saver/core/config/app_config.dart';
|
||||||
import 'package:relationship_saver/features/reminders/scheduling/reminder_scheduler.dart';
|
import 'package:relationship_saver/features/reminders/scheduling/reminder_scheduler.dart';
|
||||||
|
import 'package:relationship_saver/features/reminders/scheduling/reminder_scheduler_local_notifications.dart';
|
||||||
|
|
||||||
/// Provides reminder delivery scheduler implementation.
|
/// Provides reminder delivery scheduler implementation.
|
||||||
final Provider<ReminderScheduler> reminderSchedulerProvider =
|
final Provider<ReminderScheduler> reminderSchedulerProvider =
|
||||||
Provider<ReminderScheduler>((Ref ref) {
|
Provider<ReminderScheduler>((Ref ref) {
|
||||||
|
if (AppConfig.enableLocalNotifications) {
|
||||||
|
return LocalNotificationsReminderScheduler();
|
||||||
|
}
|
||||||
return const NoopReminderScheduler();
|
return const NoopReminderScheduler();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,10 +5,12 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import flutter_local_notifications
|
||||||
import flutter_secure_storage_darwin
|
import flutter_secure_storage_darwin
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.5"
|
version: "3.1.5"
|
||||||
|
dbus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dbus
|
||||||
|
sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.12"
|
||||||
dio:
|
dio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -246,6 +254,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_local_notifications:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications
|
||||||
|
sha256: "2b50e938a275e1ad77352d6a25e25770f4130baa61eaf02de7a9a884680954ad"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "20.1.0"
|
||||||
|
flutter_local_notifications_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_linux
|
||||||
|
sha256: dce0116868cedd2cdf768af0365fc37ff1cbef7c02c4f51d0587482e625868d0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
|
flutter_local_notifications_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_platform_interface
|
||||||
|
sha256: "23de31678a48c084169d7ae95866df9de5c9d2a44be3e5915a2ff067aeeba899"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.0"
|
||||||
|
flutter_local_notifications_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_windows
|
||||||
|
sha256: e97a1a3016512437d9c0b12fae7d1491c3c7b9aa7f03a69b974308840656b02a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
flutter_riverpod:
|
flutter_riverpod:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -600,6 +640,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -869,6 +917,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.15"
|
version: "0.6.15"
|
||||||
|
timezone:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: timezone
|
||||||
|
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -965,6 +1021,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.6.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ dependencies:
|
|||||||
flutter_riverpod: ^3.2.1
|
flutter_riverpod: ^3.2.1
|
||||||
shared_preferences: ^2.5.4
|
shared_preferences: ^2.5.4
|
||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
|
timezone: ^0.10.1
|
||||||
|
flutter_local_notifications: ^20.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:relationship_saver/features/local/local_models.dart';
|
||||||
|
import 'package:relationship_saver/features/reminders/scheduling/reminder_scheduler_local_notifications.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test(
|
||||||
|
'reconcile schedules enabled reminders and cancels previous jobs',
|
||||||
|
() async {
|
||||||
|
final FakeReminderNotificationsDriver driver =
|
||||||
|
FakeReminderNotificationsDriver();
|
||||||
|
final LocalNotificationsReminderScheduler scheduler =
|
||||||
|
LocalNotificationsReminderScheduler(
|
||||||
|
driver: driver,
|
||||||
|
now: () => DateTime(2026, 2, 16, 10),
|
||||||
|
);
|
||||||
|
|
||||||
|
await scheduler.reconcile(<ReminderRule>[
|
||||||
|
ReminderRule(
|
||||||
|
id: 'r-daily',
|
||||||
|
title: 'Daily check-in',
|
||||||
|
cadence: ReminderCadence.daily,
|
||||||
|
nextAt: DateTime(2026, 2, 16, 20),
|
||||||
|
),
|
||||||
|
ReminderRule(
|
||||||
|
id: 'r-weekly',
|
||||||
|
title: 'Weekly date plan',
|
||||||
|
cadence: ReminderCadence.weekly,
|
||||||
|
nextAt: DateTime(2026, 2, 17, 9),
|
||||||
|
),
|
||||||
|
ReminderRule(
|
||||||
|
id: 'r-disabled',
|
||||||
|
title: 'Disabled reminder',
|
||||||
|
cadence: ReminderCadence.monthly,
|
||||||
|
nextAt: DateTime(2026, 2, 18, 8),
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(driver.initializeCalls, 1);
|
||||||
|
expect(driver.cancelAllCalls, 1);
|
||||||
|
expect(driver.scheduled.length, 2);
|
||||||
|
expect(driver.scheduled[0].repeatComponents, DateTimeComponents.time);
|
||||||
|
expect(
|
||||||
|
driver.scheduled[1].repeatComponents,
|
||||||
|
DateTimeComponents.dayOfWeekAndTime,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('clearAll clears notification schedule', () async {
|
||||||
|
final FakeReminderNotificationsDriver driver =
|
||||||
|
FakeReminderNotificationsDriver();
|
||||||
|
final LocalNotificationsReminderScheduler scheduler =
|
||||||
|
LocalNotificationsReminderScheduler(driver: driver);
|
||||||
|
|
||||||
|
await scheduler.clearAll();
|
||||||
|
|
||||||
|
expect(driver.initializeCalls, 1);
|
||||||
|
expect(driver.cancelAllCalls, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeReminderNotificationsDriver implements ReminderNotificationsDriver {
|
||||||
|
int initializeCalls = 0;
|
||||||
|
int cancelAllCalls = 0;
|
||||||
|
final List<ScheduledCall> scheduled = <ScheduledCall>[];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> cancelAll() async {
|
||||||
|
cancelAllCalls += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> ensureInitialized() async {
|
||||||
|
initializeCalls += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> schedule({
|
||||||
|
required int id,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
required DateTime scheduledAt,
|
||||||
|
required DateTimeComponents? repeatComponents,
|
||||||
|
required String payload,
|
||||||
|
}) async {
|
||||||
|
scheduled.add(
|
||||||
|
ScheduledCall(
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
body: body,
|
||||||
|
scheduledAt: scheduledAt,
|
||||||
|
repeatComponents: repeatComponents,
|
||||||
|
payload: payload,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScheduledCall {
|
||||||
|
const ScheduledCall({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.body,
|
||||||
|
required this.scheduledAt,
|
||||||
|
required this.repeatComponents,
|
||||||
|
required this.payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String title;
|
||||||
|
final String body;
|
||||||
|
final DateTime scheduledAt;
|
||||||
|
final DateTimeComponents? repeatComponents;
|
||||||
|
final String payload;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
flutter_local_notifications_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
Reference in New Issue
Block a user