mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Eliminated DrawerItem, use ListTile instead (#8992)
This commit is contained in:
parent
51ea62c143
commit
606f56221b
@ -577,53 +577,41 @@ class GalleryDrawer extends StatelessWidget {
|
||||
child: new ListView(
|
||||
children: <Widget>[
|
||||
new FancyDrawerHeader(),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.brightness_5),
|
||||
onPressed: () { _changeTheme(context, true); },
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.brightness_5),
|
||||
title: new Text('Light'),
|
||||
onTap: () { _changeTheme(context, true); },
|
||||
selected: ComplexLayoutApp.of(context).lightTheme,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Light')),
|
||||
new Radio<bool>(
|
||||
value: true,
|
||||
groupValue: ComplexLayoutApp.of(context).lightTheme,
|
||||
onChanged: (bool value) { _changeTheme(context, value); }
|
||||
)
|
||||
]
|
||||
)
|
||||
trailing: new Radio<bool>(
|
||||
value: true,
|
||||
groupValue: ComplexLayoutApp.of(context).lightTheme,
|
||||
onChanged: (bool value) { _changeTheme(context, value); }
|
||||
),
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.brightness_7),
|
||||
onPressed: () { _changeTheme(context, false); },
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.brightness_7),
|
||||
title: new Text('Dark'),
|
||||
onTap: () { _changeTheme(context, false); },
|
||||
selected: !ComplexLayoutApp.of(context).lightTheme,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Dark')),
|
||||
new Radio<bool>(
|
||||
value: false,
|
||||
groupValue: ComplexLayoutApp.of(context).lightTheme,
|
||||
onChanged: (bool value) { _changeTheme(context, value); }
|
||||
)
|
||||
]
|
||||
)
|
||||
trailing: new Radio<bool>(
|
||||
value: false,
|
||||
groupValue: ComplexLayoutApp.of(context).lightTheme,
|
||||
onChanged: (bool value) { _changeTheme(context, value); },
|
||||
),
|
||||
),
|
||||
new Divider(),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.hourglass_empty),
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.hourglass_empty),
|
||||
title: new Text('Animate Slowly'),
|
||||
selected: timeDilation != 1.0,
|
||||
onPressed: () { ComplexLayoutApp.of(context).toggleAnimationSpeed(); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Animate Slowly')),
|
||||
new Checkbox(
|
||||
value: timeDilation != 1.0,
|
||||
onChanged: (bool value) { ComplexLayoutApp.of(context).toggleAnimationSpeed(); }
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
onTap: () { ComplexLayoutApp.of(context).toggleAnimationSpeed(); },
|
||||
trailing: new Checkbox(
|
||||
value: timeDilation != 1.0,
|
||||
onChanged: (bool value) { ComplexLayoutApp.of(context).toggleAnimationSpeed(); }
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -107,10 +107,10 @@ class CardCollectionState extends State<CardCollection> {
|
||||
buildFontRadioItem("Center-align text", TextAlign.center, _textAlign, _changeTextAlign, icon: Icons.format_align_center, enabled: !_editable),
|
||||
buildFontRadioItem("Right-align text", TextAlign.right, _textAlign, _changeTextAlign, icon: Icons.format_align_right, enabled: !_editable),
|
||||
new Divider(),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.dvr),
|
||||
onPressed: () { debugDumpApp(); debugDumpRenderTree(); },
|
||||
child: new Text('Dump App to Console'),
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.dvr),
|
||||
onTap: () { debugDumpApp(); debugDumpRenderTree(); },
|
||||
title: new Text('Dump App to Console'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -167,67 +167,51 @@ class CardCollectionState extends State<CardCollection> {
|
||||
}
|
||||
|
||||
Widget buildDrawerCheckbox(String label, bool value, void callback(), { bool enabled: true }) {
|
||||
return new DrawerItem(
|
||||
onPressed: enabled ? callback : null,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text(label)),
|
||||
new Checkbox(
|
||||
value: value,
|
||||
onChanged: enabled ? (_) { callback(); } : null,
|
||||
),
|
||||
],
|
||||
return new ListTile(
|
||||
onTap: enabled ? callback : null,
|
||||
title: new Text(label),
|
||||
trailing: new Checkbox(
|
||||
value: value,
|
||||
onChanged: enabled ? (_) { callback(); } : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDrawerColorRadioItem(String label, MaterialColor itemValue, MaterialColor currentValue, ValueChanged<MaterialColor> onChanged, { IconData icon, bool enabled: true }) {
|
||||
return new DrawerItem(
|
||||
icon: new Icon(icon),
|
||||
onPressed: enabled ? () { onChanged(itemValue); } : null,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text(label)),
|
||||
new Radio<MaterialColor>(
|
||||
value: itemValue,
|
||||
groupValue: currentValue,
|
||||
onChanged: enabled ? onChanged : null,
|
||||
),
|
||||
],
|
||||
return new ListTile(
|
||||
leading: new Icon(icon),
|
||||
title: new Text(label),
|
||||
onTap: enabled ? () { onChanged(itemValue); } : null,
|
||||
trailing: new Radio<MaterialColor>(
|
||||
value: itemValue,
|
||||
groupValue: currentValue,
|
||||
onChanged: enabled ? onChanged : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDrawerDirectionRadioItem(String label, DismissDirection itemValue, DismissDirection currentValue, ValueChanged<DismissDirection> onChanged, { IconData icon, bool enabled: true }) {
|
||||
return new DrawerItem(
|
||||
icon: new Icon(icon),
|
||||
onPressed: enabled ? () { onChanged(itemValue); } : null,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text(label)),
|
||||
new Radio<DismissDirection>(
|
||||
value: itemValue,
|
||||
groupValue: currentValue,
|
||||
onChanged: enabled ? onChanged : null,
|
||||
),
|
||||
],
|
||||
return new ListTile(
|
||||
leading: new Icon(icon),
|
||||
title: new Text(label),
|
||||
onTap: enabled ? () { onChanged(itemValue); } : null,
|
||||
trailing: new Radio<DismissDirection>(
|
||||
value: itemValue,
|
||||
groupValue: currentValue,
|
||||
onChanged: enabled ? onChanged : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildFontRadioItem(String label, TextAlign itemValue, TextAlign currentValue, ValueChanged<TextAlign> onChanged, { IconData icon, bool enabled: true }) {
|
||||
return new DrawerItem(
|
||||
icon: new Icon(icon),
|
||||
onPressed: enabled ? () { onChanged(itemValue); } : null,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text(label)),
|
||||
new Radio<TextAlign>(
|
||||
value: itemValue,
|
||||
groupValue: currentValue,
|
||||
onChanged: enabled ? onChanged : null,
|
||||
),
|
||||
],
|
||||
return new ListTile(
|
||||
leading: new Icon(icon),
|
||||
title: new Text(label),
|
||||
onTap: enabled ? () { onChanged(itemValue); } : null,
|
||||
trailing: new Radio<TextAlign>(
|
||||
value: itemValue,
|
||||
groupValue: currentValue,
|
||||
onChanged: enabled ? onChanged : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -85,27 +85,23 @@ class PageViewAppState extends State<PageViewApp> {
|
||||
child: new ListView(
|
||||
children: <Widget>[
|
||||
new DrawerHeader(child: new Center(child: new Text('Options'))),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.more_horiz),
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.more_horiz),
|
||||
selected: scrollDirection == Axis.horizontal,
|
||||
child: new Text('Horizontal Layout'),
|
||||
onPressed: switchScrollDirection,
|
||||
trailing: new Text('Horizontal Layout'),
|
||||
onTap: switchScrollDirection,
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.more_vert),
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.more_vert),
|
||||
selected: scrollDirection == Axis.vertical,
|
||||
child: new Text('Vertical Layout'),
|
||||
onPressed: switchScrollDirection,
|
||||
trailing: new Text('Vertical Layout'),
|
||||
onTap: switchScrollDirection,
|
||||
),
|
||||
new DrawerItem(
|
||||
onPressed: toggleItemsWrap,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Scrolling wraps around')),
|
||||
// TODO(abarth): Actually make this checkbox change this value.
|
||||
new Checkbox(value: itemsWrap, onChanged: null),
|
||||
],
|
||||
),
|
||||
new ListTile(
|
||||
onTap: toggleItemsWrap,
|
||||
title: new Text('Scrolling wraps around'),
|
||||
// TODO(abarth): Actually make this checkbox change this value.
|
||||
trailing: new Checkbox(value: itemsWrap, onChanged: null),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -116,10 +116,10 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: _drawerContents.map((String id) {
|
||||
return new DrawerItem(
|
||||
icon: new CircleAvatar(child: new Text(id)),
|
||||
child: new Text('Drawer item $id'),
|
||||
onPressed: _showNotImplementedMessage,
|
||||
return new ListTile(
|
||||
leading: new CircleAvatar(child: new Text(id)),
|
||||
title: new Text('Drawer item $id'),
|
||||
onTap: _showNotImplementedMessage,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
@ -133,15 +133,15 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.add),
|
||||
child: new Text('Add account'),
|
||||
onPressed: _showNotImplementedMessage,
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.add),
|
||||
title: new Text('Add account'),
|
||||
onTap: _showNotImplementedMessage,
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.settings),
|
||||
child: new Text('Manage accounts'),
|
||||
onPressed: _showNotImplementedMessage,
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.settings),
|
||||
title: new Text('Manage accounts'),
|
||||
onTap: _showNotImplementedMessage,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -126,96 +126,88 @@ class GalleryDrawer extends StatelessWidget {
|
||||
final TextStyle aboutTextStyle = themeData.textTheme.body2;
|
||||
final TextStyle linkStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor);
|
||||
|
||||
final Widget lightThemeItem = new DrawerItem(
|
||||
icon: new Icon(Icons.brightness_5),
|
||||
onPressed: () { onThemeChanged(true); },
|
||||
final Widget lightThemeItem = new ListTile(
|
||||
leading: new Icon(Icons.brightness_5),
|
||||
title: new Text('Light'),
|
||||
trailing: new Radio<bool>(
|
||||
value: true,
|
||||
groupValue: useLightTheme,
|
||||
onChanged: onThemeChanged,
|
||||
),
|
||||
selected: useLightTheme,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Light')),
|
||||
new Radio<bool>(
|
||||
value: true,
|
||||
groupValue: useLightTheme,
|
||||
onChanged: onThemeChanged
|
||||
)
|
||||
]
|
||||
)
|
||||
onTap: () {
|
||||
onThemeChanged(true);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget darkThemeItem = new DrawerItem(
|
||||
icon: new Icon(Icons.brightness_7),
|
||||
onPressed: () { onThemeChanged(false); },
|
||||
selected: useLightTheme,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Dark')),
|
||||
new Radio<bool>(
|
||||
value: false,
|
||||
groupValue: useLightTheme,
|
||||
onChanged: onThemeChanged
|
||||
)
|
||||
]
|
||||
)
|
||||
final Widget darkThemeItem = new ListTile(
|
||||
leading: new Icon(Icons.brightness_7),
|
||||
title: new Text('Dark'),
|
||||
trailing: new Radio<bool>(
|
||||
value: false,
|
||||
groupValue: useLightTheme,
|
||||
onChanged: onThemeChanged
|
||||
),
|
||||
selected: !useLightTheme,
|
||||
onTap: () {
|
||||
onThemeChanged(false);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget mountainViewItem = new DrawerItem(
|
||||
final Widget mountainViewItem = new ListTile(
|
||||
// on iOS, we don't want to show an Android phone icon
|
||||
icon: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star : Icons.phone_android),
|
||||
onPressed: () { onPlatformChanged(TargetPlatform.android); },
|
||||
leading: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star : Icons.phone_android),
|
||||
title: new Text('Android'),
|
||||
trailing: new Radio<TargetPlatform>(
|
||||
value: TargetPlatform.android,
|
||||
groupValue: Theme.of(context).platform,
|
||||
onChanged: onPlatformChanged,
|
||||
),
|
||||
selected: Theme.of(context).platform == TargetPlatform.android,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Android')),
|
||||
new Radio<TargetPlatform>(
|
||||
value: TargetPlatform.android,
|
||||
groupValue: Theme.of(context).platform,
|
||||
onChanged: onPlatformChanged,
|
||||
)
|
||||
]
|
||||
)
|
||||
onTap: () {
|
||||
onPlatformChanged(TargetPlatform.android);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget cupertinoItem = new DrawerItem(
|
||||
final Widget cupertinoItem = new ListTile(
|
||||
// on iOS, we don't want to show the iPhone icon
|
||||
icon: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star_border : Icons.phone_iphone),
|
||||
onPressed: () { onPlatformChanged(TargetPlatform.iOS); },
|
||||
leading: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star_border : Icons.phone_iphone),
|
||||
title: new Text('iOS'),
|
||||
trailing: new Radio<TargetPlatform>(
|
||||
value: TargetPlatform.iOS,
|
||||
groupValue: Theme.of(context).platform,
|
||||
onChanged: onPlatformChanged,
|
||||
),
|
||||
selected: Theme.of(context).platform == TargetPlatform.iOS,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('iOS')),
|
||||
new Radio<TargetPlatform>(
|
||||
value: TargetPlatform.iOS,
|
||||
groupValue: Theme.of(context).platform,
|
||||
onChanged: onPlatformChanged,
|
||||
)
|
||||
]
|
||||
)
|
||||
onTap: () {
|
||||
onPlatformChanged(TargetPlatform.iOS);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget animateSlowlyItem = new DrawerItem(
|
||||
icon: new Icon(Icons.hourglass_empty),
|
||||
final Widget animateSlowlyItem = new ListTile(
|
||||
leading: new Icon(Icons.hourglass_empty),
|
||||
title: new Text('Animate Slowly'),
|
||||
trailing: new Checkbox(
|
||||
value: timeDilation != 1.0,
|
||||
onChanged: (bool value) {
|
||||
onTimeDilationChanged(value ? 20.0 : 1.0);
|
||||
},
|
||||
),
|
||||
selected: timeDilation != 1.0,
|
||||
onPressed: () { onTimeDilationChanged(timeDilation != 1.0 ? 1.0 : 20.0); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Animate Slowly')),
|
||||
new Checkbox(
|
||||
value: timeDilation != 1.0,
|
||||
onChanged: (bool value) { onTimeDilationChanged(value ? 20.0 : 1.0); }
|
||||
)
|
||||
]
|
||||
)
|
||||
onTap: () {
|
||||
onTimeDilationChanged(timeDilation != 1.0 ? 1.0 : 20.0);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget sendFeedbackItem = new DrawerItem(
|
||||
icon: new Icon(Icons.report),
|
||||
onPressed: onSendFeedback ?? () {
|
||||
final Widget sendFeedbackItem = new ListTile(
|
||||
leading: new Icon(Icons.report),
|
||||
title: new Text('Send feedback'),
|
||||
onTap: onSendFeedback ?? () {
|
||||
UrlLauncher.launch('https://github.com/flutter/flutter/issues/new');
|
||||
},
|
||||
child: new Text('Send feedback'),
|
||||
);
|
||||
|
||||
final Widget aboutItem = new AboutDrawerItem(
|
||||
final Widget aboutItem = new AboutListTile(
|
||||
icon: const FlutterLogo(),
|
||||
applicationVersion: '2016 Q3 Preview',
|
||||
applicationIcon: const FlutterLogo(),
|
||||
@ -273,36 +265,36 @@ class GalleryDrawer extends StatelessWidget {
|
||||
];
|
||||
|
||||
if (onShowPerformanceOverlayChanged != null) {
|
||||
allDrawerItems.insert(8, new DrawerItem(
|
||||
icon: new Icon(Icons.assessment),
|
||||
onPressed: () { onShowPerformanceOverlayChanged(!showPerformanceOverlay); },
|
||||
allDrawerItems.insert(8, new ListTile(
|
||||
leading: new Icon(Icons.assessment),
|
||||
title: new Text('Performance Overlay'),
|
||||
trailing: new Checkbox(
|
||||
value: showPerformanceOverlay,
|
||||
onChanged: (bool value) {
|
||||
onShowPerformanceOverlayChanged(!showPerformanceOverlay);
|
||||
},
|
||||
),
|
||||
selected: showPerformanceOverlay,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Performance Overlay')),
|
||||
new Checkbox(
|
||||
value: showPerformanceOverlay,
|
||||
onChanged: (bool value) { onShowPerformanceOverlayChanged(!showPerformanceOverlay); }
|
||||
)
|
||||
]
|
||||
)
|
||||
onTap: () {
|
||||
onShowPerformanceOverlayChanged(!showPerformanceOverlay);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if (onCheckerboardRasterCacheImagesChanged != null) {
|
||||
allDrawerItems.insert(8, new DrawerItem(
|
||||
icon: new Icon(Icons.assessment),
|
||||
onPressed: () { onCheckerboardRasterCacheImagesChanged(!checkerboardRasterCacheImages); },
|
||||
allDrawerItems.insert(8, new ListTile(
|
||||
leading: new Icon(Icons.assessment),
|
||||
title: new Text('Checkerboard Raster Cache Images'),
|
||||
trailing: new Checkbox(
|
||||
value: checkerboardRasterCacheImages,
|
||||
onChanged: (bool value) {
|
||||
onCheckerboardRasterCacheImagesChanged(!checkerboardRasterCacheImages);
|
||||
},
|
||||
),
|
||||
selected: checkerboardRasterCacheImages,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Checkerboard Raster Cache Images')),
|
||||
new Checkbox(
|
||||
value: checkerboardRasterCacheImages,
|
||||
onChanged: (bool value) { onCheckerboardRasterCacheImagesChanged(!checkerboardRasterCacheImages); }
|
||||
)
|
||||
]
|
||||
)
|
||||
onTap: () {
|
||||
onCheckerboardRasterCacheImagesChanged(!checkerboardRasterCacheImages);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,6 @@ Future<Null> runSmokeTest(WidgetTester tester) async {
|
||||
await smokeDemo(tester, routeName);
|
||||
tester.binding.debugAssertNoTransientCallbacks('A transient callback was still active after leaving route $routeName');
|
||||
}
|
||||
|
||||
expect(errors, 0);
|
||||
|
||||
final Finder navigationMenuButton = find.byTooltip('Open navigation menu');
|
||||
@ -150,6 +149,11 @@ Future<Null> runSmokeTest(WidgetTester tester) async {
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
||||
|
||||
// scroll the 'Send feedback' item into view
|
||||
await tester.drag(find.text('Light'), const Offset(0.0, -200.0));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
||||
|
||||
// send feedback
|
||||
expect(hasFeedback, false);
|
||||
await tester.tap(find.text('Send feedback'));
|
||||
|
@ -124,19 +124,20 @@ class StockHomeState extends State<StockHome> {
|
||||
child: new ListView(
|
||||
children: <Widget>[
|
||||
new DrawerHeader(child: new Center(child: new Text('Stocks'))),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.assessment),
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.assessment),
|
||||
title: new Text('Stock List'),
|
||||
selected: true,
|
||||
child: new Text('Stock List')
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.account_balance),
|
||||
onPressed: null,
|
||||
child: new Text('Account Balance')
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.account_balance),
|
||||
title: new Text('Account Balance'),
|
||||
enabled: false,
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.dvr),
|
||||
onPressed: () {
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.dvr),
|
||||
title: new Text('Dump App to Console'),
|
||||
onTap: () {
|
||||
try {
|
||||
debugDumpApp();
|
||||
debugDumpRenderTree();
|
||||
@ -146,38 +147,43 @@ class StockHomeState extends State<StockHome> {
|
||||
debugPrint('Exception while dumping app:\n$e\n$stack');
|
||||
}
|
||||
},
|
||||
child: new Text('Dump App to Console')
|
||||
),
|
||||
new Divider(),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.thumb_up),
|
||||
onPressed: () => _handleStockModeChange(StockMode.optimistic),
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Optimistic')),
|
||||
new Radio<StockMode>(value: StockMode.optimistic, groupValue: config.configuration.stockMode, onChanged: _handleStockModeChange)
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.thumb_up),
|
||||
title: new Text('Optimistic'),
|
||||
trailing: new Radio<StockMode>(
|
||||
value: StockMode.optimistic,
|
||||
groupValue: config.configuration.stockMode,
|
||||
onChanged: _handleStockModeChange
|
||||
),
|
||||
onTap: () {
|
||||
_handleStockModeChange(StockMode.optimistic);
|
||||
},
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.thumb_down),
|
||||
onPressed: () => _handleStockModeChange(StockMode.pessimistic),
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Pessimistic')),
|
||||
new Radio<StockMode>(value: StockMode.pessimistic, groupValue: config.configuration.stockMode, onChanged: _handleStockModeChange)
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.thumb_down),
|
||||
title: new Text('Pessimistic'),
|
||||
trailing: new Radio<StockMode>(
|
||||
value: StockMode.pessimistic,
|
||||
groupValue: config.configuration.stockMode,
|
||||
onChanged: _handleStockModeChange
|
||||
),
|
||||
onTap: () {
|
||||
_handleStockModeChange(StockMode.pessimistic);
|
||||
},
|
||||
),
|
||||
new Divider(),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.settings),
|
||||
onPressed: _handleShowSettings,
|
||||
child: new Text('Settings')),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.help),
|
||||
onPressed: _handleShowAbout,
|
||||
child: new Text('About'))
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.settings),
|
||||
title: new Text('Settings'),
|
||||
onTap: _handleShowSettings,
|
||||
),
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.help),
|
||||
title: new Text('About'),
|
||||
onTap: _handleShowAbout,
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
@ -103,139 +103,99 @@ class StockSettingsState extends State<StockSettings> {
|
||||
|
||||
Widget buildSettingsPane(BuildContext context) {
|
||||
final List<Widget> rows = <Widget>[
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.thumb_up),
|
||||
onPressed: _confirmOptimismChange,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Everything is awesome')),
|
||||
new Checkbox(
|
||||
value: config.configuration.stockMode == StockMode.optimistic,
|
||||
onChanged: (bool value) => _confirmOptimismChange()
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.thumb_up),
|
||||
title: new Text('Everything is awesome'),
|
||||
onTap: _confirmOptimismChange,
|
||||
trailing: new Checkbox(
|
||||
value: config.configuration.stockMode == StockMode.optimistic,
|
||||
onChanged: (bool value) => _confirmOptimismChange(),
|
||||
),
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.backup),
|
||||
onPressed: () { _handleBackupChanged(!(config.configuration.backupMode == BackupMode.enabled)); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Back up stock list to the cloud')),
|
||||
new Switch(
|
||||
value: config.configuration.backupMode == BackupMode.enabled,
|
||||
onChanged: _handleBackupChanged
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.backup),
|
||||
title: new Text('Back up stock list to the cloud'),
|
||||
onTap: () { _handleBackupChanged(!(config.configuration.backupMode == BackupMode.enabled)); },
|
||||
trailing: new Switch(
|
||||
value: config.configuration.backupMode == BackupMode.enabled,
|
||||
onChanged: _handleBackupChanged,
|
||||
),
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.picture_in_picture),
|
||||
onPressed: () { _handleShowPerformanceOverlayChanged(!config.configuration.showPerformanceOverlay); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Show rendering performance overlay')),
|
||||
new Switch(
|
||||
value: config.configuration.showPerformanceOverlay,
|
||||
onChanged: _handleShowPerformanceOverlayChanged
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.picture_in_picture),
|
||||
title: new Text('Show rendering performance overlay'),
|
||||
onTap: () { _handleShowPerformanceOverlayChanged(!config.configuration.showPerformanceOverlay); },
|
||||
trailing: new Switch(
|
||||
value: config.configuration.showPerformanceOverlay,
|
||||
onChanged: _handleShowPerformanceOverlayChanged,
|
||||
),
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.accessibility),
|
||||
onPressed: () { _handleShowSemanticsDebuggerChanged(!config.configuration.showSemanticsDebugger); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Show semantics overlay')),
|
||||
new Switch(
|
||||
value: config.configuration.showSemanticsDebugger,
|
||||
onChanged: _handleShowSemanticsDebuggerChanged
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.accessibility),
|
||||
title: new Text('Show semantics overlay'),
|
||||
onTap: () { _handleShowSemanticsDebuggerChanged(!config.configuration.showSemanticsDebugger); },
|
||||
trailing: new Switch(
|
||||
value: config.configuration.showSemanticsDebugger,
|
||||
onChanged: _handleShowSemanticsDebuggerChanged,
|
||||
),
|
||||
),
|
||||
];
|
||||
assert(() {
|
||||
// material grid and size construction lines are only available in checked mode
|
||||
rows.addAll(<Widget>[
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.border_clear),
|
||||
onPressed: () { _handleShowGridChanged(!config.configuration.debugShowGrid); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Show material grid (for debugging)')),
|
||||
new Switch(
|
||||
value: config.configuration.debugShowGrid,
|
||||
onChanged: _handleShowGridChanged
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.border_clear),
|
||||
title: new Text('Show material grid (for debugging)'),
|
||||
onTap: () { _handleShowGridChanged(!config.configuration.debugShowGrid); },
|
||||
trailing: new Switch(
|
||||
value: config.configuration.debugShowGrid,
|
||||
onChanged: _handleShowGridChanged,
|
||||
),
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.border_all),
|
||||
onPressed: () { _handleShowSizesChanged(!config.configuration.debugShowSizes); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Show construction lines (for debugging)')),
|
||||
new Switch(
|
||||
value: config.configuration.debugShowSizes,
|
||||
onChanged: _handleShowSizesChanged
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.border_all),
|
||||
title: new Text('Show construction lines (for debugging)'),
|
||||
onTap: () { _handleShowSizesChanged(!config.configuration.debugShowSizes); },
|
||||
trailing: new Switch(
|
||||
value: config.configuration.debugShowSizes,
|
||||
onChanged: _handleShowSizesChanged,
|
||||
),
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.format_color_text),
|
||||
onPressed: () { _handleShowBaselinesChanged(!config.configuration.debugShowBaselines); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Show baselines (for debugging)')),
|
||||
new Switch(
|
||||
value: config.configuration.debugShowBaselines,
|
||||
onChanged: _handleShowBaselinesChanged
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.format_color_text),
|
||||
title: new Text('Show baselines (for debugging)'),
|
||||
onTap: () { _handleShowBaselinesChanged(!config.configuration.debugShowBaselines); },
|
||||
trailing: new Switch(
|
||||
value: config.configuration.debugShowBaselines,
|
||||
onChanged: _handleShowBaselinesChanged,
|
||||
),
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.filter_none),
|
||||
onPressed: () { _handleShowLayersChanged(!config.configuration.debugShowLayers); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Show layer boundaries (for debugging)')),
|
||||
new Switch(
|
||||
value: config.configuration.debugShowLayers,
|
||||
onChanged: _handleShowLayersChanged
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.filter_none),
|
||||
title: new Text('Show layer boundaries (for debugging)'),
|
||||
onTap: () { _handleShowLayersChanged(!config.configuration.debugShowLayers); },
|
||||
trailing: new Switch(
|
||||
value: config.configuration.debugShowLayers,
|
||||
onChanged: _handleShowLayersChanged,
|
||||
),
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.mouse),
|
||||
onPressed: () { _handleShowPointersChanged(!config.configuration.debugShowPointers); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Show pointer hit-testing (for debugging)')),
|
||||
new Switch(
|
||||
value: config.configuration.debugShowPointers,
|
||||
onChanged: _handleShowPointersChanged
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.mouse),
|
||||
title: new Text('Show pointer hit-testing (for debugging)'),
|
||||
onTap: () { _handleShowPointersChanged(!config.configuration.debugShowPointers); },
|
||||
trailing: new Switch(
|
||||
value: config.configuration.debugShowPointers,
|
||||
onChanged: _handleShowPointersChanged,
|
||||
),
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.gradient),
|
||||
onPressed: () { _handleShowRainbowChanged(!config.configuration.debugShowRainbow); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(child: new Text('Show repaint rainbow (for debugging)')),
|
||||
new Switch(
|
||||
value: config.configuration.debugShowRainbow,
|
||||
onChanged: _handleShowRainbowChanged
|
||||
),
|
||||
]
|
||||
)
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.gradient),
|
||||
title: new Text('Show repaint rainbow (for debugging)'),
|
||||
onTap: () { _handleShowRainbowChanged(!config.configuration.debugShowRainbow); },
|
||||
trailing: new Switch(
|
||||
value: config.configuration.debugShowRainbow,
|
||||
onChanged: _handleShowRainbowChanged,
|
||||
),
|
||||
),
|
||||
]);
|
||||
return true;
|
||||
|
@ -38,12 +38,9 @@ Element findElementOfExactWidgetTypeGoingUp(Element node, Type targetType) {
|
||||
final RegExp materialIconAssetNameColorExtractor = new RegExp(r'[^/]+/ic_.+_(white|black)_[0-9]+dp\.png');
|
||||
|
||||
void checkIconColor(WidgetTester tester, String label, Color color) {
|
||||
// The icon is going to be in the same merged semantics box as the text
|
||||
// regardless of how the menu item is represented, so this is a good
|
||||
// way to find the menu item. I hope.
|
||||
final Element semantics = findElementOfExactWidgetTypeGoingUp(tester.element(find.text(label)), MergeSemantics);
|
||||
expect(semantics, isNotNull);
|
||||
final Element asset = findElementOfExactWidgetTypeGoingDown(semantics, RichText);
|
||||
final Element listTile = findElementOfExactWidgetTypeGoingUp(tester.element(find.text(label)), ListTile);
|
||||
expect(listTile, isNotNull);
|
||||
final Element asset = findElementOfExactWidgetTypeGoingDown(listTile, RichText);
|
||||
final RichText richText = asset.widget;
|
||||
expect(richText.text.style.color, equals(color));
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ export 'src/material/dialog.dart';
|
||||
export 'src/material/divider.dart';
|
||||
export 'src/material/drawer.dart';
|
||||
export 'src/material/drawer_header.dart';
|
||||
export 'src/material/drawer_item.dart';
|
||||
export 'src/material/dropdown.dart';
|
||||
export 'src/material/expand_icon.dart';
|
||||
export 'src/material/expansion_panel.dart';
|
||||
|
@ -257,8 +257,8 @@ class LicenseEntryWithLineBreaks extends LicenseEntry {
|
||||
/// * [showAboutDialog], which shows a Material-style dialog with information
|
||||
/// about the application, including a button that shows a [LicensePage] that
|
||||
/// uses this API to select licenses to show.
|
||||
/// * [AboutDrawerItem], which is a widget to put in a [Drawer] which
|
||||
/// automatically calls [showAboutDialog].
|
||||
/// * [AboutListTile], which is a widget that can be added to a [Drawer]. When
|
||||
/// tapped it calls [showAboutDialog].
|
||||
class LicenseRegistry {
|
||||
LicenseRegistry._();
|
||||
|
||||
|
@ -11,21 +11,21 @@ import 'package:flutter/foundation.dart';
|
||||
import 'app_bar.dart';
|
||||
import 'debug.dart';
|
||||
import 'dialog.dart';
|
||||
import 'drawer_item.dart';
|
||||
import 'flat_button.dart';
|
||||
import 'icon.dart';
|
||||
import 'icon_theme.dart';
|
||||
import 'icon_theme_data.dart';
|
||||
import 'list_tile.dart';
|
||||
import 'page.dart';
|
||||
import 'progress_indicator.dart';
|
||||
import 'scaffold.dart';
|
||||
import 'scrollbar.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// A [DrawerItem] to show an about box.
|
||||
/// A [ListTile] that shows an about box.
|
||||
///
|
||||
/// Place this in a [Drawer], specifying your preferred application name,
|
||||
/// version, icon, and copyright in the appropriate fields.
|
||||
/// This widget is often added to an app's [Drawer]. When tapped it shows
|
||||
/// an about box dialog with [showAboutDialog].
|
||||
///
|
||||
/// The about box will include a button that shows licenses for software used by
|
||||
/// the application. The licenses shown are those returned by the
|
||||
@ -33,13 +33,13 @@ import 'theme.dart';
|
||||
///
|
||||
/// If your application does not have a [Drawer], you should provide an
|
||||
/// affordance to call [showAboutDialog] or (at least) [showLicensePage].
|
||||
class AboutDrawerItem extends StatelessWidget {
|
||||
/// Creates a drawer item for showing an about box.
|
||||
class AboutListTile extends StatelessWidget {
|
||||
/// Creates a list tile for showing an about box.
|
||||
///
|
||||
/// The arguments are all optional. The application name, if omitted, will be
|
||||
/// derived from the nearest [Title] widget. The version, icon, and legalese
|
||||
/// values default to the empty string.
|
||||
AboutDrawerItem({
|
||||
AboutListTile({
|
||||
Key key,
|
||||
this.icon: const Icon(null),
|
||||
this.child,
|
||||
@ -109,10 +109,10 @@ class AboutDrawerItem extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
return new DrawerItem(
|
||||
icon: icon,
|
||||
child: child ?? new Text('About ${applicationName ?? _defaultApplicationName(context)}'),
|
||||
onPressed: () {
|
||||
return new ListTile(
|
||||
leading: icon,
|
||||
title: child ?? new Text('About ${applicationName ?? _defaultApplicationName(context)}'),
|
||||
onTap: () {
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationName: applicationName,
|
||||
@ -131,7 +131,7 @@ class AboutDrawerItem extends StatelessWidget {
|
||||
///
|
||||
/// The arguments correspond to the properties on [AboutDialog].
|
||||
///
|
||||
/// If the application has a [Drawer], consider using [AboutDrawerItem] instead
|
||||
/// If the application has a [Drawer], consider using [AboutListTile] instead
|
||||
/// of calling this directly.
|
||||
///
|
||||
/// If you do not need an about box in your application, you should at least
|
||||
@ -164,7 +164,7 @@ void showAboutDialog({
|
||||
///
|
||||
/// The arguments correspond to the properties on [LicensePage].
|
||||
///
|
||||
/// If the application has a [Drawer], consider using [AboutDrawerItem] instead
|
||||
/// If the application has a [Drawer], consider using [AboutListTile] instead
|
||||
/// of calling this directly.
|
||||
///
|
||||
/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
|
||||
@ -196,7 +196,7 @@ void showLicensePage({
|
||||
///
|
||||
/// To show an [AboutDialog], use [showAboutDialog].
|
||||
///
|
||||
/// If the application has a [Drawer], the [AboutDrawerItem] widget can make the
|
||||
/// If the application has a [Drawer], the [AboutListTile] widget can make the
|
||||
/// process of showing an about dialog simpler.
|
||||
///
|
||||
/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
|
||||
@ -316,7 +316,7 @@ class AboutDialog extends StatelessWidget {
|
||||
///
|
||||
/// To show a [LicensePage], use [showLicensePage].
|
||||
///
|
||||
/// The [AboutDialog] shown by [showAboutDialog] and [AboutDrawerItem] includes
|
||||
/// The [AboutDialog] shown by [showAboutDialog] and [AboutListTile] includes
|
||||
/// a button that calls [showLicensePage].
|
||||
///
|
||||
/// The licenses shown on the [LicensePage] are those returned by the
|
||||
|
@ -92,7 +92,7 @@ class ButtonTheme extends InheritedWidget {
|
||||
/// Defaults to 16.0 pixels of horizontal padding.
|
||||
final EdgeInsets padding;
|
||||
|
||||
/// The color from the closest instance of this class that encloses the given context.
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
import 'list_tile.dart';
|
||||
import 'material.dart';
|
||||
|
||||
// TODO(eseidel): Draw width should vary based on device size:
|
||||
@ -30,7 +31,23 @@ const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
|
||||
///
|
||||
/// Drawers are typically used with the [Scaffold.drawer] property. The child of
|
||||
/// the drawer is usually a [ListView] whose first child is a [DrawerHeader]
|
||||
/// that displays status information about the current user.
|
||||
/// that displays status information about the current user. The remaining
|
||||
/// drawer children are often constructed with [ListTile]s, often concluding
|
||||
/// with an [AboutListTile].
|
||||
///
|
||||
/// An open drawer can be closed by calling [Navigator.pop]. For example
|
||||
/// a drawer item might close the drawer when tapped:
|
||||
///
|
||||
/// ```dart
|
||||
/// new ListTile(
|
||||
/// leading: new Icon(Icons.change_history),
|
||||
/// title: new Text('Change history'),
|
||||
/// onTap: () {
|
||||
/// // change app state...
|
||||
/// Navigator.pop(context); // close the drawer
|
||||
/// },
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// The [AppBar] automatically displays an appropriate [IconButton] to show the
|
||||
/// [Drawer] when a [Drawer] is available in the [Scaffold]. The [Scaffold]
|
||||
@ -43,9 +60,6 @@ const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
|
||||
/// * [Scaffold.of], to obtain the current [ScaffoldState], which manages the
|
||||
/// display and animation of the drawer.
|
||||
/// * [ScaffoldState.openDrawer], which displays its [Drawer], if any.
|
||||
/// * [Navigator.pop], which closes the drawer if it is open.
|
||||
/// * [DrawerItem], a widget for items in drawers.
|
||||
/// * [DrawerHeader], a widget for the top part of a drawer.
|
||||
/// * <https://material.google.com/patterns/navigation-drawer.html>
|
||||
class Drawer extends StatelessWidget {
|
||||
/// Creates a material design drawer.
|
||||
@ -233,8 +247,7 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
|
||||
final ColorTween _color = new ColorTween(begin: Colors.transparent, end: Colors.black54);
|
||||
final GlobalKey _gestureDetectorKey = new GlobalKey();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget _buildDrawer(BuildContext context) {
|
||||
if (_controller.status == AnimationStatus.dismissed) {
|
||||
return new Align(
|
||||
alignment: FractionalOffset.centerLeft,
|
||||
@ -245,7 +258,7 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
|
||||
behavior: HitTestBehavior.translucent,
|
||||
excludeFromSemantics: true,
|
||||
child: new Container(width: _kEdgeDragWidth)
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return new GestureDetector(
|
||||
@ -263,8 +276,8 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
|
||||
decoration: new BoxDecoration(
|
||||
backgroundColor: _color.evaluate(_controller)
|
||||
),
|
||||
child: new Container()
|
||||
)
|
||||
child: new Container(),
|
||||
),
|
||||
),
|
||||
new Align(
|
||||
alignment: FractionalOffset.centerLeft,
|
||||
@ -275,14 +288,21 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
|
||||
child: new Focus(
|
||||
key: _drawerKey,
|
||||
child: config.child
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new ListTileTheme(
|
||||
style: ListTileStyle.drawer,
|
||||
child: _buildDrawer(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,12 @@ const double _kDrawerHeaderHeight = 160.0 + 1.0; // bottom edge
|
||||
/// Part of the material design [Drawer].
|
||||
///
|
||||
/// Requires one of its ancestors to be a [Material] widget. This condition is
|
||||
/// satisfied by putting the [DrawerItem] in a [Drawer].
|
||||
/// satisfied by putting the [DrawerHeader] in a [Drawer].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Drawer]
|
||||
/// * [UserAccountsDrawerHeader], a variant of [DrawerHeader] that is
|
||||
/// specialized for showing user accounts.
|
||||
/// * [DrawerItem]
|
||||
/// * <https://material.google.com/patterns/navigation-drawer.html>
|
||||
class DrawerHeader extends StatelessWidget {
|
||||
/// Creates a material design drawer header.
|
||||
|
@ -1,146 +0,0 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
import 'constants.dart';
|
||||
import 'debug.dart';
|
||||
import 'icon.dart';
|
||||
import 'icon_theme.dart';
|
||||
import 'icon_theme_data.dart';
|
||||
import 'image_icon.dart';
|
||||
import 'ink_well.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// An item in a material design drawer.
|
||||
///
|
||||
/// Part of the material design [Drawer].
|
||||
///
|
||||
/// Requires one of its ancestors to be a [Material] widget. This condition is
|
||||
/// satisfied by putting the [DrawerItem] in a [Drawer].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Drawer]
|
||||
/// * [DrawerHeader]
|
||||
/// * <https://material.google.com/patterns/navigation-drawer.html>
|
||||
class DrawerItem extends StatelessWidget {
|
||||
/// Creates a material design drawer item.
|
||||
///
|
||||
/// Requires one of its ancestors to be a [Material] widget.
|
||||
const DrawerItem({
|
||||
Key key,
|
||||
this.icon: const Icon(null),
|
||||
@required this.child,
|
||||
this.onPressed,
|
||||
this.selected: false
|
||||
}) : super(key: key);
|
||||
|
||||
/// The icon to display before the child widget.
|
||||
///
|
||||
/// The size and color of the icon is configured automatically using an
|
||||
/// [IconTheme] and therefore do not need to be explicitly given in the
|
||||
/// icon widget.
|
||||
///
|
||||
/// See [Icon], [ImageIcon].
|
||||
final Widget icon;
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
final Widget child;
|
||||
|
||||
/// Called when the user taps this drawer item.
|
||||
///
|
||||
/// If null, the drawer item is displayed as disabled.
|
||||
///
|
||||
/// To close the [Drawer] when an item is pressed, call [Navigator.pop].
|
||||
final VoidCallback onPressed;
|
||||
|
||||
/// Whether this drawer item is currently selected.
|
||||
///
|
||||
/// The currently selected item is highlighted to distinguish it from other
|
||||
/// drawer items.
|
||||
final bool selected;
|
||||
|
||||
Color _getIconColor(ThemeData themeData) {
|
||||
switch (themeData.brightness) {
|
||||
case Brightness.light:
|
||||
if (selected)
|
||||
return themeData.primaryColor;
|
||||
if (onPressed == null)
|
||||
return Colors.black26;
|
||||
return Colors.black45;
|
||||
case Brightness.dark:
|
||||
if (selected)
|
||||
return themeData.accentColor;
|
||||
if (onPressed == null)
|
||||
return Colors.white30;
|
||||
return null; // use default icon theme color unmodified
|
||||
}
|
||||
assert(themeData.brightness != null);
|
||||
return null;
|
||||
}
|
||||
|
||||
TextStyle _getTextStyle(ThemeData themeData) {
|
||||
final TextStyle result = themeData.textTheme.body2;
|
||||
if (selected) {
|
||||
switch (themeData.brightness) {
|
||||
case Brightness.light:
|
||||
return result.copyWith(color: themeData.primaryColor);
|
||||
case Brightness.dark:
|
||||
return result.copyWith(color: themeData.accentColor);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
|
||||
final List<Widget> children = <Widget>[];
|
||||
if (icon != null) {
|
||||
children.add(
|
||||
new Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: new IconTheme.merge(
|
||||
context: context,
|
||||
data: new IconThemeData(
|
||||
color: _getIconColor(themeData),
|
||||
size: 24.0
|
||||
),
|
||||
child: icon
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
if (child != null) {
|
||||
children.add(
|
||||
new Expanded(
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: new AnimatedDefaultTextStyle(
|
||||
style: _getTextStyle(themeData),
|
||||
duration: kThemeChangeDuration,
|
||||
child: child
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return new MergeSemantics(
|
||||
child: new Container(
|
||||
height: 48.0,
|
||||
child: new InkWell(
|
||||
onTap: onPressed,
|
||||
child: new Row(children: children)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -5,8 +5,11 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
import 'constants.dart';
|
||||
import 'debug.dart';
|
||||
import 'icon_theme.dart';
|
||||
import 'icon_theme_data.dart';
|
||||
import 'ink_well.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
@ -28,7 +31,20 @@ enum MaterialListType {
|
||||
twoLine,
|
||||
|
||||
/// A list tile that contains three lines of text.
|
||||
threeLine
|
||||
threeLine,
|
||||
}
|
||||
|
||||
/// Defines the title font used for [ListTile] descendants of a [ListTileTheme].
|
||||
///
|
||||
/// List tiles that appear in a [Drawer] use the theme's [TextTheme.body2]
|
||||
/// text style, which is a little smaller than the theme's [TextTheme.subhead]
|
||||
/// text style, which is used by default.
|
||||
enum ListTileStyle {
|
||||
// Use a title font that's appropriate for a [ListTile] in a list.
|
||||
list,
|
||||
|
||||
// Use a title font that's appropriate for a [ListTile] that appears in a [Drawer].
|
||||
drawer,
|
||||
}
|
||||
|
||||
/// The vertical extent of the different types of material list tiles.
|
||||
@ -46,6 +62,64 @@ Map<MaterialListType, double> kListTileExtent = const <MaterialListType, double>
|
||||
MaterialListType.threeLine: 88.0,
|
||||
};
|
||||
|
||||
/// An inherited widget that defines color and style parameters for [ListTile]s
|
||||
/// in this widget's subtree.
|
||||
///
|
||||
/// Values specified here are used for [ListTile] properties that are not given
|
||||
/// an explicit non-null value.
|
||||
///
|
||||
/// The [Drawer] widget specifies a tile theme for its children which sets
|
||||
/// [style] to [ListTileStyle.drawer].
|
||||
class ListTileTheme extends InheritedWidget {
|
||||
/// Creates an inherited widget that defines color and style parameters for [ListTile]s.
|
||||
const ListTileTheme({
|
||||
Key key,
|
||||
this.dense: false,
|
||||
this.style: ListTileStyle.list,
|
||||
this.selectedColor,
|
||||
this.iconColor,
|
||||
this.textColor,
|
||||
Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// If true then [ListTile]s will have the vertically dense layout.
|
||||
final bool dense;
|
||||
|
||||
/// If specified, [style] defines the font used for [ListTile] titles.
|
||||
final ListTileStyle style;
|
||||
|
||||
/// If specified, the color used for icons and text when a [ListTile] is selected.
|
||||
final Color selectedColor;
|
||||
|
||||
/// If specified, the icon color used for enabled [ListTile]s that are not selected.
|
||||
final Color iconColor;
|
||||
|
||||
/// If specified, the text color used for enabled [ListTile]s that are not selected.
|
||||
final Color textColor;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// ListTileTheme theme = ListTileTheme.of(context);
|
||||
/// ```
|
||||
static ListTileTheme of(BuildContext context) {
|
||||
final ListTileTheme result = context.inheritFromWidgetOfExactType(ListTileTheme);
|
||||
return result ?? new ListTileTheme();
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ListTileTheme oldTheme) {
|
||||
return dense != oldTheme.dense
|
||||
|| style != oldTheme.style
|
||||
|| selectedColor != oldTheme.selectedColor
|
||||
|| iconColor != oldTheme.iconColor
|
||||
|| textColor != oldTheme.textColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A single fixed-height row that typically contains some text as well as
|
||||
/// a leading or trailing icon.
|
||||
///
|
||||
@ -69,14 +143,15 @@ Map<MaterialListType, double> kListTileExtent = const <MaterialListType, double>
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kListTileExtent], which defines the ListTile sizes.
|
||||
/// * [ListTileTheme], which defines visual properties for [ListTile]s.
|
||||
/// * [ListView], which can display an arbitrary number of [ListTile]s
|
||||
/// in a scrolling list.
|
||||
/// * [Card], which can be used with [Column] to show a few [ListTile]s.
|
||||
/// * [CircleAvatar], which shows an icon representing a person and is often
|
||||
/// used as the [leading] element of a ListTile.
|
||||
/// * [Card], which can be used with [Column] to show a few [ListTile]s.
|
||||
/// * [Divider], which can be used to separate [ListTile]s.
|
||||
/// * [ListTile.divideTiles], a utility for inserting [Divider]s in between [ListTile]s.
|
||||
/// * [kListTileExtent], which defines the ListTile sizes.
|
||||
/// * <https://material.google.com/components/lists.html>
|
||||
class ListTile extends StatelessWidget {
|
||||
/// Creates a list tile.
|
||||
@ -91,15 +166,20 @@ class ListTile extends StatelessWidget {
|
||||
this.subtitle,
|
||||
this.trailing,
|
||||
this.isThreeLine: false,
|
||||
this.dense: false,
|
||||
this.dense,
|
||||
this.enabled: true,
|
||||
this.onTap,
|
||||
this.onLongPress
|
||||
}) : super(key: key);
|
||||
this.onLongPress,
|
||||
this.selected: false,
|
||||
}) : super(key: key) {
|
||||
assert(isThreeLine != null);
|
||||
assert(enabled != null);
|
||||
assert(selected != null);
|
||||
}
|
||||
|
||||
/// A widget to display before the title.
|
||||
///
|
||||
/// Typically a [CircleAvatar] widget.
|
||||
/// Typically an [Icon] or a [CircleAvatar] widget.
|
||||
final Widget leading;
|
||||
|
||||
/// The primary content of the list tile.
|
||||
@ -124,6 +204,8 @@ class ListTile extends StatelessWidget {
|
||||
final bool isThreeLine;
|
||||
|
||||
/// Whether this list tile is part of a vertically dense list.
|
||||
///
|
||||
/// If this property is null then its value is based on [ListTileTheme.dense].
|
||||
final bool dense;
|
||||
|
||||
/// Whether this list tile is interactive.
|
||||
@ -143,6 +225,12 @@ class ListTile extends StatelessWidget {
|
||||
/// Inoperative if [enabled] is false.
|
||||
final GestureLongPressCallback onLongPress;
|
||||
|
||||
/// If this tile is also [enabled] then icons and text are rendered with the same color.
|
||||
///
|
||||
/// By default the selected color is the theme's primary color. The selected color
|
||||
/// can be overridden with a [ListTileTheme].
|
||||
final bool selected;
|
||||
|
||||
/// Add a one pixel border in between each tile. If color isn't specified the
|
||||
/// [ThemeData.dividerColor] of the context's [Theme] is used.
|
||||
///
|
||||
@ -174,50 +262,103 @@ class ListTile extends StatelessWidget {
|
||||
yield tile;
|
||||
}
|
||||
|
||||
TextStyle _primaryTextStyle(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final TextStyle style = theme.textTheme.subhead;
|
||||
if (!enabled) {
|
||||
final Color color = theme.disabledColor;
|
||||
return dense ? style.copyWith(fontSize: 13.0, color: color) : style.copyWith(color: color);
|
||||
Color _iconColor(ThemeData theme, ListTileTheme tileTheme) {
|
||||
if (!enabled)
|
||||
return theme.disabledColor;
|
||||
|
||||
if (selected && tileTheme?.selectedColor != null)
|
||||
return tileTheme.selectedColor;
|
||||
|
||||
if (!selected && tileTheme?.iconColor != null)
|
||||
return tileTheme.iconColor;
|
||||
|
||||
switch (theme.brightness) {
|
||||
case Brightness.light:
|
||||
return selected ? theme.primaryColor : Colors.black45;
|
||||
case Brightness.dark:
|
||||
return selected ? theme.accentColor : null; // null - use current icon theme color
|
||||
}
|
||||
return dense ? style.copyWith(fontSize: 13.0) : style;
|
||||
assert(theme.brightness != null);
|
||||
return null;
|
||||
}
|
||||
|
||||
TextStyle _secondaryTextStyle(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final Color color = theme.textTheme.caption.color;
|
||||
Color _textColor(ThemeData theme, ListTileTheme tileTheme, Color defaultColor) {
|
||||
if (!enabled)
|
||||
return theme.disabledColor;
|
||||
|
||||
if (selected && tileTheme?.selectedColor != null)
|
||||
return tileTheme.selectedColor;
|
||||
|
||||
if (!selected && tileTheme?.textColor != null)
|
||||
return tileTheme.textColor;
|
||||
|
||||
if (selected) {
|
||||
switch (theme.brightness) {
|
||||
case Brightness.light:
|
||||
return theme.primaryColor;
|
||||
case Brightness.dark:
|
||||
return theme.accentColor;
|
||||
}
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
bool _denseLayout(ListTileTheme tileTheme) {
|
||||
return dense != null ? dense : (tileTheme?.dense ?? false);
|
||||
}
|
||||
|
||||
TextStyle _titleTextStyle(ThemeData theme, ListTileTheme tileTheme) {
|
||||
final TextStyle style = tileTheme?.style == ListTileStyle.drawer
|
||||
? theme.textTheme.body2
|
||||
: theme.textTheme.subhead;
|
||||
final Color color = _textColor(theme, tileTheme, style.color);
|
||||
return _denseLayout(tileTheme)
|
||||
? style.copyWith(fontSize: 13.0, color: color)
|
||||
: style.copyWith(color: color);
|
||||
}
|
||||
|
||||
TextStyle _subtitleTextStyle(ThemeData theme, ListTileTheme tileTheme) {
|
||||
final TextStyle style = theme.textTheme.body1;
|
||||
return dense ? style.copyWith(color: color, fontSize: 12.0) : style.copyWith(color: color);
|
||||
final Color color = _textColor(theme, tileTheme, theme.textTheme.caption.color);
|
||||
return _denseLayout(tileTheme)
|
||||
? style.copyWith(color: color, fontSize: 12.0)
|
||||
: style.copyWith(color: color);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final ListTileTheme tileTheme = ListTileTheme.of(context);
|
||||
|
||||
final bool isTwoLine = !isThreeLine && subtitle != null;
|
||||
final bool isOneLine = !isThreeLine && !isTwoLine;
|
||||
double tileHeight;
|
||||
if (isOneLine)
|
||||
tileHeight = dense ? 48.0 : 56.0;
|
||||
tileHeight = _denseLayout(tileTheme) ? 48.0 : 56.0;
|
||||
else if (isTwoLine)
|
||||
tileHeight = dense ? 60.0 : 72.0;
|
||||
tileHeight = _denseLayout(tileTheme) ? 60.0 : 72.0;
|
||||
else
|
||||
tileHeight = dense ? 76.0 : 88.0;
|
||||
tileHeight = _denseLayout(tileTheme) ? 76.0 : 88.0;
|
||||
|
||||
// Overall, the list tile is a Row() with these children.
|
||||
final List<Widget> children = <Widget>[];
|
||||
|
||||
if (leading != null) {
|
||||
children.add(new Container(
|
||||
margin: const EdgeInsets.only(right: 16.0),
|
||||
width: 40.0,
|
||||
alignment: FractionalOffset.centerLeft,
|
||||
child: leading
|
||||
children.add(new IconTheme.merge(
|
||||
context: context,
|
||||
data: new IconThemeData(color: _iconColor(theme, tileTheme)),
|
||||
child: new Container(
|
||||
margin: const EdgeInsets.only(right: 16.0),
|
||||
width: 40.0,
|
||||
alignment: FractionalOffset.centerLeft,
|
||||
child: leading
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
final Widget primaryLine = new AnimatedDefaultTextStyle(
|
||||
style: _primaryTextStyle(context),
|
||||
style: _titleTextStyle(theme, tileTheme),
|
||||
duration: kThemeChangeDuration,
|
||||
child: title ?? new Container()
|
||||
);
|
||||
@ -229,22 +370,22 @@ class ListTile extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
primaryLine,
|
||||
new AnimatedDefaultTextStyle(
|
||||
style: _secondaryTextStyle(context),
|
||||
style: _subtitleTextStyle(theme, tileTheme),
|
||||
duration: kThemeChangeDuration,
|
||||
child: subtitle
|
||||
child: subtitle,
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
children.add(new Expanded(
|
||||
child: center
|
||||
child: center,
|
||||
));
|
||||
|
||||
if (trailing != null) {
|
||||
children.add(new Container(
|
||||
margin: const EdgeInsets.only(left: 16.0),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
child: trailing
|
||||
child: trailing,
|
||||
));
|
||||
}
|
||||
|
||||
@ -254,9 +395,7 @@ class ListTile extends StatelessWidget {
|
||||
child: new Container(
|
||||
height: tileHeight,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: new Row(
|
||||
children: children
|
||||
)
|
||||
child: new Row(children: children),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('AboutDrawerItem control test', (WidgetTester tester) async {
|
||||
testWidgets('AboutListTile control test', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
title: 'Pirate app',
|
||||
@ -20,7 +20,7 @@ void main() {
|
||||
drawer: new Drawer(
|
||||
child: new ListView(
|
||||
children: <Widget>[
|
||||
new AboutDrawerItem(
|
||||
new AboutListTile(
|
||||
applicationVersion: '0.1.2',
|
||||
applicationIcon: const FlutterLogo(),
|
||||
applicationLegalese: 'I am the very model of a modern major general.',
|
||||
@ -67,12 +67,12 @@ void main() {
|
||||
|
||||
testWidgets('About box logic defaults to executable name for app name', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Material(child: new AboutDrawerItem()),
|
||||
new Material(child: new AboutListTile()),
|
||||
);
|
||||
expect(find.text('About sky_shell'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('AboutDrawerItem control test', (WidgetTester tester) async {
|
||||
testWidgets('AboutListTile control test', (WidgetTester tester) async {
|
||||
final List<String> log = <String>[];
|
||||
|
||||
Future<Null> licenseFuture;
|
||||
|
@ -20,9 +20,9 @@ void main() {
|
||||
child: new Text('header')
|
||||
)
|
||||
),
|
||||
new DrawerItem(
|
||||
icon: new Icon(Icons.archive),
|
||||
child: new Text('Archive')
|
||||
new ListTile(
|
||||
leading: new Icon(Icons.archive),
|
||||
title: new Text('Archive')
|
||||
)
|
||||
]
|
||||
)
|
||||
|
@ -1,50 +0,0 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('ListTile control test', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Center(
|
||||
child: new ListTile(
|
||||
leading: new Icon(Icons.thumb_up),
|
||||
title: new Text('Title'),
|
||||
subtitle: new Text('Subtitle'),
|
||||
trailing: new Icon(Icons.info),
|
||||
enabled: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(find.text('Title'), findsOneWidget);
|
||||
expect(find.text('Subtitle'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('ListTile control test', (WidgetTester tester) async {
|
||||
final List<String> titles = <String>[ 'first', 'second', 'third' ];
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new ListView(
|
||||
children: ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: titles.map((String title) => new ListTile(title: new Text(title))),
|
||||
).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(find.text('first'), findsOneWidget);
|
||||
expect(find.text('second'), findsOneWidget);
|
||||
expect(find.text('third'), findsOneWidget);
|
||||
});
|
||||
}
|
234
packages/flutter/test/material/list_tile_test.dart
Normal file
234
packages/flutter/test/material/list_tile_test.dart
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class TestIcon extends StatefulWidget {
|
||||
TestIcon({ Key key }) : super(key: key);
|
||||
|
||||
@override
|
||||
TestIconState createState() => new TestIconState();
|
||||
}
|
||||
|
||||
class TestIconState extends State<TestIcon> {
|
||||
IconThemeData iconTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
iconTheme = IconTheme.of(context);
|
||||
return new Icon(Icons.add);
|
||||
}
|
||||
}
|
||||
|
||||
class TestText extends StatefulWidget {
|
||||
TestText(this.text, { Key key }) : super(key: key);
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
TestTextState createState() => new TestTextState();
|
||||
}
|
||||
|
||||
class TestTextState extends State<TestText> {
|
||||
TextStyle textStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
textStyle = DefaultTextStyle.of(context).style;
|
||||
return new Text(config.text);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('ListTile geometry', (WidgetTester tester) async {
|
||||
// See https://material.io/guidelines/components/lists.html
|
||||
|
||||
bool hasSubtitle;
|
||||
|
||||
Widget buildFrame({ bool dense: false, bool isTwoLine: false, bool isThreeLine: false }) {
|
||||
hasSubtitle = isTwoLine || isThreeLine;
|
||||
return new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Center(
|
||||
child: new ListTile(
|
||||
leading: new Text('leading'),
|
||||
title: new Text('title'),
|
||||
subtitle: hasSubtitle ? new Text('subtitle') : null,
|
||||
trailing: new Text('trailing'),
|
||||
dense: dense,
|
||||
isThreeLine: isThreeLine,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void testChildren() {
|
||||
expect(find.text('leading'), findsOneWidget);
|
||||
expect(find.text('title'), findsOneWidget);
|
||||
if (hasSubtitle)
|
||||
expect(find.text('subtitle'), findsOneWidget);
|
||||
expect(find.text('trailing'), findsOneWidget);
|
||||
}
|
||||
|
||||
double left(String text) => tester.getTopLeft(find.text(text)).x;
|
||||
double right(String text) => tester.getTopRight(find.text(text)).x;
|
||||
double top(String text) => tester.getTopLeft(find.text(text)).y;
|
||||
double bottom(String text) => tester.getBottomLeft(find.text(text)).y;
|
||||
|
||||
// 16.0 padding to the left and right of the leading and trailing widgets
|
||||
void testHorizontalGeometry() {
|
||||
expect(left('leading'), 16.0);
|
||||
expect(left('title'), 72.0);
|
||||
if (hasSubtitle)
|
||||
expect(left('subtitle'), 72.0);
|
||||
expect(left('title'), right('leading') + 16.0);
|
||||
expect(right('trailing'), 800.0 - 16.0);
|
||||
}
|
||||
|
||||
void testVerticalGeometry(double expectedHeight) {
|
||||
expect(tester.getSize(find.byType(ListTile)), new Size(800.0, expectedHeight));
|
||||
if (hasSubtitle)
|
||||
expect(top('subtitle'), bottom('title'));
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame());
|
||||
testChildren();
|
||||
testHorizontalGeometry();
|
||||
testVerticalGeometry(56.0);
|
||||
|
||||
await tester.pumpWidget(buildFrame(dense: true));
|
||||
testChildren();
|
||||
testHorizontalGeometry();
|
||||
testVerticalGeometry(48.0);
|
||||
|
||||
await tester.pumpWidget(buildFrame(isTwoLine: true));
|
||||
testChildren();
|
||||
testHorizontalGeometry();
|
||||
testVerticalGeometry(72.0);
|
||||
|
||||
await tester.pumpWidget(buildFrame(isTwoLine: true, dense: true));
|
||||
testChildren();
|
||||
testHorizontalGeometry();
|
||||
testVerticalGeometry(60.0);
|
||||
|
||||
await tester.pumpWidget(buildFrame(isThreeLine: true));
|
||||
testChildren();
|
||||
testHorizontalGeometry();
|
||||
testVerticalGeometry(88.0);
|
||||
|
||||
await tester.pumpWidget(buildFrame(isThreeLine: true, dense: true));
|
||||
testChildren();
|
||||
testHorizontalGeometry();
|
||||
testVerticalGeometry(76.0);
|
||||
});
|
||||
|
||||
testWidgets('ListTile.divideTiles', (WidgetTester tester) async {
|
||||
final List<String> titles = <String>[ 'first', 'second', 'third' ];
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new ListView(
|
||||
children: ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: titles.map((String title) => new ListTile(title: new Text(title))),
|
||||
).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(find.text('first'), findsOneWidget);
|
||||
expect(find.text('second'), findsOneWidget);
|
||||
expect(find.text('third'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('ListTileTheme', (WidgetTester tester) async {
|
||||
final Key titleKey = new UniqueKey();
|
||||
final Key subtitleKey = new UniqueKey();
|
||||
ThemeData theme;
|
||||
|
||||
Widget buildFrame({
|
||||
bool enabled: true,
|
||||
bool dense: false,
|
||||
bool selected: false,
|
||||
Color selectedColor,
|
||||
Color iconColor,
|
||||
Color textColor,
|
||||
}) {
|
||||
return new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Center(
|
||||
child: new ListTileTheme(
|
||||
dense: dense,
|
||||
selectedColor: selectedColor,
|
||||
iconColor: iconColor,
|
||||
textColor: textColor,
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
theme = Theme.of(context);
|
||||
return new ListTile(
|
||||
enabled: enabled,
|
||||
selected: selected,
|
||||
leading: new TestIcon(),
|
||||
title: new TestText('title', key: titleKey),
|
||||
subtitle: new TestText('subtitle', key: subtitleKey),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const Color green = const Color(0xFF00FF00);
|
||||
const Color red = const Color(0xFFFF0000);
|
||||
|
||||
Color iconColor() => tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color;
|
||||
Color textColor(Key key) => tester.state<TestTextState>(find.byKey(key)).textStyle.color;
|
||||
|
||||
// A selected ListTile's leading and text get the primary color by default
|
||||
await(tester.pumpWidget(buildFrame(selected: true)));
|
||||
await(tester.pump(const Duration(milliseconds: 300))); // DefaultTextStyle changes animate
|
||||
expect(iconColor(), theme.primaryColor);
|
||||
expect(textColor(titleKey), theme.primaryColor);
|
||||
expect(textColor(subtitleKey), theme.primaryColor);
|
||||
|
||||
// A selected ListTile's leading and text get the ListTileTheme's selectedColor
|
||||
await(tester.pumpWidget(buildFrame(selected: true, selectedColor: green)));
|
||||
await(tester.pump(const Duration(milliseconds: 300))); // DefaultTextStyle changes animate
|
||||
expect(iconColor(), green);
|
||||
expect(textColor(titleKey), green);
|
||||
expect(textColor(subtitleKey), green);
|
||||
|
||||
// An unselected ListTile's leading icon gets the ListTileTheme's iconColor
|
||||
// An unselected ListTile's title texts get the ListTileTheme's textColor
|
||||
await(tester.pumpWidget(buildFrame(iconColor: red, textColor: green)));
|
||||
await(tester.pump(const Duration(milliseconds: 300))); // DefaultTextStyle changes animate
|
||||
expect(iconColor(), red);
|
||||
expect(textColor(titleKey), green);
|
||||
expect(textColor(subtitleKey), green);
|
||||
|
||||
// If the item is disabled it's rendered with the theme's disabled color.
|
||||
await(tester.pumpWidget(buildFrame(enabled: false)));
|
||||
await(tester.pump(const Duration(milliseconds: 300))); // DefaultTextStyle changes animate
|
||||
expect(iconColor(), theme.disabledColor);
|
||||
expect(textColor(titleKey), theme.disabledColor);
|
||||
expect(textColor(subtitleKey), theme.disabledColor);
|
||||
|
||||
// If the item is disabled it's rendered with the theme's disabled color.
|
||||
// Even if it's selected.
|
||||
await(tester.pumpWidget(buildFrame(enabled: false, selected: true)));
|
||||
await(tester.pump(const Duration(milliseconds: 300))); // DefaultTextStyle changes animate
|
||||
expect(iconColor(), theme.disabledColor);
|
||||
expect(textColor(titleKey), theme.disabledColor);
|
||||
expect(textColor(subtitleKey), theme.disabledColor);
|
||||
});
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user