Add Firestore integration with real-time sync
This commit is contained in:
+107
-27
@@ -23,6 +23,12 @@ class FirestoreService {
|
||||
await _db.collection('users').doc(user.id).update(user.toFirestore());
|
||||
}
|
||||
|
||||
Stream<UserModel?> watchUser(String userId) {
|
||||
return _db.collection('users').doc(userId).snapshots().map((doc) {
|
||||
return doc.exists ? UserModel.fromFirestore(doc) : null;
|
||||
});
|
||||
}
|
||||
|
||||
// === PROJECTS ===
|
||||
Future<void> createProject(ProjectModel project) async {
|
||||
await _db.collection('projects').doc(project.id).set(project.toFirestore());
|
||||
@@ -34,7 +40,7 @@ class FirestoreService {
|
||||
.where('teamId', isEqualTo: teamId)
|
||||
.orderBy('createdAt', descending: true)
|
||||
.get();
|
||||
|
||||
|
||||
return snapshot.docs.map((doc) => ProjectModel.fromFirestore(doc)).toList();
|
||||
}
|
||||
|
||||
@@ -43,10 +49,20 @@ class FirestoreService {
|
||||
.collection('projects')
|
||||
.where('memberIds', arrayContains: userId)
|
||||
.get();
|
||||
|
||||
|
||||
return snapshot.docs.map((doc) => ProjectModel.fromFirestore(doc)).toList();
|
||||
}
|
||||
|
||||
Stream<List<ProjectModel>> watchProjectsByUser(String userId) {
|
||||
return _db
|
||||
.collection('projects')
|
||||
.where('memberIds', arrayContains: userId)
|
||||
.snapshots()
|
||||
.map((snapshot) => snapshot.docs
|
||||
.map((doc) => ProjectModel.fromFirestore(doc))
|
||||
.toList());
|
||||
}
|
||||
|
||||
// === TASKS ===
|
||||
Future<void> createTask(TaskModel task) async {
|
||||
await _db.collection('tasks').doc(task.id).set(task.toFirestore());
|
||||
@@ -58,7 +74,7 @@ class FirestoreService {
|
||||
.where('projectId', isEqualTo: projectId)
|
||||
.orderBy('createdAt', descending: true)
|
||||
.get();
|
||||
|
||||
|
||||
return snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList();
|
||||
}
|
||||
|
||||
@@ -66,9 +82,17 @@ class FirestoreService {
|
||||
final snapshot = await _db
|
||||
.collection('tasks')
|
||||
.where('assigneeId', isEqualTo: assigneeId)
|
||||
.where('status', whereIn: ['todo', 'inProgress', 'review'])
|
||||
.where('status', whereIn: ['todo', 'inProgress', 'review']).get();
|
||||
|
||||
return snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList();
|
||||
}
|
||||
|
||||
Future<List<TaskModel>> getAllTasks() async {
|
||||
final snapshot = await _db
|
||||
.collection('tasks')
|
||||
.orderBy('createdAt', descending: true)
|
||||
.get();
|
||||
|
||||
|
||||
return snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList();
|
||||
}
|
||||
|
||||
@@ -86,13 +110,31 @@ class FirestoreService {
|
||||
.collection('tasks')
|
||||
.where('projectId', isEqualTo: projectId)
|
||||
.get();
|
||||
|
||||
|
||||
return snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList();
|
||||
}
|
||||
|
||||
// Поток задач для реального времени
|
||||
Stream<List<TaskModel>> watchTasks(String? projectId) {
|
||||
Query query = _db.collection('tasks');
|
||||
|
||||
if (projectId != null && projectId.isNotEmpty) {
|
||||
query = query.where('projectId', isEqualTo: projectId);
|
||||
}
|
||||
|
||||
return query.orderBy('createdAt', descending: true).snapshots().map(
|
||||
(snapshot) =>
|
||||
snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList());
|
||||
}
|
||||
|
||||
// === MIND MAPS ===
|
||||
Future<void> createMindMapNode(MindMapNode node) async {
|
||||
await _db.collection('mindmaps').doc(node.projectId).collection('nodes').doc(node.id).set(node.toFirestore());
|
||||
await _db
|
||||
.collection('mindmaps')
|
||||
.doc(node.projectId)
|
||||
.collection('nodes')
|
||||
.doc(node.id)
|
||||
.set(node.toFirestore());
|
||||
}
|
||||
|
||||
Future<List<MindMapNode>> getMindMapNodes(String projectId) async {
|
||||
@@ -102,7 +144,7 @@ class FirestoreService {
|
||||
.collection('nodes')
|
||||
.orderBy('level')
|
||||
.get();
|
||||
|
||||
|
||||
return snapshot.docs.map((doc) => MindMapNode.fromFirestore(doc)).toList();
|
||||
}
|
||||
|
||||
@@ -115,6 +157,15 @@ class FirestoreService {
|
||||
.update(node.toFirestore());
|
||||
}
|
||||
|
||||
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());
|
||||
@@ -129,13 +180,15 @@ class FirestoreService {
|
||||
.collection('events')
|
||||
.where('startTime', isLessThanOrEqualTo: Timestamp.fromDate(end))
|
||||
.where('endTime', isGreaterThanOrEqualTo: Timestamp.fromDate(start));
|
||||
|
||||
|
||||
if (userId != null) {
|
||||
query = query.where('attendeeIds', arrayContains: userId);
|
||||
}
|
||||
|
||||
|
||||
final snapshot = await query.get();
|
||||
return snapshot.docs.map((doc) => CalendarEventModel.fromFirestore(doc)).toList();
|
||||
return snapshot.docs
|
||||
.map((doc) => CalendarEventModel.fromFirestore(doc))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> updateEvent(CalendarEventModel event) async {
|
||||
@@ -146,6 +199,17 @@ class FirestoreService {
|
||||
await _db.collection('events').doc(eventId).delete();
|
||||
}
|
||||
|
||||
Stream<List<CalendarEventModel>> watchEvents(String userId) {
|
||||
return _db
|
||||
.collection('events')
|
||||
.where('attendeeIds', arrayContains: userId)
|
||||
.orderBy('startTime')
|
||||
.snapshots()
|
||||
.map((snapshot) => snapshot.docs
|
||||
.map((doc) => CalendarEventModel.fromFirestore(doc))
|
||||
.toList());
|
||||
}
|
||||
|
||||
// === ALARMS ===
|
||||
Future<void> createAlarm(AlarmModel alarm) async {
|
||||
await _db.collection('alarms').doc(alarm.id).set(alarm.toFirestore());
|
||||
@@ -157,7 +221,7 @@ class FirestoreService {
|
||||
.where('isEnabled', isEqualTo: true)
|
||||
.orderBy('dateTime')
|
||||
.get();
|
||||
|
||||
|
||||
return snapshot.docs.map((doc) => AlarmModel.fromFirestore(doc)).toList();
|
||||
}
|
||||
|
||||
@@ -172,29 +236,45 @@ class FirestoreService {
|
||||
// === BATCH OPERATIONS ===
|
||||
Future<void> syncTasks(List<TaskModel> tasks) async {
|
||||
final batch = _db.batch();
|
||||
|
||||
|
||||
for (final task in tasks) {
|
||||
batch.set(_db.collection('tasks').doc(task.id), task.toFirestore());
|
||||
}
|
||||
|
||||
|
||||
await batch.commit();
|
||||
}
|
||||
|
||||
// === STREAMS (для реального времени) ===
|
||||
Stream<List<TaskModel>> watchTasks(String projectId) {
|
||||
return _db
|
||||
.collection('tasks')
|
||||
.where('projectId', isEqualTo: projectId)
|
||||
.snapshots()
|
||||
.map((snapshot) => snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList());
|
||||
// === TEAMS ===
|
||||
Future<void> createTeam(String teamId, String name, String ownerId) async {
|
||||
await _db.collection('teams').doc(teamId).set({
|
||||
'name': name,
|
||||
'ownerId': ownerId,
|
||||
'memberIds': [ownerId],
|
||||
'createdAt': Timestamp.now(),
|
||||
'updatedAt': Timestamp.now(),
|
||||
});
|
||||
}
|
||||
|
||||
Stream<List<CalendarEventModel>> watchEvents(String userId) {
|
||||
return _db
|
||||
.collection('events')
|
||||
.where('attendeeIds', arrayContains: userId)
|
||||
.snapshots()
|
||||
.map((snapshot) => snapshot.docs.map((doc) => CalendarEventModel.fromFirestore(doc)).toList());
|
||||
Future<void> addTeamMember(String teamId, String userId) async {
|
||||
await _db.collection('teams').doc(teamId).update({
|
||||
'memberIds': FieldValue.arrayUnion([userId]),
|
||||
'updatedAt': Timestamp.now(),
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> removeTeamMember(String teamId, String userId) async {
|
||||
await _db.collection('teams').doc(teamId).update({
|
||||
'memberIds': FieldValue.arrayRemove([userId]),
|
||||
'updatedAt': Timestamp.now(),
|
||||
});
|
||||
}
|
||||
|
||||
Stream<List<Map<String, dynamic>>> watchTeamMembers(String teamId) {
|
||||
return _db.collection('teams').doc(teamId).snapshots().map((doc) {
|
||||
if (!doc.exists) return [];
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return List<Map<String, dynamic>>.from(data['memberIds'] ?? []);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../screens/auth_screen.dart';
|
||||
import '../screens/home_screen.dart';
|
||||
import '../screens/tasks_screen.dart';
|
||||
@@ -7,10 +8,16 @@ import '../screens/mind_map_screen.dart';
|
||||
import '../screens/calendar_screen.dart';
|
||||
import '../screens/alarms_screen.dart';
|
||||
import '../screens/ai_test_screen.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
|
||||
// Глобальный ключ для навигации
|
||||
final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
class AppRouter {
|
||||
static final GoRouter router = GoRouter(
|
||||
navigatorKey: _rootNavigatorKey,
|
||||
initialLocation: '/auth',
|
||||
refreshListenable: GoRouterRefreshNotifier(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/auth',
|
||||
@@ -74,5 +81,44 @@ class AppRouter {
|
||||
],
|
||||
),
|
||||
],
|
||||
redirect: (context, state) {
|
||||
final isLoggedIn = GoRouterRefreshNotifier.isAuthenticated;
|
||||
final isLoggingIn = state.matchedLocation == '/auth';
|
||||
|
||||
if (!isLoggedIn && !isLoggingIn) {
|
||||
return '/auth';
|
||||
}
|
||||
|
||||
if (isLoggedIn && isLoggingIn) {
|
||||
return '/home';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Класс для уведомления об изменении состояния аутентификации
|
||||
class GoRouterRefreshNotifier extends ChangeNotifier {
|
||||
static bool isAuthenticated = false;
|
||||
|
||||
GoRouterRefreshNotifier() {
|
||||
// Подписка на изменения аутентификации будет добавлена через provider
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Provider для обновления роутера при изменении аутентификации
|
||||
final routerRefreshProvider = ChangeNotifierProvider((ref) {
|
||||
final auth = ref.watch(authProvider);
|
||||
|
||||
auth.whenData((user) {
|
||||
GoRouterRefreshNotifier.isAuthenticated = user != null;
|
||||
});
|
||||
|
||||
return GoRouterRefreshNotifier();
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ class AuthScreen extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final authState = ref.watch(authProvider);
|
||||
final authNotifier = ref.read(authProvider.notifier);
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
@@ -43,6 +44,7 @@ class AuthScreen extends ConsumerWidget {
|
||||
authState.when(
|
||||
data: (user) {
|
||||
if (user != null) {
|
||||
// Пользователь авторизован - переходим на главную
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.go('/home');
|
||||
});
|
||||
@@ -57,9 +59,11 @@ class AuthScreen extends ConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
ref.read(authProvider.notifier).signInWithGoogle();
|
||||
},
|
||||
onPressed: authNotifier.isLoggedIn
|
||||
? null
|
||||
: () {
|
||||
authNotifier.signInWithGoogle();
|
||||
},
|
||||
icon: const Icon(Icons.login),
|
||||
label: const Text('Войти через Google'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
@@ -69,6 +73,14 @@ class AuthScreen extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (authNotifier.isLoggedIn) ...[
|
||||
const SizedBox(height: 16),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => authNotifier.signOut(),
|
||||
icon: const Icon(Icons.logout),
|
||||
label: const Text('Выйти'),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user