Commit 8edeecbc authored by Hans Muller's avatar Hans Muller

Merge pull request #2216 from HansMuller/stocks_demo

Add CheckedPopupMenuitem, update the Stocks demo
parents 29e55735 3d377305
......@@ -21,7 +21,7 @@ class ListDemoState extends State<ListDemo> {
ScaffoldFeatureController _bottomSheet;
ListDemoItemSize _itemSize = ListDemoItemSize.threeLine;
bool _isDense = true;
bool _dense = true;
bool _showAvatar = true;
bool _showIcon = false;
bool _reverseSort = false;
......@@ -47,7 +47,7 @@ class ListDemoState extends State<ListDemo> {
alignItems: FlexAlignItems.stretch,
children: <Widget>[
new ListItem(
isDense: true,
dense: true,
primary: new Text('One-line'),
right: new Radio<ListDemoItemSize>(
value: ListDemoItemSize.oneLine,
......@@ -56,7 +56,7 @@ class ListDemoState extends State<ListDemo> {
)
),
new ListItem(
isDense: true,
dense: true,
primary: new Text('Two-line'),
right: new Radio<ListDemoItemSize>(
value: ListDemoItemSize.twoLine,
......@@ -65,7 +65,7 @@ class ListDemoState extends State<ListDemo> {
)
),
new ListItem(
isDense: true,
dense: true,
primary: new Text('Three-line'),
right: new Radio<ListDemoItemSize>(
value: ListDemoItemSize.threeLine,
......@@ -74,7 +74,7 @@ class ListDemoState extends State<ListDemo> {
)
),
new ListItem(
isDense: true,
dense: true,
primary: new Text('Show Avatar'),
right: new Checkbox(
value: _showAvatar,
......@@ -87,7 +87,7 @@ class ListDemoState extends State<ListDemo> {
)
),
new ListItem(
isDense: true,
dense: true,
primary: new Text('Show Icon'),
right: new Checkbox(
value: _showIcon,
......@@ -100,13 +100,13 @@ class ListDemoState extends State<ListDemo> {
)
),
new ListItem(
isDense: true,
dense: true,
primary: new Text('Dense Layout'),
right: new Checkbox(
value: _isDense,
value: _dense,
onChanged: (bool value) {
setState(() {
_isDense = value;
_dense = value;
});
_bottomSheet?.setState(() { });
}
......@@ -131,7 +131,7 @@ class ListDemoState extends State<ListDemo> {
}
return new ListItem(
isThreeLine: _itemSize == ListDemoItemSize.threeLine,
isDense: _isDense,
dense: _dense,
left: _showAvatar ? new CircleAvatar(child: new Text(item)) : null,
primary: new Text('This item represents $item'),
secondary: secondary,
......@@ -140,7 +140,7 @@ class ListDemoState extends State<ListDemo> {
}
Widget build(BuildContext context) {
final String layoutText = _isDense ? " \u2013 Dense" : "";
final String layoutText = _dense ? " \u2013 Dense" : "";
String itemSizeText;
switch(_itemSize) {
case ListDemoItemSize.oneLine:
......@@ -176,7 +176,7 @@ class ListDemoState extends State<ListDemo> {
]
),
body: new Block(
padding: new EdgeDims.all(_isDense ? 4.0 : 8.0),
padding: new EdgeDims.all(_dense ? 4.0 : 8.0),
children: items.map((String item) => buildListItem(context, item)).toList()
)
);
......
......@@ -94,7 +94,7 @@ class MenuDemoState extends State<MenuDemo> {
child: new Text('Context menu item one')
),
new PopupMenuItem(
disabled: true,
enabled: false,
child: new Text('A disabled menu item')
),
new PopupMenuItem(
......@@ -176,34 +176,26 @@ class MenuDemoState extends State<MenuDemo> {
right: new PopupMenuButton<String>(
onSelected: showCheckedMenuSelections,
items: <PopupMenuItem>[
new PopupMenuItem(
new CheckedPopupMenuItem(
value: _checkedValue1,
child: new ListItem(
left: new Icon(icon: isChecked(_checkedValue1) ? 'action/done' : null),
primary: new Text(_checkedValue1)
)
checked: isChecked(_checkedValue1),
child: new Text(_checkedValue1)
),
new PopupMenuItem(
value: _checkedValue2,
child: new ListItem(
left: new Icon(icon: isChecked(_checkedValue2) ? 'action/done' : null),
primary: new Text(_checkedValue2)
)
new CheckedPopupMenuItem(
enabled: false,
checked: isChecked(_checkedValue2),
child: new Text(_checkedValue2)
),
new PopupMenuItem(
new CheckedPopupMenuItem(
value: _checkedValue3,
child: new ListItem(
left: new Icon(icon: isChecked(_checkedValue3) ? 'action/done' : null),
primary: new Text(_checkedValue3)
)
checked: isChecked(_checkedValue3),
child: new Text(_checkedValue3)
),
new PopupMenuItem(
new CheckedPopupMenuItem(
value: _checkedValue4,
child: new ListItem(
left: new Icon(icon: isChecked(_checkedValue4) ? 'action/done' : null),
primary: new Text(_checkedValue4)
)
),
checked: isChecked(_checkedValue4),
child: new Text(_checkedValue4)
)
]
)
)
......
......@@ -6,6 +6,7 @@ material-design-icons:
- name: action/account_balance
- name: action/assessment
- name: action/backup
- name: action/done
- name: action/help
- name: action/picture_in_picture
- name: action/search
......
......@@ -6,18 +6,50 @@ import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show debugDumpRenderTree, debugDumpLayerTree, debugDumpSemanticsTree;
import 'package:flutter/scheduler.dart' show timeDilation;
import 'stock_data.dart';
import 'stock_list.dart';
import 'stock_menu.dart';
import 'stock_strings.dart';
import 'stock_symbol_viewer.dart';
import 'stock_types.dart';
typedef void ModeUpdater(StockMode mode);
enum _StockMenuItem { autorefresh, refresh, speedUp, speedDown }
enum StockHomeTab { market, portfolio }
class _NotImplementedDialog extends StatelessComponent {
Widget build(BuildContext context) {
return new Dialog(
title: new Text('Not Implemented'),
content: new Text('This feature has not yet been implemented.'),
actions: <Widget>[
new FlatButton(
child: new Row(
children: <Widget>[
new Icon(
icon: 'device/dvr',
size: IconSize.s18
),
new Container(
width: 8.0
),
new Text('DUMP APP TO CONSOLE'),
]
),
onPressed: () { debugDumpApp(); }
),
new FlatButton(
child: new Text('OH WELL'),
onPressed: () {
Navigator.pop(context, false);
}
)
]
);
}
}
class StockHome extends StatefulComponent {
const StockHome(this.stocks, this.symbols, this.configuration, this.updater);
......@@ -34,6 +66,7 @@ class StockHomeState extends State<StockHome> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
bool _isSearching = false;
InputValue _searchQuery = InputValue.empty;
bool _autorefresh = false;
void _handleSearchBegin() {
ModalRoute.of(context).addLocalHistoryEntry(new LocalHistoryEntry(
......@@ -59,24 +92,31 @@ class StockHomeState extends State<StockHome> {
});
}
bool _autorefresh = false;
void _handleAutorefreshChanged(bool value) {
setState(() {
_autorefresh = value;
});
}
void _handleStockModeChange(StockMode value) {
if (config.updater != null)
config.updater(config.configuration.copyWith(stockMode: value));
}
void _handleMenuShow() {
showStockMenu(
context: context,
autorefresh: _autorefresh,
onAutorefreshChanged: _handleAutorefreshChanged
);
void _handleStockMenu(BuildContext context, _StockMenuItem value) {
switch(value) {
case _StockMenuItem.autorefresh:
setState(() {
_autorefresh = !_autorefresh;
});
break;
case _StockMenuItem.refresh:
showDialog(
context: context,
child: new _NotImplementedDialog()
);
break;
case _StockMenuItem.speedUp:
timeDilation /= 5.0;
break;
case _StockMenuItem.speedDown:
timeDilation *= 5.0;
break;
}
}
Widget _buildDrawer(BuildContext context) {
......@@ -176,10 +216,27 @@ class StockHomeState extends State<StockHome> {
onPressed: _handleSearchBegin,
tooltip: 'Search'
),
new IconButton(
icon: "navigation/more_vert",
onPressed: _handleMenuShow,
tooltip: 'Show menu'
new PopupMenuButton<_StockMenuItem>(
onSelected: (_StockMenuItem value) { _handleStockMenu(context, value); },
items: <PopupMenuItem>[
new CheckedPopupMenuItem(
value: _StockMenuItem.autorefresh,
checked: _autorefresh,
child: new Text('Autorefresh')
),
new PopupMenuItem(
value: _StockMenuItem.refresh,
child: new Text('Refresh')
),
new PopupMenuItem(
value: _StockMenuItem.speedUp,
child: new Text('Increase animation speed')
),
new PopupMenuItem(
value: _StockMenuItem.speedDown,
child: new Text('Decrease animation speed')
)
]
)
],
tabBar: new TabBar<StockHomeTab>(
......
// 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 'dart:async';
import 'dart:ui' as ui show window;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
enum _MenuItems { autorefresh, autorefreshCheckbox, refresh, speedUp, speedDown }
const double _kMenuMargin = 16.0; // 24.0 on tablet
Future showStockMenu({BuildContext context, bool autorefresh, ValueChanged<bool> onAutorefreshChanged }) async {
StateSetter autorefreshStateSetter;
switch (await showMenu(
context: context,
position: new ModalPosition(
right: ui.window.padding.right + _kMenuMargin,
top: ui.window.padding.top + _kMenuMargin
),
items: <PopupMenuItem>[
new PopupMenuItem(
value: _MenuItems.autorefresh,
child: new Row(
children: <Widget>[
new Flexible(child: new Text('Autorefresh')),
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
autorefreshStateSetter = setState;
return new Checkbox(
value: autorefresh,
onChanged: (bool value) {
setState(() {
autorefresh = value;
});
Navigator.pop(context, _MenuItems.autorefreshCheckbox);
}
);
}
)
]
)
),
new PopupMenuItem(
value: _MenuItems.refresh,
child: new Text('Refresh')
),
new PopupMenuItem(
value: _MenuItems.speedUp,
child: new Text('Increase animation speed')
),
new PopupMenuItem(
value: _MenuItems.speedDown,
child: new Text('Decrease animation speed')
),
]
)) {
case _MenuItems.autorefresh:
autorefreshStateSetter(() {
autorefresh = !autorefresh;
});
continue autorefreshNotify;
autorefreshNotify:
case _MenuItems.autorefreshCheckbox:
onAutorefreshChanged(autorefresh);
break;
case _MenuItems.speedUp:
timeDilation /= 5.0;
break;
case _MenuItems.speedDown:
timeDilation *= 5.0;
break;
case _MenuItems.refresh:
await showDialog(
context: context,
child: new Dialog(
title: new Text('Not Implemented'),
content: new Text('This feature has not yet been implemented.'),
actions: <Widget>[
new FlatButton(
child: new Row(
children: <Widget>[
new Icon(
icon: 'device/dvr',
size: IconSize.s18
),
new Container(
width: 8.0
),
new Text('DUMP APP TO CONSOLE'),
]
),
onPressed: () { debugDumpApp(); }
),
new FlatButton(
child: new Text('OH WELL'),
onPressed: () {
Navigator.pop(context, false);
}
),
]
)
);
break;
default:
// menu was canceled.
}
}
......@@ -11,7 +11,7 @@ import 'theme.dart';
/// Icons are defined with the [left] and [right] parameters. The first line of text
/// is not optional and is specified with [primary]. The value of [secondary] will
/// occupy the space allocated for an aditional line of text, or two lines if
/// isThreeLine: true is specified. If isDense: true is specified then the overall
/// isThreeLine: true is specified. If dense: true is specified then the overall
/// height of this list item and the size of the DefaultTextStyles that wrap
/// the [primary] and [secondary] widget are reduced.
class ListItem extends StatelessComponent {
......@@ -22,7 +22,8 @@ class ListItem extends StatelessComponent {
this.secondary,
this.right,
this.isThreeLine: false,
this.isDense: false,
this.dense: false,
this.enabled: true,
this.onTap,
this.onLongPress
}) : super(key: key) {
......@@ -35,20 +36,26 @@ class ListItem extends StatelessComponent {
final Widget secondary;
final Widget right;
final bool isThreeLine;
final bool isDense;
final bool dense;
final bool enabled;
final GestureTapCallback onTap;
final GestureLongPressCallback onLongPress;
TextStyle primaryTextStyle(BuildContext context) {
final TextStyle style = Theme.of(context).text.subhead;
return isDense ? style.copyWith(fontSize: 13.0) : style;
final ThemeData theme = Theme.of(context);
final TextStyle style = theme.text.subhead;
if (!enabled) {
final Color color = theme.disabledColor;
return dense ? style.copyWith(fontSize: 13.0, color: color) : style.copyWith(color: color);
}
return dense ? style.copyWith(fontSize: 13.0) : style;
}
TextStyle secondaryTextStyle(BuildContext context) {
final ThemeData theme = Theme.of(context);
final Color color = theme.text.caption.color;
final TextStyle style = theme.text.body1;
return isDense ? style.copyWith(color: color, fontSize: 12.0) : style.copyWith(color: color);
return dense ? style.copyWith(color: color, fontSize: 12.0) : style.copyWith(color: color);
}
Widget build(BuildContext context) {
......@@ -56,15 +63,15 @@ class ListItem extends StatelessComponent {
final bool isOneLine = !isThreeLine && !isTwoLine;
double itemHeight;
if (isOneLine)
itemHeight = isDense ? 48.0 : 56.0;
itemHeight = dense ? 48.0 : 56.0;
else if (isTwoLine)
itemHeight = isDense ? 60.0 : 72.0;
itemHeight = dense ? 60.0 : 72.0;
else
itemHeight = isDense ? 76.0 : 88.0;
itemHeight = dense ? 76.0 : 88.0;
double iconMarginTop = 0.0;
if (isThreeLine)
iconMarginTop = isDense ? 8.0 : 16.0;
iconMarginTop = dense ? 8.0 : 16.0;
// Overall, the list item is a Row() with these children.
final List<Widget> children = <Widget>[];
......@@ -113,8 +120,8 @@ class ListItem extends StatelessComponent {
}
return new InkWell(
onTap: onTap,
onLongPress: onLongPress,
onTap: enabled ? onTap : null,
onLongPress: enabled ? onLongPress : null,
child: new Container(
height: itemHeight,
padding: const EdgeDims.symmetric(horizontal: 16.0),
......
......@@ -6,8 +6,12 @@ import 'dart:async';
import 'package:flutter/widgets.dart';
import 'icon.dart';
import 'icon_button.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'ink_well.dart';
import 'list_item.dart';
import 'material.dart';
import 'theme.dart';
......@@ -26,22 +30,37 @@ class PopupMenuItem<T> extends StatelessComponent {
PopupMenuItem({
Key key,
this.value,
this.disabled: false,
this.enabled: true,
this.hasDivider: false,
this.child
}) : super(key: key);
final T value;
final bool disabled;
final bool enabled;
final bool hasDivider;
final Widget child;
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
TextStyle style = theme.text.subhead;
if (disabled)
if (!enabled)
style = style.copyWith(color: theme.disabledColor);
Widget item = new DefaultTextStyle(
style: style,
child: new Baseline(
baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
child: child
)
);
if (!enabled) {
final bool isDark = theme.brightness == ThemeBrightness.dark;
item = new IconTheme(
data: new IconThemeData(opacity: isDark ? 0.5 : 0.38),
child: item
);
}
return new MergeSemantics(
child: new Container(
height: _kMenuItemHeight,
......@@ -49,18 +68,31 @@ class PopupMenuItem<T> extends StatelessComponent {
decoration: !hasDivider ? null : new BoxDecoration(
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
),
child: new DefaultTextStyle(
style: style,
child: new Baseline(
baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
child: child
)
)
child: item
)
);
}
}
class CheckedPopupMenuItem<T> extends PopupMenuItem<T> {
CheckedPopupMenuItem({
Key key,
T value,
checked: false,
bool enabled: true,
Widget child
}) : super(
key: key,
value: value,
enabled: enabled,
child: new ListItem(
enabled: enabled,
left: new Icon(icon: checked ? 'action/done' : null),
primary: child
)
);
}
class _PopupMenu<T> extends StatelessComponent {
_PopupMenu({
Key key,
......@@ -80,7 +112,7 @@ class _PopupMenu<T> extends StatelessComponent {
parent: route.animation,
curve: new Interval(start, end)
);
final bool disabled = route.items[i].disabled;
final bool enabled = route.items[i].enabled;
Widget item = route.items[i];
if (route.initialValue != null && route.initialValue == route.items[i].value) {
item = new Container(
......@@ -91,7 +123,7 @@ class _PopupMenu<T> extends StatelessComponent {
children.add(new FadeTransition(
opacity: opacity,
child: new InkWell(
onTap: disabled ? null : () { Navigator.pop(context, route.items[i].value); },
onTap: enabled ? () { Navigator.pop(context, route.items[i].value); } : null,
child: item
)
));
......@@ -298,7 +330,7 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
)
)
.then((T value) {
if (config.onSelected != null)
if (value != null && config.onSelected != null)
config.onSelected(value);
});
}
......
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