Add Firestore integration with real-time sync

This commit is contained in:
2026-03-27 19:02:43 +05:00
parent 2336df8e86
commit 6e428b4ede
5 changed files with 362 additions and 76 deletions
+82 -24
View File
@@ -1,24 +1,63 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/user_model.dart';
class AuthNotifier extends StateNotifier<AsyncValue<UserModel?>> {
final FirebaseAuth _auth;
final GoogleSignIn _googleSignIn;
final FirebaseFirestore _firestore;
AuthNotifier(this._auth, this._googleSignIn) : super(const AsyncValue.data(null)) {
AuthNotifier(this._auth, this._googleSignIn, this._firestore)
: super(const AsyncValue.data(null)) {
// Слушаем изменения состояния аутентификации
_auth.authStateChanges().listen((user) {
_auth.authStateChanges().listen((user) async {
if (user != null) {
state = AsyncValue.data(UserModel(
id: user.uid,
email: user.email ?? '',
displayName: user.displayName,
photoUrl: user.photoURL,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
));
// Загружаем или создаём пользователя в Firestore
try {
final userDoc =
await _firestore.collection('users').doc(user.uid).get();
if (userDoc.exists) {
// Пользователь уже есть
state = AsyncValue.data(UserModel.fromFirestore(userDoc));
} else {
// Создаём нового пользователя
final newUser = UserModel(
id: user.uid,
email: user.email ?? '',
displayName: user.displayName,
photoUrl: user.photoURL,
role: UserRole.member,
teamIds: [],
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
settings: {
'theme': 'system',
'notifications': true,
'language': 'ru',
},
);
await _firestore
.collection('users')
.doc(user.uid)
.set(newUser.toFirestore());
state = AsyncValue.data(newUser);
}
} catch (e) {
print('Error loading user from Firestore: $e');
// Создаём временного пользователя
state = AsyncValue.data(UserModel(
id: user.uid,
email: user.email ?? '',
displayName: user.displayName,
photoUrl: user.photoURL,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
));
}
} else {
state = const AsyncValue.data(null);
}
@@ -42,17 +81,11 @@ class AuthNotifier extends StateNotifier<AsyncValue<UserModel?>> {
);
final userCredential = await _auth.signInWithCredential(credential);
// Здесь можно создать/обновить пользователя в Firestore
state = AsyncValue.data(UserModel(
id: userCredential.user!.uid,
email: userCredential.user!.email ?? '',
displayName: userCredential.user!.displayName,
photoUrl: userCredential.user!.photoURL,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
));
// Пользователь будет загружен через authStateChanges listener
print('Signed in: ${userCredential.user?.email}');
} catch (e, st) {
print('Google Sign-In Error: $e');
state = AsyncValue.error(e, st);
}
}
@@ -69,16 +102,41 @@ class AuthNotifier extends StateNotifier<AsyncValue<UserModel?>> {
User? get currentUser => _auth.currentUser;
bool get isLoggedIn => _auth.currentUser != null;
String? get userId => _auth.currentUser?.uid;
}
final authProvider = StateNotifierProvider<AuthNotifier, AsyncValue<UserModel?>>((ref) {
return AuthNotifier(FirebaseAuth.instance, GoogleSignIn());
// Provider для аутентификации
final authProvider =
StateNotifierProvider<AuthNotifier, AsyncValue<UserModel?>>((ref) {
return AuthNotifier(
FirebaseAuth.instance,
GoogleSignIn(),
FirebaseFirestore.instance,
);
});
final authStateProvider = Provider<AuthNotifier>((ref) {
return ref.read(authProvider.notifier);
// Provider для текущего пользователя
final currentUserProvider = Provider<UserModel?>((ref) {
return ref.watch(authProvider).value;
});
// Provider для текущего userId
final currentUserIdProvider = Provider<String?>((ref) {
return ref.watch(authProvider).value?.id;
});
// Provider для проверки авторизации
final isAuthenticatedProvider = Provider<bool>((ref) {
return ref.watch(authProvider).value != null;
});
// Provider для роли пользователя
final userRoleProvider = Provider<UserRole?>((ref) {
return ref.watch(authProvider).value?.role;
});
// Provider для проверки является ли пользователь админом
final isAdminProvider = Provider<bool>((ref) {
final role = ref.watch(userRoleProvider);
return role == UserRole.admin;
});
+112 -22
View File
@@ -6,45 +6,85 @@ import '../core/firestore_service.dart';
import '../core/local_storage_service.dart';
import '../core/deepseek_service.dart';
// Provider для текущего пользователя (заглушка пока нет auth)
final currentUserIdProvider = Provider<String?>((ref) {
// TODO: Заменить на реального пользователя из Firebase Auth
return null; // Пока нет авторизации
});
// Provider для списка всех задач
final tasksProvider = StateNotifierProvider<TasksNotifier, AsyncValue<List<TaskModel>>>((ref) {
return TasksNotifier();
final tasksProvider =
StateNotifierProvider<TasksNotifier, AsyncValue<List<TaskModel>>>((ref) {
return TasksNotifier(ref);
});
// Provider для задач конкретного проекта
final tasksByProjectProvider = Provider.family<AsyncValue<List<TaskModel>>, String>((ref, projectId) {
final tasksByProjectProvider =
Provider.family<AsyncValue<List<TaskModel>>, String>((ref, projectId) {
final tasks = ref.watch(tasksProvider);
return tasks.when(
data: (allTasks) => AsyncValue.data(allTasks.where((t) => t.projectId == projectId).toList()),
data: (allTasks) => AsyncValue.data(
allTasks.where((t) => t.projectId == projectId).toList()),
loading: () => const AsyncValue.loading(),
error: (e, st) => AsyncValue.error(e, st),
);
});
// Provider для задач пользователя
final tasksByUserProvider = Provider.family<AsyncValue<List<TaskModel>>, String>((ref, userId) {
final tasksByUserProvider =
Provider.family<AsyncValue<List<TaskModel>>, String>((ref, userId) {
final tasks = ref.watch(tasksProvider);
return tasks.when(
data: (allTasks) => AsyncValue.data(allTasks.where((t) => t.assigneeId == userId).toList()),
data: (allTasks) =>
AsyncValue.data(allTasks.where((t) => t.assigneeId == userId).toList()),
loading: () => const AsyncValue.loading(),
error: (e, st) => AsyncValue.error(e, st),
);
});
// Stream provider для задач в реальном времени
final tasksStreamProvider =
StreamProvider.family<List<TaskModel>, String?>((ref, projectId) {
return firestoreService.watchTasks(projectId);
});
class TasksNotifier extends StateNotifier<AsyncValue<List<TaskModel>>> {
TasksNotifier() : super(const AsyncValue.loading()) {
final Ref ref;
StreamSubscription? _subscription;
TasksNotifier(this.ref) : super(const AsyncValue.loading()) {
_loadTasks();
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
// Загрузка задач
Future<void> _loadTasks() async {
try {
// Сначала загружаем из локального хранилища
// Сначала загружаем из локального хранилища (офлайн режим)
final localTasks = localStorageService.getTasks();
state = AsyncValue.data(localTasks);
// Потом синхронизируем с Firestore
// TODO: Реализовать когда будет Firebase Auth
// Подписываемся на обновления из Firestore
final currentUserId = ref.read(currentUserIdProvider);
_subscription =
firestoreService.watchTasks(null).listen((remoteTasks) async {
// Сохраняем локально для офлайн режима
await localStorageService.syncWithRemote(remoteTasks);
// Обновляем состояние
state = AsyncValue.data(remoteTasks);
}, onError: (error, stackTrace) {
// Если ошибка Firestore, используем локальные данные
print('Firestore error: $error');
final localTasks = localStorageService.getTasks();
state = AsyncValue.data(localTasks);
});
} catch (e, st) {
state = AsyncValue.error(e, st);
}
@@ -59,8 +99,11 @@ class TasksNotifier extends StateNotifier<AsyncValue<List<TaskModel>>> {
TaskPriority priority = TaskPriority.medium,
DateTime? dueDate,
int estimatedHours = 0,
String? assigneeId,
}) async {
try {
final currentUserId = ref.read(currentUserIdProvider);
final task = TaskModel(
id: const Uuid().v4(),
title: title,
@@ -70,20 +113,26 @@ class TasksNotifier extends StateNotifier<AsyncValue<List<TaskModel>>> {
priority: priority,
dueDate: dueDate,
estimatedHours: estimatedHours,
assigneeId: assigneeId ?? currentUserId,
creatorId: currentUserId,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
// Сохраняем локально
// Сохраняем локально (для офлайн режима)
await localStorageService.saveTask(task);
// Сохраняем в Firestore
try {
await firestoreService.createTask(task);
} catch (e) {
print('Firestore save failed, saved locally only: $e');
}
// Обновляем состояние
final currentTasks = state.value ?? [];
state = AsyncValue.data([...currentTasks, task]);
// TODO: Сохранить в Firestore когда будет авторизация
// await firestoreService.createTask(task);
return task;
} catch (e, st) {
state = AsyncValue.error(e, st);
@@ -92,18 +141,20 @@ class TasksNotifier extends StateNotifier<AsyncValue<List<TaskModel>>> {
}
// Обновление задачи
Future<void> updateTask(String taskId, {
Future<void> updateTask(
String taskId, {
String? title,
String? description,
TaskStatus? status,
TaskPriority? priority,
DateTime? dueDate,
String? assigneeId,
Map<String, dynamic>? aiMetadata,
}) async {
try {
final currentTasks = state.value ?? [];
final taskIndex = currentTasks.indexWhere((t) => t.id == taskId);
if (taskIndex == -1) throw Exception('Task not found');
final oldTask = currentTasks[taskIndex];
@@ -114,17 +165,23 @@ class TasksNotifier extends StateNotifier<AsyncValue<List<TaskModel>>> {
priority: priority ?? oldTask.priority,
dueDate: dueDate ?? oldTask.dueDate,
assigneeId: assigneeId ?? oldTask.assigneeId,
aiMetadata: aiMetadata ?? oldTask.aiMetadata,
updatedAt: DateTime.now(),
);
// Сохраняем локально
await localStorageService.saveTask(updatedTask);
// Сохраняем в Firestore
try {
await firestoreService.updateTask(updatedTask);
} catch (e) {
print('Firestore update failed, saved locally only: $e');
}
// Обновляем состояние
currentTasks[taskIndex] = updatedTask;
state = AsyncValue.data(List.from(currentTasks));
// TODO: Обновить в Firestore
} catch (e, st) {
state = AsyncValue.error(e, st);
rethrow;
@@ -137,12 +194,17 @@ class TasksNotifier extends StateNotifier<AsyncValue<List<TaskModel>>> {
// Удаляем локально
await localStorageService.deleteTask(taskId);
// Удаляем из Firestore
try {
await firestoreService.deleteTask(taskId);
} catch (e) {
print('Firestore delete failed, deleted locally only: $e');
}
// Обновляем состояние
final currentTasks = state.value ?? [];
currentTasks.removeWhere((t) => t.id == taskId);
state = AsyncValue.data(List.from(currentTasks));
// TODO: Удалить из Firestore
} catch (e, st) {
state = AsyncValue.error(e, st);
rethrow;
@@ -193,13 +255,25 @@ class TasksNotifier extends StateNotifier<AsyncValue<List<TaskModel>>> {
);
}
// AI: Генерация описания
Future<String> generateDescriptionWithAI({
required String title,
String? context,
}) async {
return await deepSeekService.generateTaskDescription(
title: title,
context: context,
);
}
// Фильтрация задач по статусу
List<TaskModel> filterByStatus(List<TaskModel> tasks, TaskStatus status) {
return tasks.where((t) => t.status == status).toList();
}
// Фильтрация задач по приоритету
List<TaskModel> filterByPriority(List<TaskModel> tasks, TaskPriority priority) {
List<TaskModel> filterByPriority(
List<TaskModel> tasks, TaskPriority priority) {
return tasks.where((t) => t.priority == priority).toList();
}
@@ -207,4 +281,20 @@ class TasksNotifier extends StateNotifier<AsyncValue<List<TaskModel>>> {
List<TaskModel> getOverdueTasks(List<TaskModel> tasks) {
return tasks.where((t) => t.isOverdue).toList();
}
// Завершение задачи
Future<void> completeTask(String taskId) async {
await updateTask(
taskId,
status: TaskStatus.done,
);
}
// Возобновление задачи
Future<void> reopenTask(String taskId) async {
await updateTask(
taskId,
status: TaskStatus.todo,
);
}
}