refactor: 重构提醒通知系统

This commit is contained in:
zl-q
2026-04-01 00:42:34 +08:00
parent 9a231dae9e
commit 6722f3d74b
21 changed files with 375 additions and 171 deletions
@@ -0,0 +1,80 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:social_app/core/notification/models/reminder_alarm.dart';
import 'package:social_app/core/notification/services/reminder_reconcile_service.dart';
import 'package:social_app/core/notification/services/reminder_scheduler_service.dart';
class _FakeReminderSchedulerService extends ReminderSchedulerService {
int upsertCount = 0;
int cancelCount = 0;
List<ReminderAlarm> lastScheduled = const <ReminderAlarm>[];
@override
Future<void> upsertEventReminders(
ReminderEventSnapshot event, {
DateTime? now,
}) async {
upsertCount += 1;
}
@override
Future<void> cancelEventReminders(String eventId) async {
cancelCount += 1;
}
@override
Future<void> scheduleAlarms(List<ReminderAlarm> alarms) async {
lastScheduled = alarms;
}
}
ReminderEventSnapshot _event({bool isArchived = false}) {
return ReminderEventSnapshot(
eventId: 'evt_lock',
title: 'Review',
startAt: DateTime(2026, 3, 30, 10, 0),
endAt: DateTime(2026, 3, 30, 11, 0),
timezone: 'Asia/Shanghai',
reminderMinutes: 15,
isArchived: isArchived,
);
}
void main() {
test('snooze suppresses reconcile before first snooze fire time', () async {
final now = DateTime(2026, 3, 30, 9, 50);
final scheduler = _FakeReminderSchedulerService();
final service = ReminderReconcileService(
scheduler: scheduler,
nowProvider: () => now,
);
await service.snooze10m(_event());
expect(scheduler.cancelCount, 1);
expect(scheduler.lastScheduled, isNotEmpty);
expect(scheduler.lastScheduled.first.fireAt, DateTime(2026, 3, 30, 10, 0));
await service.reconcileEvent(_event(), now: DateTime(2026, 3, 30, 9, 59));
expect(scheduler.upsertCount, 0);
await service.reconcileEvent(_event(), now: DateTime(2026, 3, 30, 10, 0));
expect(scheduler.upsertCount, 1);
});
test('archived event cancels reminder and clears suppress state', () async {
final now = DateTime(2026, 3, 30, 9, 50);
final scheduler = _FakeReminderSchedulerService();
final service = ReminderReconcileService(
scheduler: scheduler,
nowProvider: () => now,
);
await service.snooze10m(_event());
expect(scheduler.cancelCount, 1);
await service.reconcileEvent(_event(isArchived: true), now: now);
expect(scheduler.cancelCount, 2);
await service.reconcileEvent(_event(), now: DateTime(2026, 3, 30, 9, 55));
expect(scheduler.upsertCount, 1);
});
}
@@ -26,28 +26,44 @@ void main() {
expect(alarms.last.fireAt, DateTime(2026, 3, 30, 10, 35));
});
test(
'buildAlarms compensates by scheduling near-now when remindAt passed',
() {
final event = ReminderEventSnapshot(
eventId: 'evt_3',
title: 'Review',
startAt: DateTime(2026, 3, 30, 10, 0),
endAt: DateTime(2026, 3, 30, 10, 20),
timezone: 'Asia/Shanghai',
reminderMinutes: 30,
);
final now = DateTime(2026, 3, 30, 10, 5, 0);
test('buildAlarms starts from near-now when remindAt passed', () {
final event = ReminderEventSnapshot(
eventId: 'evt_3',
title: 'Review',
startAt: DateTime(2026, 3, 30, 10, 0),
endAt: DateTime(2026, 3, 30, 10, 20),
timezone: 'Asia/Shanghai',
reminderMinutes: 30,
);
final now = DateTime(2026, 3, 30, 10, 5, 0);
final alarms = ReminderSchedulerService.buildAlarmsForEvent(
event,
now: now,
);
final alarms = ReminderSchedulerService.buildAlarmsForEvent(
event,
now: now,
);
expect(alarms, isNotEmpty);
expect(alarms.first.fireAt, now.add(const Duration(seconds: 5)));
},
);
expect(alarms, isNotEmpty);
expect(alarms.first.fireAt, DateTime(2026, 3, 30, 10, 5, 2));
expect(alarms.last.fireAt, DateTime(2026, 3, 30, 10, 15, 2));
});
test('buildAlarms returns empty when next cadence is after endAt', () {
final event = ReminderEventSnapshot(
eventId: 'evt_3b',
title: 'Review',
startAt: DateTime(2026, 3, 30, 10, 0),
endAt: DateTime(2026, 3, 30, 10, 5),
timezone: 'Asia/Shanghai',
reminderMinutes: 30,
);
final alarms = ReminderSchedulerService.buildAlarmsForEvent(
event,
now: DateTime(2026, 3, 30, 10, 5, 30),
);
expect(alarms, isEmpty);
});
test('buildAlarms returns empty when event already ended', () {
final event = ReminderEventSnapshot(