diff --git a/site/lib/jaspr_options.dart b/site/lib/jaspr_options.dart index bb2e8c266f6..4e4a38b7f14 100644 --- a/site/lib/jaspr_options.dart +++ b/site/lib/jaspr_options.dart @@ -13,21 +13,25 @@ import 'package:docs_flutter_dev_site/src/components/client/dartpad_injector.dar as prefix2; import 'package:docs_flutter_dev_site/src/components/client/download_latest_button.dart' as prefix3; -import 'package:docs_flutter_dev_site/src/components/client/on_this_page_button.dart' +import 'package:docs_flutter_dev_site/src/components/client/learning_resource_filters.dart' as prefix4; -import 'package:docs_flutter_dev_site/src/components/header/menu_toggle.dart' +import 'package:docs_flutter_dev_site/src/components/client/learning_resource_filters_sidebar.dart' as prefix5; -import 'package:docs_flutter_dev_site/src/components/header/site_switcher.dart' +import 'package:docs_flutter_dev_site/src/components/client/on_this_page_button.dart' as prefix6; -import 'package:docs_flutter_dev_site/src/components/header/theme_switcher.dart' +import 'package:docs_flutter_dev_site/src/components/header/menu_toggle.dart' as prefix7; -import 'package:docs_flutter_dev_site/src/components/cookie_notice.dart' +import 'package:docs_flutter_dev_site/src/components/header/site_switcher.dart' as prefix8; -import 'package:docs_flutter_dev_site/src/components/copy_button.dart' +import 'package:docs_flutter_dev_site/src/components/header/theme_switcher.dart' as prefix9; -import 'package:docs_flutter_dev_site/src/components/feedback.dart' as prefix10; -import 'package:docs_flutter_dev_site/src/components/os_selector.dart' +import 'package:docs_flutter_dev_site/src/components/cookie_notice.dart' + as prefix10; +import 'package:docs_flutter_dev_site/src/components/copy_button.dart' as prefix11; +import 'package:docs_flutter_dev_site/src/components/feedback.dart' as prefix12; +import 'package:docs_flutter_dev_site/src/components/os_selector.dart' + as prefix13; /// Default [JasprOptions] for use with your jaspr project. /// @@ -66,37 +70,47 @@ JasprOptions get defaultJasprOptions => JasprOptions( params: _prefix3DownloadLatestButton, ), - prefix4.OnThisPageButton: ClientTarget( + prefix4.LearningResourceFilters: + ClientTarget( + 'src/components/client/learning_resource_filters', + ), + + prefix5.LearningResourceFiltersSidebar: + ClientTarget( + 'src/components/client/learning_resource_filters_sidebar', + ), + + prefix6.OnThisPageButton: ClientTarget( 'src/components/client/on_this_page_button', ), - prefix8.CookieNotice: ClientTarget( + prefix10.CookieNotice: ClientTarget( 'src/components/cookie_notice', ), - prefix9.CopyButton: ClientTarget( + prefix11.CopyButton: ClientTarget( 'src/components/copy_button', - params: _prefix9CopyButton, + params: _prefix11CopyButton, ), - prefix10.FeedbackComponent: ClientTarget( + prefix12.FeedbackComponent: ClientTarget( 'src/components/feedback', - params: _prefix10FeedbackComponent, + params: _prefix12FeedbackComponent, ), - prefix5.MenuToggle: ClientTarget( + prefix7.MenuToggle: ClientTarget( 'src/components/header/menu_toggle', ), - prefix6.SiteSwitcher: ClientTarget( + prefix8.SiteSwitcher: ClientTarget( 'src/components/header/site_switcher', ), - prefix7.ThemeSwitcher: ClientTarget( + prefix9.ThemeSwitcher: ClientTarget( 'src/components/header/theme_switcher', ), - prefix11.OsSelector: ClientTarget( + prefix13.OsSelector: ClientTarget( 'src/components/os_selector', ), }, @@ -116,11 +130,11 @@ Map _prefix2DartPadInjector(prefix2.DartPadInjector c) => { Map _prefix3DownloadLatestButton( prefix3.DownloadLatestButton c, ) => {'os': c.os, 'arch': c.arch}; -Map _prefix9CopyButton(prefix9.CopyButton c) => { +Map _prefix11CopyButton(prefix11.CopyButton c) => { 'toCopy': c.toCopy, 'buttonText': c.buttonText, 'classes': c.classes, 'title': c.title, }; -Map _prefix10FeedbackComponent(prefix10.FeedbackComponent c) => +Map _prefix12FeedbackComponent(prefix12.FeedbackComponent c) => {'issueUrl': c.issueUrl}; diff --git a/site/lib/main.dart b/site/lib/main.dart index 42dcd851269..7a0dd09486e 100644 --- a/site/lib/main.dart +++ b/site/lib/main.dart @@ -15,7 +15,6 @@ import 'src/components/expansion_list.dart'; import 'src/components/os_selector.dart'; import 'src/components/pages/learning_resource_index.dart'; import 'src/components/tabs.dart'; -import 'src/data/learning_resources.dart'; import 'src/extensions/registry.dart'; import 'src/layouts/catalog_page_layout.dart'; import 'src/layouts/doc_layout.dart'; @@ -127,7 +126,7 @@ List get _embeddableComponents => [ ), CustomComponent( pattern: RegExp('LearningResourceIndex', caseSensitive: false), - builder: (_, _, _) => LearningResourceIndex(allLearningResources), + builder: (_, _, _) => LearningResourceIndex(), ), CustomComponent( pattern: RegExp('ArchiveTable'), diff --git a/site/lib/src/analytics/analytics_web.dart b/site/lib/src/analytics/analytics_web.dart index dfa0cb5cb7e..568e56e3c9c 100644 --- a/site/lib/src/analytics/analytics_web.dart +++ b/site/lib/src/analytics/analytics_web.dart @@ -6,6 +6,7 @@ import 'package:meta/meta.dart'; import 'package:universal_web/js_interop.dart'; import 'package:universal_web/web.dart' as web; +import '../util.dart'; import 'analytics.dart'; /// Web implementation of [Analytics]. @@ -15,6 +16,9 @@ import 'analytics.dart'; final class AnalyticsImplementation extends Analytics { @override void sendEvent(String eventName, Map parameters) { + if (!productionBuild) { + return; + } final dataLayer = web.window['dataLayer']; if (dataLayer.isA()) { (dataLayer as JSArray).toDart.add( diff --git a/site/lib/src/components/client/learning_resource_filters.dart b/site/lib/src/components/client/learning_resource_filters.dart new file mode 100644 index 00000000000..9993bddb13f --- /dev/null +++ b/site/lib/src/components/client/learning_resource_filters.dart @@ -0,0 +1,207 @@ +// Copyright 2025 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 'dart:math'; + +import 'package:jaspr/jaspr.dart'; +import 'package:universal_web/js_interop.dart'; +import 'package:universal_web/web.dart' as web; + +import '../../analytics/analytics.dart'; +import '../../models/learning_resource_model.dart'; +import '../util/global_event_listener.dart'; +import 'learning_resource_filters_sidebar.dart'; + +@client +class LearningResourceFilters extends StatefulComponent { + const LearningResourceFilters({super.key}); + + @override + State createState() => + _LearningResourceFiltersState(); +} + +class _LearningResourceFiltersState extends State { + String searchQuery = ''; + + FiltersNotifier get filters => LearningResourceFiltersSidebar.filters; + + final List resources = []; + int filteredResourcesCount = 0; + + @override + void initState() { + super.initState(); + + if (kIsWeb) { + filters.addListener(setFilters); + + final resourceGrid = web.document.getElementById('all-resources-grid'); + if (resourceGrid == null) { + return; + } + + final resourceCards = resourceGrid.querySelectorAll('.card'); + recreateResources(resourceCards); + shuffleCards(resourceGrid); + } + } + + void recreateResources(web.NodeList resourceCards) { + for (var i = 0; i < resourceCards.length; i++) { + final element = resourceCards.item(i) as web.Element; + final info = LearningResource.fromElement(element); + resources.add(info); + + element.addEventListener( + 'click', + ((web.Event event) { + analytics.sendEvent('learning_resource_index_click', { + 'learning_resource_type': info.type, + 'learning_resource_title': info.name, + }); + }).toJS, + ); + } + filteredResourcesCount = resources.length; + } + + void shuffleCards(web.Element container) { + final r = Random(); + final elements = container.childNodes; + for (var i = elements.length; i > 0; i--) { + final card = elements.item(r.nextInt(i)); + container.appendChild(card!); + } + } + + /// Update the filter state and re-evaluate which resources to show. + /// + /// Use like the `setState` method by passing a callback that updates + /// the relevant state variables. + /// + /// Example: + /// + /// ```dart + /// setFilters(() { + /// searchQuery = '...'; + /// }); + /// ``` + void setFilters([void Function()? callback]) { + setState(callback ?? () {}); + + final resourcesToShow = filters.filterResources(resources, searchQuery); + filteredResourcesCount = resourcesToShow.length; + for (final info in resources) { + final element = + web.document.getElementById(info.name) as web.HTMLElement?; + if (element == null) { + continue; + } + + if (resourcesToShow.contains(info)) { + element.classList.remove('hidden'); + } else { + element.classList.add('hidden'); + } + } + } + + @override + void dispose() { + if (kIsWeb) { + filters.removeListener(setFilters); + } + super.dispose(); + } + + @override + Component build(BuildContext context) { + return div(id: 'resource-search-group', classes: 'chip-filters-group', [ + div(classes: 'top-row', [ + div(classes: 'search-wrapper', id: 'resource-search', [ + span( + classes: 'material-symbols leading-icon', + attributes: {'aria-hidden': 'true', 'translate': 'no'}, + [text('search')], + ), + input( + type: InputType.search, + attributes: { + 'placeholder': 'Try "button" or "networking"...', + 'aria-label': 'Search learning resources by name and category', + }, + value: searchQuery, + onInput: (value) { + setFilters(() { + searchQuery = value as String; + }); + }, + ), + ]), + GlobalEventListener( + onClick: (event) { + final target = event.target as web.Element?; + // If clicking outside the filters or toggle, close the filters. + if (target?.closest('#resource-filter-group-wrapper') == null && + target?.closest('.show-filters-button') == null) { + final toggle = + web.document.getElementById('open-filter-toggle') + as web.HTMLInputElement?; + toggle?.checked = false; + } + }, + button( + classes: 'icon-button show-filters-button', + onClick: () { + final toggle = + web.document.getElementById('open-filter-toggle') + as web.HTMLInputElement?; + toggle?.checked = !toggle.checked; + }, + [ + span( + classes: 'material-symbols', + attributes: {'aria-hidden': 'true', 'translate': 'no'}, + [text('filter_list')], + ), + ], + ), + ), + ]), + div(classes: 'label-row', [ + label( + attributes: {'for': 'resource-search'}, + [ + text('Showing '), + span([text('$filteredResourcesCount')]), + text(' / '), + span([text('${resources.length}')]), + ], + ), + button( + attributes: { + if (searchQuery.isEmpty && + filters.selectedTags.isEmpty && + filters.selectedTypes.isEmpty) + 'disabled': 'true', + }, + onClick: () { + // No setState needed, since resetting filters will trigger it. + searchQuery = ''; + filters.reset(); + }, + [ + span( + classes: 'material-symbols', + attributes: {'aria-hidden': 'true', 'translate': 'no'}, + [text('close_small')], + ), + span([text('Clear filters')]), + ], + ), + ]), + ]); + } +} diff --git a/site/lib/src/components/client/learning_resource_filters_sidebar.dart b/site/lib/src/components/client/learning_resource_filters_sidebar.dart new file mode 100644 index 00000000000..559a22e13a4 --- /dev/null +++ b/site/lib/src/components/client/learning_resource_filters_sidebar.dart @@ -0,0 +1,212 @@ +// Copyright 2025 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:jaspr/jaspr.dart'; + +import '../../analytics/analytics.dart'; +import '../../models/learning_resource_model.dart'; +import '../../util.dart'; +import '../material_icon.dart'; +import 'learning_resource_filters.dart'; + +@client +class LearningResourceFiltersSidebar extends StatelessComponent { + const LearningResourceFiltersSidebar({super.key}); + + /// The filter state for the resources list. + /// + /// This is static so that [LearningResourceFilters] can access it, + /// since both client components don't share a common ancestor. + static FiltersNotifier filters = FiltersNotifier(); + + @override + Component build(BuildContext context) { + return div(classes: 'right-col', [ + input( + type: InputType.checkbox, + id: 'open-filter-toggle', + attributes: {'hidden': 'true'}, + ), + div(id: 'resource-filter-group-wrapper', [ + div(id: 'resource-filter-group', [ + div(classes: 'filter-header', [ + label( + attributes: {'for': 'open-filter-toggle', 'aria-hidden': 'true'}, + classes: 'close-icon', + [const MaterialIcon('close')], + ), + ]), + div(classes: 'table-title', [text('Filter by')]), + ListenableBuilder( + listenable: filters, + builder: (context) { + return div(classes: 'table-content', [ + h4([text('Subject')]), + ul(classes: filters.tagsExpanded ? '' : 'collapsed', [ + for (final (index, tag) in LearningResourceTag.values.indexed) + li( + classes: [ + if (!filters.tagsExpanded && index > 3) 'hidden', + ].toClasses, + [ + input( + type: InputType.checkbox, + attributes: { + 'role': 'checkbox', + 'name': 'filter-${tag.name}', + }, + id: 'filter-${tag.name}', + checked: filters.selectedTags.contains(tag), + onChange: (checked) { + filters.setTag(tag, checked as bool); + }, + ), + label( + attributes: {'for': 'filter-${tag.name}'}, + [text(tag.label)], + ), + ], + ), + ]), + button(onClick: filters.toggleTagsExpanded, [ + span(classes: 'label', [ + text(filters.tagsExpanded ? 'Less' : 'More'), + ]), + span( + classes: 'material-symbols', + attributes: {'aria-hidden': 'true', 'translate': 'no'}, + [ + text( + filters.tagsExpanded ? 'expand_less' : 'expand_more', + ), + ], + ), + ]), + h4([text('Type')]), + ul([ + for (final type in LearningResourceType.values) + li([ + input( + type: InputType.checkbox, + attributes: { + 'role': 'checkbox', + 'name': 'filter-${type.name}', + }, + id: 'filter-${type.name}', + checked: filters.selectedTypes.contains(type), + onChange: (checked) { + filters.setType(type, checked as bool); + }, + ), + label( + attributes: {'for': 'filter-${type.name}'}, + [text(type.label)], + ), + ]), + ]), + ]); + }, + ), + ]), + ]), + ]); + } +} + +/// Notifier to manage the state of the filters. +class FiltersNotifier extends ChangeNotifier { + Set selectedTags = {}; + Set selectedTypes = {}; + + bool tagsExpanded = false; + + void setTag(LearningResourceTag tag, bool isSelected) { + if (isSelected) { + selectedTags.add(tag); + + analytics.sendEvent( + 'learning_resource_index_filter_selected', + { + 'learning_resource_filter_name': tag.label.toLowerCase(), + 'learning_resource_filter_type': 'tags', + }, + ); + } else { + selectedTags.remove(tag); + } + notifyListeners(); + } + + void setType(LearningResourceType type, bool isSelected) { + if (isSelected) { + selectedTypes.add(type); + + analytics.sendEvent( + 'learning_resource_index_filter_selected', + { + 'learning_resource_filter_name': type.label.toLowerCase(), + 'learning_resource_filter_type': 'type', + }, + ); + } else { + selectedTypes.remove(type); + } + notifyListeners(); + } + + void toggleTagsExpanded() { + tagsExpanded = !tagsExpanded; + notifyListeners(); + } + + void reset() { + selectedTags.clear(); + selectedTypes.clear(); + notifyListeners(); + } + + Set filterResources( + List resources, + String searchQuery, + ) { + if (searchQuery.isEmpty && selectedTags.isEmpty && selectedTypes.isEmpty) { + // No filters applied, return all resources. + return resources.toSet(); + } + + final resourcesToShow = {}; + searchQuery = searchQuery.trim().toLowerCase(); + + final filterTags = selectedTags.expand((e) => e.tags).toSet(); + final filterTypes = selectedTypes.expand((e) => e.tags).toSet(); + + for (final info in resources) { + final matchesTags = + selectedTags.isEmpty || info.tags.any(filterTags.contains); + if (!matchesTags) { + continue; + } + + final matchesTypes = + selectedTypes.isEmpty || filterTypes.contains(info.type); + if (!matchesTypes) { + continue; + } + + final matchesSearchQuery = + searchQuery.isEmpty || + info.name.toLowerCase().contains(searchQuery) || + info.tags.any((t) => t.contains(searchQuery)) || + info.type.contains(searchQuery) || + info.description.toLowerCase().contains(searchQuery); + if (!matchesSearchQuery) { + continue; + } + + resourcesToShow.add(info); + } + + return resourcesToShow; + } +} diff --git a/site/lib/src/components/pages/learning_resource_index.dart b/site/lib/src/components/pages/learning_resource_index.dart index 55df04b57aa..e1ec065c494 100644 --- a/site/lib/src/components/pages/learning_resource_index.dart +++ b/site/lib/src/components/pages/learning_resource_index.dart @@ -3,225 +3,42 @@ // found in the LICENSE file. import 'package:jaspr/jaspr.dart'; +import 'package:jaspr_content/jaspr_content.dart'; import '../../models/learning_resource_model.dart'; import '../../util.dart'; -import '../material_icon.dart'; +import '../client/learning_resource_filters.dart'; +import '../client/learning_resource_filters_sidebar.dart'; final class LearningResourceIndex extends StatelessComponent { - LearningResourceIndex(this.resources); - - final List resources; + LearningResourceIndex({super.key}); @override - Component build(BuildContext _) => div( - id: 'resource-index-content', - [ - _buildMainContent(), - _buildFilterSidebar(), - ], - ); + Component build(BuildContext context) { + final resourcesData = + context.page.data['learning-resources-index'] as Map?; - Component _buildMainContent() => div( - classes: 'left-col', - id: 'resource-index-main-content', - [ - div( - id: 'resource-search-group', - classes: 'chip-filters-group', - [ - div( - classes: 'top-row', - [ - div( - classes: 'search-wrapper', - id: 'resource-search', - [ - span( - classes: 'material-symbols leading-icon', - attributes: { - 'aria-hidden': 'true', - 'translate': 'no', - }, - [text('search')], - ), - input( - type: InputType.search, - attributes: { - 'placeholder': 'Try "button" or "networking"...', - 'aria-label': - 'Search learning resources by name and category', - }, - ), - ], - ), - button( - classes: 'icon-button show-filters-button', - [ - span( - classes: 'material-symbols', - attributes: { - 'aria-hidden': 'true', - 'translate': 'no', - }, - [text('filter_list')], - ), - ], - ), - ], - ), - div( - classes: 'label-row', - [ - label( - attributes: {'for': 'resource-search'}, - [ - text('Showing '), - span(id: 'displayed-resource-card-count', [text('0')]), - text(' / '), - span(id: 'total-resource-card-count', [text('0')]), - ], - ), - button( - id: 'clear-resource-index-filters', - attributes: {'disabled': 'true'}, - [ - span( - classes: 'material-symbols', - attributes: { - 'aria-hidden': 'true', - 'translate': 'no', - }, - [text('close_small')], - ), - span([text('Clear filters')]), - ], - ), - ], - ), - ], - ), - section( - classes: 'card-grid', - id: 'all-resources-grid', - [ - for (final item in resources) _ResourceCard(item), - ], - ), - ], - ); + final learningResources = []; + if (resourcesData != null) { + for (final group in resourcesData.values) { + for (final resource in group as List) { + learningResources.add( + LearningResource.fromMap(resource as Map), + ); + } + } + } - Component _buildFilterSidebar() => div( - classes: 'right-col', - [ - input( - type: InputType.checkbox, - id: 'open-filter-toggle', - attributes: {'hidden': 'true'}, - ), - div( - id: 'resource-filter-group-wrapper', - [ - div( - id: 'resource-filter-group', - [ - div( - classes: 'filter-header', - [ - label( - attributes: { - 'for': 'open-filter-toggle', - 'aria-hidden': 'true', - }, - classes: 'close-icon', - [ - const MaterialIcon('close'), - ], - ), - ], - ), - div( - classes: 'table-title', - [text('Filter by')], - ), - div( - classes: 'table-content', - [ - h4([text('Subject')]), - ul( - id: 'filters-tags', - classes: 'collapsed', - [ - for (final tag in LearningResourceTag.values) - li( - classes: 'hidden', - [ - input( - type: InputType.checkbox, - attributes: { - 'role': 'checkbox', - 'name': 'filter-${tag.id}', - 'data-category': 'tags', - }, - id: 'filter-${tag.id}', - ), - label( - attributes: {'for': 'filter-${tag.id}'}, - [text(tag.formattedName)], - ), - ], - ), - ], - ), - button( - id: 'filters-tags-show-button', - [ - span( - classes: 'label', - [text('More')], - ), - span( - classes: 'material-symbols', - attributes: { - 'aria-hidden': 'true', - 'translate': 'no', - }, - [text('expand_more')], - ), - ], - ), - h4([text('Type')]), - ul( - id: 'filters-type', - [ - for (final type in LearningResourceType.values) - li( - [ - input( - type: InputType.checkbox, - attributes: { - 'role': 'checkbox', - 'data-category': 'type', - 'name': 'filter-${type.id}', - }, - id: 'filter-${type.id}', - ), - label( - attributes: {'for': 'filter-${type.id}'}, - [text(type.formattedName)], - ), - ], - ), - ], - ), - ], - ), - ], - ), - ], - ), - ], - ); + return div(id: 'resource-index-content', [ + div(classes: 'left-col', id: 'resource-index-main-content', [ + const LearningResourceFilters(), + section(classes: 'card-grid', id: 'all-resources-grid', [ + for (final item in learningResources) _ResourceCard(item), + ]), + ]), + const LearningResourceFiltersSidebar(), + ]); + } } final class _ResourceCard extends StatelessComponent { @@ -234,118 +51,96 @@ final class _ResourceCard extends StatelessComponent { return a( id: resource.name, classes: 'card outlined-card', - href: resource.link.url, + href: resource.link?.url ?? '#', target: Target.blank, attributes: { - 'data-type': resource.type.id, - 'data-tags': resource.tags.map((tag) => tag.id).join(', '), + 'data-type': resource.type, + 'data-tags': resource.tags.join(', '), 'data-description': resource.description, }, [ if (resource.imageUrl case final imageUrl?) - div( - classes: 'card-image-holder-material-3', + div(classes: 'card-image-holder-material-3', [ + img(src: imageUrl, alt: ''), + ]), + div(classes: 'card-leading', [ + span( + classes: [ + 'pill-sm', + switch (resource.type) { + 'codelab' || 'workshop' => 'flutter-blue', + 'quickstart' || 'demo' => 'purple', + _ => 'teal', + }, + ].toClasses, [ - img(src: imageUrl, alt: ''), + text( + resource.type.substring(0, 1).toUpperCase() + + resource.type.substring(1), + ), ], ), - div( - classes: 'card-leading', - [ - span( - classes: [ - 'pill-sm', - switch (resource.type) { - LearningResourceType.recipe => 'teal', - LearningResourceType.sample => 'purple', - LearningResourceType.tutorial => 'flutter-blue', - LearningResourceType.workshop => 'flutter-blue', - }, - ].toClasses, - [text(resource.type.formattedName)], - ), - _iconForSource(resource.link.source), - ], - ), - div( - classes: 'card-header', - [ - span( - classes: 'card-title', - [text(resource.name)], - ), - ], - ), - div( - classes: 'card-content', - [text(resource.description)], - ), + _iconForLabel(resource.link?.label ?? ''), + ]), + div(classes: 'card-header', [ + span(classes: 'card-title', [ + text(resource.name), + ]), + ]), + div(classes: 'card-content', [ + text(resource.description), + ]), ], ); } - Component _iconForSource(LearningResourceSource source) => switch (source) { - LearningResourceSource.gitHub => svg( + Component _iconForLabel(String label) => switch (label) { + 'Flutter GitHub' => svg( classes: 'monochrome-icon', width: 24.px, height: 24.px, [ const Component.element( tag: 'use', - attributes: { - 'href': '/assets/images/social/github.svg#github', - }, + attributes: {'href': '/assets/images/social/github.svg#github'}, ), ], ), - LearningResourceSource.dartDocs => img( + 'Dart GitHub' || 'Dart docs' => img( src: '/assets/images/branding/dart/logo.svg', width: 24, alt: 'Dart logo', ), - LearningResourceSource.flutterDocs => img( - src: '/assets/images/branding/flutter/icon/1080.png', - alt: 'Flutter logo', - width: 24, - ), - LearningResourceSource.googleCodelab => svg( - width: 24.px, - height: 24.px, - [ - const Component.element( - tag: 'use', - attributes: { - 'href': - '/assets/images/social/google-developers.svg#google-developers', - }, - ), - ], - ), - LearningResourceSource.youTube => svg( + 'Google Codelab' => svg(width: 24.px, height: 24.px, [ + const Component.element( + tag: 'use', + attributes: { + 'href': + '/assets/images/social/google-developers.svg#google-developers', + }, + ), + ]), + 'YouTube' => svg( attributes: {'style': 'color: red'}, width: 24.px, height: 24.px, [ const Component.element( tag: 'use', - attributes: { - 'href': '/assets/images/social/youtube.svg#youtube', - }, + attributes: {'href': '/assets/images/social/youtube.svg#youtube'}, ), ], ), - LearningResourceSource.medium => svg( - classes: 'monochrome-icon', - width: 24.px, - height: 24.px, - [ - const Component.element( - tag: 'use', - attributes: { - 'href': '/assets/images/social/medium.svg#medium', - }, - ), - ], + 'Medium' => svg(classes: 'monochrome-icon', width: 24.px, height: 24.px, [ + const Component.element( + tag: 'use', + attributes: {'href': '/assets/images/social/medium.svg#medium'}, + ), + ]), + 'Flutter docs' || _ => img( + src: '/assets/images/branding/flutter/icon/1080.png', + alt: 'Flutter logo', + width: 24, ), }; } diff --git a/site/lib/src/data/learning_resources.dart b/site/lib/src/data/learning_resources.dart deleted file mode 100644 index 6fe2235a30a..00000000000 --- a/site/lib/src/data/learning_resources.dart +++ /dev/null @@ -1,1844 +0,0 @@ -// Copyright 2025 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 '../models/learning_resource_model.dart'; - -final List allLearningResources = [ - ..._codelabs, - ..._cookbookRecipes, - ..._demos, - ..._videos, - ..._quickStartsForDart, - ..._quickStartsForFlutter, -]; - -final List _videos = [ - LearningResource( - name: 'Your first Flutter app workshop', - description: - 'An instructor-led version of our very popular ' - '\'Write your first Flutter app\' codelab.', - type: LearningResourceType.workshop, - tags: [ - LearningResourceTag.goodForBeginners, - ], - link: ( - url: 'https://www.youtube.com/watch?v=8sAyPDLorek', - source: LearningResourceSource.youTube, - ), - ), -]; - -final List _codelabs = [ - LearningResource( - name: 'Your first Flutter app', - description: - 'Create a simple random-name generator app. ' - 'This app is responsive and runs on mobile, desktop, and web.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.goodForBeginners, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-codelab-first', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Records and Patterns in Dart', - description: - 'Discover Dart 3\'s new records and patterns features. ' - 'Learn how you can use them in a Flutter app to help you ' - 'write more readable and maintainable Dart code.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.dart, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/dart-patterns-records', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Scrolling experiences in Flutter', - description: - 'Start with an app that performs ' - 'simple, straightforward scrolling and enhance it to ' - 'create fancy and custom scrolling effects by using slivers.', - type: LearningResourceType.tutorial, - tags: [], - link: ( - url: 'https://www.youtube.com/watch?v=YY-_yrZdjGc', - source: LearningResourceSource.youTube, - ), - ), - LearningResource( - name: 'Take your Flutter app from boring to beautiful', - description: - 'Learn how to use some of the features in Material 3 to ' - 'make your app beautiful and responsive.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.design, - LearningResourceTag.material, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-boring-to-beautiful', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Building next generation UIs in Flutter', - description: - 'Learn how to build a Flutter app that uses the ' - 'power of `flutter_animate`, fragment shaders, and particle fields.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.animation, - LearningResourceTag.design, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-next-gen-uis', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Adaptive Apps in Flutter', - description: - 'Learn how to build a Flutter app that adapts to ' - 'the platform that it\'s running on, be that ' - 'Android, iOS, the web, Windows, macOS, or Linux.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.desktop, - LearningResourceTag.ios, - LearningResourceTag.web, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-adaptive-app', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Animations in Flutter', - description: - 'Learn how to build animated effects in Flutter. ' - 'You\'ll learn how to build implicit and explicit animations, ' - 'and customize navigation transition animations using ' - 'the animations package and predictive back on Android.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.animation, - ], - link: ( - url: 'https://codelabs.developers.google.com/advanced-flutter-animations', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Building Beautiful Transitions with Material Motion for Flutter', - description: - 'Learn how to use the Material animations package to ' - 'add pre-built transitions to a Material app called Reply.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.animation, - LearningResourceTag.material, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/material-motion-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'How to debug layout issues with the Flutter Inspector', - description: - 'Step-by-step instructions on how to debug common layout problems ' - 'using the Flutter Inspector and Layout Explorer.', - type: LearningResourceType.tutorial, - tags: [], - link: ( - url: - 'https://blog.flutter.dev/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db', - source: LearningResourceSource.medium, - ), - ), - LearningResource( - name: 'Implicit animations', - description: - 'Use DartPad (no downloads required!) to learn how ' - 'to use implicit animations to add motion and ' - 'create visual effects for the widgets in your UI.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.animation, - ], - link: ( - url: '/codelabs/implicit-animations', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'MDC-101 - Material Components (MDC) Basics', - description: - 'Learn the basics of using Material Components by ' - 'building a simple app with core components.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.material, - LearningResourceTag.design, - ], - link: ( - url: 'https://codelabs.developers.google.com/codelabs/mdc-101-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'MDC-102 - Material Structure and Layout', - description: - 'Learn how to use Material for structure and layout in Flutter. ' - 'Continue building the e-commerce app, introduced in MDC-101, ' - 'by adding navigation, structure, and data.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.material, - LearningResourceTag.design, - ], - link: ( - url: 'https://codelabs.developers.google.com/codelabs/mdc-102-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'MDC-103 - Material Theming with Color, Shape, Elevation, and Type', - description: - 'Discover how Material Components for Flutter make it easy to ' - 'differentiate your product, and express your brand through design.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.material, - LearningResourceTag.design, - ], - link: ( - url: 'https://codelabs.developers.google.com/codelabs/mdc-103-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'MDC-104 - Material Advanced Components', - description: - 'Improve your design and learn to use our advanced backdrop menu.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.material, - LearningResourceTag.design, - ], - link: ( - url: 'https://codelabs.developers.google.com/codelabs/mdc-104-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Adding AdMob Ads to a Flutter app', - description: - 'Learn how to add an AdMob banner, an interstitial ad, and ' - 'a rewarded ad to an app called Awesome Drawing Quiz, ' - 'a game that lets players guess the name of the drawing.', - type: LearningResourceType.tutorial, - tags: [], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Adding an AdMob banner and native inline ads to a Flutter app', - description: - 'Learn how to implement inline banner and native ads to a ' - 'travel booking app that lists possible flight destinations.', - type: LearningResourceType.tutorial, - tags: [], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/admob-inline-ads-in-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Adding in-app purchases to your Flutter app', - description: - 'Extend a simple gaming app that uses the Dash mascot as ' - 'currency to offer three types of in-app purchases: ' - 'consumable, non-consumable, and subscription.', - type: LearningResourceType.tutorial, - tags: [], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Add a user authentication flow using FirebaseUI', - description: - 'Learn how to add Firebase authentication to a Flutter app with ' - 'only a few lines of code.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.firebase, - ], - link: ( - url: 'https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Get to know Firebase for Flutter', - description: - 'Build an event RSVP and guestbook chat app on ' - 'both Android and iOS using Flutter, authenticating users with ' - 'Firebase Authentication, and sync data using Cloud Firestore.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.firebase, - LearningResourceTag.ios, - ], - link: ( - url: 'https://firebase.google.com/codelabs/firebase-get-to-know-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Notifications with Firebase Cloud Messaging', - description: - 'Learn how to develop a multi-platform app with ' - 'Flutter and Firebase Cloud Messaging, integrating FCM to ' - 'send and receive messages on Android, iOS, and web.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.firebase, - LearningResourceTag.web, - LearningResourceTag.ios, - ], - link: ( - url: 'https://firebase.google.com/codelabs/firebase-fcm-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Add sound and music to your Flutter game with SoLoud', - description: - 'The SoLoud package, a free and portable engine, ' - 'delivers the low-latency and high-performance sound ' - 'that\'s essential for many games.', - type: LearningResourceType.tutorial, - tags: [], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-codelab-soloud', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Build a 2D physics game with Flutter and Flame', - description: - 'This codelab guides you through crafting game mechanics in a ' - 'Flutter and Flame game using a 2D physics simulation called Forge2D.', - type: LearningResourceType.tutorial, - tags: [], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-flame-forge2d', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Build a word puzzle with Flutter', - description: - 'This codelab focuses on building word puzzle games, and ' - 'dives into using Flutter\'s background processing to ' - 'generate expansive crossword-style grids of interlocking words.', - type: LearningResourceType.tutorial, - tags: [], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-word-puzzle', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Introduction to Flame with Flutter', - description: - 'Build a Breakout clone using the Flame 2D game engine and ' - 'embed it in a Flutter wrapper.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.goodForBeginners, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-flame-brick-breaker', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Adding Google Maps to a Flutter app', - description: - 'Display a Google map in an app, retrieve data from a web service, ' - 'and display the data as markers on the map.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.googleApis, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/google-maps-in-flutter', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Adding WebView to your Flutter app', - description: - 'With the WebView Flutter plugin you can add a WebView widget to ' - 'your Android or iOS Flutter app.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.ios, - ], - link: ( - url: 'https://codelabs.developers.google.com/codelabs/flutter-webview', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Using FFI in a Flutter plugin', - description: - 'Learn how to use Dart\'s FFI (foreign function interface) library, ' - 'ffigen, allowing you to leverage existing native libraries that ' - 'provide a C interface.', - type: LearningResourceType.tutorial, - tags: [], - link: ( - url: 'https://codelabs.developers.google.com/codelabs/flutter-ffigen', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'How to test a Flutter app', - description: - 'Start with a simple app that manages state with the Provider package. ' - 'Unit test the provider package. ' - 'Write widget tests for two of the widgets. ' - 'Use Flutter Driver to create an integration test.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-app-testing/', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Adding a Home Screen widget to your Flutter app', - description: - 'Learn how to add a Home Screen widget to your Flutter app on iOS. ' - 'This applies to your home screen, lock screen, or the today view.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.ios, - ], - link: ( - url: 'https://codelabs.developers.google.com/flutter-home-screen-widgets', - source: LearningResourceSource.googleCodelab, - ), - ), - LearningResource( - name: 'Write a Flutter desktop application', - description: - 'Build a Flutter desktop app (Windows, Linux, or macOS) that ' - 'accesses GitHub APIs, and create and use plugins to ' - 'interact with native APIs and desktop applications.', - type: LearningResourceType.tutorial, - tags: [ - LearningResourceTag.desktop, - ], - link: ( - url: - 'https://codelabs.developers.google.com/codelabs/flutter-github-client', - source: LearningResourceSource.googleCodelab, - ), - ), -]; - -final List _cookbookRecipes = [ - LearningResource( - name: 'Animate a page route transition', - description: - 'Transition between routes by animating the new route ' - 'into view from the bottom of the screen.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.animation, - ], - link: ( - url: '/cookbook/animation/page-route-animation', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Animate a widget using a physics simulation', - description: - 'Learn how to move a widget from a dragged point back to ' - 'the center using a spring simulation.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.animation, - ], - link: ( - url: '/cookbook/animation/physics-simulation', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Animate the properties of a container', - description: - 'Use AnimatedContainer to animate the ' - 'size, background color, and border radius of a Container.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.animation, - ], - link: ( - url: '/cookbook/animation/animated-container', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Fade a widget in and out', - description: - 'The AnimatedOpacity widget makes it easy to ' - 'perform opacity animations.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.animation, - ], - link: ( - url: '/cookbook/animation/opacity-animation', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Add a drawer to a screen', - description: - 'Use the Drawer widget in combination with a Scaffold to ' - 'create a layout with a Material Design drawer.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.widgets, - LearningResourceTag.material, - LearningResourceTag.layout, - ], - link: ( - url: '/cookbook/design/drawer', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Display a snackbar', - description: 'Use the Snackbar widget to display messages to your users.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/design/snackbars', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Export fonts from a package', - description: 'Use a font across multiple apps.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/design/package-fonts', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Update the UI based on orientation', - description: - 'Build a list that displays two columns in portrait mode and ' - 'three columns in landscape mode.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/design/orientation', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Use a custom font', - description: 'Apply fonts to your entire app or individual widgets.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/design/fonts', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Use themes to share colors and font styles', - description: 'To share styles across your app, use Themes.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/design/themes', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Work with tabs', - description: - 'Working with tabs is a common pattern in mobile apps that ' - 'follow the Material Design or Cupertino guidelines.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/design/tabs', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Create a download button', - description: - 'Build a download button that transitions through ' - 'multiple visual states, based on the status of an app download.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.design, - LearningResourceTag.animation, - ], - link: ( - url: '/cookbook/effects/download-button', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Create a nested navigation flow', - description: - 'Create top level routes, and routes nested below specific widgets.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.routingAndNavigation, - ], - link: ( - url: '/cookbook/effects/nested-nav', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Create a scrolling parallax effect', - description: - 'Create the parallax effect by building a ' - 'list of cards with images that \'move\'.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.design, - ], - link: ( - url: '/cookbook/effects/parallax-scrolling', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Create a shimmer loading effect', - description: - 'Communicate that data is loading with a ' - 'chrome color shimmer on the screen.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.animation, - ], - link: ( - url: '/cookbook/effects/shimmer-loading', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Create a staggered menu animation', - description: - 'Build a drawer menu with animated content that ' - 'is staggered and has a button that pops in at the bottom', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.animation, - LearningResourceTag.design, - ], - link: ( - url: '/cookbook/effects/staggered-menu-animation', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Create a typing indicator', - description: - 'Build a speech bubble typing indicator that ' - 'animates in and out of view.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/effects/typing-indicator', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Create an expandable FAB', - description: - 'Create a floating action button that spawns other action buttons.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.design, - ], - link: ( - url: '/cookbook/effects/expandable-fab', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Drag a UI element', - description: - 'Build a drag-and-drop interaction when the user long presses.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.design, - ], - link: ( - url: '/cookbook/effects/drag-a-widget', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Build a form with validation', - description: 'Learn how to add validation to a form.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/forms/validation', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Create and style a text field', - description: 'In this recipe, explore how to create and style text fields.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/forms/text-input', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Focus and text fields', - description: 'Shift focus to a text field programmatically.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/forms/focus', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Handle changes to a text field', - description: 'Listen for changes to a TextField using a callback.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/forms/text-field-changes', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Retrieve the value of a text field', - description: - 'Learn how to retrieve the text a user has entered into a text field.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/forms/retrieve-input', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Add achievements and leaderboards to your game', - description: - 'Use the games_services package to ' - 'add leaderboard functionality to your mobile game.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/games/achievements-leaderboard', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Add multiplayer support to your Flutter game', - description: - 'Use the cloud_firestore package to ' - 'implement multiplayer capabilities in your game.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.firebase, - ], - link: ( - url: '/cookbook/games/firestore-multiplayer', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Add ads to your Flutter game', - description: - 'Use the google_mobile_ads package to ' - 'add a banner ad to your app or game.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/plugins/google-mobile-ads', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Add Material touch ripples', - description: 'Use the Inkwell widget to display a ripple animation.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.material, - ], - link: ( - url: '/cookbook/gestures/ripples', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Handle taps', - description: - 'Use the GestureDetector widget to respond to ' - 'fundamental actions, such as tapping and dragging.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/gestures/handling-taps', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Implement swipe to dismiss', - description: 'Learn how to use the Dismissible widget.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/gestures/dismissible', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Display images from the internet', - description: - 'To work with images from a URL, use the Image.network() constructor.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.goodForBeginners, - ], - link: ( - url: '/cookbook/images/network-image', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Fade in images with a placeholder', - description: - 'Use the FadeInImage widget to ' - 'show a visual placeholder before an image loads.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/images/fading-in-images', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Grid lists', - description: 'Learn to use a GridView widget.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.layout, - ], - link: ( - url: '/cookbook/lists/grid-lists', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Horizontal lists', - description: 'Learn to display items horizontally in a scrollable list.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.layout, - ], - link: ( - url: '/cookbook/lists/horizontal-list', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Lists with different types of items', - description: 'Create a list with headers followed by a few list items.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.layout, - ], - link: ( - url: '/cookbook/lists/mixed-list', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Lists and floating app bars', - description: 'Place a floating app bar or navigation bar above a list.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.layout, - ], - link: ( - url: '/cookbook/lists/floating-app-bar', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Basic lists', - description: 'Learn to display items with the ListView widget.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.layout, - ], - link: ( - url: '/cookbook/lists/basic-list', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Long lists', - description: - 'Work with longer lists with the Listview.builder constructor.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.layout, - ], - link: ( - url: '/cookbook/lists/long-lists', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Lists with spaced items', - description: 'Create a list with padding between items.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.layout, - ], - link: ( - url: '/cookbook/lists/spaced-items', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Animate a widget across screens', - description: - 'Use the Hero widget to animate a widget from one screen to the next.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.routingAndNavigation, - LearningResourceTag.animation, - ], - link: ( - url: '/cookbook/navigation/hero-animations', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Navigate to a new screen and back', - description: 'This recipe uses the Navigator to navigate to a new route.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.routingAndNavigation, - ], - link: ( - url: '/cookbook/navigation/navigation-basics', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Named routes', - description: 'Create named routes and navigate to them.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.routingAndNavigation, - ], - link: ( - url: '/cookbook/navigation/named-routes', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Arguments and named routes', - description: - 'Pass arguments to a named route and read the arguments on that route.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.routingAndNavigation, - ], - link: ( - url: '/cookbook/navigation/navigate-with-arguments', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Android app links', - description: 'Set up deep linking on Android', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.routingAndNavigation, - ], - link: ( - url: '/cookbook/navigation/set-up-app-links', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'iOS universal links', - description: 'Set up universal links for iOS', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.routingAndNavigation, - LearningResourceTag.ios, - ], - link: ( - url: '/cookbook/navigation/set-up-universal-links', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Return data from a screen', - description: - 'Return data from one screen to another with the Navigator.pop method.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.routingAndNavigation, - ], - link: ( - url: '/cookbook/navigation/returning-data', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Send data to a new screen', - description: 'Send data from one screen to new one.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.routingAndNavigation, - ], - link: ( - url: '/cookbook/navigation/passing-data', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Fetch data from the internet', - description: 'Learn to use HTTP in your app.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/networking/fetch-data', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Make authenticated requests', - description: 'Authorization headers in HTTP', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/networking/authenticated-requests', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Send data to the internet', - description: 'Send HTTP POST requests in your app.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/networking/send-data', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Update data over the internet', - description: 'Send an HTTP put request.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/networking/update-data', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Delete data on the internet', - description: 'Send an HTTP delete request.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/networking/delete-data', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'WebSockets', - description: 'Connect to and communicate with a websocket.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/networking/web-sockets', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Parse JSON in the background', - description: 'Learn to use Dart\'s Isolate objects', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/networking/background-parsing', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Persist data with SQLite', - description: 'Use the sqflite package.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/persistence/sqlite', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Read and write files', - description: - 'Use the dart:io library and path_provider plugin to ' - 'save files to disk.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/persistence/reading-writing-files', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Store key-value data on disk', - description: 'Persist data with shared_preferences', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/persistence/key-value', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Play and pause a video', - description: - 'Play videos stored on the file system, as an asset, ' - 'or from the internet.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/plugins/play-video', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Use the camera', - description: 'Learn to use a devices camera.', - type: LearningResourceType.recipe, - tags: [], - link: ( - url: '/cookbook/plugins/picture-using-camera', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Report errors to a service', - description: 'Report errors to Sentry crash reporting.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: '/cookbook/maintenance/error-reporting', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Performance profiling', - description: 'Write a test that records a performance timeline.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: '/cookbook/testing/integration/profiling', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Write unit tests', - description: 'An introduction to writing unit tests.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: '/cookbook/testing/unit/introduction', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Write widget tests', - description: 'An introduction to writing widget tests.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: '/cookbook/testing/widget/introduction', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Mock dependencies in tests', - description: 'The basics of mocking with the Mockito package.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: '/cookbook/testing/unit/mocking', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Find widgets in tests', - description: - 'This recipe looks at the \'find\' constant ' - 'provided by the flutter_test package.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: '/cookbook/testing/widget/finders', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Handle scrolling', - description: 'Learn how to scroll in widget tests.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: '/cookbook/testing/widget/scrolling', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'App orientation', - description: 'Learn how to check app orientation in widget tests.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: '/cookbook/testing/widget/orientation', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Tap, drag, and enter text', - description: 'Interact with widgets in widget tests.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: '/cookbook/testing/widget/tap-drag', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Persistent storage architecture - SQL', - description: 'Save complex application data to a user\'s device with SQL.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.architecture, - ], - link: ( - url: '/app-architecture/design-patterns/sql', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Error handling with Result objects', - description: 'Improve error handling across classes with Result objects.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.testing, - LearningResourceTag.architecture, - ], - link: ( - url: '/app-architecture/design-patterns/result', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Optimistic state', - description: - 'Improve the perception of responsiveness of an application by ' - 'implementing optimistic state.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.architecture, - ], - link: ( - url: '/app-architecture/design-patterns/optimistic-state', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Offline First', - description: - 'Implement offline-first support for one feature in an application.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.architecture, - ], - link: ( - url: '/app-architecture/design-patterns/offline-first', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Persistent storage architecture - Key-value data', - description: - 'Save application data to a user\'s on-device key-value store.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.architecture, - ], - link: ( - url: '/app-architecture/design-patterns/key-value-data', - source: LearningResourceSource.flutterDocs, - ), - ), - LearningResource( - name: 'Command pattern', - description: 'Simplify view model logic by implementing a Command class.', - type: LearningResourceType.recipe, - tags: [ - LearningResourceTag.architecture, - ], - link: ( - url: '/app-architecture/design-patterns/command', - source: LearningResourceSource.flutterDocs, - ), - ), -]; - -final List _demos = [ - LearningResource( - name: 'Add-to-app', - description: 'Recommended approaches for adding Flutter to existing apps.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.ios, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/add_to_app', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Android splash screen', - description: - 'A Flutter sample app that exemplifies how to ' - 'implement an animated splash screen for Android devices.', - type: LearningResourceType.sample, - tags: [], - link: ( - url: 'https://github.com/flutter/samples/tree/main/android_splash_screen', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'iOS app clip', - description: - 'A sample project demonstrating integration with iOS App Clip.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.ios, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/ios_app_clip', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Swift platform view', - description: - 'A Flutter sample app that combines a iOS-native ' - 'UIViewController with a full-screen Flutter view.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.ios, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/platform_view_swift', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Simplistic editor', - description: - 'This sample text editor showcases the use of TextEditingDeltas and ' - 'a DeltaTextInputClient to expand and contract styled ranges of text.', - type: LearningResourceType.sample, - tags: [], - link: ( - url: 'https://github.com/flutter/samples/tree/main/simplistic_editor', - source: LearningResourceSource.gitHub, - ), - ), -]; - -final List _quickStartsForDart = [ - LearningResource( - name: 'Command-line app', - description: - 'A command line app that parses command-line options and ' - 'fetches data from GitHub.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.dart, - ], - link: ( - url: 'https://github.com/dart-lang/samples/tree/main/command_line', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Extension methods', - description: 'Demonstrates Dart\'s extensions method syntax.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.dart, - ], - link: ( - url: 'https://github.com/dart-lang/samples/tree/main/extension_methods', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'FFI', - description: - 'A series of simple examples demonstrating how to ' - 'call C libraries from Dart.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.dart, - ], - link: ( - url: 'https://github.com/dart-lang/samples/tree/main/ffi', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Isolates (in a CLI)', - description: - 'Command line applications that demonstrate how to ' - 'work with Concurrency in Dart using isolates.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.dart, - ], - link: ( - url: 'https://github.com/dart-lang/samples/tree/main/ffi', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Native Dart app', - description: - 'A command line application that can be compiled to ' - 'native code using `dart compile exe`.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.dart, - ], - link: ( - url: 'https://github.com/dart-lang/samples/tree/main/native_app', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Server side Dart', - description: 'Examples of running Dart on the server.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.dart, - ], - link: ( - url: 'https://github.com/dart-lang/samples/tree/main/server', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Package constraint solver', - description: - 'Demonstrates best-practices for publishing packages on pub.dev.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.dart, - ], - link: ( - url: 'https://github.com/dart-lang/samples/tree/main/server', - source: LearningResourceSource.gitHub, - ), - ), -]; - -final List _quickStartsForFlutter = [ - LearningResource( - name: 'Asset transformation', - description: - 'Demonstrates how to transform images\' color scales and formats.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.design, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/asset_transformation', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Background isolate channels', - description: 'Demonstrates how to use long-lived isolates.', - type: LearningResourceType.sample, - tags: [], - link: ( - url: - 'https://github.com/flutter/samples/tree/main/background_isolate_channels', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Code sharing', - description: - 'Demonstrates how to share business logic between ' - 'a Flutter client and Dart server using `package:shelf`.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.dart, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/code_sharing', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Context menus', - description: - 'This sample shows how to create and customize ' - 'cross-platform context menus, such as the ' - 'text selection toolbar on mobile or the right click menu on desktop.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.desktop, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/context_menus', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Desktop UI', - description: - 'Demonstrates desktop features in both ' - 'Material and FluentUI design systems.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.material, - LearningResourceTag.desktop, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/desktop_photo_search', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'AI generated dynamic theme', - description: - 'Demonstrates how to call on-device Flutter APIs ' - 'based on output from the Gemini API.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.ai, - LearningResourceTag.googleApis, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/dynamic_theme', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Form app', - description: - 'A sample demonstrating different types of forms and best practices.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.layout, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/form_app', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'AI todo list', - description: - 'A developer sample written in Flutter demonstrating how to ' - 'interact with a to-do list in natural language using the Gemini API.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.ai, - LearningResourceTag.googleApis, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/gemini_tasks', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Google Maps plugin', - description: 'Demonstrates the Google Maps for Flutter plugin.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.googleApis, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/google_maps', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Infinite list', - description: - 'A Flutter sample app that shows an implementation of ' - 'the \'infinite list\' UX pattern.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.layout, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/infinite_list', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Isolates', - description: - 'A sample application that demonstrates ' - 'best practices when using isolates.', - type: LearningResourceType.sample, - tags: [], - link: ( - url: 'https://github.com/flutter/samples/tree/main/isolate_example', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Navigation and routing', - description: - 'A sample that shows how to use `go_router` API to ' - 'handle common navigation scenarios.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.routingAndNavigation, - ], - link: ( - url: - 'https://github.com/flutter/samples/tree/main/navigation_and_routing', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Google Maps Flutter plugin', - description: - 'A sample place tracking app that uses the google_apps_flutter plugin.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.googleApis, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/place_tracker', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Platform adaptive design', - description: - 'This sample project shows a Flutter app that ' - 'maximizes application code reuse while adhering to ' - 'different design patterns on Android and iOS.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.design, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/platform_design', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Counter app with Provider', - description: - 'The starter Flutter application, but ' - 'using the Provider package to manage state.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.stateManagement, - LearningResourceTag.architecture, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/provider_counter', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Shopping app with Provider', - description: - 'A Flutter sample app that shows a ' - 'state management approach using the Provider package.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.stateManagement, - LearningResourceTag.architecture, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/provider_shopper', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Simple shaders', - description: 'A simple Flutter fragment shaders project.', - type: LearningResourceType.sample, - tags: [], - link: ( - url: 'https://github.com/flutter/samples/tree/main/simple_shader', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Desktop calculator', - description: - 'A calculator sample to demonstrate a ' - 'simple start for a desktop Flutter app.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.desktop, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/simplistic_calculator', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Testing app', - description: - 'A sample app that shows different types of testing in Flutter.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.testing, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/testing_app', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Web element embedding', - description: - 'Modifies the index.html of a Flutter app so ' - 'it is launched in a custom hostElement.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.web, - ], - link: ( - url: - 'https://github.com/flutter/samples/tree/main/web_embedding/element_embedding_demo', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'ng-flutter', - description: - 'A simple Angular app (and component) that ' - 'replicates the element embedding example, but in an Angular app.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.web, - LearningResourceTag.googleApis, - ], - link: ( - url: - 'https://github.com/flutter/samples/tree/main/web_embedding/ng-flutter', - source: LearningResourceSource.gitHub, - ), - ), - LearningResource( - name: 'Platform channels', - description: - 'A sample Flutter app which demonstrates how to ' - 'use `MethodChannel`, `EventChannel`, ' - '`BasicMessageChannel` and `MessageCodec`.', - type: LearningResourceType.sample, - tags: [ - LearningResourceTag.ios, - ], - link: ( - url: 'https://github.com/flutter/samples/tree/main/platform_channels', - source: LearningResourceSource.gitHub, - ), - ), -]; diff --git a/site/lib/src/models/learning_resource_model.dart b/site/lib/src/models/learning_resource_model.dart index 263c5decd74..12e0224df13 100644 --- a/site/lib/src/models/learning_resource_model.dart +++ b/site/lib/src/models/learning_resource_model.dart @@ -2,67 +2,107 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -final class LearningResource { - final String name; - final String description; - final LearningResourceType type; - final List tags; - final ({String url, LearningResourceSource source}) link; - final String? imageUrl; +import 'package:universal_web/web.dart' as web; +final class LearningResource { LearningResource({ required this.name, required this.description, required this.type, required this.tags, - required this.link, + this.link, this.imageUrl, }); + + /// Creates a [LearningResource] from a Map, used on the server + /// when parsing the yaml data files. + factory LearningResource.fromMap(Map map) { + return LearningResource( + name: map['name'] as String, + description: map['description'] as String, + type: map['type'] as String, + tags: (map['tags'] as List?)?.cast() ?? [], + link: ( + label: (map['link'] as Map)['label'] as String, + url: (map['link'] as Map)['url'] as String, + ), + imageUrl: map['imageUrl'] as String?, + ); + } + + /// Creates a [LearningResource] from a DOM Element, used on the client + /// for recreating and filtering existing resources. + factory LearningResource.fromElement(web.Element element) { + final dataType = element.getAttribute('data-type') ?? ''; + final dataTags = element.getAttribute('data-tags') ?? ''; + final dataDescription = element.getAttribute('data-description') ?? ''; + + return LearningResource( + name: element.id, + type: dataType, + tags: dataTags.split(',').map((t) => t.trim().toLowerCase()).toList(), + description: dataDescription, + ); + } + + final String name; + final String description; + final String type; + final List tags; + final ({String url, String label})? link; + final String? imageUrl; } enum LearningResourceType { - recipe('cookbook', 'Cookbook recipe'), - sample('demo', 'Demo'), - tutorial('codelab', 'Codelab'), - workshop('workshop', 'Workshop'); + tutorial('Tutorial', ['codelab', 'tutorial']), + sampleCode('Sample code', ['quickstart', 'demo', 'sample', 'sample code']), + workshop('Workshop', ['workshop', 'video']), + recipe('Recipe', ['recipe', 'how to', 'cookbook']); - const LearningResourceType(this.id, this.formattedName); + const LearningResourceType(this.label, this.tags); - final String id; - final String formattedName; + final String label; + final List tags; } enum LearningResourceTag { - ai('ai', 'AI'), - animation('animation', 'Animation'), - architecture('architecture', 'Architecture'), - cupertino('cupertino', 'Cupertino'), - dart('dart', 'Dart'), - design('design', 'Design'), - desktop('desktop', 'Desktop'), - firebase('firebase', 'Firebase'), - goodForBeginners('good-for-beginners', 'Good for beginners'), - googleApis('google-apis', 'Google APIs'), - ios('ios', 'iOS'), - layout('layout', 'Layout'), - material('material', 'Material'), - routingAndNavigation('routing-and-navigation', 'Routing and navigation'), - stateManagement('state-management', 'State management'), - testing('testing', 'Testing'), - web('web', 'Web'), - widgets('widgets', 'Widgets'); - - const LearningResourceTag(this.id, this.formattedName); + ai('AI', ['ai', 'gemini', 'llm']), + animation('Animation', ['animations', 'animate', 'animation']), + architecture('Architecture', [ + 'state-management', + 'architecture', + 'provider', + 'bloc', + 'stream', + ]), + cupertino('Cupertino', ['cupertino', 'ios', 'macos']), + design('Design', ['design', 'widgets']), + desktop('Desktop', ['windows', 'macos', 'linux']), + firebase('Firebase', ['firebase', 'firestore', 'cloud']), + goodForBeginners('Good for beginners', ['beginner', 'beginners']), + googleApis('Google APIs', ['google', 'gemini', 'maps', 'firebase', 'cloud']), + ios('iOS', ['cupertino', 'ios']), + layout('Layout', ['layout', 'lists', 'scrolling', 'widgets']), + material('Material', ['material', 'android']), + routingAndNavigation('Routing and navigation', [ + 'routing', + 'route', + 'navigation', + 'navigator', + ]), + stateManagement('State management', [ + 'state-management', + 'architecture', + 'provider', + 'bloc', + 'stream', + ]), + testing('Testing', ['testing', 'tests', 'test', 'perf', 'performance']), + web('Web', ['web', 'wasm']), + widgets('Widgets', ['widgets', 'layout']); - final String id; - final String formattedName; -} + const LearningResourceTag(this.label, this.tags); -enum LearningResourceSource { - dartDocs, - flutterDocs, - gitHub, - youTube, - googleCodelab, - medium, + final String label; + final List tags; } diff --git a/site/pubspec.yaml b/site/pubspec.yaml index f83d692add4..92f6f528639 100644 --- a/site/pubspec.yaml +++ b/site/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: crypto: ^3.0.6 html: ^0.15.6 http: ^1.5.0 - jaspr: ^0.21.5 + jaspr: ^0.21.6 jaspr_content: ^0.4.2 # Used as our template engine. liquify: ^1.3.0 @@ -32,7 +32,7 @@ dev_dependencies: ref: 88aa84df953e67b7595b1e214b717f26d81ed538 build_runner: ^2.9.0 build_web_compilers: ^4.3.0 - jaspr_builder: ^0.21.5 + jaspr_builder: ^0.21.6 sass: ^1.93.2 sass_builder: ^2.4.0 diff --git a/site/web/assets/js/learning-resources-index.js b/site/web/assets/js/learning-resources-index.js deleted file mode 100644 index 127b8c8eaf1..00000000000 --- a/site/web/assets/js/learning-resources-index.js +++ /dev/null @@ -1,344 +0,0 @@ -const filters = { - // These properties track the active filters and/or search term - type: new Set(), - tags: new Set(), - searchTerm: '', - // The keys correspond to a checkbox in the filter sidebar (created from - // 'type' in src/_data/learning-resources-index/filters.yml) - // The items inside the value array correspond to the 'type' property that - // can exist in the metadata (on items in the files in src/_data/learning-resources-index) - resourceTypeMapping: { - 'tutorial': ['codelab', 'tutorial'], - 'sample code': ['quickstart', 'demo', 'sample', 'sample code'], - 'workshop': ['workshop', 'video'], - 'recipe': ['recipe', 'how to', 'cookbook'] - }, - resourceTagMapping: { - 'ai': ['ai', 'gemini', 'llm'], - 'animation': ['animations', 'animate', 'animation'], - 'architecture': ['state-management', 'architecture', 'provider', 'bloc', 'stream'], - 'cupertino': ['cupertino', 'ios', 'macos'], - 'dart': ['dart', 'cli'], - 'design': ['design', 'widgets'], - 'desktop': ['windows', 'macos', 'linux'], - 'firebase': ['firebase', 'firestore', 'cloud'], - 'good for beginners': ['beginner', 'beginners'], - 'google apis': ['google', 'gemini', 'maps', 'firebase', 'cloud'], - 'ios': ['cupertino', 'ios'], - 'layout': ['layout', 'lists', 'scrolling', 'widgets'], - 'material': ['material', 'android'], - 'routing and navigation': ['routing', 'route', 'navigation', 'navigator'], - 'state management': ['state-management', 'architecture', 'provider', 'bloc', 'stream'], - 'testing': ['testing', 'tests', "test", 'perf', 'performance'], - 'web': ['web', 'wasm'], - 'widgets': ['widgets', 'layout'], - }, - - // Checks for existing filters, but not search terms - hasFilters: function () { - return this.type.size > 0 || - this.tags.size > 0; - }, - // Takes a Set, and returns a filtered Set - filter: function (resources) { - const resourcesToShow = new Set(); - let filteredResources = []; - if (this.hasFilters()) { - for (const resource of resources) { - const tags = resource.tags.join(' ').toLowerCase(); - const selectedFilterTags = Array.from(filters.tags); - const matchesTags = selectedFilterTags.some(t => { - return tags.includes(t) - }); - - const type = resource.type.toLowerCase(); - const selectedTypes = Array.from(filters.type); - const matchesTypes = selectedTypes.some(t => { - return t === type - }); - - if (matchesTags || matchesTypes) { - filteredResources.push(resource); - } - } - } else { - filteredResources = resources; - } - - for (const resource of filteredResources) { - const tags = resource.tags.join('').toLowerCase(); - const type = resource.type.toLowerCase(); - const description = resource.description.toLowerCase(); - const name = resource.name.toLowerCase(); - - if (name.includes(this.searchTerm) || - tags.toLowerCase().includes(this.searchTerm) || - type.includes(this.searchTerm) || - description.includes(this.searchTerm) - ) { - resourcesToShow.add(resource.name); - } - } - - return resourcesToShow; - }, - clear: function () { - this.type.clear(); - this.tags.clear(); - this.searchTerm = ''; - } -} - -function _setupResourceFilters() { - // index the resource metadata - const resourceGrid = document.getElementById('all-resources-grid'); - if (!resourceGrid) return; - const resourceCards = resourceGrid.querySelectorAll('.card'); - const resourcesInfo = _setupResourceInfo(resourceCards); - - // sets up the resource count element that says "Showing x / y" below the search bar. - const allResourcesCount = document.getElementById('total-resource-card-count'); - allResourcesCount.textContent = (resourcesInfo.length).toString(); - - // set up search bar interaction - const searchSection = document.getElementById('resource-search-group'); - const searchInput = searchSection.querySelector('.search-wrapper input'); - searchInput.addEventListener('input', _ => { - filters.searchTerm = searchInput.value.toLowerCase(); - filterResources(); - }); - - // set up checkbox interaction handling - const filterSection = document.getElementById('resource-filter-group'); - const allCheckboxes = filterSection.querySelectorAll('input'); - allCheckboxes.forEach(checkbox => { - _setupFilterChange(checkbox, filterResources); - }); - - // Clear filters button - const clearFiltersButton = document.getElementById("clear-resource-index-filters"); - clearFiltersButton.addEventListener('click', _ => { - filters.clear(); - searchInput.value = ''; - filterResources(); - allCheckboxes.forEach(box => { - box.checked = false; - }) - }); - - function toggleClearFiltersButton() { - clearFiltersButton.disabled = !(filters.hasFilters() || filters.searchTerm > 0); - } - - function filterResources() { - toggleClearFiltersButton(); - const resourcesToShow = filters.filter(resourcesInfo); - resourceCards.forEach(card => { - const resourceName = card.id; - if (resourcesToShow.has(resourceName)) { - card.classList.remove('hidden'); - } else { - card.classList.add('hidden'); - } - }); - const resourcesCount = document.getElementById('displayed-resource-card-count'); - resourcesCount.textContent = resourcesToShow.size.toString(); - } - - filterResources(); -} - - -function _setUpCollapsibleFilterLists() { - const filterSidebar = document.getElementById('resource-filter-group'); - const filterGroups = filterSidebar.querySelectorAll('ul'); - const toggleButtons = filterSidebar.querySelectorAll('button'); - - toggleButtons.forEach(button => { - const id = button.id; - const correspondingUlId = "#" + id.split('-').slice(0, 2).join('-'); - const ul = filterSidebar.querySelector(correspondingUlId); - - const liCount = Array.from(ul.querySelectorAll('li')).length; - if (liCount <= 2) { - button.classList.add('hidden'); - return; - } - - button.addEventListener('click', _ => { - const nodeList = ul.querySelectorAll('li'); - const liElements = Array.from(nodeList); - const isCollapsed = ul.classList.contains('collapsed'); - const icon = button.querySelector('.material-symbols'); - const label = button.querySelector('.label'); - if (isCollapsed) { - liElements.forEach(li => li.classList.remove('hidden')); - label.textContent = 'Less'; - icon.textContent = 'expand_less'; - ul.classList.remove('collapsed'); - } else { - liElements.slice(2).forEach(li => li.classList.add('hidden')); - icon.textContent = 'expand_more'; - label.textContent = 'More'; - ul.classList.add('collapsed'); - } - }); - }); - - // Show the first few items to start. - filterGroups.forEach(ul => { - const allFiltersForGroup = ul.querySelectorAll('li'); - const initialAmountToShow = 4; - for (let filterIndex = 0; filterIndex < initialAmountToShow && filterIndex < allFiltersForGroup.length; filterIndex += 1) { - allFiltersForGroup[filterIndex].classList.remove('hidden'); - } - }); -} - -function _setupResourceInfo(resourceCards) { - const resourcesInfo = []; - resourceCards.forEach(card => { - const resourceName = card.id; - if (!resourceName) return; - const tags = card.dataset.tags.split(', ').map(t => t.toLowerCase()); - resourcesInfo.push({ - name: resourceName, - type: card.dataset.type, - tags: tags, - description: card.dataset.description, - }); - - card.addEventListener('click', async (_) => { - window.dataLayer?.push({ - 'event': 'learning_resource_index_click', - 'learning_resource_type': card.dataset.type, - 'learning_resource_title': resourceName, - }); - }); - }); - return resourcesInfo; -} - -function _setupFilterChange(checkbox, filterResources) { - const id = checkbox.id; - const filter = id.split('-')[1].toLowerCase(); - // category refers to the filter types: tags, type - const category = checkbox.dataset.category.toLowerCase(); - - checkbox.addEventListener('change', _ => { - if (checkbox.checked) { - window.dataLayer?.push({ - 'event': 'learning_resource_index_filter_selected', - 'learning_resource_filter_name': filter, - 'learning_resource_filter_type': category, - }); - switch (category) { - case 'tags': - const tagGroup = filters.resourceTagMapping[filter]; - tagGroup.forEach(tag => filters[category].add(tag)) - break; - case 'type': - const typeGroup = filters.resourceTypeMapping[filter]; - typeGroup.forEach(type => filters[category].add(type)) - break; - } - } else { - switch (category) { - case 'tags': - const tagGroup = filters.resourceTagMapping[filter]; - tagGroup.forEach(tag => filters[category].delete(tag)) - break; - case 'type': - const typeGroup = filters.resourceTypeMapping[filter]; - typeGroup.forEach(type => filters[category].delete(type)) - break; - } - } - - filterResources(); - }); -} - -// This button is only displayed on smaller screens -function _setupDropdownMenu() { - const pageContent = document.getElementById('resource-index-content'); - if (!pageContent) return; - - const filtersButton = pageContent.querySelector('.show-filters-button'); - const filtersEl = document.getElementById('resource-filter-group-wrapper') || pageContent.querySelector('.right-col'); - const openToggleCheckbox = document.getElementById('open-filter-toggle'); - - if (!filtersButton || !filtersEl) return; - - function _closeMenu() { - if (openToggleCheckbox) { - openToggleCheckbox.checked = false; - } else { - filtersEl.classList.remove('show'); - } - filtersButton.ariaExpanded = 'false'; - } - - function _openMenu() { - if (openToggleCheckbox) { - openToggleCheckbox.checked = true; - } else { - filtersEl.classList.add('show'); - } - filtersButton.ariaExpanded = 'true'; - } - - function _isMenuOpen() { - if (openToggleCheckbox) { - return openToggleCheckbox.checked; - } else { - return filtersEl.classList.contains('show'); - } - } - - filtersButton.addEventListener('click', (_) => { - if (_isMenuOpen()) { - _closeMenu(); - } else { - _openMenu(); - } - }); - - document.addEventListener('keydown', (event) => { - if (event.key === 'Escape') { - _closeMenu(); - } - }); - - // Close the dropdown if anywhere not in the filters menu is. - const content = document.getElementById('all-resources-grid'); - if (content) { - content.addEventListener('click', () => { - if (_isMenuOpen()) { - _closeMenu(); - } - }); - } -} - -function shuffleElements(container) { - const elements = container.children; - for (let i = elements.length; i >= 0; i--) { - container.appendChild(elements[Math.random() * i | 0]); - } -} - -window.addEventListener('load', (_) => { - const resourceGrid = document.getElementById('all-resources-grid'); - shuffleElements(resourceGrid); -}) - - -document.onreadystatechange = () => { - if (document.readyState === "interactive" || - document.readyState === "complete") { - _setupResourceFilters(); - _setUpCollapsibleFilterLists(); - _setupDropdownMenu(); - } -} diff --git a/src/content/reference/learning-resources.md b/src/content/reference/learning-resources.md index 8589bce072e..f3826a95b9a 100644 --- a/src/content/reference/learning-resources.md +++ b/src/content/reference/learning-resources.md @@ -5,7 +5,6 @@ shortTitle: Learning resources showBreadcrumbs: false bodyClass: wide-site-content showToc: false -js: [ { url: '/assets/js/learning-resources-index.js', defer: true } ] --- :::secondary diff --git a/src/data/learning-resources-index/codelabs.yml b/src/data/learning-resources-index/codelabs.yml new file mode 100644 index 00000000000..e992280934d --- /dev/null +++ b/src/data/learning-resources-index/codelabs.yml @@ -0,0 +1,438 @@ +# Good for beginners +- name: Your first Flutter app workshop + description: | + An instructor-led version of our very popular + 'Write your first Flutter app' codelab. + tags: + - beginner + - intro + type: workshop + link: + label: YouTube + url: https://www.youtube.com/watch?v=8sAyPDLorek + +- name: Your first Flutter app + description: | + Create a simple random-name generator app. + This app is responsive and runs on mobile, desktop, and web. + tags: + - beginner + - intro + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-codelab-first + +# Next steps + +- name: Records and Patterns in Dart + description: | + Discover Dart 3's new records and patterns features. + Learn how you can use them in a Flutter app to help you + write more readable and maintainable Dart code. + tags: + - dart + - records + - pattern matching + sdk: dart + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/dart-patterns-records + +- name: Scrolling experiences in Flutter + description: | + Start with an app that performs simple, straightforward scrolling + and enhance it to create fancy and custom scrolling effects + by using slivers. + tags: + - scrolling + - scroll + type: codelab + link: + label: YouTube + url: https://www.youtube.com/watch?v=YY-_yrZdjGc + +#Design + +- name: Take your Flutter app from boring to beautiful + description: | + Learn how to use some of the features in Material 3 + to make your app beautiful and responsive. + tags: + - design + - material + - responsive + - adaptive + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-boring-to-beautiful + +- name: Building next generation UIs in Flutter + description: | + Learn how to build a Flutter app that uses the power of `flutter_animate`, + fragment shaders, and particle fields. + tags: + - animation + - flutter animate + - ui + - shaders + + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-next-gen-uis + +- name: Adaptive Apps in Flutter + description: | + Learn how to build a Flutter app that adapts to the + platform that it's running on, be that Android, iOS, + the web, Windows, macOS, or Linux. + tags: + - adaptive + - linux + - macos + - desktop + - windows + - android + - ios + - web + + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-adaptive-app + +- name: Animations in Flutter + description: | + Learn how to build animated effects in Flutter. You'll learn how to build + implicit and explicit animations, and customize + navigation transition animations the animations package and predictive back + on Android. + tags: + - animations + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/advanced-flutter-animations + +- name: Building Beautiful Transitions with Material Motion for Flutter + description: | + Learn how to use the Material animations package to + add pre-built transitions to a Material app called Reply. + tags: + - animations + - material + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/material-motion-flutter + +- name: How to debug layout issues with the Flutter Inspector + description: | + Step-by-step instructions on how to debug + common layout problems using the Flutter + Inspector and Layout Explorer. + tags: + - debug + - tooling + - developer tools + type: codelab + link: + label: Medium + url: https://blog.flutter.dev/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db + +- name: Implicit animations + description: | + Use DartPad (no downloads required!) to learn how to use + implicit animations to add motion and create + visual effects for the widgets in your UI. + tags: + - animations + type: codelab + link: + label: Flutter docs + url: /codelabs/implicit-animations + +- name: MDC-101 - Material Components (MDC) Basics + description: | + Learn the basics of using Material Components by building + a simple app with core components. + tags: + - material + - design + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/mdc-101-flutter + +- name: MDC-102 - Material Structure and Layout + description: | + Learn how to use Material for structure and layout in Flutter. + Continue building the e-commerce app, introduced in MDC-101, + by adding navigation, structure, and data. + tags: + - material + - design + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/mdc-102-flutter + +- name: MDC-103 - Material Theming with Color, Shape, Elevation, and Type + description: | + Discover how Material Components for Flutter make it + easy to differentiate your product, and express your + brand through design. + tags: + - material + - design + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/mdc-103-flutter + +- name: MDC-104 - Material Advanced Components + description: | + Improve your design and learn to use our advanced + component backdrop menu. + tags: + - material + - design + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/mdc-104-flutter + +#Flutter with + +## Monetizing Flutter +- name: Adding AdMob Ads to a Flutter app + description: | + Learn how to add an AdMob banner, an interstitial ad, + and a rewarded ad to an app called Awesome Drawing Quiz, + a game that lets players guess the name of the drawing. + tags: + - ads + - admob + - monetization + + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter + +- name: Adding an AdMob banner and native inline ads to a Flutter app + description: | + Learn how to implement inline banner and native ads + to a travel booking app that lists possible + flight destinations. + tags: + - ads + - admob + - monetization + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/admob-inline-ads-in-flutter + +- name: Adding in-app purchases to your Flutter app + description: | + Extend a simple gaming app that uses the Dash mascot as + currency to offer three types of in-app purchases: + consumable, non-consumable, and subscription. + tags: + - in app purchases + - monetization + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases + +## Firebase + +- name: Add a user authentication flow using FirebaseUI + description: | + Learn how to add Firebase authentication to a Flutter app + with only a few lines of code. + tags: + - firebase + - authentication + - firebase UI + type: codelab + link: + label: Google Codelab + url: https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps + +- name: Get to know Firebase for Flutter + description: | + Build an event RSVP and guestbook chat app on both Android + and iOS using Flutter, authenticating users with Firebase + Authentication, and sync data using Cloud Firestore. + tags: + - firebase + - android + - ios + - firestore + - real time database + type: codelab + link: + label: Google Codelab + url: https://firebase.google.com/codelabs/firebase-get-to-know-flutter + +- name: Notifications with Firebase Cloud Messaging + description: | + Learn how to develop a multi-platform app with Flutter + and Firebase Cloud Messaging, integrating FCM to send and + receive messages on Android, iOS, and web. + tags: + - firebase + - cloud messaging + - multi platform + - web + - android + - ios + type: codelab + link: + label: Google Codelab + url: https://firebase.google.com/codelabs/firebase-fcm-flutter + +## Games + +- name: Add sound and music to your Flutter game with SoLoud + description: | + The SoLoud package, a free and portable engine, + delivers the low-latency and high-performance sound that's + essential for many games. + tags: + - game + - audio + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-codelab-soloud + +- name: Build a 2D physics game with Flutter and Flame + description: | + This codelab guides you through crafting game mechanics in a + Flutter and Flame game using a 2D physics simulation called Forge2D. + tags: + - game + - physics + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-flame-forge2d + +- name: Build a word puzzle with Flutter + description: | + This codelab focuses on building word puzzle games, + and dives into using Flutter's background processing + to generate expansive crossword-style grids of interlocking words. + tags: + - game + - isolate + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-word-puzzle + +- name: Introduction to Flame with Flutter + description: | + Build a Breakout clone using the Flame 2D game engine and + embed it in a Flutter wrapper. + tags: + - game + - intro + - beginner + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-flame-brick-breaker + +## Other techs + +- name: Adding Google Maps to a Flutter app + description: | + Display a Google map in an app, retrieve data from a + web service, and display the data as markers on the map. + tags: + - google maps + - maps + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/google-maps-in-flutter + +- name: Adding WebView to your Flutter app + description: | + With the WebView Flutter plugin you can add a WebView + widget to your Android or iOS Flutter app. + tags: + - android + - ios + - webview + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-webview + +- name: Using FFI in a Flutter plugin + description: | + Learn how to use Dart's FFI (foreign function interface) + library, ffigen, allowing you to leverage + existing native libraries that provide a + C interface. + tags: + - FFI + - interop + - plugin + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-ffigen + +# Testing + +- name: How to test a Flutter app + description: | + Start with a simple app that manages state with the Provider package. + Unit test the provider package. Write widget tests for two of the + widgets. Use Flutter Driver to create an integration test. + tags: + - testing + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-app-testing/ + +# Platform + +- name: Adding a Home Screen widget to your Flutter app + description: | + Learn how to add a Home Screen widget to your Flutter app + on iOS. This applies to your home screen, lock screen, or the + today view. + tags: + - ios + - home screen widget + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/flutter-home-screen-widgets + +- name: Write a Flutter desktop application + description: | + Build a Flutter desktop app (Windows, Linux, or macOS) + that accesses GitHub APIs, and + create and use plugins to interact with native APIs and desktop applications. + tags: + - desktop + - windows + - linux + - macos + type: codelab + link: + label: Google Codelab + url: https://codelabs.developers.google.com/codelabs/flutter-github-client diff --git a/src/data/learning-resources-index/cookbook.yml b/src/data/learning-resources-index/cookbook.yml new file mode 100644 index 00000000000..b77022e2e57 --- /dev/null +++ b/src/data/learning-resources-index/cookbook.yml @@ -0,0 +1,731 @@ +# Animations + +- name: Animate a page route transition + description: Transition between routes by animating the new route into view from the bottom of the screen. + tags: + - animation + - route + type: recipe + link: + label: Flutter docs + url: /cookbook/animation/page-route-animation + +- name: Animate a widget using a physics simulation + description: Learn how to move a widget from a dragged point back to the center using a spring simulation. + tags: + - animation + type: recipe + link: + label: Flutter docs + url: /cookbook/animation/physics-simulation + +- name: Animate the properties of a container + description: Use AnimatedContainer to animate the size, background color, and border radius of a Container. + tags: + - animation + type: recipe + link: + label: Flutter docs + url: /cookbook/animation/animated-container + +- name: Fade a widget in and out + description: The AnimatedOpacity widget makes it easy to perform opacity animations. + tags: + - animation + type: recipe + link: + label: Flutter docs + url: /cookbook/animation/opacity-animation + +# Design + +- name: Add a drawer to a screen + description: Use the Drawer widget in combination with a Scaffold to create a layout with a Material Design drawer. + tags: + - widget + - material + - drawer + - layout + type: recipe + link: + label: Flutter docs + url: /cookbook/design/drawer + +- name: Display a snackbar + description: Use the Snackbar widget to display messages to your users. + type: recipe + link: + label: Flutter docs + url: /cookbook/design/snackbars + +- name: Export fonts from a package + description: Use a font across multiple apps. + type: recipe + link: + label: Flutter docs + url: /cookbook/design/package-fonts + +- name: Update the UI based on orientation + description: Build a list that displays two columns in portrait mode and three columns in landscape mode. + type: recipe + link: + label: Flutter docs + url: /cookbook/design/orientation + +- name: Use a custom font + description: Apply fonts to your entire app or individual widgets. + type: recipe + link: + label: Flutter docs + url: /cookbook/design/fonts + +- name: Use themes to share colors and font styles + description: To share styles across your app, use Themes. + type: recipe + link: + label: Flutter docs + url: /cookbook/design/themes + +- name: Work with tabs + description: Working with tabs is a common pattern in mobile apps that follow the Material Design or Cupertino guidelines. + type: recipe + link: + label: Flutter docs + url: /cookbook/design/tabs + +# Effects + +- name: Create a download button + description: Build a download button that transitions through multiple visual states, based on the status of an app download. + tags: + - ui + - effects + - animations + type: recipe + link: + label: Flutter docs + url: /cookbook/effects/download-button + +- name: Create a nested navigation flow + description: Create top level routes, and routes nested below specific widgets. + tags: + - navigation + - routing + type: recipe + link: + label: Flutter docs + url: /cookbook/effects/nested-nav + +- name: Create a scrolling parallax effect + description: Create the parallax effect by building a list of cards with images that 'move'. + type: recipe + tags: + - design + link: + label: Flutter docs + url: /cookbook/effects/parallax-scrolling + +- name: Create a shimmer loading effect + description: Communicate that data is loading with a chrome color shimmer on the screen. + type: recipe + tags: + - animation + link: + label: Flutter docs + url: /cookbook/effects/shimmer-loading + +- name: Create a staggered menu animation + description: Build a drawer menu with animated content that is staggered and has a button that pops in at the bottom + type: recipe + tags: + - animation + - design + link: + label: Flutter docs + url: /cookbook/effects/staggered-menu-animation + +- name: Create a typing indicator + description: Build a speech bubble typing indicator that animates in and out of view. + type: recipe + link: + label: Flutter docs + url: /cookbook/effects/typing-indicator + +- name: Create an expandable FAB + description: Create a floating action button that spawns other action buttons. + tags: + - ui + - effects + type: recipe + link: + label: Flutter docs + url: /cookbook/effects/expandable-fab + +- name: Drag a UI element + description: Build a drag-and-drop interaction when the user long presses. + tags: + - interaction + - design + type: recipe + link: + label: Flutter docs + url: /cookbook/effects/drag-a-widget + +# Forms +- name: Build a form with validation + description: Learn how to add validation to a form. + tags: + - input + - interaction + type: recipe + link: + label: Flutter docs + url: /cookbook/forms/validation + +- name: Create and style a text field + description: In this recipe, explore how to create and style text fields. + tags: + - input + type: recipe + link: + label: Flutter docs + url: /cookbook/forms/text-input + +- name: Focus and text fields + description: Shift focus to a text field programmatically. + tags: + - input + type: recipe + link: + label: Flutter docs + url: /cookbook/forms/focus + +- name: Handle changes to a text field + description: Listen for changes to a TextField using a callback. + tags: + - input + type: recipe + link: + label: Flutter docs + url: /cookbook/forms/text-field-changes + +- name: Retrieve the value of a text field + description: Learn how to retrieve the text a user has entered into a text field. + tags: + - input + type: recipe + link: + label: Flutter docs + url: /cookbook/forms/retrieve-input + +# Games + +- name: Add achievements and leaderboards to your game + description: Use the games_services package to add leaderboard functionality to your mobile game. + tags: + - games + type: recipe + link: + label: Flutter docs + url: /cookbook/games/achievements-leaderboard + +- name: Add multiplayer support to your Flutter game + description: Use the cloud_firestore package to implement multiplayer capabilities in your game. + tags: + - games + - ads + - firebase + type: recipe + link: + label: Flutter docs + url: /cookbook/games/firestore-multiplayer + +- name: Add ads to your Flutter game + description: Use the google_mobile_ads package to add a banner ad to your app or game. + tags: + - games + - ads + type: recipe + link: + label: Flutter docs + url: /cookbook/plugins/google-mobile-ads + +# Gestures + +- name: Add Material touch ripples + description: Use the Inkwell widget to display a ripple animation. + tags: + - input + - material + type: recipe + link: + label: Flutter docs + url: /cookbook/gestures/ripples + +- name: Handle taps + description: Use the GestureDetector widget to respond to fundamental actions, such as tapping and dragging. + tags: + - input + type: recipe + link: + label: Flutter docs + url: /cookbook/gestures/handling-taps + +- name: Implement swipe to dismiss + description: Learn how to use the Dismissible widget. + tags: + - input + type: recipe + link: + label: Flutter docs + url: /cookbook/gestures/dismissible + +# Images + +- name: Display images from the internet + description: To work with images from a URL, use the Image.network() constructor. + tags: + - image + - beginner + type: recipe + link: + label: Flutter docs + url: /cookbook/images/network-image + +- name: Fade in images with a placeholder + description: Use the FadeInImage widget to show a visual placeholder before an image loads. + tags: + - image + type: recipe + link: + label: Flutter docs + url: /cookbook/images/fading-in-images + +# Lists + +- name: Grid lists + description: Learn to use a GridView widget. + tags: + - list + - layout + type: recipe + link: + label: Flutter docs + url: /cookbook/lists/grid-lists + +- name: Horizontal lists + description: Learn to display items horizontally in a scrollable list. + tags: + - list + - layout + type: recipe + link: + label: Flutter docs + url: /cookbook/lists/horizontal-list + +- name: Lists with different types of items + description: Create a list with headers followed by a few list items. + tags: + - list + - layout + type: recipe + link: + label: Flutter docs + url: /cookbook/lists/mixed-list + +- name: Lists and floating app bars + description: Place a floating app bar or navigation bar above a list. + tags: + - list + - layout + - scrolling + type: recipe + link: + label: Flutter docs + url: /cookbook/lists/floating-app-bar + +- name: Basic lists + description: Learn to display items with the ListView widget. + tags: + - list + - layout + - scrolling + type: recipe + link: + label: Flutter docs + url: /cookbook/lists/basic-list + +- name: Long lists + description: Work with longer lists with the Listview.builder constructor. + tags: + - list + - layout + type: recipe + link: + label: Flutter docs + url: /cookbook/lists/long-lists + +- name: Lists with spaced items + description: Create a list with padding between items. + tags: + - list + - layout + type: recipe + link: + label: Flutter docs + url: /cookbook/lists/spaced-items + +- name: Animate a widget across screens + description: Use the Hero widget to animate a widget from one screen to the next. + tags: + - navigation + - animation + type: recipe + link: + label: Flutter docs + url: /cookbook/navigation/hero-animations + +- name: Navigate to a new screen and back + description: This recipe uses the Navigator to navigate to a new route. + tags: + - navigation + type: recipe + link: + label: Flutter docs + url: /cookbook/navigation/navigation-basics + +- name: Named routes + description: Create named routes and navigate to them. + tags: + - navigation + - routing + type: recipe + link: + label: Flutter docs + url: /cookbook/navigation/named-routes + +- name: Arguments and named routes + description: Pass arguments to a named route and read the arguments on that route. + tags: + - navigation + type: recipe + link: + label: Flutter docs + url: /cookbook/navigation/navigate-with-arguments + +- name: Android app links + description: Set up deep linking on Android + tags: + - navigation + - android + type: recipe + link: + label: Flutter docs + url: /cookbook/navigation/set-up-app-links + +- name: iOS universal links + description: Set up universal links for iOS + tags: + - navigation + - iOS + type: recipe + link: + label: Flutter docs + url: /cookbook/navigation/set-up-universal-links + +- name: Return data from a screen + description: Return data from one screen to another with the Navigator.pop method. + tags: + - navigation + - routing + type: recipe + link: + label: Flutter docs + url: /cookbook/navigation/returning-data + +- name: Send data to a new screen + description: Send data from one screen to new one. + tags: + - navigation + type: recipe + link: + label: Flutter docs + url: /cookbook/navigation/passing-data + +# Networking + +- name: Fetch data from the internet + description: Learn to use HTTP in your app. + tags: + - networking + - http + type: recipe + link: + label: Flutter docs + url: /cookbook/networking/fetch-data + +- name: Make authenticated requests + description: Authorization headers in HTTP + tags: + - networking + - http + - auth + type: recipe + link: + label: Flutter docs + url: /cookbook/networking/authenticated-requests + +- name: Send data to the internet + description: Send HTTP POST requests in your app. + tags: + - networking + - http + type: recipe + link: + label: Flutter docs + url: /cookbook/networking/send-data + +- name: Update data over the internet + description: Send an HTTP put request. + tags: + - networking + - http + type: recipe + link: + label: Flutter docs + url: /cookbook/networking/update-data + +- name: Delete data on the internet + description: Send an HTTP delete request. + tags: + - networking + - http + type: recipe + link: + label: Flutter docs + url: /cookbook/networking/delete-data + +- name: WebSockets + description: Connect to and communicate with a websocket. + tags: + - networking + - websocket + type: recipe + link: + label: Flutter docs + url: /cookbook/networking/web-sockets + +- name: Parse JSON in the background + description: Learn to use Dart's Isolate objects + tags: + - networking + - isolates + - threading + type: recipe + link: + label: Flutter docs + url: /cookbook/networking/background-parsing + +- name: Persist data with SQLite + description: Use the sqflite package. + tags: + - data + - persistence + - SQL + type: recipe + link: + label: Flutter docs + url: /cookbook/persistence/sqlite + +- name: Read and write files + description: Use the dart:io library and path_provider plugin to save files to disk. + tags: + - data + - persistence + - io + type: recipe + link: + label: Flutter docs + url: /cookbook/persistence/reading-writing-files + +- name: Store key-value data on disk + description: Persist data with shared_preferences + tags: + - data + - persistence + type: recipe + link: + label: Flutter docs + url: /cookbook/persistence/key-value + +- name: Play and pause a video + description: Play videos stored on the file system, as an asset, or from the internet. + tags: + - plugins + - video + type: recipe + link: + label: Flutter docs + url: /cookbook/plugins/play-video + +- name: Use the camera + description: Learn to use a devices camera. + tags: + - plugins + type: recipe + link: + label: Flutter docs + url: /cookbook/plugins/picture-using-camera + +- name: Report errors to a service + description: Report errors to Sentry crash reporting. + tags: + - testing + - reporting + type: recipe + link: + label: Flutter docs + url: /cookbook/maintenance/error-reporting + +- name: Performance profiling + description: Write a test that records a performance timeline. + tags: + - testing + - performance + - perf + type: recipe + link: + label: Flutter docs + url: /cookbook/testing/integration/profiling + +- name: Write unit tests + description: An introduction to writing unit tests. + tags: + - testing + - unit testing + type: recipe + link: + label: Flutter docs + url: /cookbook/testing/unit/introduction + +- name: Write widget tests + description: An introduction to writing widget tests. + tags: + - testing + - unit testing + type: recipe + link: + label: Flutter docs + url: /cookbook/testing/widget/introduction + +- name: Mock dependencies in tests + description: The basics of mocking with the Mockito package. + tags: + - testing + - unit testing + type: recipe + link: + label: Flutter docs + url: /cookbook/testing/unit/mocking + +- name: Find widgets in tests + description: This recipe looks at the 'find' constant provided by the flutter_test package. + tags: + - testing + type: recipe + link: + label: Flutter docs + url: /cookbook/testing/widget/finders + +- name: Handle scrolling + description: Learn how to scroll in widget tests. + tags: + - testing + type: recipe + link: + label: Flutter docs + url: /cookbook/testing/widget/scrolling + +- name: App orientation + description: Learn how to check app orientation in widget tests. + tags: + - testing + type: recipe + link: + label: Flutter docs + url: /cookbook/testing/widget/orientation + +- name: Tap, drag, and enter text + description: Interact with widgets in widget tests. + tags: + - testing + type: recipe + link: + label: Flutter docs + url: /cookbook/testing/widget/tap-drag + +- name: Persistent storage architecture - SQL + description: Save complex application data to a user's device with SQL. + tags: + - data + - SQL + - architecture + type: recipe + link: + label: Flutter docs + url: /app-architecture/design-patterns/sql + +- name: Error handling with Result objects + description: Improve error handling across classes with Result objects. + tags: + - error handling + - testing + - architecture + type: recipe + link: + label: Flutter docs + url: /app-architecture/design-patterns/result + +- name: Optimistic state + description: Improve the perception of responsiveness of an application by implementing optimistic state. + tags: + - user experience + - asynchronous dart + - architecture + type: recipe + link: + label: Flutter docs + url: /app-architecture/design-patterns/optimistic-state + +- name: Offline First + description: Implement offline-first support for one feature in an application. + tags: + - user experience + - network + - architecture + type: recipe + link: + label: Flutter docs + url: /app-architecture/design-patterns/offline-first + +- name: Persistent storage architecture - Key-value data + description: Save application data to a user's on-device key-value store. + tags: + - data + - network + - architecture + type: recipe + link: + label: Flutter docs + url: /app-architecture/design-patterns/key-value-data + +- name: Command pattern + description: Simplify view model logic by implementing a Command class. + tags: + - mvvm + - asynchronous dart + - architecture + type: recipe + link: + label: Flutter docs + url: /app-architecture/design-patterns/command diff --git a/src/data/learning-resources-index/demos.yml b/src/data/learning-resources-index/demos.yml new file mode 100644 index 00000000000..dfc9d47f083 --- /dev/null +++ b/src/data/learning-resources-index/demos.yml @@ -0,0 +1,55 @@ +- name: Add-to-app + description: >- + Recommended approaches for adding Flutter to existing apps. + tags: + - platforms + - iOS + - Android + type: demo + link: + url: https://github.com/flutter/samples/tree/main/add_to_app + label: Flutter GitHub + +- name: Android splash screen + description: >- + A Flutter sample app that exemplifies how to implement an + animated splash screen for Android devices. + tags: + - Android + type: demo + link: + url: https://github.com/flutter/samples/tree/main/android_splash_screen + label: Flutter GitHub + +- name: iOS app clip + description: >- + A sample project demonstrating integration with iOS App Clip. + tags: + - iOS + type: demo + link: + url: https://github.com/flutter/samples/tree/main/ios_app_clip + label: Flutter GitHub + +- name: Swift platform view + description: >- + A Flutter sample app that combines a native iOS UIViewController with + a full-screen Flutter view. + tags: + - swift + - ios + type: demo + link: + url: https://github.com/flutter/samples/tree/main/platform_view_swift + label: Flutter GitHub + +- name: Simplistic editor + description: >- + This sample text editor showcases the use of TextEditingDeltas and + a DeltaTextInputClient to expand and contract styled ranges of text. + tags: + - text + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/simplistic_editor + label: Flutter GitHub diff --git a/src/data/learning-resources-index/quickstarts_dart.yml b/src/data/learning-resources-index/quickstarts_dart.yml new file mode 100644 index 00000000000..258906fe5da --- /dev/null +++ b/src/data/learning-resources-index/quickstarts_dart.yml @@ -0,0 +1,78 @@ +- name: Command-line app + description: >- + A command line app that parses command-line options and fetches from GitHub. + tags: + - cli + - Dart + type: quickstart + link: + url: https://github.com/dart-lang/samples/tree/main/command_line + label: Dart GitHub + +- name: Extension methods + description: >- + Demonstrates Dart's extensions method syntax. + tags: + - syntax + - Dart + type: quickstart + link: + url: https://github.com/dart-lang/samples/tree/main/extension_methods + label: Dart GitHub + +- name: FFI + description: >- + A series of simple examples demonstrating how to call C libraries from Dart. + tags: + - platforms + - Dart + type: quickstart + link: + url: https://github.com/dart-lang/samples/tree/main/ffi + label: Dart GitHub + +- name: Isolates (in a CLI) + description: >- + Command line applications that demonstrate how to + work with Concurrency in Dart using isolates. + tags: + - cli + - Dart + type: quickstart + link: + url: https://github.com/dart-lang/samples/tree/main/ffi + label: Dart GitHub + +- name: Native Dart app + description: >- + A command line application that can be compiled to + native code using `dart compile exe`. + tags: + - cli + - Dart + type: quickstart + link: + url: https://github.com/dart-lang/samples/tree/main/native_app + label: Dart GitHub + +- name: Server side Dart + description: >- + Examples of running Dart on the server. + tags: + - cli + - Dart + type: quickstart + link: + url: https://github.com/dart-lang/samples/tree/main/server + label: Dart GitHub + +- name: Package constraint solver + description: >- + Demonstrates best-practices for publishing packages on pub.dev. + tags: + - pub + - Dart + type: quickstart + link: + url: https://github.com/dart-lang/samples/tree/main/server + label: Dart GitHub diff --git a/src/data/learning-resources-index/quickstarts_flutter.yml b/src/data/learning-resources-index/quickstarts_flutter.yml new file mode 100644 index 00000000000..774ed0f9c9b --- /dev/null +++ b/src/data/learning-resources-index/quickstarts_flutter.yml @@ -0,0 +1,257 @@ +- name: Asset transformation + description: >- + Demonstrates how to transform images' color scales and formats. + tags: + - images + - UI + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/asset_transformation + label: Flutter GitHub + +- name: Background isolate channels + description: >- + Demonstrates how to use long-lived isolates. + tags: + - performance + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/background_isolate_channels + label: Flutter GitHub + +- name: Code sharing + description: >- + Demonstrates how to share business logic between a + Flutter client and Dart server using `package:shelf`. + tags: + - Dart + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/code_sharing + label: Flutter GitHub + +- name: Context menus + description: >- + This sample shows how to create and customize cross-platform context menus, + such as the text selection toolbar on mobile or + the right click menu on desktop. + tags: + - macos + - windows + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/context_menus + label: Flutter GitHub + +- name: Desktop UI + description: >- + Demonstrates desktop features in both Material and FluentUI design systems. + tags: + - material + - macos + - windows + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/desktop_photo_search + label: Flutter GitHub + +- name: AI generated dynamic theme + description: >- + Demonstrates how to call on-device Flutter APIs based on + output from the Gemini API. + tags: + - AI + - google + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/dynamic_theme + label: Flutter GitHub + +- name: Form app + description: >- + A sample demonstrating different types of forms and best practices. + tags: + - input + - layout + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/form_app + label: Flutter GitHub + +- name: AI todo list + description: >- + A developer sample written in Flutter demonstrating how to + interact with a to-do list in natural language using the Gemini API. + tags: + - AI + - google + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/gemini_tasks + label: Flutter GitHub + +- name: Google Maps plugin + description: >- + Demonstrates the Google Maps for Flutter plugin. + tags: + - google + - maps + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/google_maps + label: Flutter GitHub + +- name: Infinite list + description: >- + A Flutter sample app that shows an implementation of + the 'infinite list' UX pattern. + tags: + - lists + - layout + - scrolling + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/infinite_list + label: Flutter GitHub + +- name: Isolates + description: >- + A sample application that + demonstrate best practices when using isolates. + tags: + - isolates + - perf + - threads + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/isolate_example + label: Flutter GitHub + +- name: Navigation and routing + description: >- + A sample that shows how to use `go_router` API to + handle common navigation scenarios. + tags: + - navigation + - routing + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/navigation_and_routing + label: Flutter GitHub + +- name: Google Maps Flutter plugin + description: >- + A sample place tracking app that uses the google_apps_flutter plugin. + tags: + - maps + - google + - plugins + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/place_tracker + label: Flutter GitHub + +- name: Platform adaptive design + description: >- + This sample project shows a Flutter app that + maximizes application code reuse while adhering to + different design patterns on Android and iOS. + tags: + - adaptive + - design + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/platform_design + label: Flutter GitHub + +- name: Counter app with Provider + description: >- + The starter Flutter application, but + using the Provider package to manage state. + tags: + - state management + - architecture + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/provider_counter + label: Flutter GitHub + +- name: Shopping app with Provider + description: >- + A Flutter sample app that shows a + state management approach using the Provider package. + tags: + - state management + - architecture + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/provider_shopper + label: Flutter GitHub + +- name: Simple shaders + description: >- + A simple Flutter fragment shaders project. + tags: + - shaders + - gpu + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/simple_shader + label: Flutter GitHub + +- name: Desktop calculator + description: >- + A calculator sample to demonstrate a simple start for a desktop Flutter app. + tags: + - desktop + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/simplistic_calculator + label: Flutter GitHub + +- name: Testing app + description: >- + A sample app that shows different types of testing in Flutter. + tags: + - testing + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/testing_app + label: Flutter GitHub + +- name: Web element embedding + description: >- + Modifies the index.html of a flutter app so it + is launched in a custom hostElement. + This is the most basic embedding example. + tags: + - web + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/web_embedding/element_embedding_demo + label: Flutter GitHub + +- name: ng-flutter + description: >- + A simple Angular app (and component) that + replicates the element embedding example, but in an Angular app. + tags: + - web + - google + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/web_embedding/ng-flutter + label: Flutter GitHub + +- name: Platform channels + description: >- + A sample Flutter app which demonstrates how to use + `MethodChannel`, `EventChannel`, `BasicMessageChannel` and `MessageCodec`. + tags: + - platforms + - android + - ios + type: quickstart + link: + url: https://github.com/flutter/samples/tree/main/platform_channels + label: Flutter GitHub diff --git a/tool/dash_site/lib/src/utils.dart b/tool/dash_site/lib/src/utils.dart index 2e1a25ab1cf..f3cb366ad64 100644 --- a/tool/dash_site/lib/src/utils.dart +++ b/tool/dash_site/lib/src/utils.dart @@ -33,7 +33,7 @@ int installJasprCliIfNecessary() { 'global', 'activate', 'jaspr_cli', - '^0.21.5', + '^0.21.6', ]); if (activateOutput.exitCode != 0) {