diff --git a/lib/features/tasks/data/datasources/task_local_datasource.dart b/lib/features/tasks/data/datasources/task_local_datasource.dart new file mode 100644 index 0000000..f161bfc --- /dev/null +++ b/lib/features/tasks/data/datasources/task_local_datasource.dart @@ -0,0 +1,149 @@ +import 'package:drift/drift.dart'; + +import '../../../../core/database/app_database.dart'; +import '../../domain/enums/priority.dart'; +import '../models/task_model.dart'; + +abstract class TaskLocalDataSource { + Future> getTasksByDate(String date, {String? status}); + Future getTaskById(String id); + Future createTask(TaskModel task); + Future updateTask(TaskModel task); + Future deleteTask(String id); + Future toggleTaskStatus(String id); + Future rescheduleTask(String id, String targetDate); + Future> getAllTasks(); +} + +class TaskLocalDataSourceImpl implements TaskLocalDataSource { + final AppDatabase database; + + TaskLocalDataSourceImpl({required this.database}); + + TaskModel _taskFromDb(Task dbTask) { + return TaskModel( + id: dbTask.id, + title: dbTask.title, + description: dbTask.description, + date: dbTask.date, + time: dbTask.time, + priority: Priority.fromString(dbTask.priority), + isDone: dbTask.isDone, + createdAt: dbTask.createdAt, + updatedAt: dbTask.updatedAt, + ); + } + + TasksCompanion _taskToDb(TaskModel task) { + return TasksCompanion( + id: Value(task.id), + title: Value(task.title), + description: Value(task.description), + date: Value(task.date), + time: Value(task.time), + priority: Value(task.priority.name), + isDone: Value(task.isDone), + createdAt: Value(task.createdAt ?? DateTime.now()), + updatedAt: Value(DateTime.now()), + ); + } + + @override + Future> getTasksByDate(String date, {String? status}) async { + final dateTime = DateTime.parse(date); + final tasks = await database.getTasksByDate(dateTime); + + var result = tasks.map(_taskFromDb).toList(); + + if (status != null && status != 'all') { + if (status == 'completed') { + result = result.where((t) => t.isDone).toList(); + } else if (status == 'active') { + result = result.where((t) => !t.isDone).toList(); + } + } + + return result; + } + + @override + Future getTaskById(String id) async { + final task = await database.getTaskById(id); + if (task == null) { + throw Exception('Task not found'); + } + return _taskFromDb(task); + } + + @override + Future createTask(TaskModel task) async { + final companion = _taskToDb(task); + await database.insertTask(companion); + return task; + } + + @override + Future updateTask(TaskModel task) async { + final updatedTask = TaskModel( + id: task.id, + title: task.title, + description: task.description, + date: task.date, + time: task.time, + priority: task.priority, + isDone: task.isDone, + createdAt: task.createdAt, + updatedAt: DateTime.now(), + ); + final companion = _taskToDb(updatedTask); + await database.updateTask(companion); + return updatedTask; + } + + @override + Future deleteTask(String id) async { + await database.deleteTaskById(id); + } + + @override + Future toggleTaskStatus(String id) async { + final task = await getTaskById(id); + final updated = TaskModel( + id: task.id, + title: task.title, + description: task.description, + date: task.date, + time: task.time, + priority: task.priority, + isDone: !task.isDone, + createdAt: task.createdAt, + updatedAt: DateTime.now(), + ); + await updateTask(updated); + return updated; + } + + @override + Future rescheduleTask(String id, String targetDate) async { + final task = await getTaskById(id); + final updated = TaskModel( + id: task.id, + title: task.title, + description: task.description, + date: DateTime.parse(targetDate), + time: task.time, + priority: task.priority, + isDone: task.isDone, + createdAt: task.createdAt, + updatedAt: DateTime.now(), + ); + await updateTask(updated); + return updated; + } + + @override + Future> getAllTasks() async { + final tasks = await database.getAllTasks(); + return tasks.map(_taskFromDb).toList(); + } +} diff --git a/lib/features/tasks/data/repositories/local_task_repository_impl.dart b/lib/features/tasks/data/repositories/local_task_repository_impl.dart new file mode 100644 index 0000000..1705750 --- /dev/null +++ b/lib/features/tasks/data/repositories/local_task_repository_impl.dart @@ -0,0 +1,121 @@ +import '../../../../core/errors/failures.dart'; +import '../../../../core/errors/result.dart'; +import '../../../../core/logging/app_logger.dart'; +import '../../domain/entities/task_entity.dart'; +import '../../domain/repositories/task_repository.dart'; +import '../datasources/task_local_datasource.dart'; +import '../models/task_model.dart'; + +class LocalTaskRepositoryImpl implements TaskRepository { + final TaskLocalDataSource localDataSource; + final AppLogger logger; + + LocalTaskRepositoryImpl({ + required this.localDataSource, + required this.logger, + }); + + String _formatDate(DateTime date) { + return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; + } + + @override + Future>> getTasksByDate(DateTime date, {String? status}) async { + try { + final tasks = await localDataSource.getTasksByDate( + _formatDate(date), + status: status, + ); + logger.info('Loaded ${tasks.length} tasks for ${_formatDate(date)} (local)'); + return Success(tasks); + } catch (e, stackTrace) { + logger.error('Local storage error', e, stackTrace); + return Error(UnexpectedFailure(message: e.toString())); + } + } + + @override + Future> getTaskById(String id) async { + try { + final task = await localDataSource.getTaskById(id); + return Success(task); + } catch (e, stackTrace) { + logger.error('Local storage error', e, stackTrace); + if (e.toString().contains('not found')) { + return Error(NotFoundFailure(message: 'Task not found')); + } + return Error(UnexpectedFailure(message: e.toString())); + } + } + + @override + Future> createTask(TaskEntity task) async { + try { + final model = TaskModel.fromEntity(task); + final created = await localDataSource.createTask(model); + logger.info('Created task: ${created.id} (local)'); + return Success(created); + } catch (e, stackTrace) { + logger.error('Local storage error', e, stackTrace); + return Error(UnexpectedFailure(message: e.toString())); + } + } + + @override + Future> updateTask(TaskEntity task) async { + try { + final model = TaskModel.fromEntity(task); + final updated = await localDataSource.updateTask(model); + logger.info('Updated task: ${updated.id} (local)'); + return Success(updated); + } catch (e, stackTrace) { + logger.error('Local storage error', e, stackTrace); + if (e.toString().contains('not found')) { + return Error(NotFoundFailure(message: 'Task not found')); + } + return Error(UnexpectedFailure(message: e.toString())); + } + } + + @override + Future> deleteTask(String id) async { + try { + await localDataSource.deleteTask(id); + logger.info('Deleted task: $id (local)'); + return const Success(null); + } catch (e, stackTrace) { + logger.error('Local storage error', e, stackTrace); + return Error(UnexpectedFailure(message: e.toString())); + } + } + + @override + Future> toggleTaskStatus(String id) async { + try { + final task = await localDataSource.toggleTaskStatus(id); + logger.info('Toggled task status: $id -> ${task.isDone} (local)'); + return Success(task); + } catch (e, stackTrace) { + logger.error('Local storage error', e, stackTrace); + if (e.toString().contains('not found')) { + return Error(NotFoundFailure(message: 'Task not found')); + } + return Error(UnexpectedFailure(message: e.toString())); + } + } + + @override + Future> rescheduleTask(String id, DateTime targetDate) async { + try { + final task = await localDataSource.rescheduleTask(id, _formatDate(targetDate)); + logger.info('Rescheduled task: $id -> ${_formatDate(targetDate)} (local)'); + return Success(task); + } catch (e, stackTrace) { + logger.error('Local storage error', e, stackTrace); + if (e.toString().contains('not found')) { + return Error(NotFoundFailure(message: 'Task not found')); + } + return Error(UnexpectedFailure(message: e.toString())); + } + } +}