feat(11-02): add DAO queries, update CalendarDayState, implement pre-population provider

- Add watchAllActiveRecurringTasks() and watchAllActiveRecurringTasksInRoom() to CalendarDao
- Add watchCompletionsInRange() for period-completion filtering
- Extend CalendarDayState with prePopulatedTasks field (default empty, backward compat)
- Update isEmpty getter to include prePopulatedTasks.isEmpty
- Add _isInCurrentIntervalWindow() and _calculatePreviousDueDate() helpers
- Rewrite calendarDayProvider and roomCalendarDayProvider with pre-population logic
- Fix _subtractMonths() year-boundary bug using total-month arithmetic
- Add 9 new DAO tests for watchAllActiveRecurringTasks, watchAllActiveRecurringTasksInRoom, watchCompletionsInRange
This commit is contained in:
2026-04-03 21:25:44 +02:00
parent 7c5242d070
commit 9a67c51568
4 changed files with 432 additions and 6 deletions

View File

@@ -165,4 +165,59 @@ class CalendarDao extends DatabaseAccessor<AppDatabase>
final result = await query.getSingle();
return result.read(countExp) ?? 0;
}
/// Watch ALL active tasks with their rooms.
///
/// Used by the pre-population logic to determine which tasks should appear
/// on days within their current interval window (before their next due date).
Stream<List<TaskWithRoom>> watchAllActiveRecurringTasks() {
final query = select(tasks).join([
innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
]);
query.where(tasks.isActive.equals(true));
query.orderBy([OrderingTerm.asc(tasks.name)]);
return query.watch().map((rows) {
return rows.map((row) {
final task = row.readTable(tasks);
final room = row.readTable(rooms);
return TaskWithRoom(task: task, roomName: room.name, roomId: room.id);
}).toList();
});
}
/// Watch ALL active tasks with their rooms, filtered to a specific [roomId].
///
/// Room-scoped variant of [watchAllActiveRecurringTasks], used by
/// [roomCalendarDayProvider] to pre-populate tasks for a single room.
Stream<List<TaskWithRoom>> watchAllActiveRecurringTasksInRoom(int roomId) {
final query = select(tasks).join([
innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
]);
query.where(tasks.isActive.equals(true) & tasks.roomId.equals(roomId));
query.orderBy([OrderingTerm.asc(tasks.name)]);
return query.watch().map((rows) {
return rows.map((row) {
final task = row.readTable(tasks);
final room = row.readTable(rooms);
return TaskWithRoom(task: task, roomName: room.name, roomId: room.id);
}).toList();
});
}
/// Watch completions for a given [taskId] within a date range [start]..[end].
///
/// Used for period-completion filtering: if a task was completed in the
/// current interval window, it should not appear as a pre-populated task.
/// [start] is inclusive; [end] is exclusive.
Stream<List<TaskCompletion>> watchCompletionsInRange(
int taskId, DateTime start, DateTime end) {
return (select(taskCompletions)
..where((c) =>
c.taskId.equals(taskId) &
c.completedAt.isBiggerOrEqualValue(start) &
c.completedAt.isSmallerThanValue(end)))
.watch();
}
}