feat: 实现日历提醒完整功能(操作执行、通知服务重构、归档)
- 新增 ReminderActionExecutor 处理取消/稍后提醒操作 - 新增 ReminderOutboxStore 本地存储待处理操作 - 重构 LocalNotificationService 支持聚合提醒和交互操作 - 新增 event_color_resolver 工具类统一颜色解析 - 新增 CalendarService.archiveEvent 归档方法 - 增强 ModelTracking 支持缓存命中、推理token和成本追踪 - 添加 qwen3.5-35b-a3b 模型配置 - 更新 AndroidManifest 全屏intent权限 - 补充相关单元测试和文档
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import '../data/models/schedule_item_model.dart';
|
||||
|
||||
class ReminderOverlapGroup {
|
||||
final DateTime fireAt;
|
||||
final List<ScheduleItemModel> events;
|
||||
|
||||
const ReminderOverlapGroup({required this.fireAt, required this.events});
|
||||
|
||||
bool get isAggregate => events.length > 1;
|
||||
}
|
||||
|
||||
class ReminderOverlapPolicy {
|
||||
const ReminderOverlapPolicy();
|
||||
|
||||
List<ReminderOverlapGroup> groupByMinute(
|
||||
Iterable<ScheduleItemModel> events, {
|
||||
required DateTime now,
|
||||
}) {
|
||||
final buckets = <String, List<ScheduleItemModel>>{};
|
||||
final minuteToFireAt = <String, DateTime>{};
|
||||
|
||||
for (final event in events) {
|
||||
final fireAt = resolveFirstFireAt(event, now: now);
|
||||
if (fireAt == null) {
|
||||
continue;
|
||||
}
|
||||
final minute = DateTime(
|
||||
fireAt.year,
|
||||
fireAt.month,
|
||||
fireAt.day,
|
||||
fireAt.hour,
|
||||
fireAt.minute,
|
||||
);
|
||||
final key = minute.toIso8601String();
|
||||
buckets.putIfAbsent(key, () => <ScheduleItemModel>[]).add(event);
|
||||
minuteToFireAt[key] = minuteToFireAt[key] ?? fireAt;
|
||||
}
|
||||
|
||||
final groups = buckets.entries
|
||||
.map(
|
||||
(entry) => ReminderOverlapGroup(
|
||||
fireAt: minuteToFireAt[entry.key]!,
|
||||
events: entry.value,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
groups.sort((left, right) => left.fireAt.compareTo(right.fireAt));
|
||||
return groups;
|
||||
}
|
||||
|
||||
DateTime? resolveFirstFireAt(
|
||||
ScheduleItemModel event, {
|
||||
required DateTime now,
|
||||
}) {
|
||||
if (event.status != ScheduleStatus.active) {
|
||||
return null;
|
||||
}
|
||||
final reminderMinutes = event.metadata?.reminderMinutes;
|
||||
if (reminderMinutes == null) {
|
||||
return null;
|
||||
}
|
||||
final remindAt = event.startAt.subtract(Duration(minutes: reminderMinutes));
|
||||
final endAt = event.endAt;
|
||||
|
||||
if (endAt != null && !now.isBefore(endAt)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (now.isBefore(remindAt)) {
|
||||
return remindAt;
|
||||
}
|
||||
|
||||
if (endAt != null && now.isBefore(endAt)) {
|
||||
return now.add(const Duration(seconds: 5));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user