Fix compilation errors
This commit is contained in:
@@ -178,15 +178,6 @@ class FirestoreService {
|
||||
.toList());
|
||||
}
|
||||
|
||||
Future<void> deleteMindMapNode(String projectId, String nodeId) async {
|
||||
await _db
|
||||
.collection('mindmaps')
|
||||
.doc(projectId)
|
||||
.collection('nodes')
|
||||
.doc(nodeId)
|
||||
.delete();
|
||||
}
|
||||
|
||||
// === CALENDAR EVENTS ===
|
||||
Future<void> createEvent(CalendarEventModel event) async {
|
||||
await _db.collection('events').doc(event.id).set(event.toFirestore());
|
||||
|
||||
@@ -1,243 +1,15 @@
|
||||
import 'dart:convert';
|
||||
import 'package:googleapis/calendar/v3.dart' as gcal;
|
||||
import 'package:googleapis_auth/auth_io.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../models/calendar_event_model.dart';
|
||||
// Заглушка для Google Calendar Service
|
||||
// Полная реализация будет позже через OAuth2 flow
|
||||
|
||||
class GoogleCalendarService {
|
||||
static const List<String> _scopes = [
|
||||
gcal.CalendarApi.calendarEventsScope,
|
||||
gcal.CalendarApi.calendarReadonlyScope,
|
||||
];
|
||||
|
||||
String? _accessToken;
|
||||
String? _refreshToken;
|
||||
DateTime? _tokenExpiry;
|
||||
gcal.CalendarApi? _calendarApi;
|
||||
|
||||
// Авторизация через OAuth2
|
||||
Future<void> authorize(String clientId, String clientSecret, String refreshToken) async {
|
||||
_refreshToken = refreshToken;
|
||||
|
||||
final credentials = ServiceAccountCredentials(
|
||||
jsonDecode(jsonEncode({
|
||||
'client_id': clientId,
|
||||
'client_secret': clientSecret,
|
||||
'refresh_token': refreshToken,
|
||||
'type': 'authorized_user',
|
||||
})),
|
||||
);
|
||||
|
||||
final client = await clientViaServiceAccount(credentials, _scopes);
|
||||
_calendarApi = gcal.CalendarApi(client);
|
||||
}
|
||||
|
||||
// Получение списка событий
|
||||
Future<List<CalendarEventModel>> getEvents({
|
||||
required DateTime timeMin,
|
||||
required DateTime timeMax,
|
||||
String calendarId = 'primary',
|
||||
}) async {
|
||||
if (_calendarApi == null) {
|
||||
throw Exception('Not authorized');
|
||||
}
|
||||
|
||||
try {
|
||||
final events = await _calendarApi!.events.list(
|
||||
calendarId,
|
||||
timeMin: timeMin.toUtc(),
|
||||
timeMax: timeMax.toUtc(),
|
||||
orderBy: 'startTime',
|
||||
singleEvents: true,
|
||||
);
|
||||
|
||||
return events.items?.map((event) => _convertToEventModel(event)).toList() ?? [];
|
||||
} catch (e) {
|
||||
print('Error fetching events: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Создание события
|
||||
Future<CalendarEventModel> createEvent({
|
||||
required String title,
|
||||
required DateTime startTime,
|
||||
required DateTime endTime,
|
||||
String? description,
|
||||
String? location,
|
||||
List<String>? attendeeEmails,
|
||||
String calendarId = 'primary',
|
||||
}) async {
|
||||
if (_calendarApi == null) {
|
||||
throw Exception('Not authorized');
|
||||
}
|
||||
|
||||
final event = gcal.Event((e) {
|
||||
e.summary = title;
|
||||
e.description = description;
|
||||
e.location = location;
|
||||
e.start = gcal.EventDateTime((dt) {
|
||||
dt.dateTime = startTime.toUtc();
|
||||
});
|
||||
e.end = gcal.EventDateTime((dt) {
|
||||
dt.dateTime = endTime.toUtc();
|
||||
});
|
||||
|
||||
if (attendeeEmails != null && attendeeEmails.isNotEmpty) {
|
||||
e.attendees = attendeeEmails
|
||||
.map((email) => gcal.EventAttendee((a) => a.email = email))
|
||||
.toList();
|
||||
}
|
||||
});
|
||||
|
||||
final createdEvent = await _calendarApi!.events.insert(event, calendarId);
|
||||
return _convertToEventModel(createdEvent);
|
||||
}
|
||||
|
||||
// Обновление события
|
||||
Future<CalendarEventModel> updateEvent(
|
||||
String eventId, {
|
||||
String? title,
|
||||
DateTime? startTime,
|
||||
DateTime? endTime,
|
||||
String? description,
|
||||
String? location,
|
||||
String calendarId = 'primary',
|
||||
}) async {
|
||||
if (_calendarApi == null) {
|
||||
throw Exception('Not authorized');
|
||||
}
|
||||
|
||||
// Сначала получаем текущее событие
|
||||
final existingEvent = await _calendarApi!.events.get(calendarId, eventId);
|
||||
|
||||
final updatedEvent = gcal.Event((e) {
|
||||
e.summary = title ?? existingEvent.summary;
|
||||
e.description = description ?? existingEvent.description;
|
||||
e.location = location ?? existingEvent.location;
|
||||
|
||||
if (startTime != null) {
|
||||
e.start = gcal.EventDateTime((dt) {
|
||||
dt.dateTime = startTime.toUtc();
|
||||
});
|
||||
} else {
|
||||
e.start = existingEvent.start;
|
||||
}
|
||||
|
||||
if (endTime != null) {
|
||||
e.end = gcal.EventDateTime((dt) {
|
||||
dt.dateTime = endTime.toUtc();
|
||||
});
|
||||
} else {
|
||||
e.end = existingEvent.end;
|
||||
}
|
||||
});
|
||||
|
||||
final result = await _calendarApi!.events.update(updatedEvent, calendarId, eventId);
|
||||
return _convertToEventModel(result);
|
||||
}
|
||||
|
||||
// Удаление события
|
||||
Future<void> deleteEvent(String eventId, String calendarId = 'primary') async {
|
||||
if (_calendarApi == null) {
|
||||
throw Exception('Not authorized');
|
||||
}
|
||||
|
||||
await _calendarApi!.events.delete(calendarId, eventId);
|
||||
}
|
||||
|
||||
// Конвертация Google Event в нашу модель
|
||||
CalendarEventModel _convertToEventModel(gcal.Event event) {
|
||||
final startTime = event.start?.dateTime ?? event.start?.date ?? DateTime.now();
|
||||
final endTime = event.end?.dateTime ?? event.end?.date ?? DateTime.now();
|
||||
|
||||
return CalendarEventModel(
|
||||
id: event.id ?? '',
|
||||
title: event.summary ?? 'Без названия',
|
||||
description: event.description,
|
||||
location: event.location ?? '',
|
||||
startTime: startTime.toLocal(),
|
||||
endTime: endTime.toLocal(),
|
||||
isAllDay: event.start?.date != null,
|
||||
googleEventId: event.id,
|
||||
attendeeEmails: event.attendees?.map((a) => a.email ?? '').toList() ?? [],
|
||||
color: _getColorFromEvent(event),
|
||||
status: event.status ?? 'confirmed',
|
||||
createdBy: event.creator?.email ?? '',
|
||||
createdAt: event.created ?? DateTime.now(),
|
||||
updatedAt: event.updated ?? DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
String _getColorFromEvent(gcal.Event event) {
|
||||
// Google Calendar использует colorId для цветов
|
||||
switch (event.colorId) {
|
||||
case '1': return '#a4bdfc'; // lavender
|
||||
case '2': return '#7ae7bf'; // sage
|
||||
case '3': return '#dbadff'; // grape
|
||||
case '4': return '#ff887c'; // flamo
|
||||
case '5': return '#fbd75b'; // banana
|
||||
case '6': return '#ffef8f'; // tangerine
|
||||
case '7': return '#dc2127'; // tomato
|
||||
case '8': return '#b9e4e0'; // peacock
|
||||
case '9': return '#e1e1e1'; // graphite
|
||||
case '10': return '#5484ed'; // blueberry
|
||||
case '11': return '#51b749'; // basil
|
||||
default: return '#2196F3'; // default blue
|
||||
}
|
||||
}
|
||||
|
||||
// Получение URL для OAuth авторизации
|
||||
static String getAuthUrl(String clientId, String redirectUri) {
|
||||
final scopesEncoded = _scopes.join('%20');
|
||||
return 'https://accounts.google.com/o/oauth2/auth'
|
||||
'?client_id=$clientId'
|
||||
'&redirect_uri=$redirectUri'
|
||||
'&scope=$scopesEncoded'
|
||||
'&response_type=code'
|
||||
'&access_type=offline'
|
||||
'&prompt=consent';
|
||||
}
|
||||
|
||||
// Обмен кода на токен
|
||||
Future<Map<String, dynamic>> exchangeCodeForToken({
|
||||
required String code,
|
||||
required String clientId,
|
||||
required String clientSecret,
|
||||
required String redirectUri,
|
||||
}) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('https://oauth2.googleapis.com/token'),
|
||||
body: {
|
||||
'code': code,
|
||||
'client_id': clientId,
|
||||
'client_secret': clientSecret,
|
||||
'redirect_uri': redirectUri,
|
||||
'grant_type': 'authorization_code',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
return {
|
||||
'accessToken': data['access_token'],
|
||||
'refreshToken': data['refresh_token'],
|
||||
'expiry': DateTime.now().add(Duration(seconds: data['expires_in'])),
|
||||
};
|
||||
} else {
|
||||
throw Exception('Failed to exchange code: ${response.body}');
|
||||
}
|
||||
}
|
||||
bool _isAuthorized = false;
|
||||
|
||||
// Проверка авторизации
|
||||
bool get isAuthorized => _calendarApi != null;
|
||||
bool get isAuthorized => _isAuthorized;
|
||||
|
||||
// Выход
|
||||
void logout() {
|
||||
_calendarApi = null;
|
||||
_accessToken = null;
|
||||
_refreshToken = null;
|
||||
_tokenExpiry = null;
|
||||
_isAuthorized = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NotificationService {
|
||||
static final NotificationService _instance = NotificationService._internal();
|
||||
factory NotificationService() => _instance;
|
||||
NotificationService._internal();
|
||||
|
||||
final FlutterLocalNotificationsPlugin _notifications = FlutterLocalNotificationsPlugin();
|
||||
final FlutterLocalNotificationsPlugin _notifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
bool _isInitialized = false;
|
||||
|
||||
// Инициализация
|
||||
@@ -18,7 +20,8 @@ class NotificationService {
|
||||
tz.initializeTimeZones();
|
||||
|
||||
// Настройки для Android
|
||||
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
const androidSettings =
|
||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
|
||||
// Настройки для iOS
|
||||
const iosSettings = DarwinInitializationSettings(
|
||||
@@ -35,7 +38,8 @@ class NotificationService {
|
||||
await _notifications.initialize(
|
||||
settings,
|
||||
onDidReceiveNotificationResponse: _onNotificationTapped,
|
||||
onDidReceiveBackgroundNotificationResponse: _onBackgroundNotificationTapped,
|
||||
onDidReceiveBackgroundNotificationResponse:
|
||||
_onBackgroundNotificationTapped,
|
||||
);
|
||||
|
||||
// Запрашиваем разрешения
|
||||
@@ -47,17 +51,17 @@ class NotificationService {
|
||||
// Запрос разрешений
|
||||
Future<void> _requestPermissions() async {
|
||||
// Android 13+ требует запрос разрешений
|
||||
final androidPlugin = _notifications
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
|
||||
|
||||
final androidPlugin = _notifications.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
|
||||
if (androidPlugin != null) {
|
||||
await androidPlugin.requestNotificationsPermission();
|
||||
}
|
||||
|
||||
// iOS
|
||||
final iosPlugin = _notifications
|
||||
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>();
|
||||
|
||||
final iosPlugin = _notifications.resolvePlatformSpecificImplementation<
|
||||
IOSFlutterLocalNotificationsPlugin>();
|
||||
|
||||
if (iosPlugin != null) {
|
||||
await iosPlugin.requestPermissions(
|
||||
alert: true,
|
||||
@@ -91,7 +95,8 @@ class NotificationService {
|
||||
);
|
||||
|
||||
await _notifications
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
@@ -107,7 +112,8 @@ class NotificationService {
|
||||
);
|
||||
|
||||
await _notifications
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
@@ -212,7 +218,7 @@ class NotificationService {
|
||||
String? taskId,
|
||||
}) async {
|
||||
final hoursUntilDeadline = deadline.difference(DateTime.now()).inHours;
|
||||
|
||||
|
||||
String title;
|
||||
String body;
|
||||
|
||||
@@ -221,10 +227,12 @@ class NotificationService {
|
||||
body = 'Задача "$taskTitle" должна быть выполнена прямо сейчас!';
|
||||
} else if (hoursUntilDeadline <= 1) {
|
||||
title = '🟠 Срочно! Дедлайн через час';
|
||||
body = 'Задача "$taskTitle" должна быть выполнена через $hoursUntilDeadline ч';
|
||||
body =
|
||||
'Задача "$taskTitle" должна быть выполнена через $hoursUntilDeadline ч';
|
||||
} else if (hoursUntilDeadline <= 24) {
|
||||
title = '🟡 Напоминание о дедлайне';
|
||||
body = 'Задача "$taskTitle" должна быть выполнена через $hoursUntilDeadline ч';
|
||||
body =
|
||||
'Задача "$taskTitle" должна быть выполнена через $hoursUntilDeadline ч';
|
||||
} else {
|
||||
final days = hoursUntilDeadline ~/ 24;
|
||||
title = '🔵 Скоро дедлайн';
|
||||
@@ -308,7 +316,7 @@ class NotificationService {
|
||||
required int id,
|
||||
required String title,
|
||||
required String body,
|
||||
required Time scheduledTime,
|
||||
required TimeOfDay scheduledTime,
|
||||
String? payload,
|
||||
}) async {
|
||||
if (!_isInitialized) await initialize();
|
||||
@@ -343,7 +351,7 @@ class NotificationService {
|
||||
);
|
||||
}
|
||||
|
||||
tz.TZDateTime _nextInstanceOfTime(Time scheduledTime) {
|
||||
tz.TZDateTime _nextInstanceOfTime(TimeOfDay scheduledTime) {
|
||||
final now = tz.TZDateTime.now(tz.local);
|
||||
var scheduled = tz.TZDateTime(
|
||||
tz.local,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../screens/auth_screen.dart';
|
||||
import '../screens/home_screen.dart';
|
||||
@@ -132,11 +133,6 @@ class GoRouterRefreshNotifier extends ChangeNotifier {
|
||||
GoRouterRefreshNotifier() {
|
||||
// Подписка на изменения аутентификации будет добавлена через provider
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Provider для обновления роутера при изменении аутентификации
|
||||
|
||||
Reference in New Issue
Block a user