567 lines
23 KiB
Markdown
567 lines
23 KiB
Markdown
---
|
|
phase: 11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks
|
|
plan: 02
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["11-01"]
|
|
files_modified:
|
|
- lib/features/home/data/calendar_dao.dart
|
|
- lib/features/home/presentation/calendar_providers.dart
|
|
- lib/features/home/domain/calendar_models.dart
|
|
- lib/features/home/presentation/calendar_day_list.dart
|
|
- lib/features/home/presentation/calendar_task_row.dart
|
|
- test/features/home/data/calendar_dao_test.dart
|
|
autonomous: true
|
|
requirements:
|
|
- TM-03
|
|
- TM-04
|
|
- TM-05
|
|
|
|
must_haves:
|
|
truths:
|
|
- "A weekly task appears on every day within its current interval window, not just on the due date"
|
|
- "A monthly task appears on every day within its current interval window"
|
|
- "A daily task appears every day"
|
|
- "Tasks already completed in the current interval period do not reappear as pre-populated"
|
|
- "Pre-populated tasks that are not yet due have a muted/lighter visual appearance"
|
|
- "Tasks on their actual due date appear with full styling"
|
|
- "Overdue tasks keep their existing red/orange accent"
|
|
artifacts:
|
|
- path: "lib/features/home/data/calendar_dao.dart"
|
|
provides: "watchAllActiveRecurringTasks query and watchCompletionsInRange query"
|
|
contains: "watchAllActiveRecurringTasks"
|
|
- path: "lib/features/home/presentation/calendar_providers.dart"
|
|
provides: "Pre-population logic combining due-today + virtual instances + overdue"
|
|
contains: "isPrePopulated"
|
|
- path: "lib/features/home/domain/calendar_models.dart"
|
|
provides: "CalendarDayState with pre-populated task support"
|
|
contains: "prePopulatedTasks"
|
|
- path: "lib/features/home/presentation/calendar_task_row.dart"
|
|
provides: "Visual distinction for pre-populated tasks"
|
|
contains: "isPrePopulated"
|
|
- path: "lib/features/home/presentation/calendar_day_list.dart"
|
|
provides: "Renders pre-populated section with muted styling"
|
|
contains: "prePopulatedTasks"
|
|
key_links:
|
|
- from: "lib/features/home/presentation/calendar_providers.dart"
|
|
to: "lib/features/home/data/calendar_dao.dart"
|
|
via: "watchAllActiveRecurringTasks stream for pre-population source data"
|
|
pattern: "watchAllActiveRecurringTasks"
|
|
- from: "lib/features/home/presentation/calendar_providers.dart"
|
|
to: "lib/features/tasks/domain/scheduling.dart"
|
|
via: "calculateNextDueDate used to determine interval window boundaries"
|
|
pattern: "calculateNextDueDate"
|
|
- from: "lib/features/home/presentation/calendar_day_list.dart"
|
|
to: "CalendarTaskRow"
|
|
via: "isPrePopulated flag passed for visual distinction"
|
|
pattern: "isPrePopulated"
|
|
---
|
|
|
|
<objective>
|
|
Pre-populate recurring tasks on all applicable days within their interval window. A weekly task due Monday shows on all 7 days leading up to that Monday. Tasks already completed in the current period are hidden. Pre-populated (not-yet-due) tasks get a muted visual style.
|
|
|
|
Purpose: Users want a consistent checklist — tasks should be visible and completable before their due date, not appear only when due. This makes the app feel like a reliable weekly/monthly checklist.
|
|
Output: Virtual task instances in provider layer, period-completion filtering, muted visual styling for upcoming tasks.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-CONTEXT.md
|
|
@.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-01-SUMMARY.md
|
|
|
|
<interfaces>
|
|
<!-- Key types and contracts after Plan 01 -->
|
|
|
|
From lib/features/home/domain/daily_plan_models.dart:
|
|
```dart
|
|
class TaskWithRoom {
|
|
final Task task;
|
|
final String roomName;
|
|
final int roomId;
|
|
const TaskWithRoom({required this.task, required this.roomName, required this.roomId});
|
|
}
|
|
```
|
|
|
|
From lib/features/home/domain/calendar_models.dart:
|
|
```dart
|
|
class CalendarDayState {
|
|
final DateTime selectedDate;
|
|
final List<TaskWithRoom> dayTasks;
|
|
final List<TaskWithRoom> overdueTasks;
|
|
final int totalTaskCount;
|
|
bool get isEmpty => dayTasks.isEmpty && overdueTasks.isEmpty;
|
|
}
|
|
```
|
|
|
|
From lib/features/tasks/domain/frequency.dart:
|
|
```dart
|
|
enum IntervalType {
|
|
daily, everyNDays, weekly, biweekly, monthly, everyNMonths, quarterly, yearly,
|
|
}
|
|
```
|
|
|
|
From lib/features/tasks/domain/scheduling.dart:
|
|
```dart
|
|
DateTime calculateNextDueDate({
|
|
required DateTime currentDueDate,
|
|
required IntervalType intervalType,
|
|
required int intervalDays,
|
|
int? anchorDay,
|
|
});
|
|
```
|
|
|
|
From lib/features/home/data/calendar_dao.dart:
|
|
```dart
|
|
class CalendarDao extends DatabaseAccessor<AppDatabase> with _$CalendarDaoMixin {
|
|
Stream<List<TaskWithRoom>> watchTasksForDate(DateTime date);
|
|
Stream<List<TaskWithRoom>> watchOverdueTasks(DateTime referenceDate);
|
|
// + room-scoped variants
|
|
}
|
|
```
|
|
|
|
From lib/core/database/database.dart (Tasks table):
|
|
```dart
|
|
class Tasks extends Table {
|
|
IntColumn get id => integer().autoIncrement()();
|
|
IntColumn get roomId => integer().references(Rooms, #id)();
|
|
TextColumn get name => text().withLength(min: 1, max: 200)();
|
|
IntColumn get intervalType => intEnum<IntervalType>()();
|
|
IntColumn get intervalDays => integer().withDefault(const Constant(1))();
|
|
IntColumn get anchorDay => integer().nullable()();
|
|
DateTimeColumn get nextDueDate => dateTime()();
|
|
BoolColumn get isActive => BoolColumn().withDefault(const Constant(true))();
|
|
}
|
|
```
|
|
|
|
From lib/core/database/database.dart (TaskCompletions table):
|
|
```dart
|
|
class TaskCompletions extends Table {
|
|
IntColumn get id => integer().autoIncrement()();
|
|
IntColumn get taskId => integer().references(Tasks, #id)();
|
|
DateTimeColumn get completedAt => dateTime()();
|
|
}
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Add DAO queries, update CalendarDayState model, and implement pre-population provider logic</name>
|
|
<files>
|
|
lib/features/home/data/calendar_dao.dart
|
|
lib/features/home/domain/calendar_models.dart
|
|
lib/features/home/presentation/calendar_providers.dart
|
|
test/features/home/data/calendar_dao_test.dart
|
|
</files>
|
|
<read_first>
|
|
lib/features/home/data/calendar_dao.dart
|
|
lib/features/home/domain/calendar_models.dart
|
|
lib/features/home/presentation/calendar_providers.dart
|
|
lib/features/tasks/domain/scheduling.dart
|
|
lib/features/tasks/domain/frequency.dart
|
|
lib/core/database/database.dart
|
|
test/features/home/data/calendar_dao_test.dart
|
|
</read_first>
|
|
<action>
|
|
**Per D-04 through D-10: Query-time virtual instances, no schema migration.**
|
|
|
|
### Step 1: New DAO methods in calendar_dao.dart
|
|
|
|
Add two new methods to `CalendarDao`:
|
|
|
|
**1a. `watchAllActiveRecurringTasks()`** — Fetch ALL active tasks with their rooms (for pre-population logic):
|
|
```dart
|
|
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();
|
|
});
|
|
}
|
|
```
|
|
|
|
**1b. `watchCompletionsInRange(int taskId, DateTime start, DateTime end)`** — Check if a task was completed within a date range (for period-completion filtering per D-09):
|
|
```dart
|
|
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();
|
|
}
|
|
```
|
|
|
|
**1c. Room-scoped variant `watchAllActiveRecurringTasksInRoom(int roomId)`**:
|
|
Same as 1a but with additional `.where(tasks.roomId.equals(roomId))`.
|
|
|
|
### Step 2: Update CalendarDayState in calendar_models.dart
|
|
|
|
Add a `prePopulatedTasks` field:
|
|
```dart
|
|
class CalendarDayState {
|
|
final DateTime selectedDate;
|
|
final List<TaskWithRoom> dayTasks;
|
|
final List<TaskWithRoom> overdueTasks;
|
|
final List<TaskWithRoom> prePopulatedTasks; // NEW — tasks visible via pre-population
|
|
final int totalTaskCount;
|
|
|
|
const CalendarDayState({
|
|
required this.selectedDate,
|
|
required this.dayTasks,
|
|
required this.overdueTasks,
|
|
this.prePopulatedTasks = const [], // Default empty for backward compat
|
|
required this.totalTaskCount,
|
|
});
|
|
|
|
bool get isEmpty => dayTasks.isEmpty && overdueTasks.isEmpty && prePopulatedTasks.isEmpty;
|
|
}
|
|
```
|
|
|
|
### Step 3: Rewrite calendarDayProvider in calendar_providers.dart
|
|
|
|
The provider needs to combine three streams: due-today tasks, overdue tasks, and pre-populated virtual tasks.
|
|
|
|
Add a top-level helper function `_isInCurrentIntervalWindow` that determines if a task should appear on a given date:
|
|
|
|
```dart
|
|
/// Determines whether [task] should appear on [selectedDate] via pre-population.
|
|
///
|
|
/// Per D-07: A task shows on all days within the current interval window
|
|
/// leading up to its nextDueDate. For example:
|
|
/// - weekly task due Monday: shows on all 7 days (Tue-Mon) before nextDueDate
|
|
/// - monthly task due 15th: shows on all ~30 days leading up to the 15th
|
|
/// - daily task: shows every day (interval window = 1 day, always matches)
|
|
bool _isInCurrentIntervalWindow(Task task, DateTime selectedDate) {
|
|
final dueDate = DateTime(task.nextDueDate.year, task.nextDueDate.month, task.nextDueDate.day);
|
|
final selected = DateTime(selectedDate.year, selectedDate.month, selectedDate.day);
|
|
|
|
// Task is not yet due or due today — it might be in the window
|
|
// If selected date IS the due date, it is a "due today" task (not pre-populated)
|
|
if (selected == dueDate) return false;
|
|
// If selected date is after the due date, task is overdue (handled separately)
|
|
if (selected.isAfter(dueDate)) return false;
|
|
|
|
// Calculate the start of the current interval window:
|
|
// The previous due date = current nextDueDate minus one interval
|
|
final previousDue = _calculatePreviousDueDate(task);
|
|
final prevDay = DateTime(previousDue.year, previousDue.month, previousDue.day);
|
|
|
|
// Selected date must be AFTER the previous due date (exclusive)
|
|
// and BEFORE the next due date (exclusive — due date itself is "dayTasks" not pre-pop)
|
|
return selected.isAfter(prevDay) && selected.isBefore(dueDate);
|
|
}
|
|
```
|
|
|
|
Add `_calculatePreviousDueDate` helper:
|
|
```dart
|
|
/// Reverse-calculate the previous due date by subtracting one interval.
|
|
/// This gives the start of the current interval window.
|
|
DateTime _calculatePreviousDueDate(Task task) {
|
|
switch (task.intervalType) {
|
|
case IntervalType.daily:
|
|
return task.nextDueDate.subtract(const Duration(days: 1));
|
|
case IntervalType.everyNDays:
|
|
return task.nextDueDate.subtract(Duration(days: task.intervalDays));
|
|
case IntervalType.weekly:
|
|
return task.nextDueDate.subtract(const Duration(days: 7));
|
|
case IntervalType.biweekly:
|
|
return task.nextDueDate.subtract(const Duration(days: 14));
|
|
case IntervalType.monthly:
|
|
return _subtractMonths(task.nextDueDate, 1, task.anchorDay);
|
|
case IntervalType.everyNMonths:
|
|
return _subtractMonths(task.nextDueDate, task.intervalDays, task.anchorDay);
|
|
case IntervalType.quarterly:
|
|
return _subtractMonths(task.nextDueDate, 3, task.anchorDay);
|
|
case IntervalType.yearly:
|
|
return _subtractMonths(task.nextDueDate, 12, task.anchorDay);
|
|
}
|
|
}
|
|
|
|
DateTime _subtractMonths(DateTime date, int months, int? anchorDay) {
|
|
final targetMonth = date.month - months;
|
|
final targetYear = date.year + (targetMonth - 1) ~/ 12;
|
|
final normalizedMonth = ((targetMonth - 1) % 12) + 1;
|
|
final day = anchorDay ?? date.day;
|
|
final lastDay = DateTime(targetYear, normalizedMonth + 1, 0).day;
|
|
final clampedDay = day > lastDay ? lastDay : day;
|
|
return DateTime(targetYear, normalizedMonth, clampedDay);
|
|
}
|
|
```
|
|
|
|
Rewrite `calendarDayProvider` to:
|
|
1. Watch `watchAllActiveRecurringTasks()` alongside `watchTasksForDate()`
|
|
2. Filter all tasks through `_isInCurrentIntervalWindow()` to get pre-populated candidates
|
|
3. For each candidate, check if completed in current period via `watchCompletionsInRange()` (per D-09, D-10)
|
|
4. Exclude tasks already in `dayTasks` (they appear as due-today, not pre-populated)
|
|
5. Exclude tasks already in `overdueTasks`
|
|
6. Return combined state with new `prePopulatedTasks` list
|
|
|
|
```dart
|
|
final calendarDayProvider =
|
|
StreamProvider.autoDispose<CalendarDayState>((ref) {
|
|
final db = ref.watch(appDatabaseProvider);
|
|
final selectedDate = ref.watch(selectedDateProvider);
|
|
final sortOption = ref.watch(sortPreferenceProvider);
|
|
|
|
final now = DateTime.now();
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
final isToday = selectedDate == today;
|
|
|
|
final dayTasksStream = db.calendarDao.watchTasksForDate(selectedDate);
|
|
final allTasksStream = db.calendarDao.watchAllActiveRecurringTasks();
|
|
|
|
// Combine both streams
|
|
return dayTasksStream.asyncMap((dayTasks) async {
|
|
final List<TaskWithRoom> overdueTasks;
|
|
if (isToday) {
|
|
overdueTasks =
|
|
await db.calendarDao.watchOverdueTasks(selectedDate).first;
|
|
} else {
|
|
overdueTasks = const [];
|
|
}
|
|
|
|
// Get all active tasks for pre-population filtering
|
|
final allTasks = await allTasksStream.first;
|
|
|
|
// IDs of tasks already showing as due-today or overdue
|
|
final dueTodayIds = dayTasks.map((t) => t.task.id).toSet();
|
|
final overdueIds = overdueTasks.map((t) => t.task.id).toSet();
|
|
|
|
// Filter for pre-populated tasks
|
|
final prePopulated = <TaskWithRoom>[];
|
|
for (final tw in allTasks) {
|
|
// Skip if already showing as due-today or overdue
|
|
if (dueTodayIds.contains(tw.task.id)) continue;
|
|
if (overdueIds.contains(tw.task.id)) continue;
|
|
|
|
// Check if in current interval window
|
|
if (!_isInCurrentIntervalWindow(tw.task, selectedDate)) continue;
|
|
|
|
// Check if already completed in current period (D-09, D-10)
|
|
final prevDue = _calculatePreviousDueDate(tw.task);
|
|
final completions = await db.calendarDao
|
|
.watchCompletionsInRange(
|
|
tw.task.id,
|
|
DateTime(prevDue.year, prevDue.month, prevDue.day),
|
|
DateTime(tw.task.nextDueDate.year, tw.task.nextDueDate.month, tw.task.nextDueDate.day).add(const Duration(days: 1)),
|
|
)
|
|
.first;
|
|
|
|
if (completions.isEmpty) {
|
|
prePopulated.add(tw);
|
|
}
|
|
}
|
|
|
|
final totalTaskCount = await db.calendarDao.getTaskCount();
|
|
|
|
return CalendarDayState(
|
|
selectedDate: selectedDate,
|
|
dayTasks: _sortTasks(dayTasks, sortOption),
|
|
overdueTasks: overdueTasks,
|
|
prePopulatedTasks: _sortTasks(prePopulated, sortOption),
|
|
totalTaskCount: totalTaskCount,
|
|
);
|
|
});
|
|
});
|
|
```
|
|
|
|
Apply the SAME changes to `roomCalendarDayProvider`, but use `watchAllActiveRecurringTasksInRoom(roomId)` instead of `watchAllActiveRecurringTasks()`.
|
|
|
|
### Step 4: Add DAO tests
|
|
|
|
Add tests to `test/features/home/data/calendar_dao_test.dart`:
|
|
1. `watchAllActiveRecurringTasks returns all active tasks` — insert 3 tasks (2 active, 1 inactive), verify 2 returned
|
|
2. `watchCompletionsInRange returns completions within date range` — insert completions at various dates, verify only in-range ones returned
|
|
3. `watchAllActiveRecurringTasksInRoom filters by room` — insert tasks in 2 rooms, verify room filter works
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jean-luc-makiola/Development/projects/HouseHoldKeaper && flutter test test/features/home/data/calendar_dao_test.dart --reporter compact</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- calendar_dao.dart contains method `watchAllActiveRecurringTasks()`
|
|
- calendar_dao.dart contains method `watchCompletionsInRange(`
|
|
- calendar_dao.dart contains method `watchAllActiveRecurringTasksInRoom(`
|
|
- calendar_models.dart CalendarDayState contains field `List<TaskWithRoom> prePopulatedTasks`
|
|
- calendar_models.dart isEmpty getter includes `prePopulatedTasks.isEmpty`
|
|
- calendar_providers.dart contains function `_isInCurrentIntervalWindow(`
|
|
- calendar_providers.dart contains function `_calculatePreviousDueDate(`
|
|
- calendar_providers.dart calendarDayProvider passes `prePopulatedTasks:` to CalendarDayState
|
|
- calendar_providers.dart roomCalendarDayProvider passes `prePopulatedTasks:` to CalendarDayState
|
|
- calendar_dao_test.dart contains test matching `watchAllActiveRecurringTasks`
|
|
- calendar_dao_test.dart contains test matching `watchCompletionsInRange`
|
|
- All tests in calendar_dao_test.dart pass (exit code 0)
|
|
</acceptance_criteria>
|
|
<done>DAO provides all-active-tasks query and completion-range query. Provider computes virtual pre-populated task instances using interval window logic. Completed-in-current-period tasks are excluded. CalendarDayState carries prePopulatedTasks. All tests pass.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Render pre-populated tasks with muted visual distinction in calendar UI</name>
|
|
<files>
|
|
lib/features/home/presentation/calendar_day_list.dart
|
|
lib/features/home/presentation/calendar_task_row.dart
|
|
</files>
|
|
<read_first>
|
|
lib/features/home/presentation/calendar_day_list.dart
|
|
lib/features/home/presentation/calendar_task_row.dart
|
|
lib/features/home/domain/calendar_models.dart
|
|
</read_first>
|
|
<action>
|
|
**Per D-11, D-12, D-13: Visual distinction for pre-populated tasks.**
|
|
|
|
### Step 1: Add `isPrePopulated` prop to CalendarTaskRow
|
|
|
|
In `calendar_task_row.dart`, add an `isPrePopulated` parameter:
|
|
```dart
|
|
class CalendarTaskRow extends StatelessWidget {
|
|
const CalendarTaskRow({
|
|
super.key,
|
|
required this.taskWithRoom,
|
|
required this.onCompleted,
|
|
this.isOverdue = false,
|
|
this.showRoomTag = true,
|
|
this.canComplete = true,
|
|
this.isPrePopulated = false, // NEW
|
|
});
|
|
|
|
final bool isPrePopulated; // NEW
|
|
```
|
|
|
|
In the `build` method, apply muted styling when `isPrePopulated` is true:
|
|
- Wrap the entire `ListTile` in an `Opacity` widget with `opacity: isPrePopulated ? 0.55 : 1.0`
|
|
- This gives pre-populated tasks a subtle "upcoming, not yet due" appearance per D-11
|
|
- Overdue tasks (`isOverdue: true`) are never pre-populated, so no conflict with D-13
|
|
- Due-today tasks are not pre-populated either, so full styling is preserved per D-12
|
|
|
|
```dart
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final task = taskWithRoom.task;
|
|
|
|
final tile = ListTile(
|
|
// ... existing ListTile code unchanged ...
|
|
);
|
|
|
|
return isPrePopulated ? Opacity(opacity: 0.55, child: tile) : tile;
|
|
}
|
|
```
|
|
|
|
### Step 2: Render pre-populated tasks in CalendarDayList
|
|
|
|
In `calendar_day_list.dart`, update `_buildTaskList` to include pre-populated tasks:
|
|
|
|
After the day tasks loop and before the `return ListView(children: items);`, add:
|
|
```dart
|
|
// Pre-populated tasks section (upcoming tasks within interval window).
|
|
if (state.prePopulatedTasks.isNotEmpty) {
|
|
items.add(_buildSectionHeader('Demnächst', theme,
|
|
color: theme.colorScheme.onSurface.withValues(alpha: 0.5)));
|
|
for (final tw in state.prePopulatedTasks) {
|
|
items.add(_buildAnimatedTaskRow(
|
|
tw,
|
|
isOverdue: false,
|
|
showRoomTag: showRoomTag,
|
|
canComplete: true,
|
|
isPrePopulated: true,
|
|
));
|
|
}
|
|
}
|
|
```
|
|
|
|
Update `_buildAnimatedTaskRow` to accept and pass `isPrePopulated`:
|
|
```dart
|
|
Widget _buildAnimatedTaskRow(
|
|
TaskWithRoom tw, {
|
|
required bool isOverdue,
|
|
required bool showRoomTag,
|
|
required bool canComplete,
|
|
bool isPrePopulated = false,
|
|
}) {
|
|
// ... existing completing animation check ...
|
|
|
|
return CalendarTaskRow(
|
|
key: ValueKey('task-${tw.task.id}'),
|
|
taskWithRoom: tw,
|
|
isOverdue: isOverdue,
|
|
showRoomTag: showRoomTag,
|
|
canComplete: canComplete,
|
|
isPrePopulated: isPrePopulated,
|
|
onCompleted: () => _onTaskCompleted(tw.task.id),
|
|
);
|
|
}
|
|
```
|
|
|
|
Also update `_CompletingTaskRow` to pass `isPrePopulated: false` (completing tasks are always full-styled).
|
|
|
|
### Step 3: Update celebration state logic
|
|
|
|
In `_buildContent`, update the celebration check to also verify prePopulatedTasks is empty:
|
|
```dart
|
|
if (isToday && state.dayTasks.isEmpty && state.overdueTasks.isEmpty && state.prePopulatedTasks.isEmpty && state.totalTaskCount > 0) {
|
|
return _buildCelebration(l10n, theme);
|
|
}
|
|
```
|
|
|
|
### Step 4: Section header "Demnächst" (German for "Coming up")
|
|
|
|
The section header text `'Demnächst'` is hardcoded for now. If a localization key is preferred, add `calendarPrePopulatedSection` to the l10n strings. For consistency with the existing pattern of using `l10n.dailyPlanSectionOverdue` for the overdue header, add a new key. However, the CONTEXT.md does not mandate localization changes, so inline German string is acceptable.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jean-luc-makiola/Development/projects/HouseHoldKeaper && dart analyze lib/features/home/presentation/calendar_day_list.dart lib/features/home/presentation/calendar_task_row.dart</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- calendar_task_row.dart contains `final bool isPrePopulated;`
|
|
- calendar_task_row.dart contains `this.isPrePopulated = false`
|
|
- calendar_task_row.dart contains `Opacity(opacity: 0.55` or `opacity: isPrePopulated ? 0.55 : 1.0`
|
|
- calendar_day_list.dart contains `state.prePopulatedTasks.isNotEmpty`
|
|
- calendar_day_list.dart contains string `'Demnächst'`
|
|
- calendar_day_list.dart contains `isPrePopulated: true` in the pre-populated tasks loop
|
|
- calendar_day_list.dart _buildAnimatedTaskRow signature contains `bool isPrePopulated`
|
|
- dart analyze reports zero issues for both files
|
|
</acceptance_criteria>
|
|
<done>Pre-populated tasks render below day tasks with "Demnächst" section header. They have 0.55 opacity for visual distinction. Due-today tasks have full styling. Overdue tasks keep coral accent. All checkboxes functional. dart analyze clean.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
1. `flutter test` — ALL tests pass (existing + new)
|
|
2. `dart analyze` — zero issues across entire project
|
|
3. `grep -n "prePopulatedTasks" lib/features/home/domain/calendar_models.dart lib/features/home/presentation/calendar_providers.dart lib/features/home/presentation/calendar_day_list.dart` — found in all three files
|
|
4. `grep -n "isPrePopulated" lib/features/home/presentation/calendar_task_row.dart lib/features/home/presentation/calendar_day_list.dart` — found in both files
|
|
5. `grep -n "watchAllActiveRecurringTasks" lib/features/home/data/calendar_dao.dart` — method exists
|
|
6. `grep -n "watchCompletionsInRange" lib/features/home/data/calendar_dao.dart` — method exists
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Weekly tasks appear on all 7 days leading up to their due date
|
|
- Monthly tasks appear on all days within their current month interval
|
|
- Daily tasks appear every day (since their interval window is 1 day, they are always "due today" and show as dayTasks, not pre-populated)
|
|
- Completing a pre-populated task triggers normal completeTask flow and the task disappears from remaining days in the period
|
|
- Pre-populated tasks have a muted 0.55 opacity appearance
|
|
- Due-today tasks show with full styling
|
|
- Overdue tasks keep coral color
|
|
- All tests pass, dart analyze clean
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-02-SUMMARY.md`
|
|
</output>
|