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:
parent
55fc245859
commit
753527aed3
1 changed files with 518 additions and 64 deletions
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue