feat(todo): rows in rounded boxes, Today as Todoist's, lists pulled to sync, a project's own colour

From the developer's review of 3 Nov 2026 (ADR 0033, D19 to D21):
- Today's tab shows today's day in its calendar, Browse the menu's lines,
  Display's button Lucide's square-menu.
- A task's row stands in a rounded box, the rows of a list a group rounded
  at its ends; the box is the row's own surface, so a chosen row's colour no
  longer shows under Upcoming's pinned week strip as the list scrolls. Its
  project and section end the line of its date at the row's right edge.
- Today has at most two groups: what is overdue, folding under its heading,
  and today's, headed "Tuesday, 3 Nov" ("Dienstag, 3. Nov."), without a
  "· Today" after it; Upcoming's days take the same form.
- Upcoming's way back to today shows whenever another day is picked.
- Today, Upcoming, the Inbox and every project pulled down sync now, an
  empty list too, as Learn's.
- A project's screen washes in its own colour, as a studyset's in Learn.
- "Add section" in place of "New section".

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 21:39:28 +01:00
commit 753527aed3
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -25,9 +25,12 @@ import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/quick_add_sheet.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_drag.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_sheet.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_tile.dart';
import 'package:halkyon_todo_flutter/src/tabs/three_day_grid.dart';
import 'package:halkyon_todo_flutter/src/tabs/today_icon.dart';
import 'package:halkyon_todo_flutter/src/tabs/week_strip.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:intl/intl.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:material_ui/material_ui.dart';
@ -50,6 +53,9 @@ void main() {
late MemorySealedRowGateway settingsRows;
late MemoryReminderGateway reminders;
/// How many syncs ran.
late int syncs;
tearDown(() async {
await sync.dispose();
keys.dispose();
@ -63,8 +69,9 @@ void main() {
FakeContentCipher(),
() async => FakeAccountCrypto().keysNumbered(1),
);
syncs = 0;
sync = SyncService(
({required onMerge}) async {},
({required onMerge}) async => syncs++,
debounce: const Duration(days: 1),
);
projectRows = MemorySealedRowGateway();
@ -267,6 +274,51 @@ void main() {
expect(find.widgetWithText(AppBar, 'Today'), findsOneWidget);
});
testWidgets("Today's tab shows today's day in its calendar, Browse the "
"menu's lines (the developer's review, 3 Nov 2026)", (tester) async {
await start(tester, usedBefore: true);
final bar = find.byType(NavigationBar);
expect(
find.descendant(
of: find.byType(TodayIcon),
matching: find.text('${DateTime.now().day}'),
),
findsOneWidget,
);
expect(
find.descendant(of: bar, matching: find.byType(TodayIcon)),
findsOneWidget,
);
expect(
find.descendant(of: bar, matching: find.byIcon(LucideIcons.menu)),
findsOneWidget,
);
await goTab(tester, 'Upcoming');
expect(
find.byIcon(LucideIcons.squareMenu),
findsOneWidget,
reason: "Display's button",
);
});
testWidgets('every list of tasks pulled down syncs now, an empty one too '
"(the developer's review, 3 Nov 2026)", (tester) async {
await start(tester, usedBefore: true);
for (final tab in ['Today', 'Inbox', 'Upcoming']) {
await goTab(tester, tab);
final before = syncs;
await tester.fling(
find.byType(Scrollable).last,
const Offset(0, 300),
1000,
);
await tester.pumpAndSettle();
expect(syncs, greaterThan(before), reason: tab);
}
});
testWidgets('each tab says what it holds while it has nothing', (
tester,
) async {
@ -1609,6 +1661,53 @@ void main() {
tester.getTopRight(where).dx,
greaterThan(tester.getTopRight(date).dx),
);
// At the row's end, as Todoist's, not halfway along (the developer's
// review, 3 Nov 2026): only the place's icon and the padding after it.
final row = find.widgetWithText(ListTile, 'Feed the dogs');
expect(
tester.getTopRight(row).dx - tester.getTopRight(where).dx,
lessThan(48),
);
});
testWidgets("Today's day is headed as Todoist heads it, without a "
'Today after it; what is overdue folds under its heading', (
tester,
) async {
final today = Day.of(DateTime.now());
await start(tester, usedBefore: true);
await seed(
tester,
TaskContent(
title: 'Pay the bill',
projectId: inbox(),
date: TaskDate(today.plus(-3)),
),
);
await seed(
tester,
TaskContent(
title: 'Water the plants',
projectId: inbox(),
date: TaskDate(today),
),
);
expect(
find.text(DateFormat('EEEE, d MMM', 'en').format(today.toDate())),
findsOneWidget,
);
expect(find.textContaining('· Today'), findsNothing);
await tester.tap(find.byTooltip('Hide what is overdue'));
await tester.pumpAndSettle();
expect(find.text('Pay the bill'), findsNothing);
expect(find.text('Water the plants'), findsOneWidget);
expect(find.text('Overdue'), findsOneWidget);
await tester.tap(find.byTooltip('Show what is overdue'));
await tester.pumpAndSettle();
expect(find.text('Pay the bill'), findsOneWidget);
});
testWidgets('Today puts what is overdue first; Reschedule moves it to '
@ -1681,6 +1780,38 @@ void main() {
expect(await tester.runAsync(() => tasks().all()), isEmpty);
});
testWidgets("a project's screen washes in its own colour, as a "
"studyset's in Learn; one without keeps Todo's strawberry", (
tester,
) async {
await start(tester, usedBefore: true);
final projects = ProjectRepository(projectRows, keys);
await tester.runAsync(() async {
await projects.save(
const ProjectContent(name: 'Garden', color: PaletteColor.pacific),
);
await projects.save(const ProjectContent(name: 'Plain'));
});
projectRows.bringRemoteChanges();
await goTab(tester, 'Browse');
await tester.tap(find.text('Garden'));
await tester.pumpAndSettle();
expect(
ScreenColor.of(tester.element(find.byType(TopGradient).last)),
PaletteColor.pacific.background,
);
await tester.pageBack();
await tester.pumpAndSettle();
await tester.tap(find.text('Plain'));
await tester.pumpAndSettle();
expect(
ScreenColor.of(tester.element(find.byType(TopGradient).last)),
PaletteColor.strawberry.background,
);
});
testWidgets("a task's sheet sets its priority, adds a sub-task the row "
'counts, and deletes it with its sub-tasks after asking', (
tester,
@ -1917,6 +2048,19 @@ void main() {
expect(find.byType(NavigationBar), findsNothing);
expect(find.byTooltip('Add task'), findsNothing);
expect(find.text('Water plants'), findsOneWidget, reason: 'kept');
// Its colour is painted on the row's own box, inside the list, not
// on the screen below it, where it showed under the pinned week
// strip as the list scrolled (the developer's review, 3 Nov 2026).
final surface = find
.ancestor(
of: find.widgetWithText(ListTile, 'Water plants'),
matching: find.byType(Material),
)
.first;
expect(
find.ancestor(of: surface, matching: find.byType(TaskTile)),
findsOneWidget,
);
await tester.tap(row('Post letter'));
await tester.pumpAndSettle();
@ -2547,13 +2691,28 @@ void main() {
});
group("Upcoming's strip (ADR 0033, D8, D14)", () {
testWidgets('once another week shows, a button beside the month goes back '
'to today', (tester) async {
testWidgets('once another day than today is picked, a button beside the '
'month goes back to today', (tester) async {
final today = Day.of(DateTime.now());
await start(tester, usedBefore: true);
await goTab(tester, 'Upcoming');
expect(find.byTooltip('Back to today'), findsNothing);
// Another day of this week picked: the way back shows already.
final strip = tester.element(find.byType(WeekStrip));
final week = WeekStrip.weekOf(
today,
MaterialLocalizations.of(strip).firstDayOfWeekIndex,
);
if (today < week.plus(6)) {
await tester.tap(find.byKey(WeekStrip.dayKey(today.plus(1))));
await tester.pumpAndSettle();
expect(find.byTooltip('Back to today'), findsOneWidget);
await tester.tap(find.byTooltip('Back to today'));
await tester.pumpAndSettle();
expect(find.byTooltip('Back to today'), findsNothing);
}
await tester.tap(find.byTooltip('Following week'));
await tester.pumpAndSettle();
expect(find.byKey(WeekStrip.dayKey(today)), findsNothing);