126 lines
4 KiB
Dart
126 lines
4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:google_sign_in/google_sign_in.dart';
|
|
import 'package:googleapis/calendar/v3.dart' as gcal;
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http/retry.dart';
|
|
|
|
import 'app_settings_repository.dart';
|
|
|
|
/// Googleカレンダーとの認証・イベント同期を担当するサービス。
|
|
class CalendarSyncService {
|
|
CalendarSyncService({AppSettingsRepository? settingsRepository})
|
|
: _settingsRepository = settingsRepository ?? AppSettingsRepository();
|
|
|
|
final AppSettingsRepository _settingsRepository;
|
|
GoogleSignInAccount? _currentAccount;
|
|
gcal.CalendarApi? _calendarApi;
|
|
static const List<String> _scopes = [gcal.CalendarApi.calendarScope];
|
|
|
|
GoogleSignIn get _googleSignIn => GoogleSignIn(scopes: _scopes);
|
|
|
|
Future<bool> ensureSignedIn() async {
|
|
final enabled = await _settingsRepository.getGoogleCalendarEnabled();
|
|
if (!enabled) return false;
|
|
_currentAccount = await _googleSignIn.signInSilently();
|
|
_currentAccount ??= await _googleSignIn.signIn();
|
|
if (_currentAccount == null) {
|
|
await _settingsRepository.clearGoogleCalendarSettings();
|
|
return false;
|
|
}
|
|
await _initializeCalendarApi();
|
|
await _settingsRepository.setGoogleCalendarAccountEmail(_currentAccount!.email);
|
|
return true;
|
|
}
|
|
|
|
Future<void> signOut() async {
|
|
await _googleSignIn.disconnect();
|
|
await _settingsRepository.clearGoogleCalendarSettings();
|
|
_calendarApi = null;
|
|
}
|
|
|
|
Future<void> _initializeCalendarApi() async {
|
|
final account = _currentAccount;
|
|
if (account == null) return;
|
|
final authHeaders = await account.authHeaders;
|
|
final authClient = _AuthClientDecorator(authHeaders, http.Client());
|
|
final retryClient = RetryClient(authClient, retries: 3);
|
|
_calendarApi = gcal.CalendarApi(retryClient);
|
|
}
|
|
|
|
Future<List<gcal.CalendarListEntry>> fetchCalendars() async {
|
|
if (_calendarApi == null) {
|
|
final ready = await ensureSignedIn();
|
|
if (!ready) return [];
|
|
}
|
|
final list = await _calendarApi!.calendarList.list(showHidden: false, minAccessRole: 'writer');
|
|
return list.items ?? [];
|
|
}
|
|
|
|
Future<void> createOrUpdateEvent({
|
|
required String eventId,
|
|
required String summary,
|
|
String? description,
|
|
DateTime? start,
|
|
DateTime? end,
|
|
String? calendarId,
|
|
Map<String, String>? extendedProperties,
|
|
}) async {
|
|
if (_calendarApi == null) {
|
|
final ready = await ensureSignedIn();
|
|
if (!ready) return;
|
|
}
|
|
final targetCalendarId = calendarId ?? (await _settingsRepository.getGoogleCalendarId()) ?? 'primary';
|
|
final event = gcal.Event()
|
|
..id = eventId
|
|
..summary = summary
|
|
..description = description
|
|
..start = start != null ? _timeFromDate(start) : null
|
|
..end = end != null ? _timeFromDate(end) : null
|
|
..extendedProperties = extendedProperties == null
|
|
? null
|
|
: gcal.EventExtendedProperties(private: extendedProperties);
|
|
|
|
try {
|
|
await _calendarApi!.events.patch(event, targetCalendarId, eventId);
|
|
} on gcal.DetailedApiRequestError catch (e) {
|
|
if (e.status == 404) {
|
|
await _calendarApi!.events.insert(event, targetCalendarId);
|
|
} else {
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> deleteEvent(String eventId, {String? calendarId}) async {
|
|
if (_calendarApi == null) {
|
|
final ready = await ensureSignedIn();
|
|
if (!ready) return;
|
|
}
|
|
final targetCalendarId = calendarId ?? (await _settingsRepository.getGoogleCalendarId()) ?? 'primary';
|
|
await _calendarApi!.events.delete(targetCalendarId, eventId);
|
|
}
|
|
|
|
gcal.EventDateTime _timeFromDate(DateTime date) {
|
|
return gcal.EventDateTime(dateTime: date, timeZone: date.timeZoneName);
|
|
}
|
|
}
|
|
|
|
class _AuthClientDecorator extends http.BaseClient {
|
|
_AuthClientDecorator(this._headers, this._inner);
|
|
|
|
final Map<String, String> _headers;
|
|
final http.Client _inner;
|
|
|
|
@override
|
|
Future<http.StreamedResponse> send(http.BaseRequest request) {
|
|
request.headers.addAll(_headers);
|
|
return _inner.send(request);
|
|
}
|
|
|
|
@override
|
|
void close() {
|
|
_inner.close();
|
|
super.close();
|
|
}
|
|
}
|