156 lines
4.7 KiB
Dart
156 lines
4.7 KiB
Dart
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<String, Object> toMap() => {
|
|
'key': key,
|
|
'calendarId': calendarId,
|
|
'title': title,
|
|
'room': room,
|
|
'description': description,
|
|
'startMs': startMs,
|
|
'endMs': endMs,
|
|
'timeZone': 'Asia/Shanghai',
|
|
};
|
|
}
|
|
|
|
abstract class CalendarGateway {
|
|
Future<bool> requestAccess();
|
|
Future<List<DeviceCalendar>> calendars();
|
|
Future<List<CalendarRecord>> ownedEvents(String calendarId);
|
|
Future<String> save(CalendarEventDraft event, String? eventId);
|
|
Future<void> delete(String eventId, String expectedKey);
|
|
}
|
|
|
|
class AndroidCalendarService extends CalendarService {
|
|
AndroidCalendarService(this.gateway);
|
|
final CalendarGateway gateway;
|
|
@override
|
|
Future<Map<String, String>> ownedEventIds(String calendarId) async => {
|
|
for (final event in await gateway.ownedEvents(calendarId))
|
|
if (event.owned && event.calendarId == calendarId) event.key: event.id,
|
|
};
|
|
@override
|
|
Future<bool> requestAccess() => gateway.requestAccess();
|
|
@override
|
|
Future<List<DeviceCalendar>> listWritableCalendars() => gateway.calendars();
|
|
@override
|
|
Future<Map<String, String>> sync(
|
|
List<CourseOccurrence> occurrences,
|
|
String calendarId,
|
|
Map<String, String> 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 = <String, String>{};
|
|
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<void> deleteOwned(Map<String, String> 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<bool> requestAccess() async =>
|
|
await channel.invokeMethod<bool>('requestAccess') ?? false;
|
|
@override
|
|
Future<List<DeviceCalendar>> calendars() async {
|
|
final rows =
|
|
await channel.invokeMethod<List<dynamic>>('listCalendars') ?? [];
|
|
return rows
|
|
.map((r) => DeviceCalendar(r['id'] as String, r['name'] as String))
|
|
.toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<CalendarRecord>> ownedEvents(String calendarId) async {
|
|
final rows =
|
|
await channel.invokeMethod<List<dynamic>>('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<String> save(CalendarEventDraft event, String? eventId) async =>
|
|
(await channel.invokeMethod<String>('saveEvent', {
|
|
...event.toMap(),
|
|
'eventId': eventId,
|
|
}))!;
|
|
@override
|
|
Future<void> delete(String eventId, String expectedKey) => channel
|
|
.invokeMethod<void>('deleteEvent', {'id': eventId, 'key': expectedKey});
|
|
}
|