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 _scopes = [gcal.CalendarApi.calendarScope]; GoogleSignIn get _googleSignIn => GoogleSignIn(scopes: _scopes); Future 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 signOut() async { await _googleSignIn.disconnect(); await _settingsRepository.clearGoogleCalendarSettings(); _calendarApi = null; } Future _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> 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 createOrUpdateEvent({ required String eventId, required String summary, String? description, DateTime? start, DateTime? end, String? calendarId, Map? 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 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 _headers; final http.Client _inner; @override Future send(http.BaseRequest request) { request.headers.addAll(_headers); return _inner.send(request); } @override void close() { _inner.close(); super.close(); } }