fix(studyset): the material's tools fold into the menu rather than cutting the tabs off

Where the row beside the material's tabs ran short, Browse, the filter
and the menu kept their room and the tabs were cut off ("Fil…"). The
tools now fold one by one, from the widest to the narrowest that leaves
the tabs their whole width: Browse loses its words first, then the
filter goes into the ⋮ menu, then Browse does too, as the studyset's own
actions fold into icons. A row of its own (CollapsingRow) measures the
tabs and each way of showing the tools and shows the first that fits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-12 20:54:55 +02:00
commit 468810c6f4
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1534,6 +1534,50 @@ void main() {
expect(find.text('Show all'), findsNothing);
});
testWidgets("the material's tools fold into the menu one by one rather "
'than cutting the tabs off: Browse loses its words first', (
tester,
) async {
stored('japanese', 'Japanese');
await card('japanese', '猫');
final seen = <String>{};
for (final width in <double>[360, 420, 600, 760, 860, 1000, 1200, 1600]) {
await showApp(
tester,
location: RoutePaths.studyset('japanese'),
width: width,
);
await tester.pumpAndSettle();
await openPart(tester, 'Material');
final tabs = tester.getRect(find.byType(TabBar));
final files = tester.getRect(find.widgetWithText(Tab, 'Files'));
// What shows: the levels folded away are there, but unseen.
bool shows(Finder finder) => finder.hitTestable().evaluate().isNotEmpty;
final level = [
if (shows(find.text('Browse'))) 'words',
if (shows(find.byTooltip('Browse flashcards'))) 'icon',
if (shows(find.byTooltip('Filter'))) 'filter',
].join('+');
seen.add(level);
// The tabs are cut only once everything is folded away (the test's
// font is wider than the app's, so narrower windows get there).
if (files.right > tabs.right) {
expect(level, isEmpty, reason: '$width');
}
if (level.isEmpty) {
// Browse in the menu.
await tester.tap(find.byTooltip('More').hitTestable());
await tester.pumpAndSettle();
expect(find.text('Browse').hitTestable(), findsOneWidget);
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
}
}
// The widest windows show the words, the narrowest folds them away.
expect(seen, contains('words+filter'));
expect(seen, contains(''));
});
testWidgets('tapping the open part again goes back to its top', (
tester,
) async {
@ -1714,11 +1758,13 @@ void main() {
)
.first,
);
// Browse, then the filter and the menu, the last at the edge.
expect(
tester.getRect(find.byType(CardListToolbar)).right,
greaterThan(material.right - 16),
);
// Browse, then the filter and the menu, the last at the edge: the
// tools as they show, the folded ways of showing them unseen.
final shown = [
for (final toolbar in find.byType(CardListToolbar).evaluate())
tester.getRect(find.byElementPredicate((each) => each == toolbar)),
].where((rect) => rect.width > 0).single;
expect(shown.right, greaterThan(material.right - 16));
});
testWidgets('Progress is always there, nothing yet before anything is '