- Add watchCompletionsForTask(taskId) to TasksDao: Stream<List<TaskCompletion>> sorted newest first - Regenerate tasks_dao.g.dart with build_runner - Add taskHistoryTitle, taskHistoryEmpty, taskHistoryCount to app_de.arb - Regenerate app_localizations.dart and app_localizations_de.dart - All 5 new DAO tests pass, zero analyze issues
111 lines
3.6 KiB
Dart
111 lines
3.6 KiB
Dart
import 'package:drift/drift.dart';
|
|
|
|
import '../../../core/database/database.dart';
|
|
import '../domain/scheduling.dart';
|
|
|
|
part 'tasks_dao.g.dart';
|
|
|
|
@DriftAccessor(tables: [Tasks, TaskCompletions])
|
|
class TasksDao extends DatabaseAccessor<AppDatabase> with _$TasksDaoMixin {
|
|
TasksDao(super.attachedDatabase);
|
|
|
|
/// Watch tasks in a room sorted by nextDueDate ascending.
|
|
Stream<List<Task>> watchTasksInRoom(int roomId) {
|
|
return (select(tasks)
|
|
..where((t) => t.roomId.equals(roomId))
|
|
..orderBy([(t) => OrderingTerm.asc(t.nextDueDate)]))
|
|
.watch();
|
|
}
|
|
|
|
/// Insert a new task. Returns the auto-generated id.
|
|
Future<int> insertTask(TasksCompanion task) => into(tasks).insert(task);
|
|
|
|
/// Update an existing task. Returns true if a row was updated.
|
|
Future<bool> updateTask(Task task) => update(tasks).replace(task);
|
|
|
|
/// Delete a task and its completions.
|
|
Future<void> deleteTask(int taskId) {
|
|
return transaction(() async {
|
|
await (delete(taskCompletions)..where((c) => c.taskId.equals(taskId)))
|
|
.go();
|
|
await (delete(tasks)..where((t) => t.id.equals(taskId))).go();
|
|
});
|
|
}
|
|
|
|
/// Mark a task as done: records completion and calculates next due date.
|
|
///
|
|
/// Uses scheduling utility for date calculation. Next due is calculated
|
|
/// from the original due date (not completion date) to keep rhythm stable.
|
|
/// If the calculated next due is in the past, catch-up advances to present.
|
|
///
|
|
/// [now] parameter allows injection of current time for testing.
|
|
Future<void> completeTask(int taskId, {DateTime? now}) {
|
|
return transaction(() async {
|
|
// 1. Get current task
|
|
final task =
|
|
await (select(tasks)..where((t) => t.id.equals(taskId))).getSingle();
|
|
|
|
final currentTime = now ?? DateTime.now();
|
|
|
|
// 2. Record completion
|
|
await into(taskCompletions).insert(TaskCompletionsCompanion.insert(
|
|
taskId: taskId,
|
|
completedAt: currentTime,
|
|
));
|
|
|
|
// 3. Calculate next due date (from original due date, not today)
|
|
var nextDue = calculateNextDueDate(
|
|
currentDueDate: task.nextDueDate,
|
|
intervalType: task.intervalType,
|
|
intervalDays: task.intervalDays,
|
|
anchorDay: task.anchorDay,
|
|
);
|
|
|
|
// 4. Catch up if next due is still in the past
|
|
final todayDateOnly = DateTime(
|
|
currentTime.year,
|
|
currentTime.month,
|
|
currentTime.day,
|
|
);
|
|
nextDue = catchUpToPresent(
|
|
nextDue: nextDue,
|
|
today: todayDateOnly,
|
|
intervalType: task.intervalType,
|
|
intervalDays: task.intervalDays,
|
|
anchorDay: task.anchorDay,
|
|
);
|
|
|
|
// 5. Update task with new due date
|
|
await (update(tasks)..where((t) => t.id.equals(taskId)))
|
|
.write(TasksCompanion(nextDueDate: Value(nextDue)));
|
|
});
|
|
}
|
|
|
|
/// Watch all completions for a task, newest first.
|
|
Stream<List<TaskCompletion>> watchCompletionsForTask(int taskId) {
|
|
return (select(taskCompletions)
|
|
..where((c) => c.taskId.equals(taskId))
|
|
..orderBy([(c) => OrderingTerm.desc(c.completedAt)]))
|
|
.watch();
|
|
}
|
|
|
|
/// Count overdue tasks in a room (nextDueDate before today).
|
|
Future<int> getOverdueTaskCount(int roomId, {DateTime? today}) async {
|
|
final now = today ?? DateTime.now();
|
|
final todayDateOnly = DateTime(now.year, now.month, now.day);
|
|
|
|
final taskList = await (select(tasks)
|
|
..where((t) => t.roomId.equals(roomId)))
|
|
.get();
|
|
|
|
return taskList.where((task) {
|
|
final dueDate = DateTime(
|
|
task.nextDueDate.year,
|
|
task.nextDueDate.month,
|
|
task.nextDueDate.day,
|
|
);
|
|
return dueDate.isBefore(todayDateOnly);
|
|
}).length;
|
|
}
|
|
}
|