- CalendarDao: filter all 6 task queries (watchTasksForDate, watchTasksForDateInRoom, watchOverdueTasks, watchOverdueTasksInRoom, getTaskCount, getTaskCountInRoom) by isActive=true - DailyPlanDao: filter all 3 queries (watchAllTasksWithRoomName, getOverdueAndTodayTaskCount, getOverdueTaskCount) by isActive=true - RoomsDao: filter watchRoomWithStats task query by isActive=true - Update migration test: add schema_v3.dart, test v1->v3 and v2->v3 paths - Update database_test schemaVersion assertion to expect 3 - Fix test helpers in home_screen_test and task_list_screen_test to pass isActive=true
82 lines
2.9 KiB
Dart
82 lines
2.9 KiB
Dart
import 'package:drift/drift.dart';
|
|
|
|
import '../../../core/database/database.dart';
|
|
import '../domain/daily_plan_models.dart';
|
|
|
|
part 'daily_plan_dao.g.dart';
|
|
|
|
@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])
|
|
class DailyPlanDao extends DatabaseAccessor<AppDatabase>
|
|
with _$DailyPlanDaoMixin {
|
|
DailyPlanDao(super.attachedDatabase);
|
|
|
|
/// Watch all active tasks joined with room name, sorted by nextDueDate ascending.
|
|
/// Includes overdue, today, and future tasks -- filtering is done in the
|
|
/// provider layer to avoid multiple queries. Excludes soft-deleted tasks.
|
|
Stream<List<TaskWithRoom>> watchAllTasksWithRoomName() {
|
|
final query = select(tasks).join([
|
|
innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
|
|
]);
|
|
query.where(tasks.isActive.equals(true));
|
|
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();
|
|
});
|
|
}
|
|
|
|
/// One-shot count of overdue + today active tasks (for notification body).
|
|
Future<int> getOverdueAndTodayTaskCount({DateTime? today}) async {
|
|
final now = today ?? DateTime.now();
|
|
final endOfToday = DateTime(now.year, now.month, now.day + 1);
|
|
final result = await (selectOnly(tasks)
|
|
..addColumns([tasks.id.count()])
|
|
..where(
|
|
tasks.nextDueDate.isSmallerThanValue(endOfToday) &
|
|
tasks.isActive.equals(true),
|
|
))
|
|
.getSingle();
|
|
return result.read(tasks.id.count()) ?? 0;
|
|
}
|
|
|
|
/// One-shot count of overdue active tasks only (for notification body split).
|
|
Future<int> getOverdueTaskCount({DateTime? today}) async {
|
|
final now = today ?? DateTime.now();
|
|
final startOfToday = DateTime(now.year, now.month, now.day);
|
|
final result = await (selectOnly(tasks)
|
|
..addColumns([tasks.id.count()])
|
|
..where(
|
|
tasks.nextDueDate.isSmallerThanValue(startOfToday) &
|
|
tasks.isActive.equals(true),
|
|
))
|
|
.getSingle();
|
|
return result.read(tasks.id.count()) ?? 0;
|
|
}
|
|
|
|
/// Count task completions recorded today.
|
|
/// Uses customSelect with readsFrom for proper stream invalidation.
|
|
Stream<int> watchCompletionsToday({DateTime? today}) {
|
|
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<int>('c'));
|
|
}
|
|
}
|