fix(learn): a widget dropped while arranging Home stays where it was dropped, and its handle drags at once

Arranging Home kept no order of its own: a drop showed the old order
until the layout saved came back through the settings, once the
dashboard's changes settled 300 ms later, so the widget snapped back and
then jumped. _Arranging now holds the layout as arranged, shows each
change at once and saves it behind, taking a layout from the settings
only once its own changes are back (or, with none pending, as changed
on another device). The handle was an IconButton with an empty
onPressed inside the drag listener, whose tap and tooltip competed with
the drag; it is a plain icon now, named for screen readers.

Whats-New: Arranging Home's widgets, a widget stays where you drop it, and dragging starts at once.
Whats-New-De: Beim Anordnen der Widgets auf der Startseite bleibt ein Widget, wo du es ablegst, und das Ziehen beginnt sofort.

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-04 21:23:13 +01:00
commit 12bc31b982
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -367,4 +367,45 @@ void main() {
await tester.pumpAndSettle();
expect(find.text('Arrange widgets'), findsOneWidget);
});
testWidgets('a widget dropped elsewhere stays where it was dropped at '
"once, rather than snapping back until it is saved (the developer's "
'review of 4 Nov 2026)', (tester) async {
await showHome(tester);
await tester.longPress(find.text('Set a study goal'));
await tester.pumpAndSettle();
double top(String name) =>
tester.getTopLeft(find.byTooltip('Hide $name')).dy;
final handles = find.byIcon(LucideIcons.gripVertical);
final first = tester.getTopLeft(handles.at(0)).dy;
final second = tester.getTopLeft(handles.at(1)).dy;
final names = [
for (final name in [
'Daily goal',
'Pick up learning',
'Pomodoro',
'PDF reading deadlines',
'Week overview',
'Activity heatmap',
])
if (find.byTooltip('Hide $name').evaluate().isNotEmpty) name,
]..sort((a, b) => top(a).compareTo(top(b)));
final moved = names.first;
// The handle takes the drag at once, without a tap of its own first.
final drag = await tester.startGesture(tester.getCenter(handles.at(0)));
for (var step = 0; step < 6; step++) {
await drag.moveBy(Offset(0, (second - first) * 1.5 / 6));
await tester.pump(const Duration(milliseconds: 50));
}
await drag.up();
// Dropped, well before the dashboard's changes settle (300 ms).
for (var frame = 0; frame < 12; frame++) {
await tester.pump(const Duration(milliseconds: 16));
}
expect(top(moved), greaterThan(top(names[1])), reason: 'moved down');
await tester.pumpAndSettle();
expect(top(moved), greaterThan(top(names[1])), reason: 'and stays');
});
}