Unverified Commit 2d2edbf7 authored by Darren Austin's avatar Darren Austin Committed by GitHub

Date picker layout exceptions (#31514)

Fixed several layout issues with the material date picker. Mostly just removed hard coded sizes to allow the grid view to scroll instead of overflowing.
parent 61236c87
......@@ -45,21 +45,12 @@ enum DatePickerMode {
year,
}
const double _kDatePickerHeaderPortraitHeight = 100.0;
const double _kDatePickerHeaderLandscapeWidth = 168.0;
const Duration _kMonthScrollDuration = Duration(milliseconds: 200);
const double _kDayPickerRowHeight = 42.0;
const int _kMaxDayPickerRowCount = 6; // A 31 day month that starts on Saturday.
// Two extra rows: one for the day-of-week header and one for the month header.
const double _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCount + 2);
const double _kMonthPickerPortraitWidth = 330.0;
const double _kMonthPickerLandscapeWidth = 344.0;
const double _kDialogActionBarHeight = 52.0;
const double _kDatePickerLandscapeHeight = _kMaxDayPickerHeight + _kDialogActionBarHeight;
// Shows the selected date in large font and toggles between year and day mode
class _DatePickerHeader extends StatelessWidget {
const _DatePickerHeader({
......@@ -100,8 +91,8 @@ class _DatePickerHeader extends StatelessWidget {
yearColor = mode == DatePickerMode.year ? Colors.white : Colors.white70;
break;
}
final TextStyle dayStyle = headerTextTheme.display1.copyWith(color: dayColor, height: 1.4);
final TextStyle yearStyle = headerTextTheme.subhead.copyWith(color: yearColor, height: 1.4);
final TextStyle dayStyle = headerTextTheme.display1.copyWith(color: dayColor);
final TextStyle yearStyle = headerTextTheme.subhead.copyWith(color: yearColor);
Color backgroundColor;
switch (themeData.brightness) {
......@@ -113,18 +104,14 @@ class _DatePickerHeader extends StatelessWidget {
break;
}
double width;
double height;
EdgeInsets padding;
MainAxisAlignment mainAxisAlignment;
switch (orientation) {
case Orientation.portrait:
height = _kDatePickerHeaderPortraitHeight;
padding = const EdgeInsets.symmetric(horizontal: 16.0);
padding = const EdgeInsets.all(16.0);
mainAxisAlignment = MainAxisAlignment.center;
break;
case Orientation.landscape:
width = _kDatePickerHeaderLandscapeWidth;
padding = const EdgeInsets.all(8.0);
mainAxisAlignment = MainAxisAlignment.start;
break;
......@@ -157,8 +144,6 @@ class _DatePickerHeader extends StatelessWidget {
);
return Container(
width: width,
height: height,
padding: padding,
color: backgroundColor,
child: Column(
......@@ -210,7 +195,8 @@ class _DayPickerGridDelegate extends SliverGridDelegate {
SliverGridLayout getLayout(SliverConstraints constraints) {
const int columnCount = DateTime.daysPerWeek;
final double tileWidth = constraints.crossAxisExtent / columnCount;
final double tileHeight = math.min(_kDayPickerRowHeight, constraints.viewportMainAxisExtent / (_kMaxDayPickerRowCount + 1));
final double viewTileHeight = constraints.viewportMainAxisExtent / (_kMaxDayPickerRowCount + 1);
final double tileHeight = math.max(_kDayPickerRowHeight, viewTileHeight);
return SliverGridRegularTileLayout(
crossAxisCount: columnCount,
mainAxisStride: tileHeight,
......@@ -493,6 +479,7 @@ class DayPicker extends StatelessWidget {
child: GridView.custom(
gridDelegate: _kDayPickerGridDelegate,
childrenDelegate: SliverChildListDelegate(labels, addRepaintBoundaries: false),
padding: EdgeInsets.zero,
),
),
],
......@@ -682,7 +669,8 @@ class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStat
@override
Widget build(BuildContext context) {
return SizedBox(
width: _kMonthPickerPortraitWidth,
// The month picker just adds month navigation to the day picker, so make
// it the same height as the DayPicker
height: _kMaxDayPickerHeight,
child: Stack(
children: <Widget>[
......@@ -994,12 +982,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final Widget picker = Flexible(
child: SizedBox(
height: _kMaxDayPickerHeight,
child: _buildPicker(),
),
);
final Widget picker = _buildPicker();
final Widget actions = ButtonTheme.bar(
child: ButtonBar(
children: <Widget>[
......@@ -1014,6 +997,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
],
),
);
final Dialog dialog = Dialog(
child: OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
......@@ -1026,44 +1010,35 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
);
switch (orientation) {
case Orientation.portrait:
return SizedBox(
width: _kMonthPickerPortraitWidth,
return Container(
color: theme.dialogBackgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
header,
Container(
color: theme.dialogBackgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
picker,
actions,
],
),
),
Flexible(child: picker),
actions,
],
),
);
case Orientation.landscape:
return SizedBox(
height: _kDatePickerLandscapeHeight,
return Container(
color: theme.dialogBackgroundColor,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
header,
Flexible(child: header),
Flexible(
child: Container(
width: _kMonthPickerLandscapeWidth,
color: theme.dialogBackgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[picker, actions],
),
flex: 2, // have the picker take up 2/3 of the dialog width
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Flexible(child: picker),
actions
],
),
),
],
......
......@@ -773,4 +773,88 @@ void _tests() {
// button and the right edge of the 800 wide window.
expect(tester.getBottomLeft(find.text('OK')).dx, 800 - ltrOkRight);
});
group('screen configurations', () {
// Test various combinations of screen sizes, orientations and text scales
// to ensure the layout doesn't overflow and cause an exception to be thrown.
// Regression tests for https://github.com/flutter/flutter/issues/21383
// Regression tests for https://github.com/flutter/flutter/issues/19744
// Regression tests for https://github.com/flutter/flutter/issues/17745
// Common screen size roughly based on a Pixel 1
const Size kCommonScreenSizePortrait = Size(1070, 1770);
const Size kCommonScreenSizeLandscape = Size(1770, 1070);
// Small screen size based on a LG K130
const Size kSmallScreenSizePortrait = Size(320, 521);
const Size kSmallScreenSizeLandscape = Size(521, 320);
Future<void> _showPicker(WidgetTester tester, Size size, [double textScaleFactor = 1.0]) async {
tester.binding.window.physicalSizeTestValue = size;
tester.binding.window.devicePixelRatioTestValue = 1.0;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
return RaisedButton(
child: const Text('X'),
onPressed: () {
showDatePicker(
context: context,
initialDate: initialDate,
firstDate: firstDate,
lastDate: lastDate,
);
},
);
},
),
),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
}
testWidgets('common screen size - portrait', (WidgetTester tester) async {
await _showPicker(tester, kCommonScreenSizePortrait);
expect(tester.takeException(), isNull);
});
testWidgets('common screen size - landscape', (WidgetTester tester) async {
await _showPicker(tester, kCommonScreenSizeLandscape);
expect(tester.takeException(), isNull);
});
testWidgets('common screen size - portrait - textScale 1.3', (WidgetTester tester) async {
await _showPicker(tester, kCommonScreenSizePortrait, 1.3);
expect(tester.takeException(), isNull);
});
testWidgets('common screen size - landscape - textScale 1.3', (WidgetTester tester) async {
await _showPicker(tester, kCommonScreenSizeLandscape, 1.3);
expect(tester.takeException(), isNull);
});
testWidgets('small screen size - portrait', (WidgetTester tester) async {
await _showPicker(tester, kSmallScreenSizePortrait);
expect(tester.takeException(), isNull);
});
testWidgets('small screen size - landscape', (WidgetTester tester) async {
await _showPicker(tester, kSmallScreenSizeLandscape);
expect(tester.takeException(), isNull);
});
testWidgets('small screen size - portrait -textScale 1.3', (WidgetTester tester) async {
await _showPicker(tester, kSmallScreenSizePortrait, 1.3);
expect(tester.takeException(), isNull);
});
testWidgets('small screen size - landscape - textScale 1.3', (WidgetTester tester) async {
await _showPicker(tester, kSmallScreenSizeLandscape, 1.3);
expect(tester.takeException(), isNull);
});
});
}
......@@ -223,6 +223,67 @@ void main() {
await tester.tap(find.text('ANNULER'));
});
group('locale fonts don\'t overflow layout', () {
// Test screen layouts in various locales to ensure the fonts used
// don't overflow the layout
// Common screen size roughly based on a Pixel 1
const Size kCommonScreenSizePortrait = Size(1070, 1770);
const Size kCommonScreenSizeLandscape = Size(1770, 1070);
Future<void> _showPicker(WidgetTester tester, Locale locale, Size size) async {
tester.binding.window.physicalSizeTestValue = size;
tester.binding.window.devicePixelRatioTestValue = 1.0;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
return Localizations(
locale: locale,
delegates: GlobalMaterialLocalizations.delegates,
child: RaisedButton(
child: const Text('X'),
onPressed: () {
showDatePicker(
context: context,
initialDate: initialDate,
firstDate: firstDate,
lastDate: lastDate,
);
},
),
);
},
),
)
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
}
// Regression test for https://github.com/flutter/flutter/issues/20171
testWidgets('common screen size - portrait - Chinese', (WidgetTester tester) async {
await _showPicker(tester, const Locale('zh', 'CN'), kCommonScreenSizePortrait);
expect(tester.takeException(), isNull);
});
testWidgets('common screen size - landscape - Chinese', (WidgetTester tester) async {
await _showPicker(tester, const Locale('zh', 'CN'), kCommonScreenSizeLandscape);
expect(tester.takeException(), isNull);
});
testWidgets('common screen size - portrait - Japanese', (WidgetTester tester) async {
await _showPicker(tester, const Locale('ja', 'JA'), kCommonScreenSizePortrait);
expect(tester.takeException(), isNull);
});
testWidgets('common screen size - landscape - Japanese', (WidgetTester tester) async {
await _showPicker(tester, const Locale('ja', 'JA'), kCommonScreenSizeLandscape);
expect(tester.takeException(), isNull);
});
});
}
Future<void> _pumpBoilerplate(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment