feat(todo): side panes widen by their line, and a tap adds a task (ADR 0033, D33)

The line beside the task pane and Today's Plan is dragged, between 300 px
for the pane and 360 px beside it, each pane's width kept on the device.
A tap or click in the hour grid opens quick add at the quarter hour under
it, on a phone too, as where it lands says when; a long press takes
quarter hours as well. A tap that stops the day scrolling adds nothing.

Whats-New: Drag the line beside a task or the Plan to resize it; tap or click into the calendar to add a task at that time.
Whats-New-De: Ziehe die Linie neben einer Aufgabe oder dem Plan, um die Breite zu ändern; tippe oder klicke in den Kalender, um dort eine Aufgabe anzulegen.

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-05 19:17:36 +01:00
commit 4aec923476
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -0,0 +1,113 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_todo_flutter/l10n/app_localizations.dart';
import 'package:halkyon_todo_flutter/src/tabs/pane_widths.dart';
import 'package:halkyon_todo_flutter/src/tabs/side_pane.dart';
import 'package:material_ui/material_ui.dart';
/// The line beside a pane dragged (ADR 0033, D33).
void main() {
const paneKey = ValueKey('pane');
Future<void> pump(WidgetTester tester, PaneWidths widths) async {
tester.view.physicalSize = const Size(1200, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.pumpWidget(
MaterialApp(
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
body: SidePane(
kind: SidePaneKind.task,
widths: widths,
fallback: 420,
main: const SizedBox.expand(),
pane: const SizedBox.expand(key: paneKey),
),
),
),
);
}
double width(WidgetTester tester) =>
tester.getSize(find.byKey(paneKey)).width;
Future<void> drag(WidgetTester tester, double dx) async {
await tester.timedDrag(
find.byKey(SidePane.gripKey(SidePaneKind.task)),
Offset(dx, 0),
const Duration(milliseconds: 200),
);
await tester.pumpAndSettle();
}
testWidgets('starts at its width, and the line dragged widens it', (
tester,
) async {
await pump(tester, PaneWidths());
expect(width(tester), 420);
await drag(tester, -100);
expect(width(tester), closeTo(520, 40));
final wider = width(tester);
await drag(tester, 60);
expect(width(tester), lessThan(wider));
});
testWidgets('stops short of too narrow a pane, and of too narrow a '
'calendar', (tester) async {
await pump(tester, PaneWidths());
await drag(tester, 800);
expect(width(tester), SidePane.least);
await drag(tester, -1100);
expect(width(tester), 1200 - SidePane.mainLeast);
});
testWidgets('a width from a wider window fits this one, and is kept', (
tester,
) async {
final widths = PaneWidths()..drag(SidePaneKind.task, 1000);
await pump(tester, widths);
expect(width(tester), 1200 - SidePane.mainLeast);
expect(widths.of(SidePaneKind.task), 1000);
});
test(
'the width chosen is there at the next start, each pane its own',
() async {
final folder = await Directory.systemTemp.createTemp('panes');
addTearDown(() => folder.delete(recursive: true));
final file = File('${folder.path}/panes.json');
final widths = await PaneWidths.open(file);
expect(widths.of(SidePaneKind.plan), isNull);
widths
..drag(SidePaneKind.plan, 380)
..drag(SidePaneKind.task, 500);
await widths.save();
final again = await PaneWidths.open(file);
expect(again.of(SidePaneKind.plan), 380);
expect(again.of(SidePaneKind.task), 500);
},
);
test("a file that can't be read means no choice yet", () async {
final folder = await Directory.systemTemp.createTemp('panes');
addTearDown(() => folder.delete(recursive: true));
final file = File('${folder.path}/panes.json')
..writeAsStringSync(jsonEncode([1]));
expect((await PaneWidths.open(file)).of(SidePaneKind.task), isNull);
file.writeAsStringSync('{');
expect((await PaneWidths.open(file)).of(SidePaneKind.task), isNull);
});
}