1// Copyright 2018 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 '../demo/all.dart'; 8import 'icons.dart'; 9 10class GalleryDemoCategory { 11 const GalleryDemoCategory._({ 12 @required this.name, 13 @required this.icon, 14 }); 15 16 final String name; 17 final IconData icon; 18 19 @override 20 bool operator ==(dynamic other) { 21 if (identical(this, other)) 22 return true; 23 if (runtimeType != other.runtimeType) 24 return false; 25 final GalleryDemoCategory typedOther = other; 26 return typedOther.name == name && typedOther.icon == icon; 27 } 28 29 @override 30 int get hashCode => hashValues(name, icon); 31 32 @override 33 String toString() { 34 return '$runtimeType($name)'; 35 } 36} 37 38const GalleryDemoCategory _kDemos = GalleryDemoCategory._( 39 name: 'Studies', 40 icon: GalleryIcons.animation, 41); 42 43const GalleryDemoCategory _kStyle = GalleryDemoCategory._( 44 name: 'Style', 45 icon: GalleryIcons.custom_typography, 46); 47 48const GalleryDemoCategory _kMaterialComponents = GalleryDemoCategory._( 49 name: 'Material', 50 icon: GalleryIcons.category_mdc, 51); 52 53const GalleryDemoCategory _kCupertinoComponents = GalleryDemoCategory._( 54 name: 'Cupertino', 55 icon: GalleryIcons.phone_iphone, 56); 57 58const GalleryDemoCategory _kMedia = GalleryDemoCategory._( 59 name: 'Media', 60 icon: GalleryIcons.drive_video, 61); 62 63class GalleryDemo { 64 const GalleryDemo({ 65 @required this.title, 66 @required this.icon, 67 this.subtitle, 68 @required this.category, 69 @required this.routeName, 70 this.documentationUrl, 71 @required this.buildRoute, 72 }) : assert(title != null), 73 assert(category != null), 74 assert(routeName != null), 75 assert(buildRoute != null); 76 77 final String title; 78 final IconData icon; 79 final String subtitle; 80 final GalleryDemoCategory category; 81 final String routeName; 82 final WidgetBuilder buildRoute; 83 final String documentationUrl; 84 85 @override 86 String toString() { 87 return '$runtimeType($title $routeName)'; 88 } 89} 90 91List<GalleryDemo> _buildGalleryDemos() { 92 final List<GalleryDemo> galleryDemos = <GalleryDemo>[ 93 // Demos 94 GalleryDemo( 95 title: 'Shrine', 96 subtitle: 'Basic shopping app', 97 icon: GalleryIcons.shrine, 98 category: _kDemos, 99 routeName: ShrineDemo.routeName, 100 buildRoute: (BuildContext context) => const ShrineDemo(), 101 ), 102 GalleryDemo( 103 title: 'Fortnightly', 104 subtitle: 'Newspaper typography app', 105 icon: GalleryIcons.custom_typography, 106 category: _kDemos, 107 routeName: FortnightlyDemo.routeName, 108 buildRoute: (BuildContext context) => FortnightlyDemo(), 109 ), 110 GalleryDemo( 111 title: 'Contact profile', 112 subtitle: 'Address book entry with a flexible appbar', 113 icon: GalleryIcons.account_box, 114 category: _kDemos, 115 routeName: ContactsDemo.routeName, 116 buildRoute: (BuildContext context) => ContactsDemo(), 117 ), 118 GalleryDemo( 119 title: 'Animation', 120 subtitle: 'Section organizer', 121 icon: GalleryIcons.animation, 122 category: _kDemos, 123 routeName: AnimationDemo.routeName, 124 buildRoute: (BuildContext context) => const AnimationDemo(), 125 ), 126 GalleryDemo( 127 title: '2D Transformations', 128 subtitle: 'Pan, Zoom, Rotate', 129 icon: GalleryIcons.grid_on, 130 category: _kDemos, 131 routeName: TransformationsDemo.routeName, 132 buildRoute: (BuildContext context) => const TransformationsDemo(), 133 ), 134 135 // Style 136 GalleryDemo( 137 title: 'Colors', 138 subtitle: 'All of the predefined colors', 139 icon: GalleryIcons.colors, 140 category: _kStyle, 141 routeName: ColorsDemo.routeName, 142 buildRoute: (BuildContext context) => ColorsDemo(), 143 ), 144 GalleryDemo( 145 title: 'Typography', 146 subtitle: 'All of the predefined text styles', 147 icon: GalleryIcons.custom_typography, 148 category: _kStyle, 149 routeName: TypographyDemo.routeName, 150 buildRoute: (BuildContext context) => TypographyDemo(), 151 ), 152 153 // Material Components 154 GalleryDemo( 155 title: 'Backdrop', 156 subtitle: 'Select a front layer from back layer', 157 icon: GalleryIcons.backdrop, 158 category: _kMaterialComponents, 159 routeName: BackdropDemo.routeName, 160 buildRoute: (BuildContext context) => BackdropDemo(), 161 ), 162 GalleryDemo( 163 title: 'Banner', 164 subtitle: 'Displaying a banner within a list', 165 icon: GalleryIcons.lists_leave_behind, 166 category: _kMaterialComponents, 167 routeName: BannerDemo.routeName, 168 documentationUrl: 'https://api.flutter.dev/flutter/material/MaterialBanner-class.html', 169 buildRoute: (BuildContext context) => const BannerDemo(), 170 ), 171 GalleryDemo( 172 title: 'Bottom app bar', 173 subtitle: 'Optional floating action button notch', 174 icon: GalleryIcons.bottom_app_bar, 175 category: _kMaterialComponents, 176 routeName: BottomAppBarDemo.routeName, 177 documentationUrl: 'https://docs.flutter.io/flutter/material/BottomAppBar-class.html', 178 buildRoute: (BuildContext context) => BottomAppBarDemo(), 179 ), 180 GalleryDemo( 181 title: 'Bottom navigation', 182 subtitle: 'Bottom navigation with cross-fading views', 183 icon: GalleryIcons.bottom_navigation, 184 category: _kMaterialComponents, 185 routeName: BottomNavigationDemo.routeName, 186 documentationUrl: 'https://docs.flutter.io/flutter/material/BottomNavigationBar-class.html', 187 buildRoute: (BuildContext context) => BottomNavigationDemo(), 188 ), 189 GalleryDemo( 190 title: 'Bottom sheet: Modal', 191 subtitle: 'A dismissible bottom sheet', 192 icon: GalleryIcons.bottom_sheets, 193 category: _kMaterialComponents, 194 routeName: ModalBottomSheetDemo.routeName, 195 documentationUrl: 'https://docs.flutter.io/flutter/material/showModalBottomSheet.html', 196 buildRoute: (BuildContext context) => ModalBottomSheetDemo(), 197 ), 198 GalleryDemo( 199 title: 'Bottom sheet: Persistent', 200 subtitle: 'A bottom sheet that sticks around', 201 icon: GalleryIcons.bottom_sheet_persistent, 202 category: _kMaterialComponents, 203 routeName: PersistentBottomSheetDemo.routeName, 204 documentationUrl: 'https://docs.flutter.io/flutter/material/ScaffoldState/showBottomSheet.html', 205 buildRoute: (BuildContext context) => PersistentBottomSheetDemo(), 206 ), 207 GalleryDemo( 208 title: 'Buttons', 209 subtitle: 'Flat, raised, dropdown, and more', 210 icon: GalleryIcons.generic_buttons, 211 category: _kMaterialComponents, 212 routeName: ButtonsDemo.routeName, 213 buildRoute: (BuildContext context) => ButtonsDemo(), 214 ), 215 GalleryDemo( 216 title: 'Buttons: Floating Action Button', 217 subtitle: 'FAB with transitions', 218 icon: GalleryIcons.buttons, 219 category: _kMaterialComponents, 220 routeName: TabsFabDemo.routeName, 221 documentationUrl: 'https://docs.flutter.io/flutter/material/FloatingActionButton-class.html', 222 buildRoute: (BuildContext context) => TabsFabDemo(), 223 ), 224 GalleryDemo( 225 title: 'Cards', 226 subtitle: 'Baseline cards with rounded corners', 227 icon: GalleryIcons.cards, 228 category: _kMaterialComponents, 229 routeName: CardsDemo.routeName, 230 documentationUrl: 'https://docs.flutter.io/flutter/material/Card-class.html', 231 buildRoute: (BuildContext context) => CardsDemo(), 232 ), 233 GalleryDemo( 234 title: 'Chips', 235 subtitle: 'Labeled with delete buttons and avatars', 236 icon: GalleryIcons.chips, 237 category: _kMaterialComponents, 238 routeName: ChipDemo.routeName, 239 documentationUrl: 'https://docs.flutter.io/flutter/material/Chip-class.html', 240 buildRoute: (BuildContext context) => ChipDemo(), 241 ), 242 GalleryDemo( 243 title: 'Data tables', 244 subtitle: 'Rows and columns', 245 icon: GalleryIcons.data_table, 246 category: _kMaterialComponents, 247 routeName: DataTableDemo.routeName, 248 documentationUrl: 'https://docs.flutter.io/flutter/material/PaginatedDataTable-class.html', 249 buildRoute: (BuildContext context) => DataTableDemo(), 250 ), 251 GalleryDemo( 252 title: 'Dialogs', 253 subtitle: 'Simple, alert, and fullscreen', 254 icon: GalleryIcons.dialogs, 255 category: _kMaterialComponents, 256 routeName: DialogDemo.routeName, 257 documentationUrl: 'https://docs.flutter.io/flutter/material/showDialog.html', 258 buildRoute: (BuildContext context) => DialogDemo(), 259 ), 260 GalleryDemo( 261 title: 'Elevations', 262 subtitle: 'Shadow values on cards', 263 // TODO(larche): Change to custom icon for elevations when one exists. 264 icon: GalleryIcons.cupertino_progress, 265 category: _kMaterialComponents, 266 routeName: ElevationDemo.routeName, 267 documentationUrl: 'https://docs.flutter.io/flutter/material/Material/elevation.html', 268 buildRoute: (BuildContext context) => ElevationDemo(), 269 ), 270 GalleryDemo( 271 title: 'Expand/collapse list control', 272 subtitle: 'A list with one sub-list level', 273 icon: GalleryIcons.expand_all, 274 category: _kMaterialComponents, 275 routeName: ExpansionTileListDemo.routeName, 276 documentationUrl: 'https://docs.flutter.io/flutter/material/ExpansionTile-class.html', 277 buildRoute: (BuildContext context) => ExpansionTileListDemo(), 278 ), 279 GalleryDemo( 280 title: 'Expansion panels', 281 subtitle: 'List of expanding panels', 282 icon: GalleryIcons.expand_all, 283 category: _kMaterialComponents, 284 routeName: ExpansionPanelsDemo.routeName, 285 documentationUrl: 'https://docs.flutter.io/flutter/material/ExpansionPanel-class.html', 286 buildRoute: (BuildContext context) => ExpansionPanelsDemo(), 287 ), 288 GalleryDemo( 289 title: 'Grid', 290 subtitle: 'Row and column layout', 291 icon: GalleryIcons.grid_on, 292 category: _kMaterialComponents, 293 routeName: GridListDemo.routeName, 294 documentationUrl: 'https://docs.flutter.io/flutter/widgets/GridView-class.html', 295 buildRoute: (BuildContext context) => const GridListDemo(), 296 ), 297 GalleryDemo( 298 title: 'Icons', 299 subtitle: 'Enabled and disabled icons with opacity', 300 icon: GalleryIcons.sentiment_very_satisfied, 301 category: _kMaterialComponents, 302 routeName: IconsDemo.routeName, 303 documentationUrl: 'https://docs.flutter.io/flutter/material/IconButton-class.html', 304 buildRoute: (BuildContext context) => IconsDemo(), 305 ), 306 GalleryDemo( 307 title: 'Lists', 308 subtitle: 'Scrolling list layouts', 309 icon: GalleryIcons.list_alt, 310 category: _kMaterialComponents, 311 routeName: ListDemo.routeName, 312 documentationUrl: 'https://docs.flutter.io/flutter/material/ListTile-class.html', 313 buildRoute: (BuildContext context) => const ListDemo(), 314 ), 315 GalleryDemo( 316 title: 'Lists: leave-behind list items', 317 subtitle: 'List items with hidden actions', 318 icon: GalleryIcons.lists_leave_behind, 319 category: _kMaterialComponents, 320 routeName: LeaveBehindDemo.routeName, 321 documentationUrl: 'https://docs.flutter.io/flutter/widgets/Dismissible-class.html', 322 buildRoute: (BuildContext context) => const LeaveBehindDemo(), 323 ), 324 GalleryDemo( 325 title: 'Lists: reorderable', 326 subtitle: 'Reorderable lists', 327 icon: GalleryIcons.list_alt, 328 category: _kMaterialComponents, 329 routeName: ReorderableListDemo.routeName, 330 documentationUrl: 'https://docs.flutter.io/flutter/material/ReorderableListView-class.html', 331 buildRoute: (BuildContext context) => const ReorderableListDemo(), 332 ), 333 GalleryDemo( 334 title: 'Menus', 335 subtitle: 'Menu buttons and simple menus', 336 icon: GalleryIcons.more_vert, 337 category: _kMaterialComponents, 338 routeName: MenuDemo.routeName, 339 documentationUrl: 'https://docs.flutter.io/flutter/material/PopupMenuButton-class.html', 340 buildRoute: (BuildContext context) => const MenuDemo(), 341 ), 342 GalleryDemo( 343 title: 'Navigation drawer', 344 subtitle: 'Navigation drawer with standard header', 345 icon: GalleryIcons.menu, 346 category: _kMaterialComponents, 347 routeName: DrawerDemo.routeName, 348 documentationUrl: 'https://docs.flutter.io/flutter/material/Drawer-class.html', 349 buildRoute: (BuildContext context) => DrawerDemo(), 350 ), 351 GalleryDemo( 352 title: 'Pagination', 353 subtitle: 'PageView with indicator', 354 icon: GalleryIcons.page_control, 355 category: _kMaterialComponents, 356 routeName: PageSelectorDemo.routeName, 357 documentationUrl: 'https://docs.flutter.io/flutter/material/TabBarView-class.html', 358 buildRoute: (BuildContext context) => PageSelectorDemo(), 359 ), 360 GalleryDemo( 361 title: 'Pickers', 362 subtitle: 'Date and time selection widgets', 363 icon: GalleryIcons.event, 364 category: _kMaterialComponents, 365 routeName: DateAndTimePickerDemo.routeName, 366 documentationUrl: 'https://docs.flutter.io/flutter/material/showDatePicker.html', 367 buildRoute: (BuildContext context) => DateAndTimePickerDemo(), 368 ), 369 GalleryDemo( 370 title: 'Progress indicators', 371 subtitle: 'Linear, circular, indeterminate', 372 icon: GalleryIcons.progress_activity, 373 category: _kMaterialComponents, 374 routeName: ProgressIndicatorDemo.routeName, 375 documentationUrl: 'https://docs.flutter.io/flutter/material/LinearProgressIndicator-class.html', 376 buildRoute: (BuildContext context) => ProgressIndicatorDemo(), 377 ), 378 GalleryDemo( 379 title: 'Pull to refresh', 380 subtitle: 'Refresh indicators', 381 icon: GalleryIcons.refresh, 382 category: _kMaterialComponents, 383 routeName: OverscrollDemo.routeName, 384 documentationUrl: 'https://docs.flutter.io/flutter/material/RefreshIndicator-class.html', 385 buildRoute: (BuildContext context) => const OverscrollDemo(), 386 ), 387 GalleryDemo( 388 title: 'Search', 389 subtitle: 'Expandable search', 390 icon: Icons.search, 391 category: _kMaterialComponents, 392 routeName: SearchDemo.routeName, 393 documentationUrl: 'https://docs.flutter.io/flutter/material/showSearch.html', 394 buildRoute: (BuildContext context) => SearchDemo(), 395 ), 396 GalleryDemo( 397 title: 'Selection controls', 398 subtitle: 'Checkboxes, radio buttons, and switches', 399 icon: GalleryIcons.check_box, 400 category: _kMaterialComponents, 401 routeName: SelectionControlsDemo.routeName, 402 buildRoute: (BuildContext context) => SelectionControlsDemo(), 403 ), 404 GalleryDemo( 405 title: 'Sliders', 406 subtitle: 'Widgets for selecting a value by swiping', 407 icon: GalleryIcons.sliders, 408 category: _kMaterialComponents, 409 routeName: SliderDemo.routeName, 410 documentationUrl: 'https://docs.flutter.io/flutter/material/Slider-class.html', 411 buildRoute: (BuildContext context) => SliderDemo(), 412 ), 413 GalleryDemo( 414 title: 'Snackbar', 415 subtitle: 'Temporary messaging', 416 icon: GalleryIcons.snackbar, 417 category: _kMaterialComponents, 418 routeName: SnackBarDemo.routeName, 419 documentationUrl: 'https://docs.flutter.io/flutter/material/ScaffoldState/showSnackBar.html', 420 buildRoute: (BuildContext context) => const SnackBarDemo(), 421 ), 422 GalleryDemo( 423 title: 'Tabs', 424 subtitle: 'Tabs with independently scrollable views', 425 icon: GalleryIcons.tabs, 426 category: _kMaterialComponents, 427 routeName: TabsDemo.routeName, 428 documentationUrl: 'https://docs.flutter.io/flutter/material/TabBarView-class.html', 429 buildRoute: (BuildContext context) => TabsDemo(), 430 ), 431 GalleryDemo( 432 title: 'Tabs: Scrolling', 433 subtitle: 'Tab bar that scrolls', 434 category: _kMaterialComponents, 435 icon: GalleryIcons.tabs, 436 routeName: ScrollableTabsDemo.routeName, 437 documentationUrl: 'https://docs.flutter.io/flutter/material/TabBar-class.html', 438 buildRoute: (BuildContext context) => ScrollableTabsDemo(), 439 ), 440 GalleryDemo( 441 title: 'Text fields', 442 subtitle: 'Single line of editable text and numbers', 443 icon: GalleryIcons.text_fields_alt, 444 category: _kMaterialComponents, 445 routeName: TextFormFieldDemo.routeName, 446 documentationUrl: 'https://docs.flutter.io/flutter/material/TextFormField-class.html', 447 buildRoute: (BuildContext context) => const TextFormFieldDemo(), 448 ), 449 GalleryDemo( 450 title: 'Tooltips', 451 subtitle: 'Short message displayed on long-press', 452 icon: GalleryIcons.tooltip, 453 category: _kMaterialComponents, 454 routeName: TooltipDemo.routeName, 455 documentationUrl: 'https://docs.flutter.io/flutter/material/Tooltip-class.html', 456 buildRoute: (BuildContext context) => TooltipDemo(), 457 ), 458 459 // Cupertino Components 460 GalleryDemo( 461 title: 'Activity Indicator', 462 icon: GalleryIcons.cupertino_progress, 463 category: _kCupertinoComponents, 464 routeName: CupertinoProgressIndicatorDemo.routeName, 465 documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoActivityIndicator-class.html', 466 buildRoute: (BuildContext context) => CupertinoProgressIndicatorDemo(), 467 ), 468 GalleryDemo( 469 title: 'Alerts', 470 icon: GalleryIcons.dialogs, 471 category: _kCupertinoComponents, 472 routeName: CupertinoAlertDemo.routeName, 473 documentationUrl: 'https://docs.flutter.io/flutter/cupertino/showCupertinoDialog.html', 474 buildRoute: (BuildContext context) => CupertinoAlertDemo(), 475 ), 476 GalleryDemo( 477 title: 'Buttons', 478 icon: GalleryIcons.generic_buttons, 479 category: _kCupertinoComponents, 480 routeName: CupertinoButtonsDemo.routeName, 481 documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoButton-class.html', 482 buildRoute: (BuildContext context) => CupertinoButtonsDemo(), 483 ), 484 GalleryDemo( 485 title: 'Navigation', 486 icon: GalleryIcons.bottom_navigation, 487 category: _kCupertinoComponents, 488 routeName: CupertinoNavigationDemo.routeName, 489 documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoTabScaffold-class.html', 490 buildRoute: (BuildContext context) => CupertinoNavigationDemo(), 491 ), 492 GalleryDemo( 493 title: 'Pickers', 494 icon: GalleryIcons.event, 495 category: _kCupertinoComponents, 496 routeName: CupertinoPickerDemo.routeName, 497 documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoPicker-class.html', 498 buildRoute: (BuildContext context) => CupertinoPickerDemo(), 499 ), 500 GalleryDemo( 501 title: 'Pull to refresh', 502 icon: GalleryIcons.cupertino_pull_to_refresh, 503 category: _kCupertinoComponents, 504 routeName: CupertinoRefreshControlDemo.routeName, 505 documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSliverRefreshControl-class.html', 506 buildRoute: (BuildContext context) => CupertinoRefreshControlDemo(), 507 ), 508 GalleryDemo( 509 title: 'Segmented Control', 510 icon: GalleryIcons.tabs, 511 category: _kCupertinoComponents, 512 routeName: CupertinoSegmentedControlDemo.routeName, 513 documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSegmentedControl-class.html', 514 buildRoute: (BuildContext context) => CupertinoSegmentedControlDemo(), 515 ), 516 GalleryDemo( 517 title: 'Sliders', 518 icon: GalleryIcons.sliders, 519 category: _kCupertinoComponents, 520 routeName: CupertinoSliderDemo.routeName, 521 documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSlider-class.html', 522 buildRoute: (BuildContext context) => CupertinoSliderDemo(), 523 ), 524 GalleryDemo( 525 title: 'Switches', 526 icon: GalleryIcons.cupertino_switch, 527 category: _kCupertinoComponents, 528 routeName: CupertinoSwitchDemo.routeName, 529 documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSwitch-class.html', 530 buildRoute: (BuildContext context) => CupertinoSwitchDemo(), 531 ), 532 GalleryDemo( 533 title: 'Text Fields', 534 icon: GalleryIcons.text_fields_alt, 535 category: _kCupertinoComponents, 536 routeName: CupertinoTextFieldDemo.routeName, 537 buildRoute: (BuildContext context) => CupertinoTextFieldDemo(), 538 ), 539 540 // Media 541 GalleryDemo( 542 title: 'Animated images', 543 subtitle: 'GIF and WebP animations', 544 icon: GalleryIcons.animation, 545 category: _kMedia, 546 routeName: ImagesDemo.routeName, 547 buildRoute: (BuildContext context) => ImagesDemo(), 548 ), 549 GalleryDemo( 550 title: 'Video', 551 subtitle: 'Video playback', 552 icon: GalleryIcons.drive_video, 553 category: _kMedia, 554 routeName: VideoDemo.routeName, 555 buildRoute: (BuildContext context) => const VideoDemo(), 556 ), 557 ]; 558 559 // Keep Pesto around for its regression test value. It is not included 560 // in (release builds) the performance tests. 561 assert(() { 562 galleryDemos.insert(0, 563 GalleryDemo( 564 title: 'Pesto', 565 subtitle: 'Simple recipe browser', 566 icon: Icons.adjust, 567 category: _kDemos, 568 routeName: PestoDemo.routeName, 569 buildRoute: (BuildContext context) => const PestoDemo(), 570 ), 571 ); 572 return true; 573 }()); 574 575 return galleryDemos; 576} 577 578final List<GalleryDemo> kAllGalleryDemos = _buildGalleryDemos(); 579 580final Set<GalleryDemoCategory> kAllGalleryDemoCategories = 581 kAllGalleryDemos.map<GalleryDemoCategory>((GalleryDemo demo) => demo.category).toSet(); 582 583final Map<GalleryDemoCategory, List<GalleryDemo>> kGalleryCategoryToDemos = 584 Map<GalleryDemoCategory, List<GalleryDemo>>.fromIterable( 585 kAllGalleryDemoCategories, 586 value: (dynamic category) { 587 return kAllGalleryDemos.where((GalleryDemo demo) => demo.category == category).toList(); 588 }, 589 ); 590 591final Map<String, String> kDemoDocumentationUrl = 592 Map<String, String>.fromIterable( 593 kAllGalleryDemos.where((GalleryDemo demo) => demo.documentationUrl != null), 594 key: (dynamic demo) => demo.routeName, 595 value: (dynamic demo) => demo.documentationUrl, 596 ); 597