feat: 实现日历提醒完整功能(操作执行、通知服务重构、归档)

- 新增 ReminderActionExecutor 处理取消/稍后提醒操作
- 新增 ReminderOutboxStore 本地存储待处理操作
- 重构 LocalNotificationService 支持聚合提醒和交互操作
- 新增 event_color_resolver 工具类统一颜色解析
- 新增 CalendarService.archiveEvent 归档方法
- 增强 ModelTracking 支持缓存命中、推理token和成本追踪
- 添加 qwen3.5-35b-a3b 模型配置
- 更新 AndroidManifest 全屏intent权限
- 补充相关单元测试和文档
This commit is contained in:
qzl
2026-03-18 19:12:47 +08:00
parent 257cb0f5d5
commit 00f37d7e19
35 changed files with 2676 additions and 244 deletions
@@ -0,0 +1,106 @@
import 'dart:math';
import '../data/services/calendar_service.dart';
import '../../../core/notifications/local_notification_service.dart';
import 'models/reminder_action.dart';
import 'models/reminder_payload.dart';
import 'reminder_outbox_store.dart';
class ReminderActionExecutor {
final CalendarService _calendarService;
final LocalNotificationService _notificationService;
final ReminderOutboxStore _outboxStore;
final Random _random;
ReminderActionExecutor({
required CalendarService calendarService,
required LocalNotificationService notificationService,
required ReminderOutboxStore outboxStore,
Random? random,
}) : _calendarService = calendarService,
_notificationService = notificationService,
_outboxStore = outboxStore,
_random = random ?? Random();
Future<void> handleAction({
required ReminderAction action,
required ReminderPayload payload,
}) async {
final ids = payload.mode == ReminderPayloadMode.aggregate
? payload.aggregateIds
: <String>[payload.eventId];
if (action == ReminderAction.cancel) {
for (final id in ids) {
await _notificationService.cancelEventReminder(id);
await _archiveEvent(id, ReminderAction.cancel);
}
return;
}
if (action == ReminderAction.snooze10m ||
action == ReminderAction.timeout30s) {
for (final id in ids) {
await _snoozeEvent(id);
}
}
}
Future<void> replayPendingActions() async {
final pending = await _outboxStore.listPending();
for (final item in pending) {
if (item.targetStatus != 'archived') {
await _outboxStore.markDone(item.opId);
continue;
}
try {
await _calendarService.archiveEvent(item.eventId);
await _outboxStore.markDone(item.opId);
} catch (error) {
await _outboxStore.markRetry(item.opId, error.toString());
}
}
}
Future<void> _snoozeEvent(String eventId) async {
final event = await _calendarService.getEventById(eventId);
if (event == null) {
return;
}
final now = DateTime.now();
final endAt = event.endAt;
if (endAt != null && !now.isBefore(endAt)) {
await _notificationService.cancelEventReminder(eventId);
await _archiveEvent(eventId, ReminderAction.autoArchive);
return;
}
final nextAt = now.add(const Duration(minutes: 10));
if (endAt != null && !nextAt.isBefore(endAt)) {
await _notificationService.cancelEventReminder(eventId);
await _archiveEvent(eventId, ReminderAction.autoArchive);
return;
}
await _notificationService.scheduleReminderAt(event, nextAt);
}
Future<void> _archiveEvent(String eventId, ReminderAction action) async {
final opId =
'${DateTime.now().millisecondsSinceEpoch}-${_random.nextInt(1 << 32)}';
final outboxItem = ReminderOutboxItem(
opId: opId,
eventId: eventId,
action: action,
targetStatus: 'archived',
occurredAt: DateTime.now(),
);
await _outboxStore.enqueue(outboxItem);
try {
await _calendarService.archiveEvent(eventId);
await _outboxStore.markDone(opId);
} catch (error) {
await _outboxStore.markRetry(opId, error.toString());
}
}
}