h-1.flutter.0/lib/services/calendar_sync_service.dart
2026-03-04 14:55:40 +09:00

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();
}
}