chip_demo.dart 10.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// 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';

7 8
import '../../gallery/demo.dart';

9
const List<String> _defaultMaterialsA = <String>[
10 11 12 13 14 15 16
  'poker',
  'tortilla',
  'fish and',
  'micro',
  'wood',
];

17 18 19 20 21 22 23 24
const List<String> _defaultMaterialsB = <String>[
  'apple',
  'orange',
  'tomato',
  'grape',
  'lettuce',
];

25
const List<String> _defaultActions = <String>[
26 27 28 29 30 31 32 33 34 35 36
  'flake',
  'cut',
  'fragment',
  'splinter',
  'nick',
  'fry',
  'solder',
  'cash in',
  'eat',
];

37
const Map<String, String> _results = <String, String>{
38 39 40 41 42 43 44 45 46 47 48
  'flake': 'flaking',
  'cut': 'cutting',
  'fragment': 'fragmenting',
  'splinter': 'splintering',
  'nick': 'nicking',
  'fry': 'frying',
  'solder': 'soldering',
  'cash in': 'cashing in',
  'eat': 'eating',
};

49
const List<String> _defaultToolsA = <String>[
50 51 52 53 54 55 56
  'hammer',
  'chisel',
  'fryer',
  'fabricator',
  'customer',
];

57 58 59 60 61 62 63 64
const List<String> _defaultToolsB = <String>[
  'keyboard',
  'mouse',
  'monitor',
  'printer',
  'cable',
];

65
const Map<String, String> _avatars = <String, String>{
66 67 68 69 70
  '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',
71 72
};

73 74 75 76 77 78
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'},
79 80
};

81 82 83 84 85 86
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'},
87 88 89 90 91 92
};

class _ChipsTile extends StatelessWidget {
  const _ChipsTile({
    this.label,
    this.children,
93
  });
94

95 96
  final String? label;
  final List<Widget>? children;
97 98 99 100

  // Wraps a list of chips into a ListTile for display as a section in the demo.
  @override
  Widget build(BuildContext context) {
101
    return Card(
102
      semanticContainer: false,
103
      child: Column(
104
        mainAxisSize: MainAxisSize.min,
105 106 107 108
        children: <Widget>[
          Container(
            padding: const EdgeInsets.only(top: 16.0, bottom: 4.0),
            alignment: Alignment.center,
109
            child: Text(label!, textAlign: TextAlign.start),
110
          ),
111
          if (children!.isNotEmpty)
112
            Wrap(
113
              children: children!.map<Widget>((Widget chip) {
114 115 116 117 118 119 120 121 122 123 124 125 126
                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),
127
                child: Text('None', style: Theme.of(context).textTheme.bodySmall!.copyWith(fontStyle: FontStyle.italic)),
128 129 130
              ),
            ),
        ],
131
      ),
132 133 134 135
    );
  }
}

136
class ChipDemo extends StatefulWidget {
137
  const ChipDemo({super.key});
138

139
  static const String routeName = '/material/chip';
140

141
  @override
142
  State<ChipDemo> createState() => _ChipDemoState();
143 144 145
}

class _ChipDemoState extends State<ChipDemo> {
146 147 148 149
  _ChipDemoState() {
    _reset();
  }

150 151
  final Set<String> _materialsA = <String>{};
  final Set<String> _materialsB = <String>{};
152 153
  String _selectedMaterial = '';
  String _selectedAction = '';
154 155
  final Set<String> _toolsA = <String>{};
  final Set<String> _toolsB = <String>{};
156 157
  final Set<String> _selectedTools = <String>{};
  final Set<String> _actions = <String>{};
158 159 160 161
  bool _showShapeBorder = false;

  // Initialize members with the default data.
  void _reset() {
162 163 164 165
    _materialsA.clear();
    _materialsA.addAll(_defaultMaterialsA);
    _materialsB.clear();
    _materialsB.addAll(_defaultMaterialsB);
166 167
    _actions.clear();
    _actions.addAll(_defaultActions);
168 169 170 171
    _toolsA.clear();
    _toolsA.addAll(_defaultToolsA);
    _toolsB.clear();
    _toolsB.addAll(_defaultToolsB);
172 173 174 175 176 177
    _selectedMaterial = '';
    _selectedAction = '';
    _selectedTools.clear();
  }

  void _removeMaterial(String name) {
178 179
    _materialsA.remove(name);
    _materialsB.remove(name);
180 181 182 183
    if (_selectedMaterial == name) {
      _selectedMaterial = '';
    }
  }
184

185
  void _removeTool(String name) {
186 187
    _toolsA.remove(name);
    _toolsB.remove(name);
188 189 190 191
    _selectedTools.remove(name);
  }

  String _capitalize(String name) {
192
    assert(name.isNotEmpty);
193 194 195 196
    return name.substring(0, 1).toUpperCase() + name.substring(1);
  }

