fix: prioritize realtime reminder archive with cold-start fallback

This commit is contained in:
qzl
2026-03-20 15:41:48 +08:00
parent f4c07287bc
commit 20f3285244
2 changed files with 57 additions and 11 deletions
@@ -1,5 +1,7 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/widgets.dart';
import '../data/services/calendar_service.dart'; import '../data/services/calendar_service.dart';
import '../../../core/notifications/local_notification_service.dart'; import '../../../core/notifications/local_notification_service.dart';
import 'models/reminder_action.dart'; import 'models/reminder_action.dart';
@@ -11,16 +13,23 @@ class ReminderActionExecutor {
final LocalNotificationService _notificationService; final LocalNotificationService _notificationService;
final ReminderOutboxStore _outboxStore; final ReminderOutboxStore _outboxStore;
final Random _random; final Random _random;
final bool Function() _isAppActive;
ReminderActionExecutor({ ReminderActionExecutor({
required CalendarService calendarService, required CalendarService calendarService,
required LocalNotificationService notificationService, required LocalNotificationService notificationService,
required ReminderOutboxStore outboxStore, required ReminderOutboxStore outboxStore,
Random? random, Random? random,
bool Function()? isAppActive,
}) : _calendarService = calendarService, }) : _calendarService = calendarService,
_notificationService = notificationService, _notificationService = notificationService,
_outboxStore = outboxStore, _outboxStore = outboxStore,
_random = random ?? Random(); _random = random ?? Random(),
_isAppActive =
isAppActive ??
(() =>
WidgetsBinding.instance.lifecycleState ==
AppLifecycleState.resumed);
Future<void> handleAction({ Future<void> handleAction({
required ReminderAction action, required ReminderAction action,
@@ -86,6 +95,11 @@ class ReminderActionExecutor {
} }
Future<void> _archiveEvent(String eventId, ReminderAction action) async { Future<void> _archiveEvent(String eventId, ReminderAction action) async {
if (_isAppActive()) {
await _calendarService.archiveEvent(eventId);
return;
}
final opId = final opId =
'${DateTime.now().millisecondsSinceEpoch}-${_random.nextInt(1 << 32)}'; '${DateTime.now().millisecondsSinceEpoch}-${_random.nextInt(1 << 32)}';
final outboxItem = ReminderOutboxItem( final outboxItem = ReminderOutboxItem(
@@ -96,11 +110,5 @@ class ReminderActionExecutor {
occurredAt: DateTime.now(), occurredAt: DateTime.now(),
); );
await _outboxStore.enqueue(outboxItem); await _outboxStore.enqueue(outboxItem);
try {
await _calendarService.archiveEvent(eventId);
await _outboxStore.markDone(opId);
} catch (error) {
await _outboxStore.markRetry(opId, error.toString());
}
} }
} }
@@ -30,6 +30,7 @@ void main() {
calendarService: calendarService, calendarService: calendarService,
notificationService: notificationService, notificationService: notificationService,
outboxStore: outboxStore, outboxStore: outboxStore,
isAppActive: () => true,
); );
}); });
@@ -57,13 +58,50 @@ void main() {
expect(pending, isEmpty); expect(pending, isEmpty);
}); });
test('archive failure writes pending outbox item', () async { test(
'archive action should send remote archive immediately when app active',
() async {
when(
() => notificationService.cancelEventReminder('evt_live'),
).thenAnswer((_) async {});
when(
() => calendarService.archiveEvent('evt_live'),
).thenAnswer((_) async => null);
executor = ReminderActionExecutor(
calendarService: calendarService,
notificationService: notificationService,
outboxStore: outboxStore,
isAppActive: () => true,
);
await executor.handleAction(
action: ReminderAction.archive,
payload: ReminderPayload(
eventId: 'evt_live',
title: 'sync',
startAt: DateTime.parse('2026-03-18T16:00:00+08:00'),
timezone: 'Asia/Shanghai',
),
);
verify(() => calendarService.archiveEvent('evt_live')).called(1);
final pending = await outboxStore.listPending();
expect(pending, isEmpty);
},
);
test('archive in inactive app writes pending outbox item', () async {
when( when(
() => notificationService.cancelEventReminder('evt_1'), () => notificationService.cancelEventReminder('evt_1'),
).thenAnswer((_) async {}); ).thenAnswer((_) async {});
when(
() => calendarService.archiveEvent('evt_1'), executor = ReminderActionExecutor(
).thenThrow(Exception('offline')); calendarService: calendarService,
notificationService: notificationService,
outboxStore: outboxStore,
isAppActive: () => false,
);
await executor.handleAction( await executor.handleAction(
action: ReminderAction.archive, action: ReminderAction.archive,