feat(todo): a sub-task opens in its task's sheet, back returning to it
A sub-task tapped in a task's sheet opened a sheet of its own over it, so Grocery List > Dairy > Milk stacked three. It now takes the sheet's place, as the steps of the sheet's path already did (TaskTile's onOpen), and the sheet keeps the tasks it showed: the system's back returns to the last. Back is caught with a BackButtonListener, not a PopScope, which would also hold back a tap beside the sheet, a swipe and Escape: those close the sheet whole. A picker open over the sheet is closed by back first. Whats-New: A sub-task opens in its task's sheet instead of over it; back returns to the task. Whats-New-De: Eine Unteraufgabe öffnet sich im Blatt ihrer Aufgabe statt darüber; Zurück führt zur Aufgabe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
a71d80fc2d
commit
e11ed481ce
3 changed files with 94 additions and 8 deletions
|
|
@ -145,7 +145,11 @@ class const _SheetScroll({
|
||||||
controller != oldWidget.controller;
|
controller != oldWidget.controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sheet's task, which a step of its path replaces in place.
|
/// A sheet's task, which a step of its path or a sub-task replaces in place
|
||||||
|
/// (the developer's review, 4 Nov 2026: a sub-task had opened a sheet over
|
||||||
|
/// the sheet). The tasks it showed before are kept, so the system's back
|
||||||
|
/// returns to the last; every other way of closing it (a tap beside it, a
|
||||||
|
/// swipe down, Escape) closes it whole.
|
||||||
class const _TaskSheetHost({required final String id}) extends StatefulWidget {
|
class const _TaskSheetHost({required final String id}) extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
State<_TaskSheetHost> createState() => _TaskSheetHostState();
|
State<_TaskSheetHost> createState() => _TaskSheetHostState();
|
||||||
|
|
@ -154,12 +158,45 @@ class const _TaskSheetHost({required final String id}) extends StatefulWidget {
|
||||||
class _TaskSheetHostState() extends State<_TaskSheetHost> {
|
class _TaskSheetHostState() extends State<_TaskSheetHost> {
|
||||||
late var _id = widget.id;
|
late var _id = widget.id;
|
||||||
|
|
||||||
|
/// The tasks shown before [_id], the last most recently.
|
||||||
|
final _history = <String>[];
|
||||||
|
|
||||||
|
/// Shows [id]; one shown before is gone back to, the steps after it
|
||||||
|
/// forgotten.
|
||||||
|
void _show(String id) {
|
||||||
|
if (id == _id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
final before = _history.indexOf(id);
|
||||||
|
if (before >= 0) {
|
||||||
|
_history.removeRange(before, _history.length);
|
||||||
|
} else {
|
||||||
|
_history.add(_id);
|
||||||
|
}
|
||||||
|
_id = id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Back from the system: the task shown before, while there is one and
|
||||||
|
/// nothing is open over the sheet (a picker, which back closes first).
|
||||||
|
Future<bool> _back() async {
|
||||||
|
if (_history.isEmpty || !(ModalRoute.of(context)?.isCurrent ?? false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setState(() => _id = _history.removeLast());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => _TaskSheet(
|
Widget build(BuildContext context) {
|
||||||
key: ValueKey(_id),
|
final sheet = _TaskSheet(key: ValueKey(_id), id: _id, onOpen: _show);
|
||||||
id: _id,
|
// Not a PopScope: that would hold back a tap beside the sheet and
|
||||||
onOpen: (id) => setState(() => _id = id),
|
// Escape too, which close it whole. Back alone goes through the router.
|
||||||
);
|
return Router.maybeOf(context) == null
|
||||||
|
? sheet
|
||||||
|
: BackButtonListener(onBackButtonPressed: _back, child: sheet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The task open beside the list from expanded windows on (ADR 0033, D2,
|
/// The task open beside the list from expanded windows on (ADR 0033, D2,
|
||||||
|
|
@ -759,7 +796,11 @@ class _TaskSheetState() extends State<_TaskSheet> {
|
||||||
),
|
),
|
||||||
for (final sub in subTasks)
|
for (final sub in subTasks)
|
||||||
if (!(content.hideDoneSubTasks && sub.content.completed))
|
if (!(content.hideDoneSubTasks && sub.content.completed))
|
||||||
TaskTile(task: sub, selectable: false),
|
TaskTile(
|
||||||
|
task: sub,
|
||||||
|
selectable: false,
|
||||||
|
onOpen: (id) => _open(context, id),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: const Icon(LucideIcons.plus),
|
icon: const Icon(LucideIcons.plus),
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ class const TaskTile({
|
||||||
final bool selectable = true,
|
final bool selectable = true,
|
||||||
final bool later = false,
|
final bool later = false,
|
||||||
final TaskRowPosition position = TaskRowPosition.alone,
|
final TaskRowPosition position = TaskRowPosition.alone,
|
||||||
|
|
||||||
|
/// Opens the task in place of the one showing, as a sheet's sub-tasks
|
||||||
|
/// do; by default a tap opens the task's sheet.
|
||||||
|
final ValueChanged<String>? onOpen,
|
||||||
super.key,
|
super.key,
|
||||||
}) extends StatelessWidget {
|
}) extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
|
|
@ -252,7 +256,10 @@ class const TaskTile({
|
||||||
),
|
),
|
||||||
onTap: selection != null && selection.active
|
onTap: selection != null && selection.active
|
||||||
? () => selection.toggle(task.id)
|
? () => selection.toggle(task.id)
|
||||||
: () => openTaskSheet(context, task.id),
|
: switch (onOpen) {
|
||||||
|
final open? => () => open(task.id),
|
||||||
|
null => () => openTaskSheet(context, task.id),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (selection == null) {
|
if (selection == null) {
|
||||||
return _boxed(context, tile);
|
return _boxed(context, tile);
|
||||||
|
|
|
||||||
|
|
@ -2184,6 +2184,44 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets("a sub-task opens in its task's sheet, not in another over "
|
||||||
|
'it; back returns to the task, a tap beside closes it (the '
|
||||||
|
"developer's review, 4 Nov 2026)", (tester) async {
|
||||||
|
await start(tester, usedBefore: true);
|
||||||
|
final list = await tester.runAsync(
|
||||||
|
() =>
|
||||||
|
tasks().add(TaskContent(title: 'Grocery List', projectId: inbox())),
|
||||||
|
);
|
||||||
|
final dairy = await tester.runAsync(
|
||||||
|
() => tasks().add(
|
||||||
|
TaskContent(title: 'Dairy', projectId: inbox(), parentId: list),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await seed(
|
||||||
|
tester,
|
||||||
|
TaskContent(title: 'Milk', projectId: inbox(), parentId: dairy),
|
||||||
|
);
|
||||||
|
await goTab(tester, 'Inbox');
|
||||||
|
|
||||||
|
await tester.tap(find.text('Grocery List').first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('Dairy').last);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('Milk').last);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(BottomSheet), findsOneWidget);
|
||||||
|
expect(find.widgetWithText(TextField, 'Milk'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.binding.handlePopRoute();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(BottomSheet), findsOneWidget);
|
||||||
|
expect(find.widgetWithText(TextField, 'Dairy'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tapAt(const Offset(20, 20));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(BottomSheet), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets("a task's menu says when it was added, duplicates it with its "
|
testWidgets("a task's menu says when it was added, duplicates it with its "
|
||||||
'sub-tasks and hides its done ones, on every device', (tester) async {
|
'sub-tasks and hides its done ones, on every device', (tester) async {
|
||||||
await start(tester, usedBefore: true);
|
await start(tester, usedBefore: true);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue