import 'package:flutter/services.dart'; import '../domain/course_occurrence.dart'; import '../domain/course.dart'; import 'calendar_service.dart'; class CalendarRecord { const CalendarRecord({ required this.id, required this.key, required this.calendarId, required this.owned, }); final String id, key, calendarId; final bool owned; } class CalendarEventDraft { const CalendarEventDraft({ required this.key, required this.calendarId, required this.title, required this.room, required this.description, required this.startMs, required this.endMs, }); final String key, calendarId, title, room, description; final int startMs, endMs; Map toMap() => { 'key': key, 'calendarId': calendarId, 'title': title, 'room': room, 'description': description, 'startMs': startMs, 'endMs': endMs, 'timeZone': 'Asia/Shanghai', }; } abstract class CalendarGateway { Future requestAccess(); Future> calendars(); Future> ownedEvents(String calendarId); Future save(CalendarEventDraft event, String? eventId); Future delete(String eventId, String expectedKey); } class AndroidCalendarService extends CalendarService { AndroidCalendarService(this.gateway); final CalendarGateway gateway; @override Future> ownedEventIds(String calendarId) async => { for (final event in await gateway.ownedEvents(calendarId)) if (event.owned && event.calendarId == calendarId) event.key: event.id, }; @override Future requestAccess() => gateway.requestAccess(); @override Future> listWritableCalendars() => gateway.calendars(); @override Future> sync( List occurrences, String calendarId, Map knownEventIds, ) async { if (!(await gateway.calendars()).any((c) => c.id == calendarId)) { throw StateError('所选日历不可写入'); } final existing = (await gateway.ownedEvents(calendarId)) .where((e) => e.owned && e.calendarId == calendarId) .toList(); final result = {}; for (final occurrence in occurrences.where( (e) => e.course.source != CourseSource.demo, )) { final matches = existing.where((e) => e.key == occurrence.key).toList(); matches.sort( (a, b) => (a.id == knownEventIds[occurrence.key] ? -1 : 0).compareTo( b.id == knownEventIds[occurrence.key] ? -1 : 0, ), ); result[occurrence.key] = await gateway.save( CalendarEventDraft( key: occurrence.key, calendarId: calendarId, title: occurrence.course.name, room: occurrence.course.room, description: '任课老师:${occurrence.course.teacher}\n河畔Everyday', startMs: occurrence.start.millisecondsSinceEpoch, endMs: occurrence.end.millisecondsSinceEpoch, ), matches.isEmpty ? null : matches.first.id, ); } for (final old in existing) { if (result[old.key] != old.id) { await gateway.delete(old.id, old.key); } } return result; } @override Future deleteOwned(Map eventIds) async { for (final entry in eventIds.entries) { await gateway.delete(entry.value, entry.key); } } } class MethodCalendarGateway extends CalendarGateway { static const channel = MethodChannel('hepan_everyday/calendar'); @override Future requestAccess() async => await channel.invokeMethod('requestAccess') ?? false; @override Future> calendars() async { final rows = await channel.invokeMethod>('listCalendars') ?? []; return rows .map((r) => DeviceCalendar(r['id'] as String, r['name'] as String)) .toList(); } @override Future> ownedEvents(String calendarId) async { final rows = await channel.invokeMethod>('ownedEvents', { 'calendarId': calendarId, }) ?? []; return rows .map( (r) => CalendarRecord( id: r['id'] as String, key: r['key'] as String, calendarId: r['calendarId'] as String, owned: r['owned'] as bool, ), ) .toList(); } @override Future save(CalendarEventDraft event, String? eventId) async => (await channel.invokeMethod('saveEvent', { ...event.toMap(), 'eventId': eventId, }))!; @override Future delete(String eventId, String expectedKey) => channel .invokeMethod('deleteEvent', {'id': eventId, 'key': expectedKey}); }