From ad70eb7ff1bea3fecdad7d57da751da54a70c5dd Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 16 Mar 2026 12:29:24 +0100 Subject: [PATCH] feat(03-01): implement DailyPlanDao with cross-room join query and completion count - watchAllTasksWithRoomName: innerJoin tasks+rooms, sorted by nextDueDate asc - watchCompletionsToday: customSelect with readsFrom for proper stream invalidation - All 7 DAO unit tests pass Co-Authored-By: Claude Opus 4.6 --- lib/features/home/data/daily_plan_dao.dart | 36 +++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/features/home/data/daily_plan_dao.dart b/lib/features/home/data/daily_plan_dao.dart index a635858..6eadf92 100644 --- a/lib/features/home/data/daily_plan_dao.dart +++ b/lib/features/home/data/daily_plan_dao.dart @@ -11,14 +11,42 @@ class DailyPlanDao extends DatabaseAccessor DailyPlanDao(super.attachedDatabase); /// Watch all tasks joined with room name, sorted by nextDueDate ascending. + /// Includes ALL tasks (overdue, today, future) -- filtering is done in the + /// provider layer to avoid multiple queries. Stream> watchAllTasksWithRoomName() { - // TODO: implement - throw UnimplementedError(); + final query = select(tasks).join([ + innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)), + ]); + query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]); + + 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(); + }); } /// Count task completions recorded today. + /// Uses customSelect with readsFrom for proper stream invalidation. Stream watchCompletionsToday({DateTime? today}) { - // TODO: implement - throw UnimplementedError(); + final now = today ?? DateTime.now(); + final startOfDay = DateTime(now.year, now.month, now.day); + final endOfDay = startOfDay.add(const Duration(days: 1)); + + return customSelect( + 'SELECT COUNT(*) AS c FROM task_completions ' + 'WHERE completed_at >= ? AND completed_at < ?', + variables: [ + Variable(startOfDay.millisecondsSinceEpoch ~/ 1000), + Variable(endOfDay.millisecondsSinceEpoch ~/ 1000), + ], + readsFrom: {taskCompletions}, + ).watchSingle().map((row) => row.read('c')); } }