feat(todo): a task's title wraps in its sheet and pane, its words read as quick add's

The title runs over as many lines as it needs instead of trailing off,
and what it says is highlighted as in quick add and set as it is saved
(the developer's review, 10 Nov 2026): a day and its recurrence, the
priority, the deadline, labels, reminders and places added, the project
the task moves to; a word tapped, or a Backspace right after it, keeps
it as text. Enter still saves. Quick add's controller and reading of
its title are shared for it (title_tokens.dart).

Whats-New: In a task's details, a long title now wraps, and words like "every Monday" in it are recognised as in quick add.
Whats-New-De: In den Details einer Aufgabe bricht ein langer Titel jetzt um, und Wörter wie „jeden Montag“ darin werden wie beim schnellen Hinzufügen erkannt.

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-10 01:03:30 +01:00
commit ca9ad6c69c
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -49,6 +49,7 @@ 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_texts.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_tile.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/title_tokens.dart';
import 'package:halkyon_todo_flutter/src/tabs/browse_tab.dart';
import 'package:halkyon_todo_flutter/src/tabs/hour_grid.dart';
import 'package:halkyon_todo_flutter/src/tabs/plan_panel.dart';
@ -4872,6 +4873,108 @@ void main() {
expect(await tester.runAsync(() => tasks().all()), isEmpty);
});
group("a task's title in its sheet and pane (the developer's review, 10 "
'Nov 2026)', () {
testWidgets('a long one wraps over several lines, whole', (tester) async {
await start(tester, usedBefore: true);
const long =
'Ask the landlord about the heating in the back room before the '
'cold weather comes';
await seed(tester, TaskContent(title: long, projectId: inbox()));
await goTab(tester, 'Inbox');
await tester.tap(find.text(long));
await tester.pumpAndSettle();
final field = find.byKey(taskTitleFieldKey);
expect(tester.widget<TextField>(field).maxLines, isNull);
final line = tester.getSize(find.byKey(taskTitleFieldKey)).height;
await tester.enterText(field, 'Short');
await tester.pump();
expect(
tester.getSize(find.byKey(taskTitleFieldKey)).height,
lessThan(line - 20),
);
});
testWidgets('a day and its recurrence typed are highlighted as quick '
"add's, and set as it is saved, the title left without them", (
tester,
) async {
await start(tester, usedBefore: true);
await seed(tester, TaskContent(title: 'Water', projectId: inbox()));
await goTab(tester, 'Inbox');
await tester.tap(find.text('Water'));
await tester.pumpAndSettle();
final field = find.byKey(taskTitleFieldKey);
await tester.enterText(field, 'Water plants every monday');
await tester.pump();
final controller =
tester.widget<TextField>(field).controller! as TitleController;
expect(
controller.tokens.map(
(token) => controller.text.substring(token.start, token.end),
),
['every monday'],
);
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
final content = (await tester.runAsync(
() => tasks().all(),
))!.single.content!;
expect(content.title, 'Water plants');
expect(content.date?.repeat, isNotNull);
expect(content.date!.day.weekday, DateTime.monday);
expect(controller.text, 'Water plants');
});
testWidgets('a word tapped is kept as text, as in quick add', (
tester,
) async {
await start(tester, usedBefore: true);
await seed(tester, TaskContent(title: 'Read', projectId: inbox()));
await goTab(tester, 'Inbox');
await tester.tap(find.text('Read'));
await tester.pumpAndSettle();
final field = find.byKey(taskTitleFieldKey);
await tester.enterText(field, 'Read Tomorrow');
await tester.pump();
final controller =
tester.widget<TextField>(field).controller! as TitleController;
controller.keep(controller.tokens.single);
await tester.pump();
expect(controller.tokens, isEmpty);
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
final content = (await tester.runAsync(
() => tasks().all(),
))!.single.content!;
expect(content.title, 'Read Tomorrow');
expect(content.date, isNull);
});
testWidgets('a line break pasted becomes a space', (tester) async {
await start(tester, usedBefore: true);
await seed(tester, TaskContent(title: 'Call', projectId: inbox()));
await goTab(tester, 'Inbox');
await tester.tap(find.text('Call'));
await tester.pumpAndSettle();
await tester.enterText(find.byKey(taskTitleFieldKey), 'Call Anna\nnow');
await tester.pump();
expect(
tester
.widget<TextField>(find.byKey(taskTitleFieldKey))
.controller!
.text,
'Call Anna now',
);
});
});
testWidgets("a task's sheet shows where it is, the project as an icon "
'once tasks are above it, each step opening what it names', (
tester,