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
@@ -4,23 +4,30 @@ import 'package:social_app/core/notifications/local_notification_service.dart';
import 'package:social_app/core/startup/auth_session_bootstrapper.dart';
import 'package:social_app/features/auth/presentation/bloc/auth_state.dart';
import 'package:social_app/features/calendar/data/services/calendar_service.dart';
import 'package:social_app/features/calendar/reminders/reminder_action_executor.dart';
class MockCalendarService extends Mock implements CalendarService {}
class MockLocalNotificationService extends Mock
implements LocalNotificationService {}
class MockReminderActionExecutor extends Mock
implements ReminderActionExecutor {}
void main() {
late MockCalendarService calendarService;
late MockLocalNotificationService notificationService;
late MockReminderActionExecutor reminderActionExecutor;
late AuthSessionBootstrapper bootstrapper;
setUp(() {
calendarService = MockCalendarService();
notificationService = MockLocalNotificationService();
reminderActionExecutor = MockReminderActionExecutor();
bootstrapper = AuthSessionBootstrapper(
calendarService: calendarService,
notificationService: notificationService,
reminderActionExecutor: reminderActionExecutor,
);
});
@@ -29,6 +36,7 @@ void main() {
verifyNever(() => calendarService.getEventsForRange(any(), any()));
verifyNever(() => notificationService.rebuildUpcomingReminders(any()));
verifyNever(() => reminderActionExecutor.replayPendingActions());
});
test('fetches upcoming events after authenticated state', () async {
@@ -38,6 +46,41 @@ void main() {
when(
() => notificationService.rebuildUpcomingReminders(any()),
).thenAnswer((_) async {});
when(
() => reminderActionExecutor.replayPendingActions(),
).thenAnswer((_) async {});
await bootstrapper.syncForAuthState(
const AuthAuthenticated(
user: AuthUser(id: 'u1', email: 'a@test.com'),
),
);
verify(() => calendarService.getEventsForRange(any(), any())).called(1);
verify(() => notificationService.rebuildUpcomingReminders(any())).called(1);
verify(() => reminderActionExecutor.replayPendingActions()).called(1);
});
test('retries sync when previous bootstrap failed', () async {
when(
() => reminderActionExecutor.replayPendingActions(),
).thenThrow(Exception('offline'));
await bootstrapper.syncForAuthState(
const AuthAuthenticated(
user: AuthUser(id: 'u1', email: 'a@test.com'),
),
);
when(
() => reminderActionExecutor.replayPendingActions(),
).thenAnswer((_) async {});
when(
() => calendarService.getEventsForRange(any(), any()),
).thenAnswer((_) async => []);
when(
() => notificationService.rebuildUpcomingReminders(any()),
).thenAnswer((_) async {});
await bootstrapper.syncForAuthState(
const AuthAuthenticated(
@@ -45,7 +88,7 @@ void main() {
),
);
verify(() => reminderActionExecutor.replayPendingActions()).called(2);
verify(() => calendarService.getEventsForRange(any(), any())).called(1);
verify(() => notificationService.rebuildUpcomingReminders(any())).called(1);
});
}