diff --git a/lib/ui2/profile/profile.dart b/lib/ui2/profile/profile.dart index 8252aa16e..6002cc659 100644 --- a/lib/ui2/profile/profile.dart +++ b/lib/ui2/profile/profile.dart @@ -212,22 +212,28 @@ Future _pickLanguage(BuildContext c) async { context: c, backgroundColor: p.card, showDragHandle: true, + // The default sheet is capped at 9/16 of the screen; with seven languages + // the list overflowed it. Lift the cap and scroll if it still doesn't fit. + isScrollControlled: true, + useSafeArea: true, // a full-height sheet must not reach under the status bar builder: (sheet) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (final code in options) - ListTile( - title: Text(_languageLabel(sheet, code), style: F.body.copyWith(color: p.ink)), - trailing: ctrl.code == code - ? Icon(LucideIcons.check, size: 18, color: p.on(C.blue)) - : null, - onTap: () async { - await ctrl.setCode(code); - if (sheet.mounted) Navigator.of(sheet).pop(); - }, - ), - ], + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final code in options) + ListTile( + title: Text(_languageLabel(sheet, code), style: F.body.copyWith(color: p.ink)), + trailing: ctrl.code == code + ? Icon(LucideIcons.check, size: 18, color: p.on(C.blue)) + : null, + onTap: () async { + await ctrl.setCode(code); + if (sheet.mounted) Navigator.of(sheet).pop(); + }, + ), + ], + ), ), ), ); diff --git a/test/language_picker_sheet_test.dart b/test/language_picker_sheet_test.dart new file mode 100644 index 000000000..2e052f401 --- /dev/null +++ b/test/language_picker_sheet_test.dart @@ -0,0 +1,47 @@ +// The language picker used to overflow its bottom sheet (capped at 9/16 of the +// screen) once the list grew past what fits. It must lay out without an +// overflow on a short screen, and every language must stay reachable. +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:openstrap_edge/l10n/app_localizations.dart'; +import 'package:openstrap_edge/state/locale_controller.dart'; +import 'package:openstrap_edge/ui2/profile/profile.dart'; +import 'package:openstrap_edge/ui2/ui2.dart'; + +void main() { + testWidgets('language sheet scrolls on a short screen and selects the last language', + (tester) async { + SharedPreferences.setMockInitialValues({}); + tester.view.physicalSize = const Size(360, 560); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + + final ctrl = LocaleController.seed(null); + await tester.pumpWidget(ChangeNotifierProvider.value( + value: ctrl, + child: MaterialApp( + theme: buildTheme(Brightness.dark), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: const ProfileHomeView(stats: ProfileStats(name: 'Sahil')), + ), + )); + await tester.pumpAndSettle(); + + await tester.tap(find.text('System default').first); + await tester.pumpAndSettle(); + expect(tester.takeException(), isNull, reason: 'sheet must not overflow'); + + final last = find.text('中文'); + await tester.ensureVisible(last); + await tester.pumpAndSettle(); + await tester.tap(last); + await tester.pumpAndSettle(); + + expect(ctrl.code, 'zh'); + expect(find.text('Deutsch'), findsNothing, reason: 'sheet should be dismissed'); + }); +}