feat(todo): reminders at a time of their own planned and set in a task's sheet, durations in its date sheet

planReminders plans each reminder at a time of its own of an open task,
whatever its date, under an ID of its own; the notification says
"Reminder" for a task without a time. The task's sheet always has its
reminders' row: those before its time where it has one, those at a time
of their own each with its remove button and "Remind at a time" (a day,
then a time), the permission asked as a first one is chosen. The date
sheet sets a duration once there is a time (none, 15 minutes to 4
hours, as Todoist's), a time cleared taking it with it. Recorded as
ADR 0033, D18 (the developer's, 3 Nov 2026).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-03 13:56:19 +01:00
commit f5b657dfad
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1470,7 +1470,13 @@ void main() {
await goTab(tester, 'Inbox');
await tester.tap(find.text('Undated'));
await tester.pumpAndSettle();
expect(find.text('No reminders'), findsNothing, reason: 'no time');
await tester.tap(find.text('No reminders'));
await tester.pumpAndSettle();
expect(find.text('At its time'), findsNothing, reason: 'no time');
expect(find.text('Remind at a time'), findsOneWidget);
await tester.tap(find.widgetWithText(FilledButton, 'Save'));
await tester.pumpAndSettle();
expect(reminders.asked, 0, reason: 'nothing to remind of');
await closeTaskSheet(tester);
await tester.pumpAndSettle();
@ -1499,6 +1505,75 @@ void main() {
);
});
testWidgets("a task's sheet adds a reminder at a time of its own, "
'without a time of the task (D18)', (tester) async {
final today = Day.of(DateTime.now());
await start(tester, usedBefore: true);
await seed(
tester,
TaskContent(title: 'Stretch', projectId: inbox()),
);
await goTab(tester, 'Inbox');
await tester.tap(find.text('Stretch'));
await tester.pumpAndSettle();
await tester.tap(find.text('No reminders'));
await tester.pumpAndSettle();
await tester.tap(find.text('Remind at a time'));
await tester.pumpAndSettle();
await tester.tap(find.text('OK'));
await tester.pumpAndSettle();
await tester.tap(find.text('OK'));
await tester.pumpAndSettle();
expect(find.text('Today 09:00'), findsOneWidget);
await tester.tap(find.widgetWithText(FilledButton, 'Save'));
await tester.pumpAndSettle();
expect(reminders.asked, 1);
expect(find.text('Today 09:00'), findsOneWidget);
final stored = await tester.runAsync(() => tasks().all());
expect(
stored!.single.content?.reminderTimes,
[ReminderTime(today, 9 * 60)],
);
});
testWidgets("a timed task's date sheet sets how long it lasts (D18)", (
tester,
) async {
final tomorrow = Day.of(DateTime.now()).plus(1);
await start(tester, usedBefore: true);
await seed(
tester,
TaskContent(
title: 'Write',
projectId: inbox(),
date: TaskDate(tomorrow, time: 14 * 60),
),
);
await goTab(tester, 'Inbox');
await tester.tap(find.text('Write'));
await tester.pumpAndSettle();
await tester.tap(find.text('Tomorrow 14:00').last);
await tester.pumpAndSettle();
await tester.tap(find.text('Duration'));
await tester.pumpAndSettle();
await tester.tap(find.text('1 h 30 min'));
await tester.pumpAndSettle();
await tester.tap(find.text('Tomorrow').first);
await tester.pumpAndSettle();
expect(find.text('Tomorrow 14:00–15:30'), findsWidgets);
final stored = await tester.runAsync(() => tasks().all());
expect(
stored!.single.content?.date,
TaskDate(
tomorrow,
time: 14 * 60,
duration: const Duration(minutes: 90),
),
);
});
testWidgets("Today names a task's project and section on the line of "
'its date, at its end', (tester) async {
final today = Day.of(DateTime.now());