  // This converts a String to a unique color, based on the hash value of the
197
  // String object. It takes the bottom 16 bits of the hash, and uses that to
198
  // pick a hue for an HSV color, and then creates the color (with a preset
199
  // saturation and value). This means that any unique strings will also have
200 201
  // unique colors, but they'll all be readable, since they have the same
  // saturation and value.
202
  Color _nameToColor(String name, ThemeData theme) {
203 204
    assert(name.length > 1);
    final int hash = name.hashCode & 0xffff;
205
    final double hue = (360.0 * hash / (1 << 15)) % 360.0;
206
    final double themeValue = HSVColor.fromColor(theme.colorScheme.background).value;
207
    return HSVColor.fromAHSV(1.0, hue, 0.4, themeValue).toColor();
208 209 210 211
  }

  AssetImage _nameToAvatar(String name) {
    assert(_avatars.containsKey(name));
212
    return AssetImage(
213
      _avatars[name]!,
214 215 216 217 218 219 220 221
      package: 'flutter_gallery_assets',
    );
  }

  String _createResult() {
    if (_selectedAction.isEmpty) {
      return '';
    }
222 223
    final String value = _capitalize(_results[_selectedAction]!);
    return '$value!';
224 225
  }

226
  @override
227
  Widget build(BuildContext context) {
228 229
    final ThemeData theme = Theme.of(context);
    final List<Widget> chips = _materialsA.map<Widget>((String name) {
230 231
      return Chip(
        key: ValueKey<String>(name),
232
        backgroundColor: _nameToColor(name, theme),
233
        label: Text(_capitalize(name)),
234 235 236 237 238 239 240 241
        onDeleted: () {
          setState(() {
            _removeMaterial(name);
          });
        },
      );
    }).toList();

242
    final List<Widget> inputChips = _toolsA.map<Widget>((String name) {
243 244 245
      return InputChip(
          key: ValueKey<String>(name),
          avatar: CircleAvatar(
246 247
            backgroundImage: _nameToAvatar(name),
          ),
248
          label: Text(_capitalize(name)),
249 250 251 252 253 254 255
          onDeleted: () {
            setState(() {
              _removeTool(name);
            });
          });
    }).toList();

256
    final List<Widget> choiceChips = _materialsB.map<Widget>((String name) {
257 258
      return ChoiceChip(
        key: ValueKey<String>(name),
259
        backgroundColor: _nameToColor(name, theme),
260
        label: Text(_capitalize(name)),
261 262 263 264 265 266 267 268
        selected: _selectedMaterial == name,
        onSelected: (bool value) {
          setState(() {
            _selectedMaterial = value ? name : '';
          });
        },
      );
    }).toList();
269

270
    final List<Widget> filterChips = _toolsB.map<Widget>((String name) {
271 272 273
      return FilterChip(
        key: ValueKey<String>(name),
        label: Text(_capitalize(name)),
274 275
        selected: _toolsB.contains(name) && _selectedTools.contains(name),
        onSelected: !_toolsB.contains(name)
276 277 278 279 280 281 282 283 284 285 286 287 288
            ? null
            : (bool value) {
                setState(() {
                  if (!value) {
                    _selectedTools.remove(name);
                  } else {
                    _selectedTools.add(name);
                  }
                });
              },
      );
    }).toList();

289
    Set<String> allowedActions = <String>{};
290
    if (_selectedMaterial.isNotEmpty) {
291
      for (final String tool in _selectedTools) {
292
        allowedActions.addAll(_toolActions[tool]!);
293
      }
294
      allowedActions = allowedActions.intersection(_materialActions[_selectedMaterial]!);
295 296
    }

297
    final List<Widget> actionChips = allowedActions.map<Widget>((String name) {
298 299
      return ActionChip(
        label: Text(_capitalize(name)),
300 301 302 303 304 305 306 307 308 309
        onPressed: () {
          setState(() {
            _selectedAction = name;
          });
        },
      );
    }).toList();

    final List<Widget> tiles = <Widget>[
      const SizedBox(height: 8.0, width: 0.0),
310 311 312 313 314
      _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),
315
      const Divider(),
316
      Padding(
317
        padding: const EdgeInsets.all(8.0),
318 319
        child: Center(
          child: Text(
320
            _createResult(),
321
            style: theme.textTheme.titleLarge,
322 323 324 325 326
          ),
        ),
      ),
    ];

327 328
    return Scaffold(
      appBar: AppBar(
329 330
        title: const Text('Chips'),
        actions: <Widget>[
331
          MaterialDemoDocumentationButton(ChipDemo.routeName),
332
          IconButton(
333 334 335 336 337
            onPressed: () {
              setState(() {
                _showShapeBorder = !_showShapeBorder;
              });
            },
338
            icon: const Icon(Icons.vignette, semanticLabel: 'Update border shape'),
339
          ),
340 341
        ],
      ),
342
      body: ChipTheme(
343 344
        data: _showShapeBorder
            ? theme.chipTheme.copyWith(
345
                shape: BeveledRectangleBorder(
346
                side: const BorderSide(width: 0.66, color: Colors.grey),
347
                borderRadius: BorderRadius.circular(10.0),
348 349
              ))
            : theme.chipTheme,
350 351 352 353 354 355
        child: Scrollbar(
          child: ListView(
            primary: true,
            children: tiles,
          )
        ),
356
      ),
357
      floatingActionButton: FloatingActionButton(
358
        onPressed: () => setState(_reset),
359
        child: const Icon(Icons.refresh, semanticLabel: 'Reset chips'),
360
      ),
361
    );
362 363
  }
}