From b83eb465fd51fedd9fabb1d2494e66e8621dc1d7 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 19 Apr 2018 16:43:22 -0700 Subject: [PATCH] Updating the Chip demo in the Gallery to include all the new types of chips. (#16522) This chip demo is more interactive than the last one, trying to exercise all of the types of chips for a demo that lets you select different types of "chips" (like tortilla, wood, micro, etc.), and then filter them and select an action on them. --- .../lib/demo/material/chip_demo.dart | 322 ++++++++++++++++-- .../lib/src/material/circle_avatar.dart | 2 +- .../flutter/lib/src/material/theme_data.dart | 2 +- .../test/material/circle_avatar_test.dart | 4 +- 4 files changed, 298 insertions(+), 32 deletions(-) diff --git a/examples/flutter_gallery/lib/demo/material/chip_demo.dart b/examples/flutter_gallery/lib/demo/material/chip_demo.dart index 74f84628e1d..fef353213a7 100644 --- a/examples/flutter_gallery/lib/demo/material/chip_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/chip_demo.dart @@ -4,6 +4,110 @@ import 'package:flutter/material.dart'; +const List _defaultMaterials = const [ + 'poker', + 'tortilla', + 'fish and', + 'micro', + 'wood', +]; + +const List _defaultActions = const [ + 'flake', + 'cut', + 'fragment', + 'splinter', + 'nick', + 'fry', + 'solder', + 'cash in', + 'eat', +]; + +const Map _results = const { + 'flake': 'flaking', + 'cut': 'cutting', + 'fragment': 'fragmenting', + 'splinter': 'splintering', + 'nick': 'nicking', + 'fry': 'frying', + 'solder': 'soldering', + 'cash in': 'cashing in', + 'eat': 'eating', +}; + +const List _defaultTools = const [ + 'hammer', + 'chisel', + 'fryer', + 'fabricator', + 'customer', +]; + +const Map _avatars = const { + 'hammer': 'shrine/vendors/ali-connors.png', + 'chisel': 'shrine/vendors/sandra-adams.jpg', + 'fryer': 'shrine/vendors/zach.jpg', + 'fabricator': 'shrine/vendors/peter-carlsson.png', + 'customer': 'shrine/vendors/16c477b.jpg', +}; + +final Map> _toolActions = >{ + 'hammer': new Set()..addAll(['flake', 'fragment', 'splinter']), + 'chisel': new Set()..addAll(['flake', 'nick', 'splinter']), + 'fryer': new Set()..addAll(['fry']), + 'fabricator': new Set()..addAll(['solder']), + 'customer': new Set()..addAll(['cash in', 'eat']), +}; + +final Map> _materialActions = >{ + 'poker': new Set()..addAll(['cash in']), + 'tortilla': new Set()..addAll(['fry', 'eat']), + 'fish and': new Set()..addAll(['fry', 'eat']), + 'micro': new Set()..addAll(['solder', 'fragment']), + 'wood': new Set()..addAll(['flake', 'cut', 'splinter', 'nick']), +}; + +class _ChipsTile extends StatelessWidget { + const _ChipsTile({ + Key key, + this.label, + this.children, + }) : super(key: key); + + final String label; + final List children; + + // Wraps a list of chips into a ListTile for display as a section in the demo. + @override + Widget build(BuildContext context) { + return new ListTile( + title: new Padding( + padding: const EdgeInsets.only(top: 16.0, bottom: 4.0), + child: new Text(label, textAlign: TextAlign.start), + ), + subtitle: children.isEmpty + ? new Center( + child: new Padding( + padding: const EdgeInsets.all(8.0), + child: new Text( + 'None', + style: Theme.of(context).textTheme.caption.copyWith(fontStyle: FontStyle.italic), + ), + ), + ) + : new Wrap( + children: children + .map((Widget chip) => new Padding( + padding: const EdgeInsets.all(4.0), + child: chip, + )) + .toList(), + ), + ); + } +} + class ChipDemo extends StatefulWidget { static const String routeName = '/material/chip'; @@ -12,43 +116,205 @@ class ChipDemo extends StatefulWidget { } class _ChipDemoState extends State { - bool _showBananas = true; + _ChipDemoState() { + _reset(); + } - void _deleteBananas() { - setState(() { - _showBananas = false; - }); + final Set _materials = new Set(); + String _selectedMaterial = ''; + String _selectedAction = ''; + final Set _tools = new Set(); + final Set _selectedTools = new Set(); + final Set _actions = new Set(); + bool _showShapeBorder = false; + + // Initialize members with the default data. + void _reset() { + _materials.clear(); + _materials.addAll(_defaultMaterials); + _actions.clear(); + _actions.addAll(_defaultActions); + _tools.clear(); + _tools.addAll(_defaultTools); + _selectedMaterial = ''; + _selectedAction = ''; + _selectedTools.clear(); + } + + void _removeMaterial(String name) { + _materials.remove(name); + if (_selectedMaterial == name) { + _selectedMaterial = ''; + } + } + + void _removeTool(String name) { + _tools.remove(name); + _selectedTools.remove(name); + } + + String _capitalize(String name) { + assert(name != null && name.isNotEmpty); + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + + // This converts a String to a unique color, based on the hash value of the + // String object. It takes the bottom 16 bits of the hash, and uses that to + // pick a hue for an HSV color, and then creates the color (with a preset + // saturation and value). This means that any unique strings will also have + // unique colors, but they'll all be readable, since they have the same + // saturation and value. + Color _nameToColor(String name) { + assert(name.length > 1); + final int hash = name.hashCode & 0xffff; + final double hue = 360.0 * hash / (1 << 15); + return new HSVColor.fromAHSV(1.0, hue, 0.4, 0.90).toColor(); + } + + AssetImage _nameToAvatar(String name) { + assert(_avatars.containsKey(name)); + return new AssetImage( + _avatars[name], + package: 'flutter_gallery_assets', + ); + } + + String _createResult() { + if (_selectedAction.isEmpty) { + return ''; + } + return _capitalize(_results[_selectedAction]) + '!'; } @override Widget build(BuildContext context) { - final List chips = [ - const Chip( - label: const Text('Apple') - ), - const Chip( - avatar: const CircleAvatar(child: const Text('B')), - label: const Text('Blueberry'), + final List chips = _materials.map((String name) { + return new Chip( + key: new ValueKey(name), + backgroundColor: _nameToColor(name), + label: new Text(_capitalize(name)), + onDeleted: () { + setState(() { + _removeMaterial(name); + }); + }, + ); + }).toList(); + + final List inputChips = _tools.map((String name) { + return new InputChip( + key: new ValueKey(name), + avatar: new CircleAvatar( + backgroundImage: _nameToAvatar(name), + ), + label: new Text(_capitalize(name)), + onDeleted: () { + setState(() { + _removeTool(name); + }); + }); + }).toList(); + + final List choiceChips = _materials.map((String name) { + return new ChoiceChip( + key: new ValueKey(name), + backgroundColor: _nameToColor(name), + label: new Text(_capitalize(name)), + selected: _selectedMaterial == name, + onSelected: (bool value) { + setState(() { + _selectedMaterial = value ? name : ''; + }); + }, + ); + }).toList(); + + final List filterChips = _defaultTools.map((String name) { + return new FilterChip( + key: new ValueKey(name), + label: new Text(_capitalize(name)), + selected: _tools.contains(name) ? _selectedTools.contains(name) : false, + onSelected: !_tools.contains(name) + ? null + : (bool value) { + setState(() { + if (!value) { + _selectedTools.remove(name); + } else { + _selectedTools.add(name); + } + }); + }, + ); + }).toList(); + + Set allowedActions = new Set(); + if (_selectedMaterial != null && _selectedMaterial.isNotEmpty) { + for (String tool in _selectedTools) { + allowedActions.addAll(_toolActions[tool]); + } + allowedActions = allowedActions.intersection(_materialActions[_selectedMaterial]); + } + + final List actionChips = allowedActions.map((String name) { + return new ActionChip( + label: new Text(_capitalize(name)), + onPressed: () { + setState(() { + _selectedAction = name; + }); + }, + ); + }).toList(); + + final ThemeData theme = Theme.of(context); + final List tiles = [ + const SizedBox(height: 8.0, width: 0.0), + new _ChipsTile(label: 'Available Materials (Chip)', children: chips), + new _ChipsTile(label: 'Available Tools (InputChip)', children: inputChips), + new _ChipsTile(label: 'Choose a Material (ChoiceChip)', children: choiceChips), + new _ChipsTile(label: 'Choose Tools (FilterChip)', children: filterChips), + new _ChipsTile(label: 'Perform Allowed Action (ActionChip)', children: actionChips), + const Divider(), + new Padding( + padding: const EdgeInsets.all(8.0), + child: new Center( + child: new Text( + _createResult(), + style: theme.textTheme.title, + ), + ), ), ]; - if (_showBananas) { - chips.add(new Chip( - label: const Text('Bananas'), - onDeleted: _deleteBananas - )); - } - return new Scaffold( - appBar: new AppBar(title: const Text('Chips')), - body: new ListView( - children: chips.map((Widget chip) { - return new Container( - height: 100.0, - child: new Center(child: chip) - ); - }).toList() - ) + appBar: new AppBar( + title: const Text('Chips'), + actions: [ + new IconButton( + onPressed: () { + setState(() { + _showShapeBorder = !_showShapeBorder; + }); + }, + icon: const Icon(Icons.vignette), + ) + ], + ), + body: new ChipTheme( + data: _showShapeBorder + ? theme.chipTheme.copyWith( + shape: new BeveledRectangleBorder( + side: const BorderSide(width: 0.66, style: BorderStyle.solid, color: Colors.grey), + borderRadius: new BorderRadius.circular(10.0), + )) + : theme.chipTheme, + child: new ListView(children: tiles), + ), + floatingActionButton: new FloatingActionButton( + onPressed: () => setState(_reset), + child: const Icon(Icons.refresh), + ), ); } } diff --git a/packages/flutter/lib/src/material/circle_avatar.dart b/packages/flutter/lib/src/material/circle_avatar.dart index c807e3a1533..7a9ba00599f 100644 --- a/packages/flutter/lib/src/material/circle_avatar.dart +++ b/packages/flutter/lib/src/material/circle_avatar.dart @@ -146,7 +146,7 @@ class CircleAvatar extends StatelessWidget { Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); final ThemeData theme = Theme.of(context); - TextStyle textStyle = theme.primaryTextTheme.title.copyWith(color: foregroundColor); + TextStyle textStyle = theme.primaryTextTheme.subhead.copyWith(color: foregroundColor); Color effectiveBackgroundColor = backgroundColor; if (effectiveBackgroundColor == null) { switch (ThemeData.estimateBrightnessForColor(textStyle.color)) { diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 00a8017d83c..cefbf2b8a5d 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -296,7 +296,7 @@ class ThemeData extends Diagnosticable { assert(iconTheme != null), assert(primaryIconTheme != null), assert(accentIconTheme != null), - assert(sliderTheme != null), + assert(sliderTheme != null), assert(chipTheme != null), assert(platform != null); diff --git a/packages/flutter/test/material/circle_avatar_test.dart b/packages/flutter/test/material/circle_avatar_test.dart index b3590fcb493..d43662726fc 100644 --- a/packages/flutter/test/material/circle_avatar_test.dart +++ b/packages/flutter/test/material/circle_avatar_test.dart @@ -156,7 +156,7 @@ void main() { ), ); - expect(tester.getSize(find.text('Z')), equals(const Size(20.0, 20.0))); + expect(tester.getSize(find.text('Z')), equals(const Size(16.0, 16.0))); await tester.pumpWidget( wrap( @@ -185,7 +185,7 @@ void main() { ), ), ); - expect(tester.getSize(find.text('Z')), equals(const Size(20.0, 20.0))); + expect(tester.getSize(find.text('Z')), equals(const Size(16.0, 16.0))); }); testWidgets('CircleAvatar respects minRadius', (WidgetTester tester) async {