// Copyright 2014 The Flutter 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 '../../gallery/demo.dart'; const List<String> _defaultMaterialsA = <String>[ 'poker', 'tortilla', 'fish and', 'micro', 'wood', ]; const List<String> _defaultMaterialsB = <String>[ 'apple', 'orange', 'tomato', 'grape', 'lettuce', ]; const List<String> _defaultActions = <String>[ 'flake', 'cut', 'fragment', 'splinter', 'nick', 'fry', 'solder', 'cash in', 'eat', ]; const Map<String, String> _results = <String, String>{ 'flake': 'flaking', 'cut': 'cutting', 'fragment': 'fragmenting', 'splinter': 'splintering', 'nick': 'nicking', 'fry': 'frying', 'solder': 'soldering', 'cash in': 'cashing in', 'eat': 'eating', }; const List<String> _defaultToolsA = <String>[ 'hammer', 'chisel', 'fryer', 'fabricator', 'customer', ]; const List<String> _defaultToolsB = <String>[ 'keyboard', 'mouse', 'monitor', 'printer', 'cable', ]; const Map<String, String> _avatars = <String, String>{ 'hammer': 'people/square/ali.png', 'chisel': 'people/square/sandra.png', 'fryer': 'people/square/trevor.png', 'fabricator': 'people/square/stella.png', 'customer': 'people/square/peter.png', }; const Map<String, Set<String>> _toolActions = <String, Set<String>>{ 'hammer': <String>{'flake', 'fragment', 'splinter'}, 'chisel': <String>{'flake', 'nick', 'splinter'}, 'fryer': <String>{'fry'}, 'fabricator': <String>{'solder'}, 'customer': <String>{'cash in', 'eat'}, }; const Map<String, Set<String>> _materialActions = <String, Set<String>>{ 'poker': <String>{'cash in'}, 'tortilla': <String>{'fry', 'eat'}, 'fish and': <String>{'fry', 'eat'}, 'micro': <String>{'solder', 'fragment'}, 'wood': <String>{'flake', 'cut', 'splinter', 'nick'}, }; class _ChipsTile extends StatelessWidget { const _ChipsTile({ this.label, this.children, }); final String? label; final List<Widget>? children; // Wraps a list of chips into a ListTile for display as a section in the demo. @override Widget build(BuildContext context) { return Card( semanticContainer: false, child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ Container( padding: const EdgeInsets.only(top: 16.0, bottom: 4.0), alignment: Alignment.center, child: Text(label!, textAlign: TextAlign.start), ), if (children!.isNotEmpty) Wrap( children: children!.map<Widget>((Widget chip) { return Padding( padding: const EdgeInsets.all(2.0), child: chip, ); }).toList(), ) else Semantics( container: true, child: Container( alignment: Alignment.center, constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0), padding: const EdgeInsets.all(8.0), child: Text('None', style: Theme.of(context).textTheme.bodySmall!.copyWith(fontStyle: FontStyle.italic)), ), ), ], ), ); } } class ChipDemo extends StatefulWidget { const ChipDemo({super.key}); static const String routeName = '/material/chip'; @override State<ChipDemo> createState() => _ChipDemoState(); } class _ChipDemoState extends State<ChipDemo> { _ChipDemoState() { _reset(); } final Set<String> _materialsA = <String>{}; final Set<String> _materialsB = <String>{}; String _selectedMaterial = ''; String _selectedAction = ''; final Set<String> _toolsA = <String>{}; final Set<String> _toolsB = <String>{}; final Set<String> _selectedTools = <String>{}; final Set<String> _actions = <String>{}; bool _showShapeBorder = false; // Initialize members with the default data. void _reset() { _materialsA.clear(); _materialsA.addAll(_defaultMaterialsA); _materialsB.clear(); _materialsB.addAll(_defaultMaterialsB); _actions.clear(); _actions.addAll(_defaultActions); _toolsA.clear(); _toolsA.addAll(_defaultToolsA); _toolsB.clear(); _toolsB.addAll(_defaultToolsB); _selectedMaterial = ''; _selectedAction = ''; _selectedTools.clear(); } void _removeMaterial(String name) { _materialsA.remove(name); _materialsB.remove(name); if (_selectedMaterial == name) { _selectedMaterial = ''; } } void _removeTool(String name) { _toolsA.remove(name); _toolsB.remove(name); _selectedTools.remove(name); } String _capitalize(String name) { assert(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, ThemeData theme) { assert(name.length > 1); final int hash = name.hashCode & 0xffff; final double hue = (360.0 * hash / (1 << 15)) % 360.0; final double themeValue = HSVColor.fromColor(theme.colorScheme.background).value; return HSVColor.fromAHSV(1.0, hue, 0.4, themeValue).toColor(); } AssetImage _nameToAvatar(String name) { assert(_avatars.containsKey(name)); return AssetImage( _avatars[name]!, package: 'flutter_gallery_assets', ); } String _createResult() { if (_selectedAction.isEmpty) { return ''; } final String value = _capitalize(_results[_selectedAction]!); return '$value!'; } @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final List<Widget> chips = _materialsA.map<Widget>((String name) { return Chip( key: ValueKey<String>(name), backgroundColor: _nameToColor(name, theme), label: Text(_capitalize(name)), onDeleted: () { setState(() { _removeMaterial(name); }); }, ); }).toList(); final List<Widget> inputChips = _toolsA.map<Widget>((String name) { return InputChip( key: ValueKey<String>(name), avatar: CircleAvatar( backgroundImage: _nameToAvatar(name), ), label: Text(_capitalize(name)), onDeleted: () { setState(() { _removeTool(name); }); }); }).toList(); final List<Widget> choiceChips = _materialsB.map<Widget>((String name) { return ChoiceChip( key: ValueKey<String>(name), backgroundColor: _nameToColor(name, theme), label: Text(_capitalize(name)), selected: _selectedMaterial == name, onSelected: (bool value) { setState(() { _selectedMaterial = value ? name : ''; }); }, ); }).toList(); final List<Widget> filterChips = _toolsB.map<Widget>((String name) { return FilterChip( key: ValueKey<String>(name), label: Text(_capitalize(name)), selected: _toolsB.contains(name) && _selectedTools.contains(name), onSelected: !_toolsB.contains(name) ? null : (bool value) { setState(() { if (!value) { _selectedTools.remove(name); } else { _selectedTools.add(name); } }); }, ); }).toList(); Set<String> allowedActions = <String>{}; if (_selectedMaterial.isNotEmpty) { for (final String tool in _selectedTools) { allowedActions.addAll(_toolActions[tool]!); } allowedActions = allowedActions.intersection(_materialActions[_selectedMaterial]!); } final List<Widget> actionChips = allowedActions.map<Widget>((String name) { return ActionChip( label: Text(_capitalize(name)), onPressed: () { setState(() { _selectedAction = name; }); }, ); }).toList(); final List<Widget> tiles = <Widget>[ const SizedBox(height: 8.0, width: 0.0), _ChipsTile(label: 'Available Materials (Chip)', children: chips), _ChipsTile(label: 'Available Tools (InputChip)', children: inputChips), _ChipsTile(label: 'Choose a Material (ChoiceChip)', children: choiceChips), _ChipsTile(label: 'Choose Tools (FilterChip)', children: filterChips), _ChipsTile(label: 'Perform Allowed Action (ActionChip)', children: actionChips), const Divider(), Padding( padding: const EdgeInsets.all(8.0), child: Center( child: Text( _createResult(), style: theme.textTheme.titleLarge, ), ), ), ]; return Scaffold( appBar: AppBar( title: const Text('Chips'), actions: <Widget>[ MaterialDemoDocumentationButton(ChipDemo.routeName), IconButton( onPressed: () { setState(() { _showShapeBorder = !_showShapeBorder; }); }, icon: const Icon(Icons.vignette, semanticLabel: 'Update border shape'), ), ], ), body: ChipTheme( data: _showShapeBorder ? theme.chipTheme.copyWith( shape: BeveledRectangleBorder( side: const BorderSide(width: 0.66, color: Colors.grey), borderRadius: BorderRadius.circular(10.0), )) : theme.chipTheme, child: Scrollbar( child: ListView( primary: true, children: tiles, ) ), ), floatingActionButton: FloatingActionButton( onPressed: () => setState(_reset), child: const Icon(Icons.refresh, semanticLabel: 'Reset chips'), ), ); } }