mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Fix niggling PaginatedDataTable bugs (#13556)
Prevent header from thinking it can wrap and then overflowing. Fix default footer string which lost its colon (localized values are fine). Make the "rows per page" drop-down include at least one value even when the table lacks many items. (Previously it would assert if your table was too short.) Make the footer scrollable. Fix some todos and improve some debug output. Tests for much of the above.
This commit is contained in:
parent
b5eac0422a
commit
78ff7707e1
@ -109,6 +109,13 @@ List<GalleryItem> _buildGalleryItems() {
|
|||||||
routeName: ChipDemo.routeName,
|
routeName: ChipDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ChipDemo(),
|
buildRoute: (BuildContext context) => new ChipDemo(),
|
||||||
),
|
),
|
||||||
|
new GalleryItem(
|
||||||
|
title: 'Data tables',
|
||||||
|
subtitle: 'Data tables',
|
||||||
|
category: 'Material Components',
|
||||||
|
routeName: DataTableDemo.routeName,
|
||||||
|
buildRoute: (BuildContext context) => new DataTableDemo(),
|
||||||
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Date and time pickers',
|
title: 'Date and time pickers',
|
||||||
subtitle: 'Date and time selection widgets',
|
subtitle: 'Date and time selection widgets',
|
||||||
|
@ -411,14 +411,15 @@ class DataTable extends StatelessWidget {
|
|||||||
alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
|
alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
|
||||||
child: new AnimatedDefaultTextStyle(
|
child: new AnimatedDefaultTextStyle(
|
||||||
style: new TextStyle(
|
style: new TextStyle(
|
||||||
// TODO(ianh): font family should be Roboto; see https://github.com/flutter/flutter/issues/3116
|
// TODO(ianh): font family should match Theme; see https://github.com/flutter/flutter/issues/3116
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: _kHeadingFontSize,
|
fontSize: _kHeadingFontSize,
|
||||||
height: _kHeadingRowHeight / _kHeadingFontSize,
|
height: math.min(1.0, _kHeadingRowHeight / _kHeadingFontSize),
|
||||||
color: (Theme.of(context).brightness == Brightness.light)
|
color: (Theme.of(context).brightness == Brightness.light)
|
||||||
? ((onSort != null && sorted) ? Colors.black87 : Colors.black54)
|
? ((onSort != null && sorted) ? Colors.black87 : Colors.black54)
|
||||||
: ((onSort != null && sorted) ? Colors.white : Colors.white70),
|
: ((onSort != null && sorted) ? Colors.white : Colors.white70),
|
||||||
),
|
),
|
||||||
|
softWrap: false,
|
||||||
duration: _kSortArrowAnimationDuration,
|
duration: _kSortArrowAnimationDuration,
|
||||||
child: label,
|
child: label,
|
||||||
),
|
),
|
||||||
|
@ -503,7 +503,7 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rowsPerPageTitle => 'Rows per page';
|
String get rowsPerPageTitle => 'Rows per page:';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String selectedRowCountTitle(int selectedRowCount) {
|
String selectedRowCountTitle(int selectedRowCount) {
|
||||||
|
@ -324,7 +324,7 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
|
|||||||
final List<Widget> footerWidgets = <Widget>[];
|
final List<Widget> footerWidgets = <Widget>[];
|
||||||
if (widget.onRowsPerPageChanged != null) {
|
if (widget.onRowsPerPageChanged != null) {
|
||||||
final List<Widget> availableRowsPerPage = widget.availableRowsPerPage
|
final List<Widget> availableRowsPerPage = widget.availableRowsPerPage
|
||||||
.where((int value) => value <= _rowCount)
|
.where((int value) => (value <= _rowCount || value == widget.rowsPerPage))
|
||||||
.map<DropdownMenuItem<int>>((int value) {
|
.map<DropdownMenuItem<int>>((int value) {
|
||||||
return new DropdownMenuItem<int>(
|
return new DropdownMenuItem<int>(
|
||||||
value: value,
|
value: value,
|
||||||
@ -333,15 +333,22 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
|
|||||||
})
|
})
|
||||||
.toList();
|
.toList();
|
||||||
footerWidgets.addAll(<Widget>[
|
footerWidgets.addAll(<Widget>[
|
||||||
|
new Container(width: 14.0), // to match trailing padding in case we overflow and end up scrolling
|
||||||
new Text(localizations.rowsPerPageTitle),
|
new Text(localizations.rowsPerPageTitle),
|
||||||
new DropdownButtonHideUnderline(
|
new ConstrainedBox(
|
||||||
child: new DropdownButton<int>(
|
constraints: const BoxConstraints(minWidth: 64.0), // 40.0 for the text, 24.0 for the icon
|
||||||
items: availableRowsPerPage,
|
child: new Align(
|
||||||
value: widget.rowsPerPage,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
onChanged: widget.onRowsPerPageChanged,
|
child: new DropdownButtonHideUnderline(
|
||||||
style: footerTextStyle,
|
child: new DropdownButton<int>(
|
||||||
iconSize: 24.0
|
items: availableRowsPerPage,
|
||||||
)
|
value: widget.rowsPerPage,
|
||||||
|
onChanged: widget.onRowsPerPageChanged,
|
||||||
|
style: footerTextStyle,
|
||||||
|
iconSize: 24.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -422,15 +429,18 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
|
|||||||
),
|
),
|
||||||
child: new Container(
|
child: new Container(
|
||||||
height: 56.0,
|
height: 56.0,
|
||||||
child: new Row(
|
child: new SingleChildScrollView(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
scrollDirection: Axis.horizontal,
|
||||||
children: footerWidgets
|
reverse: true,
|
||||||
)
|
child: new Row(
|
||||||
)
|
children: footerWidgets,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
]
|
),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -407,7 +407,7 @@ class TextPainter {
|
|||||||
final int nextCodeUnit = _text.codeUnitAt(offset);
|
final int nextCodeUnit = _text.codeUnitAt(offset);
|
||||||
if (nextCodeUnit == null)
|
if (nextCodeUnit == null)
|
||||||
return null;
|
return null;
|
||||||
// TODO(goderbauer): doesn't handle flag emojis (https://github.com/flutter/flutter/issues/13404).
|
// TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404).
|
||||||
return _isUtf16Surrogate(nextCodeUnit) ? offset + 2 : offset + 1;
|
return _isUtf16Surrogate(nextCodeUnit) ? offset + 2 : offset + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +417,7 @@ class TextPainter {
|
|||||||
final int prevCodeUnit = _text.codeUnitAt(offset - 1);
|
final int prevCodeUnit = _text.codeUnitAt(offset - 1);
|
||||||
if (prevCodeUnit == null)
|
if (prevCodeUnit == null)
|
||||||
return null;
|
return null;
|
||||||
// TODO(goderbauer): doesn't handle flag emojis (https://github.com/flutter/flutter/issues/13404).
|
// TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404).
|
||||||
return _isUtf16Surrogate(prevCodeUnit) ? offset - 2 : offset - 1;
|
return _isUtf16Surrogate(prevCodeUnit) ? offset - 2 : offset - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +113,9 @@ class IntrinsicColumnWidth extends TableColumnWidth {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
double flex(Iterable<RenderBox> cells) => _flex;
|
double flex(Iterable<RenderBox> cells) => _flex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '$runtimeType(flex: ${_flex?.toStringAsFixed(1)})';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sizes the column to a specific number of pixels.
|
/// Sizes the column to a specific number of pixels.
|
||||||
|
@ -144,6 +144,10 @@ class DefaultTextStyle extends InheritedWidget {
|
|||||||
void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
||||||
super.debugFillProperties(description);
|
super.debugFillProperties(description);
|
||||||
style?.debugFillProperties(description);
|
style?.debugFillProperties(description);
|
||||||
|
description.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
|
||||||
|
description.add(new FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
|
||||||
|
description.add(new EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null));
|
||||||
|
description.add(new IntProperty('maxLines', maxLines, defaultValue: null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,4 +98,116 @@ void main() {
|
|||||||
expect(log, <String>['row-selected: KitKat']);
|
expect(log, <String>['row-selected: KitKat']);
|
||||||
log.clear();
|
log.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('DataTable overflow test - header', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
home: new Material(
|
||||||
|
child: new DataTable(
|
||||||
|
columns: <DataColumn>[
|
||||||
|
new DataColumn(
|
||||||
|
label: new Text('X' * 2000),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
rows: const <DataRow>[
|
||||||
|
const DataRow(
|
||||||
|
cells: const <DataCell>[
|
||||||
|
const DataCell(
|
||||||
|
const Text('X'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.renderObject<RenderBox>(find.byType(Text).first).size.width, greaterThan(800.0));
|
||||||
|
expect(tester.renderObject<RenderBox>(find.byType(Row).first).size.width, greaterThan(800.0));
|
||||||
|
expect(tester.takeException(), isNull); // column overflows table, but text doesn't overflow cell
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('DataTable overflow test - header with spaces', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
home: new Material(
|
||||||
|
child: new DataTable(
|
||||||
|
columns: <DataColumn>[
|
||||||
|
new DataColumn(
|
||||||
|
label: new Text('X ' * 2000), // has soft wrap points, but they should be ignored
|
||||||
|
),
|
||||||
|
],
|
||||||
|
rows: const <DataRow>[
|
||||||
|
const DataRow(
|
||||||
|
cells: const <DataCell>[
|
||||||
|
const DataCell(
|
||||||
|
const Text('X'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.renderObject<RenderBox>(find.byType(Text).first).size.width, greaterThan(800.0));
|
||||||
|
expect(tester.renderObject<RenderBox>(find.byType(Row).first).size.width, greaterThan(800.0));
|
||||||
|
expect(tester.takeException(), isNull); // column overflows table, but text doesn't overflow cell
|
||||||
|
}, skip: true); // https://github.com/flutter/flutter/issues/13512
|
||||||
|
|
||||||
|
testWidgets('DataTable overflow test', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
home: new Material(
|
||||||
|
child: new DataTable(
|
||||||
|
columns: const <DataColumn>[
|
||||||
|
const DataColumn(
|
||||||
|
label: const Text('X'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
rows: <DataRow>[
|
||||||
|
new DataRow(
|
||||||
|
cells: <DataCell>[
|
||||||
|
new DataCell(
|
||||||
|
new Text('X' * 2000),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.renderObject<RenderBox>(find.byType(Text).first).size.width, lessThan(800.0));
|
||||||
|
expect(tester.renderObject<RenderBox>(find.byType(Row).first).size.width, greaterThan(800.0));
|
||||||
|
expect(tester.takeException(), isNull); // cell overflows table, but text doesn't overflow cell
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('DataTable overflow test', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
home: new Material(
|
||||||
|
child: new DataTable(
|
||||||
|
columns: const <DataColumn>[
|
||||||
|
const DataColumn(
|
||||||
|
label: const Text('X'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
rows: <DataRow>[
|
||||||
|
new DataRow(
|
||||||
|
cells: <DataCell>[
|
||||||
|
new DataCell(
|
||||||
|
new Text('X ' * 2000), // wraps
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.renderObject<RenderBox>(find.byType(Text).first).size.width, lessThan(800.0));
|
||||||
|
expect(tester.renderObject<RenderBox>(find.byType(Row).first).size.width, lessThan(800.0));
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -192,4 +192,90 @@ void main() {
|
|||||||
expect(log, <String>['action: adjust']);
|
expect(log, <String>['action: adjust']);
|
||||||
log.clear();
|
log.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('PaginatedDataTable text alignment', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(new MaterialApp(
|
||||||
|
home: new PaginatedDataTable(
|
||||||
|
header: const Text('HEADER'),
|
||||||
|
source: new TestDataSource(),
|
||||||
|
rowsPerPage: 8,
|
||||||
|
availableRowsPerPage: <int>[
|
||||||
|
8, 9,
|
||||||
|
],
|
||||||
|
onRowsPerPageChanged: (int rowsPerPage) { },
|
||||||
|
columns: <DataColumn>[
|
||||||
|
const DataColumn(label: const Text('COL1')),
|
||||||
|
const DataColumn(label: const Text('COL2')),
|
||||||
|
const DataColumn(label: const Text('COL3')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
expect(find.text('Rows per page:'), findsOneWidget);
|
||||||
|
expect(find.text('8'), findsOneWidget);
|
||||||
|
expect(tester.getTopRight(find.text('8')).dx, tester.getTopRight(find.text('Rows per page:')).dx + 40.0); // per spec
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('PaginatedDataTable with large text', (WidgetTester tester) async {
|
||||||
|
final TestDataSource source = new TestDataSource();
|
||||||
|
await tester.pumpWidget(new MaterialApp(
|
||||||
|
home: new MediaQuery(
|
||||||
|
data: const MediaQueryData(
|
||||||
|
textScaleFactor: 20.0,
|
||||||
|
),
|
||||||
|
child: new PaginatedDataTable(
|
||||||
|
header: const Text('HEADER'),
|
||||||
|
source: source,
|
||||||
|
rowsPerPage: 501,
|
||||||
|
availableRowsPerPage: <int>[ 501 ],
|
||||||
|
onRowsPerPageChanged: (int rowsPerPage) { },
|
||||||
|
columns: <DataColumn>[
|
||||||
|
const DataColumn(label: const Text('COL1')),
|
||||||
|
const DataColumn(label: const Text('COL2')),
|
||||||
|
const DataColumn(label: const Text('COL3')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
// the column overflows because we're forcing it to 600 pixels high
|
||||||
|
expect(tester.takeException(), contains('A RenderFlex overflowed by'));
|
||||||
|
expect(find.text('Rows per page:'), findsOneWidget);
|
||||||
|
// Test that we will show some options in the drop down even if the lowest option is bigger than the source:
|
||||||
|
assert(501 > source.rowCount);
|
||||||
|
expect(find.text('501'), findsOneWidget);
|
||||||
|
// Test that it fits:
|
||||||
|
expect(tester.getTopRight(find.text('501')).dx, greaterThanOrEqualTo(tester.getTopRight(find.text('Rows per page:')).dx + 40.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('PaginatedDataTable footer scrolls', (WidgetTester tester) async {
|
||||||
|
final TestDataSource source = new TestDataSource();
|
||||||
|
await tester.pumpWidget(new MaterialApp(
|
||||||
|
home: new Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: new SizedBox(
|
||||||
|
width: 100.0,
|
||||||
|
child: new PaginatedDataTable(
|
||||||
|
header: const Text('HEADER'),
|
||||||
|
source: source,
|
||||||
|
rowsPerPage: 5,
|
||||||
|
availableRowsPerPage: <int>[ 5 ],
|
||||||
|
onRowsPerPageChanged: (int rowsPerPage) { },
|
||||||
|
columns: <DataColumn>[
|
||||||
|
const DataColumn(label: const Text('COL1')),
|
||||||
|
const DataColumn(label: const Text('COL2')),
|
||||||
|
const DataColumn(label: const Text('COL3')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
expect(find.text('Rows per page:'), findsOneWidget);
|
||||||
|
expect(tester.getTopLeft(find.text('Rows per page:')).dx, lessThan(0.0)); // off screen
|
||||||
|
await tester.dragFrom(
|
||||||
|
new Offset(50.0, tester.getTopLeft(find.text('Rows per page:')).dy),
|
||||||
|
const Offset(1000.0, 0.0),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('Rows per page:'), findsOneWidget);
|
||||||
|
expect(tester.getTopLeft(find.text('Rows per page:')).dx, 18.0); // 14 padding in the footer row, 4 padding from the card
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ void main() {
|
|||||||
' │ parentData: offset=Offset(335.0, 185.0) (can use size)\n'
|
' │ parentData: offset=Offset(335.0, 185.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
||||||
' │ size: Size(130.0, 230.0)\n'
|
' │ size: Size(130.0, 230.0)\n'
|
||||||
' │ default column width: IntrinsicColumnWidth\n'
|
' │ default column width: IntrinsicColumnWidth(flex: null)\n'
|
||||||
' │ table size: 5×5\n'
|
' │ table size: 5×5\n'
|
||||||
' │ column offsets: 0.0, 10.0, 30.0, 130.0, 130.0\n'
|
' │ column offsets: 0.0, 10.0, 30.0, 130.0, 130.0\n'
|
||||||
' │ row offsets: 0.0, 30.0, 30.0, 30.0, 30.0, 230.0\n'
|
' │ row offsets: 0.0, 30.0, 30.0, 30.0, 30.0, 230.0\n'
|
||||||
|
Loading…
Reference in New Issue
Block a user