1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'package:flutter/material.dart'; 6 7import '../../gallery/demo.dart'; 8 9const List<String> _defaultMaterials = <String>[ 10 'poker', 11 'tortilla', 12 'fish and', 13 'micro', 14 'wood', 15]; 16 17const List<String> _defaultActions = <String>[ 18 'flake', 19 'cut', 20 'fragment', 21 'splinter', 22 'nick', 23 'fry', 24 'solder', 25 'cash in', 26 'eat', 27]; 28 29const Map<String, String> _results = <String, String>{ 30 'flake': 'flaking', 31 'cut': 'cutting', 32 'fragment': 'fragmenting', 33 'splinter': 'splintering', 34 'nick': 'nicking', 35 'fry': 'frying', 36 'solder': 'soldering', 37 'cash in': 'cashing in', 38 'eat': 'eating', 39}; 40 41const List<String> _defaultTools = <String>[ 42 'hammer', 43 'chisel', 44 'fryer', 45 'fabricator', 46 'customer', 47]; 48 49const Map<String, String> _avatars = <String, String>{ 50 'hammer': 'people/square/ali.png', 51 'chisel': 'people/square/sandra.png', 52 'fryer': 'people/square/trevor.png', 53 'fabricator': 'people/square/stella.png', 54 'customer': 'people/square/peter.png', 55}; 56 57const Map<String, Set<String>> _toolActions = <String, Set<String>>{ 58 'hammer': <String>{'flake', 'fragment', 'splinter'}, 59 'chisel': <String>{'flake', 'nick', 'splinter'}, 60 'fryer': <String>{'fry'}, 61 'fabricator': <String>{'solder'}, 62 'customer': <String>{'cash in', 'eat'}, 63}; 64 65const Map<String, Set<String>> _materialActions = <String, Set<String>>{ 66 'poker': <String>{'cash in'}, 67 'tortilla': <String>{'fry', 'eat'}, 68 'fish and': <String>{'fry', 'eat'}, 69 'micro': <String>{'solder', 'fragment'}, 70 'wood': <String>{'flake', 'cut', 'splinter', 'nick'}, 71}; 72 73class _ChipsTile extends StatelessWidget { 74 const _ChipsTile({ 75 Key key, 76 this.label, 77 this.children, 78 }) : super(key: key); 79 80 final String label; 81 final List<Widget> children; 82 83 // Wraps a list of chips into a ListTile for display as a section in the demo. 84 @override 85 Widget build(BuildContext context) { 86 final List<Widget> cardChildren = <Widget>[ 87 Container( 88 padding: const EdgeInsets.only(top: 16.0, bottom: 4.0), 89 alignment: Alignment.center, 90 child: Text(label, textAlign: TextAlign.start), 91 ), 92 ]; 93 if (children.isNotEmpty) { 94 cardChildren.add(Wrap( 95 children: children.map<Widget>((Widget chip) { 96 return Padding( 97 padding: const EdgeInsets.all(2.0), 98 child: chip, 99 ); 100 }).toList())); 101 } else { 102 final TextStyle textStyle = Theme.of(context).textTheme.caption.copyWith(fontStyle: FontStyle.italic); 103 cardChildren.add( 104 Semantics( 105 container: true, 106 child: Container( 107 alignment: Alignment.center, 108 constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0), 109 padding: const EdgeInsets.all(8.0), 110 child: Text('None', style: textStyle), 111 ), 112 )); 113 } 114 115 return Card( 116 semanticContainer: false, 117 child: Column( 118 mainAxisSize: MainAxisSize.min, 119 children: cardChildren, 120 ), 121 ); 122 } 123} 124 125class ChipDemo extends StatefulWidget { 126 static const String routeName = '/material/chip'; 127 128 @override 129 _ChipDemoState createState() => _ChipDemoState(); 130} 131 132class _ChipDemoState extends State<ChipDemo> { 133 _ChipDemoState() { 134 _reset(); 135 } 136 137 final Set<String> _materials = <String>{}; 138 String _selectedMaterial = ''; 139 String _selectedAction = ''; 140 final Set<String> _tools = <String>{}; 141 final Set<String> _selectedTools = <String>{}; 142 final Set<String> _actions = <String>{}; 143 bool _showShapeBorder = false; 144 145 // Initialize members with the default data. 146 void _reset() { 147 _materials.clear(); 148 _materials.addAll(_defaultMaterials); 149 _actions.clear(); 150 _actions.addAll(_defaultActions); 151 _tools.clear(); 152 _tools.addAll(_defaultTools); 153 _selectedMaterial = ''; 154 _selectedAction = ''; 155 _selectedTools.clear(); 156 } 157 158 void _removeMaterial(String name) { 159 _materials.remove(name); 160 if (_selectedMaterial == name) { 161 _selectedMaterial = ''; 162 } 163 } 164 165 void _removeTool(String name) { 166 _tools.remove(name); 167 _selectedTools.remove(name); 168 } 169 170 String _capitalize(String name) { 171 assert(name != null && name.isNotEmpty); 172 return name.substring(0, 1).toUpperCase() + name.substring(1); 173 } 174 175 // This converts a String to a unique color, based on the hash value of the 176 // String object. It takes the bottom 16 bits of the hash, and uses that to 177 // pick a hue for an HSV color, and then creates the color (with a preset 178 // saturation and value). This means that any unique strings will also have 179 // unique colors, but they'll all be readable, since they have the same 180 // saturation and value. 181 Color _nameToColor(String name) { 182 assert(name.length > 1); 183 final int hash = name.hashCode & 0xffff; 184 final double hue = (360.0 * hash / (1 << 15)) % 360.0; 185 return HSVColor.fromAHSV(1.0, hue, 0.4, 0.90).toColor(); 186 } 187 188 AssetImage _nameToAvatar(String name) { 189 assert(_avatars.containsKey(name)); 190 return AssetImage( 191 _avatars[name], 192 package: 'flutter_gallery_assets', 193 ); 194 } 195 196 String _createResult() { 197 if (_selectedAction.isEmpty) { 198 return ''; 199 } 200 return _capitalize(_results[_selectedAction]) + '!'; 201 } 202 203 @override 204 Widget build(BuildContext context) { 205 final List<Widget> chips = _materials.map<Widget>((String name) { 206 return Chip( 207 key: ValueKey<String>(name), 208 backgroundColor: _nameToColor(name), 209 label: Text(_capitalize(name)), 210 onDeleted: () { 211 setState(() { 212 _removeMaterial(name); 213 }); 214 }, 215 ); 216 }).toList(); 217 218 final List<Widget> inputChips = _tools.map<Widget>((String name) { 219 return InputChip( 220 key: ValueKey<String>(name), 221 avatar: CircleAvatar( 222 backgroundImage: _nameToAvatar(name), 223 ), 224 label: Text(_capitalize(name)), 225 onDeleted: () { 226 setState(() { 227 _removeTool(name); 228 }); 229 }); 230 }).toList(); 231 232 final List<Widget> choiceChips = _materials.map<Widget>((String name) { 233 return ChoiceChip( 234 key: ValueKey<String>(name), 235 backgroundColor: _nameToColor(name), 236 label: Text(_capitalize(name)), 237 selected: _selectedMaterial == name, 238 onSelected: (bool value) { 239 setState(() { 240 _selectedMaterial = value ? name : ''; 241 }); 242 }, 243 ); 244 }).toList(); 245 246 final List<Widget> filterChips = _defaultTools.map<Widget>((String name) { 247 return FilterChip( 248 key: ValueKey<String>(name), 249 label: Text(_capitalize(name)), 250 selected: _tools.contains(name) && _selectedTools.contains(name), 251 onSelected: !_tools.contains(name) 252 ? null 253 : (bool value) { 254 setState(() { 255 if (!value) { 256 _selectedTools.remove(name); 257 } else { 258 _selectedTools.add(name); 259 } 260 }); 261 }, 262 ); 263 }).toList(); 264 265 Set<String> allowedActions = <String>{}; 266 if (_selectedMaterial != null && _selectedMaterial.isNotEmpty) { 267 for (String tool in _selectedTools) { 268 allowedActions.addAll(_toolActions[tool]); 269 } 270 allowedActions = allowedActions.intersection(_materialActions[_selectedMaterial]); 271 } 272 273 final List<Widget> actionChips = allowedActions.map<Widget>((String name) { 274 return ActionChip( 275 label: Text(_capitalize(name)), 276 onPressed: () { 277 setState(() { 278 _selectedAction = name; 279 }); 280 }, 281 ); 282 }).toList(); 283 284 final ThemeData theme = Theme.of(context); 285 final List<Widget> tiles = <Widget>[ 286 const SizedBox(height: 8.0, width: 0.0), 287 _ChipsTile(label: 'Available Materials (Chip)', children: chips), 288 _ChipsTile(label: 'Available Tools (InputChip)', children: inputChips), 289 _ChipsTile(label: 'Choose a Material (ChoiceChip)', children: choiceChips), 290 _ChipsTile(label: 'Choose Tools (FilterChip)', children: filterChips), 291 _ChipsTile(label: 'Perform Allowed Action (ActionChip)', children: actionChips), 292 const Divider(), 293 Padding( 294 padding: const EdgeInsets.all(8.0), 295 child: Center( 296 child: Text( 297 _createResult(), 298 style: theme.textTheme.title, 299 ), 300 ), 301 ), 302 ]; 303 304 return Scaffold( 305 appBar: AppBar( 306 title: const Text('Chips'), 307 actions: <Widget>[ 308 MaterialDemoDocumentationButton(ChipDemo.routeName), 309 IconButton( 310 onPressed: () { 311 setState(() { 312 _showShapeBorder = !_showShapeBorder; 313 }); 314 }, 315 icon: const Icon(Icons.vignette, semanticLabel: 'Update border shape'), 316 ), 317 ], 318 ), 319 body: ChipTheme( 320 data: _showShapeBorder 321 ? theme.chipTheme.copyWith( 322 shape: BeveledRectangleBorder( 323 side: const BorderSide(width: 0.66, style: BorderStyle.solid, color: Colors.grey), 324 borderRadius: BorderRadius.circular(10.0), 325 )) 326 : theme.chipTheme, 327 child: Scrollbar(child: ListView(children: tiles)), 328 ), 329 floatingActionButton: FloatingActionButton( 330 onPressed: () => setState(_reset), 331 child: const Icon(Icons.refresh, semanticLabel: 'Reset chips'), 332 ), 333 ); 334 } 335} 336