feat(app): card types on one row, scrolling sideways

On a phone the four chips wrapped onto a second row; they now stay on
one and scroll.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-08 14:16:46 +02:00
commit ebe62e82d7
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 39 additions and 27 deletions

View file

@ -263,29 +263,31 @@ class _CardDraftEditorState() extends State<CardDraftEditor> {
],
),
const SizedBox(height: 16),
// Chips rather than segments: on a phone they flow onto a second
// row, where segments would break their names.
Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final (choice, name) in [
(CardKind.flashcard, l10n.composerKindFlashcard),
(CardKind.vocabulary, l10n.composerKindVocabulary),
(CardKind.matching, l10n.composerKindMatching),
(CardKind.map, l10n.composerKindMap),
])
ChoiceChip(
label: Text(name),
selected: choice == kind,
showCheckmark: false,
onSelected: (_) {
if (choice != kind) {
_change(choice.apply(content));
}
},
),
],
// Chips rather than segments, which would break their names on a
// phone; one row, scrolling sideways where it doesn't fit.
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
spacing: 8,
children: [
for (final (choice, name) in [
(CardKind.flashcard, l10n.composerKindFlashcard),
(CardKind.vocabulary, l10n.composerKindVocabulary),
(CardKind.matching, l10n.composerKindMatching),
(CardKind.map, l10n.composerKindMap),
])
ChoiceChip(
label: Text(name),
selected: choice == kind,
showCheckmark: false,
onSelected: (_) {
if (choice != kind) {
_change(choice.apply(content));
}
},
),
],
),
),
const SizedBox(height: 16),
Expanded(

View file

@ -393,21 +393,31 @@ void main() {
expect(blocks, contains(containsPair('type', mathBlockType)));
});
testWidgets('the card types fit a phone, none of their names broken', (
tester,
) async {
testWidgets('the card types stay on one row on a phone, scrolling '
'sideways, none of their names broken', (tester) async {
await cards.create('japanese', card('猫', 'cat'));
await showStudyset(tester, size: const Size(412, 915));
await open(tester, '猫');
for (final type in ['Flashcard', 'Vocabulary', 'Matching', 'Map']) {
const types = ['Flashcard', 'Vocabulary', 'Matching', 'Map'];
for (final type in types) {
final name = tester.renderObject<RenderParagraph>(find.text(type));
expect(
name.size.width,
moreOrLessEquals(name.getMaxIntrinsicWidth(double.infinity)),
reason: type,
);
expect(
tester.getCenter(find.text(type)).dy,
tester.getCenter(find.text(types.first)).dy,
reason: type,
);
}
await tester.ensureVisible(find.text('Map'));
await tester.pumpAndSettle();
expect(find.text('Map').hitTestable(), findsOneWidget);
await tester.ensureVisible(find.text('Vocabulary'));
await tester.pumpAndSettle();
await tester.tap(find.text('Vocabulary'));
await settle(tester);
expect(find.text('Reading'), findsOneWidget);