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,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
_Toolbar(
|
_Toolbar(
|
||||||
detailed: detailed,
|
detailed: detailed && !state.selecting,
|
||||||
onDetailed: _setDetailed,
|
// Choosing cards is the grid's: the view comes back after.
|
||||||
|
onDetailed: state.selecting ? null : _setDetailed,
|
||||||
searching: _searchOpen || state.filter.query.isNotEmpty,
|
searching: _searchOpen || state.filter.query.isNotEmpty,
|
||||||
onSearch: _toggleSearch,
|
onSearch: _toggleSearch,
|
||||||
sort: state.sort,
|
sort: state.sort,
|
||||||
|
|
@ -368,7 +369,9 @@ class _BrowserState() extends State<_Browser> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: state.shown.isEmpty
|
child: state.shown.isEmpty
|
||||||
? _NoMatches(onClear: _clearFilters)
|
? _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(
|
? _Detail(
|
||||||
cards: state.shown,
|
cards: state.shown,
|
||||||
current: _current,
|
current: _current,
|
||||||
|
|
@ -498,7 +501,9 @@ typedef _Actions = ({
|
||||||
/// and choosing cards, each tinted while it is at work.
|
/// and choosing cards, each tinted while it is at work.
|
||||||
class const _Toolbar({
|
class const _Toolbar({
|
||||||
required final bool detailed,
|
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 bool searching,
|
||||||
required final VoidCallback onSearch,
|
required final VoidCallback onSearch,
|
||||||
required final CardBrowserSort sort,
|
required final CardBrowserSort sort,
|
||||||
|
|
@ -541,7 +546,10 @@ class const _Toolbar({
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
selected: {detailed},
|
selected: {detailed},
|
||||||
onSelectionChanged: (chosen) => onDetailed(chosen.single),
|
onSelectionChanged: switch (onDetailed) {
|
||||||
|
final change? => (chosen) => change(chosen.single),
|
||||||
|
null => null,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
@ -1289,42 +1297,48 @@ class const _SelectionControls({
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
// On a narrow phone, in a longer language, they shorten.
|
// Choosing, at the start, takes the room the actions leave,
|
||||||
Flexible(
|
// so they end at the row's end: beside them, its flexible
|
||||||
child: TextButton(
|
// buttons would each claim a share of the room and leave it
|
||||||
onPressed: all
|
// unused.
|
||||||
? null
|
Expanded(
|
||||||
: () => context.read<CardBrowserBloc>().add(
|
child: Row(
|
||||||
const CardBrowserEventAllSelected(),
|
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,
|
Flexible(
|
||||||
overflow: TextOverflow.ellipsis,
|
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(
|
if (actions != null) ...[
|
||||||
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(),
|
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: actions.move,
|
onPressed: actions.move,
|
||||||
icon: const Icon(LucideIcons.folderInput, size: 18),
|
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/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/data/card_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.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/data/studyset_payload.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
|
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
|
||||||
import 'package:material_ui/material_ui.dart';
|
import 'package:material_ui/material_ui.dart';
|
||||||
|
|
@ -277,4 +278,36 @@ void main() {
|
||||||
expect(find.byType(PageView), findsOneWidget);
|
expect(find.byType(PageView), findsOneWidget);
|
||||||
expect(find.byTooltip('Next'), findsNothing);
|
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