fix(cards): the browser's actions end at the row's end; phones choose in the grid
From the expanded window on, what can be done to the chosen cards (Move, Add tags, Delete, …, Done) stood short of the row's end: Select all and Deselect all were flexible beside a spacer, so Flutter shared the room between the three and the spacer got a third. Choosing now takes the room the actions leave, in a row of its own. On a phone the detail view (a card above a strip) had no way to choose a card: a tap opened the composer even while choosing. Choosing cards now shows the grid, where each card shows whether it is chosen, whichever view was picked, and Done returns to it; the switch between them is off meanwhile. 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:
parent
f9c1f2ae97
commit
e459c972a4
2 changed files with 84 additions and 37 deletions
|
|
@ -331,8 +331,9 @@ class _BrowserState() extends State<_Browser> {
|
|||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_Toolbar(
|
||||
detailed: detailed,
|
||||
onDetailed: _setDetailed,
|
||||
detailed: detailed && !state.selecting,
|
||||
// Choosing cards is the grid's: the view comes back after.
|
||||
onDetailed: state.selecting ? null : _setDetailed,
|
||||
searching: _searchOpen || state.filter.query.isNotEmpty,
|
||||
onSearch: _toggleSearch,
|
||||
sort: state.sort,
|
||||
|
|
@ -368,7 +369,9 @@ class _BrowserState() extends State<_Browser> {
|
|||
Expanded(
|
||||
child: state.shown.isEmpty
|
||||
? _NoMatches(onClear: _clearFilters)
|
||||
: detailed
|
||||
// Cards are chosen in the grid, where each shows whether
|
||||
// it is chosen; the detail's tap opens a card.
|
||||
: detailed && !state.selecting
|
||||
? _Detail(
|
||||
cards: state.shown,
|
||||
current: _current,
|
||||
|
|
@ -498,7 +501,9 @@ typedef _Actions = ({
|
|||
/// and choosing cards, each tinted while it is at work.
|
||||
class const _Toolbar({
|
||||
required final bool detailed,
|
||||
required final ValueChanged<bool> onDetailed,
|
||||
|
||||
/// Switches between grid and detail; off while cards are chosen.
|
||||
required final ValueChanged<bool>? onDetailed,
|
||||
required final bool searching,
|
||||
required final VoidCallback onSearch,
|
||||
required final CardBrowserSort sort,
|
||||
|
|
@ -541,7 +546,10 @@ class const _Toolbar({
|
|||
),
|
||||
],
|
||||
selected: {detailed},
|
||||
onSelectionChanged: (chosen) => onDetailed(chosen.single),
|
||||
onSelectionChanged: switch (onDetailed) {
|
||||
final change? => (chosen) => change(chosen.single),
|
||||
null => null,
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
|
|
@ -1289,42 +1297,48 @@ class const _SelectionControls({
|
|||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// On a narrow phone, in a longer language, they shorten.
|
||||
Flexible(
|
||||
child: TextButton(
|
||||
onPressed: all
|
||||
? null
|
||||
: () => context.read<CardBrowserBloc>().add(
|
||||
const CardBrowserEventAllSelected(),
|
||||
// Choosing, at the start, takes the room the actions leave,
|
||||
// so they end at the row's end: beside them, its flexible
|
||||
// buttons would each claim a share of the room and leave it
|
||||
// unused.
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
// On a narrow phone, in a longer language, they shorten.
|
||||
Flexible(
|
||||
child: TextButton(
|
||||
onPressed: all
|
||||
? null
|
||||
: () => context.read<CardBrowserBloc>().add(
|
||||
const CardBrowserEventAllSelected(),
|
||||
),
|
||||
child: Text(
|
||||
l10n.cardBrowserSelectAll,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
child: Text(
|
||||
l10n.cardBrowserSelectAll,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: TextButton(
|
||||
onPressed: selected == 0
|
||||
? null
|
||||
: () => context.read<CardBrowserBloc>().add(
|
||||
const CardBrowserEventAllDeselected(),
|
||||
),
|
||||
child: Text(
|
||||
l10n.cardBrowserDeselectAll,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_CountPill(count: selected),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: TextButton(
|
||||
onPressed: selected == 0
|
||||
? null
|
||||
: () => context.read<CardBrowserBloc>().add(
|
||||
const CardBrowserEventAllDeselected(),
|
||||
),
|
||||
child: Text(
|
||||
l10n.cardBrowserDeselectAll,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (actions == null) ...[
|
||||
const SizedBox(width: 8),
|
||||
_CountPill(count: selected),
|
||||
] else ...[
|
||||
const SizedBox(width: 8),
|
||||
_CountPill(count: selected),
|
||||
const Spacer(),
|
||||
if (actions != null) ...[
|
||||
OutlinedButton.icon(
|
||||
onPressed: actions.move,
|
||||
icon: const Icon(LucideIcons.folderInput, size: 18),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
|||
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/widget/composer/card_composer_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_payload.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
|
@ -277,4 +278,36 @@ void main() {
|
|||
expect(find.byType(PageView), findsOneWidget);
|
||||
expect(find.byTooltip('Next'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('from the expanded window on, what can be done to the chosen '
|
||||
"cards ends at the row's end", (tester) async {
|
||||
await show(tester);
|
||||
|
||||
await tester.tap(find.byTooltip('Select'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final done = tester.getRect(find.widgetWithText(FilledButton, 'Done'));
|
||||
final row = tester.getRect(find.byTooltip('Select').first);
|
||||
expect(done.right, moreOrLessEquals(row.right, epsilon: 4));
|
||||
});
|
||||
|
||||
testWidgets('on a phone in the detail view, choosing cards shows the '
|
||||
'grid, where a tap chooses a card rather than opening it; Done '
|
||||
'returns to the detail', (tester) async {
|
||||
await show(tester, width: 400);
|
||||
await tester.tap(find.byTooltip('Detail'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byTooltip('Select'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(PageView), findsNothing);
|
||||
await tester.tap(find.text('cat'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('1 selected'), findsOneWidget);
|
||||
expect(find.byType(CardComposerScreen), findsNothing);
|
||||
|
||||
await tester.tap(find.text('Done'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(PageView), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue