fix: prioritize realtime reminder archive with cold-start fallback
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user