Add local task datasource and repository

- Create TaskLocalDataSource for Drift database operations
- Implement LocalTaskRepositoryImpl with same interface as remote
- Support full CRUD operations for offline task management
This commit is contained in:
m3mo 2026-02-03 14:21:40 +01:00
parent ccc7d544db
commit ece34acd85
2 changed files with 270 additions and 0 deletions

View File

@ -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<List<TaskModel>> getTasksByDate(String date, {String? status});
Future<TaskModel> getTaskById(String id);
Future<TaskModel> createTask(TaskModel task);
Future<TaskModel> updateTask(TaskModel task);
Future<void> deleteTask(String id);
Future<TaskModel> toggleTaskStatus(String id);
Future<TaskModel> rescheduleTask(String id, String targetDate);
Future<List<TaskModel>> 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<List<TaskModel>> 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<TaskModel> getTaskById(String id) async {
final task = await database.getTaskById(id);
if (task == null) {
throw Exception('Task not found');
}
return _taskFromDb(task);
}
@override
Future<TaskModel> createTask(TaskModel task) async {
final companion = _taskToDb(task);
await database.insertTask(companion);
return task;
}
@override
Future<TaskModel> 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<void> deleteTask(String id) async {
await database.deleteTaskById(id);
}
@override
Future<TaskModel> 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<TaskModel> 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<List<TaskModel>> getAllTasks() async {
final tasks = await database.getAllTasks();
return tasks.map(_taskFromDb).toList();
}
}

View File

@ -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<Result<List<TaskEntity>>> 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<Result<TaskEntity>> 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<Result<TaskEntity>> 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<Result<TaskEntity>> 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<Result<void>> 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<Result<TaskEntity>> 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<Result<TaskEntity>> 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()));
}
}
}