diff --git a/animations/pubspec.lock b/animations/pubspec.lock index 9a0f87f64..2c084ad60 100644 --- a/animations/pubspec.lock +++ b/animations/pubspec.lock @@ -108,7 +108,7 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.8.0+1" + version: "1.9.0" petitparser: dependency: transitive description: @@ -169,7 +169,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.2.15" typed_data: dependency: transitive description: diff --git a/provider_shopper/pubspec.lock b/provider_shopper/pubspec.lock index b2de40620..0c88654bd 100644 --- a/provider_shopper/pubspec.lock +++ b/provider_shopper/pubspec.lock @@ -108,7 +108,7 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.8.0+1" + version: "1.9.0" petitparser: dependency: transitive description: @@ -122,7 +122,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.0.2" + version: "4.0.4" quiver: dependency: transitive description: @@ -176,7 +176,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.14" + version: "0.2.15" typed_data: dependency: transitive description: diff --git a/web/_tool/common.dart b/web/_tool/common.dart new file mode 100644 index 000000000..7a4adfc43 --- /dev/null +++ b/web/_tool/common.dart @@ -0,0 +1,38 @@ +// Copyright 2020 The Flutter team. 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:io'; + +const ansiGreen = 32; +const ansiRed = 31; +const ansiMagenta = 35; + +Future run( + String workingDir, String commandName, List args) async { + var commandDescription = '`${([commandName]..addAll(args)).join(' ')}`'; + + logWrapped(ansiMagenta, ' Running $commandDescription'); + + var proc = await Process.start( + commandName, + args, + workingDirectory: Directory.current.path + '/' + workingDir, + mode: ProcessStartMode.inheritStdio, + ); + + var exitCode = await proc.exitCode; + + if (exitCode != 0) { + logWrapped( + ansiRed, ' Failed! ($exitCode) – $workingDir – $commandDescription'); + return false; + } else { + logWrapped(ansiGreen, ' Success! – $workingDir – $commandDescription'); + return true; + } +} + +void logWrapped(int code, String message) { + print('\x1B[${code}m$message\x1B[0m'); +} diff --git a/web/_tool/peanut_post_build.dart b/web/_tool/peanut_post_build.dart index 331260e36..3d82c40ef 100644 --- a/web/_tool/peanut_post_build.dart +++ b/web/_tool/peanut_post_build.dart @@ -1,6 +1,6 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2020 The Flutter team. All rights reserved. // Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +// found in the LICENSE file // Called by https://pub.dartlang.org/packages/peanut to generate example pages // for hosting. @@ -10,10 +10,11 @@ import 'dart:convert'; import 'dart:io'; -import 'package:markdown/markdown.dart'; import 'package:path/path.dart' as p; -void main(List args) { +import 'common.dart'; + +main(List args) async { final buildDir = args[0]; final fileMap = (jsonDecode(args[1]) as Map).cast(); @@ -32,21 +33,26 @@ void main(List args) { } } - final tocFile = File(p.join(buildDir, 'index.html')); - if (!tocFile.existsSync()) { - throw StateError('$tocFile should exist!'); + // Move each sample into a subdirectory, 'web' + for (var exampleDir in fileMap.values) { + var oldDirectory = Directory(p.join(buildDir, exampleDir)); + Directory(p.join(buildDir, 'web')).createSync(); + oldDirectory.renameSync(p.join(buildDir, 'web', exampleDir)); } - tocFile.writeAsStringSync( - _tocTemplate( - fileMap.entries.map( - (entry) => _Demo( - entry.key, - entry.value, - ), - ), - ), - flush: true); + // Build the sample index and copy the files into this directory + print('building the sample index...'); + await run('samples_index', 'pub', ['get']); + await run('samples_index', 'pub', ['run', 'grinder', 'build-release']); + + // Copy the contents of the samples_index/public directory to the build + // directory + logWrapped(ansiMagenta, ' Copying samples_index/public to build directory'); + var contents = Directory(p.join('samples_index', 'public')).listSync(); + for (var entity in contents) { + var newPath = p.join(buildDir, p.basename(entity.path)); + entity.renameSync(newPath); + } } void _updateHtml(File htmlFile, String buildDir, String exampleDir) { @@ -71,55 +77,6 @@ void _updateHtml(File htmlFile, String buildDir, String exampleDir) { } } -class _Demo { - final String pkgDir, buildDir; - - _Demo(this.pkgDir, this.buildDir); - - String get content { - final path = p.normalize(p.join(pkgDir, '..', 'README.md')); - - final readmeFile = File(path); - - if (!readmeFile.existsSync()) { - print(' $path – No readme!'); - return ''; - } - - var readmeContent = readmeFile.readAsStringSync(); - - final tripleLineIndex = readmeContent.indexOf('\n\n\n'); - var secondDoubleIndex = readmeContent.indexOf('\n\n'); - - if (secondDoubleIndex >= 0) { - secondDoubleIndex = readmeContent.indexOf('\n\n', secondDoubleIndex + 1); - } - - final endIndices = - ([tripleLineIndex, secondDoubleIndex].where((i) => i >= 0).toList() - ..sort()); - - final endIndex = - endIndices.isEmpty ? readmeContent.length : endIndices.first; - - return markdownToHtml(readmeContent.substring(0, endIndex - 1)); - } - - String get name => _prettyName(buildDir); - - String get html => ''' -
- - $name - - $name -
- ${_indent(content, 2)} -
-
-'''; -} - final _underscoreOrSlash = RegExp('_|/'); String _prettyName(String input) => @@ -139,75 +96,9 @@ const _analytics = ''' gtag('config', '$_analyticsId'); '''; -String _indent(String content, int spaces) => - LineSplitter.split(content).join('\n' + ' ' * spaces); - -const _itemsReplace = r''; - const _emptyTitle = ''; const _standardMeta = ''' $_emptyTitle'''; - -String _tocTemplate(Iterable<_Demo> items) => ''' - - - - ${_indent(_analytics, 2)} -$_standardMeta - - - - -

Flutter for web samples

- Sample source code -
- $_itemsReplace -
- - -''' - .replaceFirst( - _itemsReplace, _indent(items.map((d) => d.html).join('\n'), 4)) - .replaceFirst(_emptyTitle, 'Flutter for web samples'); diff --git a/web/_tool/pubspec.lock b/web/_tool/pubspec.lock index d9d2186bd..efcea6113 100644 --- a/web/_tool/pubspec.lock +++ b/web/_tool/pubspec.lock @@ -7,14 +7,14 @@ packages: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "1.5.3" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" markdown: dependency: "direct main" description: @@ -30,4 +30,4 @@ packages: source: hosted version: "1.6.4" sdks: - dart: ">=2.1.1 <3.0.0" + dart: ">=2.3.0 <3.0.0" diff --git a/web/_tool/verify_packages.dart b/web/_tool/verify_packages.dart index d52b50761..1530bd409 100644 --- a/web/_tool/verify_packages.dart +++ b/web/_tool/verify_packages.dart @@ -1,10 +1,11 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2020 The Flutter team. All rights reserved. // Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +// found in the LICENSE file import 'dart:io'; import 'package:path/path.dart' as p; +import 'common.dart'; const _ansiGreen = 32; const _ansiRed = 31; @@ -20,14 +21,14 @@ void main() async { final results = []; for (var i = 0; i < packageDirs.length; i++) { final dir = packageDirs[i]; - _logWrapped(_ansiMagenta, '\n$dir (${i + 1} of ${packageDirs.length})'); - results.add(await _run(dir, 'flutter', [ + logWrapped(_ansiMagenta, '\n$dir (${i + 1} of ${packageDirs.length})'); + results.add(await run(dir, 'flutter', [ 'pub', 'pub', 'upgrade', '--no-precompile', ])); - results.add(await _run( + results.add(await run( dir, 'dartanalyzer', ['--fatal-infos', '--fatal-warnings', '.'], @@ -45,45 +46,16 @@ void _printStatus(List results) { var success = (successCount == results.length); var pct = 100 * successCount / results.length; - _logWrapped(success ? _ansiGreen : _ansiRed, + logWrapped(success ? _ansiGreen : _ansiRed, '$successCount of ${results.length} (${pct.toStringAsFixed(2)}%)'); } -void _logWrapped(int code, String message) { - print('\x1B[${code}m$message\x1B[0m'); -} - -Future _run( - String workingDir, String commandName, List args) async { - var commandDescription = '`${([commandName]..addAll(args)).join(' ')}`'; - - _logWrapped(_ansiMagenta, ' Running $commandDescription'); - - var proc = await Process.start( - commandName, - args, - workingDirectory: Directory.current.path + '/' + workingDir, - mode: ProcessStartMode.inheritStdio, - ); - - var exitCode = await proc.exitCode; - - if (exitCode != 0) { - _logWrapped( - _ansiRed, ' Failed! ($exitCode) – $workingDir – $commandDescription'); - return false; - } else { - _logWrapped(_ansiGreen, ' Success! – $workingDir – $commandDescription'); - return true; - } -} - Iterable _listPackageDirs(Directory dir) sync* { if (File('${dir.path}/pubspec.yaml').existsSync()) { yield dir.path; } else { for (var subDir in dir - .listSync(followLinks: false) + .listSync(followLinks: true) .whereType() .where((d) => !Uri.file(d.path).pathSegments.last.startsWith('.'))) { yield* _listPackageDirs(subDir); diff --git a/web/animations b/web/animations new file mode 120000 index 000000000..adc148a7e --- /dev/null +++ b/web/animations @@ -0,0 +1 @@ +../animations \ No newline at end of file diff --git a/web/charts/pubspec.lock b/web/charts/pubspec.lock index eaf17ffc4..c2a9a005a 100644 --- a/web/charts/pubspec.lock +++ b/web/charts/pubspec.lock @@ -7,14 +7,14 @@ packages: name: charts_common url: "https://pub.dartlang.org" source: hosted - version: "0.8.1" + version: "0.9.0" charts_flutter: dependency: "direct main" description: name: charts_flutter url: "https://pub.dartlang.org" source: hosted - version: "0.8.1" + version: "0.9.0" collection: dependency: transitive description: @@ -75,4 +75,4 @@ packages: source: hosted version: "2.0.8" sdks: - dart: ">=2.2.2 <3.0.0" + dart: ">=2.3.0 <3.0.0" diff --git a/web/filipino_cuisine/pubspec.lock b/web/filipino_cuisine/pubspec.lock index 632141498..34daf9458 100644 --- a/web/filipino_cuisine/pubspec.lock +++ b/web/filipino_cuisine/pubspec.lock @@ -14,7 +14,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" collection: dependency: transitive description: diff --git a/web/github_dataviz/pubspec.lock b/web/github_dataviz/pubspec.lock index c343b98ab..79bc898a9 100644 --- a/web/github_dataviz/pubspec.lock +++ b/web/github_dataviz/pubspec.lock @@ -14,7 +14,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" collection: dependency: transitive description: diff --git a/web/peanut.yaml b/web/peanut.yaml index e10b96155..158d43d61 100644 --- a/web/peanut.yaml +++ b/web/peanut.yaml @@ -1,6 +1,8 @@ # Configuration for https://pub.dartlang.org/packages/peanut directories: +- animations/web +- provider_shopper/web - charts/web - filipino_cuisine/web - github_dataviz/web diff --git a/web/provider_shopper b/web/provider_shopper new file mode 120000 index 000000000..ffb1f231d --- /dev/null +++ b/web/provider_shopper @@ -0,0 +1 @@ +../provider_shopper \ No newline at end of file diff --git a/web/readme.md b/web/readme.md index 163df7198..eff0bb2de 100644 --- a/web/readme.md +++ b/web/readme.md @@ -1,40 +1,68 @@ -Web samples +# Sample Index and Web Demos -## See the samples in action +This directory contains the index hosted at [flutter.github.io/samples][samples] +and web demos hosted with it. + +## See the demos in action Compiled versions of the samples are hosted at -[flutter.github.io/samples][samples]. +[flutter.github.io/samples/#?platform=web][samples]. ## Building samples code -Go into one of the sample directories, get packages, and run using the `chrome` -device: +Update Flutter and enable web support ```console $ flutter channel dev $ flutter upgrade -$ cd gallery -$ flutter pub get +$ flutter config --enable-web +``` + +Run the demo using the `chrome` device type: + +```console +$ cd slide_puzzle +$ flutter packages get $ flutter run -d chrome ``` +You should see a message printing the URL to access: `http://localhost:8080` + Web support is available as a technical preview and is only available in the `dev` or `master` channels. -You should see a message printing the URL to access: `http://localhost:8080` - ## Deploying to GitHub Pages This project uses [peanut][peanut] to build the samples and commit the output to the gh-pages branch. To deploy, run these commands in the `web/` directory: +Install the peanut command: + ```console $ flutter pub global activate peanut +``` + +Verify `pub get` has been run on each demo: + +```console +$ dart _tool/verify_packages +``` + +Build all demos, along with the sample index: + +```console $ flutter pub global run peanut +``` + +Deploy to GitHub Pages: + +```console $ git push origin gh-pages:gh-pages ``` -Note: `flutter packages get` must be run in each sample before running `peanut`. +## Building the sample index + +See sample_index/README.md for details [web]: https://flutter.dev/web [samples]: https://flutter.github.io/samples/ diff --git a/web/samples_index/.gitignore b/web/samples_index/.gitignore new file mode 100644 index 000000000..a07407b78 --- /dev/null +++ b/web/samples_index/.gitignore @@ -0,0 +1,17 @@ +# firebase public directory +public/ + +# Files and directories created by pub +.dart_tool/ +.packages +# Remove the following pattern if you wish to check in your lock file +pubspec.lock + +# Conventional directory for build outputs +build/ + +# Directory created by dartdoc +doc/api/ + +# All HTML files are generated by `grind generate` +web/*.html diff --git a/web/samples_index/CHANGELOG.md b/web/samples_index/CHANGELOG.md new file mode 100644 index 000000000..687440bac --- /dev/null +++ b/web/samples_index/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version, created by Stagehand diff --git a/web/samples_index/README.md b/web/samples_index/README.md new file mode 100644 index 000000000..67f539a0c --- /dev/null +++ b/web/samples_index/README.md @@ -0,0 +1,43 @@ +# Flutter samples index generator + +This tool is used to generate the visual samples index for Flutter samples. + +## Generating the index + +We use [grinder](https://pub.dev/packages/grinder) to run the build tasks: + +```bash +$ pub get +$ pub global activate grinder +$ grind build +``` + +This will generate the index into `./web` + +## Serving the index locally + +If you want to serve the index locally, you can use +[webdev](https://pub.dev/packages/webdev): + +```bash +$ webdev serve +``` + +## Publishing the index + +You can build the complete index into a publishable directory using Grinder: + +```bash +$ grind build-release +``` + +This outputs the completely built index to `./public`. + +## Generating cookbook content + +The cookbook articles are generated using a WebDriver script that scrapes the +flutter.dev website. To run: + +1. Install [ChromeDriver](https://chromedriver.chromium.org/downloads) +2. run `chromedriver --port=4444 --url-base=wd/hub --verbose` +3. run `grind scrape-cookbook` diff --git a/web/samples_index/analysis_options.yaml b/web/samples_index/analysis_options.yaml new file mode 100644 index 000000000..a686c1b45 --- /dev/null +++ b/web/samples_index/analysis_options.yaml @@ -0,0 +1,14 @@ +# Defines a default set of lint rules enforced for +# projects at Google. For details and rationale, +# see https://github.com/dart-lang/pedantic#enabled-lints. +include: package:pedantic/analysis_options.yaml + +# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. +# Uncomment to specify additional rules. +# linter: +# rules: +# - camel_case_types + +analyzer: +# exclude: +# - path/to/excluded/files/** diff --git a/web/samples_index/lib/browser.dart b/web/samples_index/lib/browser.dart new file mode 100644 index 000000000..e4de0a262 --- /dev/null +++ b/web/samples_index/lib/browser.dart @@ -0,0 +1,5 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file + +export 'src/search.dart'; diff --git a/web/samples_index/lib/cookbook.dart b/web/samples_index/lib/cookbook.dart new file mode 100644 index 000000000..d6a37c472 --- /dev/null +++ b/web/samples_index/lib/cookbook.dart @@ -0,0 +1,76 @@ +// Copyright 2020 The Flutter team. 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:io'; + +import 'package:samples_index/src/data.dart'; + +/// Utilities for generating cookbook article data +import 'package:webdriver/io.dart'; +import 'package:html/parser.dart' show parse; +import 'package:path/path.dart' as path; + +class CookbookScraper { + WebDriver _driver; + + Future init() async { + _driver = await createDriver(desired: {}); + } + + Future dispose() async { + await _driver.quit(); + } + + Future> fetchCookbookLinks() async { + var flutterUrl = 'https://flutter.dev'; + var url = Uri.parse('$flutterUrl/docs/cookbook'); + await _driver.get(url); + var pageContent = await _driver.pageSource; + var page = parse(pageContent); + var links = page.querySelectorAll('main>.container>ul>li>a'); + return links.map((e) => '$flutterUrl${e.attributes["href"]}').toList(); + } + + Future getMetadata(String url) async { + await _driver.get(Uri.parse(url)); + var pageContent = await _driver.pageSource; + var page = parse(pageContent); + var name = page.querySelector('main>.container>header>h1').text; + var description = page.querySelectorAll('main>.container>p').first.text; + + var urlSegments = Uri.parse(url).pathSegments; + var category = urlSegments[urlSegments.length-2]; + + return Sample( + name: name, + description: description, + author: 'Flutter', + type: 'cookbook', + screenshots: [Screenshot(screenshotPath(url), 'Cookbook article')], + tags: ['cookbook', category], + source: url, + ); + } + + Future takeScreenshot(String url) async { + var screenshot = await _driver.captureScreenshotAsList(); + var file = File('web${screenshotPath(url)}'); + await file.create(recursive: true); + await file.writeAsBytes(screenshot); + } +} + +String screenshotPath(String url) { + var filename = parseFileName(url); + return 'images/cookbook/$filename.png'; +} + +/// Parses a filename from a cookbook link. E.g. +/// `https://flutter.dev/docs/cookbook/navigation/returning-data.html` changes +/// to `returning_data.png` +String parseFileName(String link) { + var p = path.basename(link); + var dot = p.indexOf('.'); + return p.substring(0, dot); +} diff --git a/web/samples_index/lib/samples_index.dart b/web/samples_index/lib/samples_index.dart new file mode 100644 index 000000000..87e30d6ad --- /dev/null +++ b/web/samples_index/lib/samples_index.dart @@ -0,0 +1,23 @@ +// Copyright 2020 The Flutter team. 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:convert'; + +import 'package:resource/resource.dart'; + +import 'src/data.dart'; +import 'package:checked_yaml/checked_yaml.dart'; + +export 'src/data.dart'; + +Future> getSamples() async { + var yamlFile = Resource('package:samples_index/src/samples.yaml'); + var cookbookFile = Resource('package:samples_index/src/cookbook.json'); + var contents = await yamlFile.readAsString(); + var cookbookContents = await cookbookFile.readAsString(); + var index = checkedYamlDecode(contents, (m) => Index.fromJson(m), + sourceUrl: yamlFile.uri); + var cookbookIndex = Index.fromJson(json.decode(cookbookContents)); + return index.samples..addAll(cookbookIndex.samples); +} diff --git a/web/samples_index/lib/src/cookbook.json b/web/samples_index/lib/src/cookbook.json new file mode 100644 index 000000000..73c97eddc --- /dev/null +++ b/web/samples_index/lib/src/cookbook.json @@ -0,0 +1,1329 @@ +{ + "samples": [ + { + "name": "Animate a page route transition", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/page-route-animation.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/animation/page-route-animation.html", + "web": null, + "description": "A design language, such as Material, defines standard behaviors when\ntransitioning between routes (or screens). Sometimes, though, a custom\ntransition between screens can make an app more unique. To help,\nPageRouteBuilder provides an Animation object.\nThis Animation can be used with Tween and\nCurve objects to customize the transition animation.\nThis recipe shows how to transition between\nroutes by animating the new route into view from\nthe bottom of the screen.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "animation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Animate a widget using a physics simulation", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/physics-simulation.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/animation/physics-simulation.html", + "web": null, + "description": "Physics simulations can make app interactions feel realistic and interactive.\nFor example, you might want to animate a widget to act as if it were attached to\na spring or falling with gravity.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "animation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Animate the properties of a container", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/animated-container.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/animation/animated-container.html", + "web": null, + "description": "The Container class provides a convenient way\nto create a widget with specific properties:\nwidth, height, background color, padding, borders, and more.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "animation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Fade a widget in and out", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/opacity-animation.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/animation/opacity-animation.html", + "web": null, + "description": "UI developers often need to show and hide elements on screen.\nHowever, quickly popping elements on and off the screen can\nfeel jarring to end users. Instead,\nfade elements in and out with an opacity animation to create\na smooth experience.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "animation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Add a Drawer to a screen", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/drawer.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/design/drawer.html", + "web": null, + "description": "In apps that use Material Design,\nthere are two primary options for navigation: tabs and drawers.\nWhen there is insufficient space to support tabs,\ndrawers provide a handy alternative.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "design" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Display a snackbar", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/snackbars.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/design/snackbars.html", + "web": null, + "description": "It can be useful to briefly inform your users when certain actions\ntake place. For example, when a user swipes away a message in a list,\nyou might want to inform them that the message has been deleted.\nYou might even want to give them an option to undo the action.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "design" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Export fonts from a package", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/package-fonts.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/design/package-fonts.html", + "web": null, + "description": "Rather than declaring a font as part of an app,\nyou can declare a font as part of a separate package.\nThis is a convenient way to share the same font across\nseveral different projects,\nor for coders publishing their packages to pub.dev.\nThis recipe uses the following steps:", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "design" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Update the UI based on orientation", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/orientation.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/design/orientation.html", + "web": null, + "description": "In some situations,\nyou want to update the display of an app when the user\nrotates the screen from portrait mode to landscape mode. For example,\nthe app might show one item after the next in portrait mode,\nyet put those same items side-by-side in landscape mode.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "design" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Use a custom font", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/fonts.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/design/fonts.html", + "web": null, + "description": "Although Android and iOS offer high quality system fonts,\none of the most common requests from designers is for custom fonts.\nFor example, you might have a custom-built font from a designer,\nor perhaps you downloaded a font from Google Fonts.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "design" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Use themes to share colors and font styles", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/themes.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/design/themes.html", + "web": null, + "description": "To share colors and font styles throughout an app, use themes.\nYou can either define app-wide themes, or use Theme widgets\nthat define the colors and font styles for a particular part\nof the application. In fact,\napp-wide themes are just Theme widgets created at\nthe root of an app by the MaterialApp.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "design" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Work with tabs", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/tabs.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/design/tabs.html", + "web": null, + "description": "Working with tabs is a common pattern in apps that follow the\nMaterial Design guidelines.\nFlutter includes a convenient way to create tab layouts as part of\nthe material library.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "design" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Build a form with validation", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/validation.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/forms/validation.html", + "web": null, + "description": "Apps often require users to enter information into a text field.\nFor example, you might require users to log in with an email address\nand password combination.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "forms" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Create and style a text field", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/text-input.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/forms/text-input.html", + "web": null, + "description": "Text fields allow users to type text into an app.\nThey are used to build forms,\nsend messages, create search experiences, and more.\nIn this recipe, explore how to create and style text fields.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "forms" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Focus and text fields", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/focus.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/forms/focus.html", + "web": null, + "description": "When a text field is selected and accepting input,\nit is said to have “focus.”\nGenerally, users shift focus to a text field by tapping,\nand developers shift focus to a text field programmatically by\nusing the tools described in this recipe.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "forms" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Handle changes to a text field", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/text-field-changes.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/forms/text-field-changes.html", + "web": null, + "description": "In some cases, it’s useful to run a callback function every time the text\nin a text field changes. For example, you might want to build a search\nscreen with autocomplete functionality where you want to update the\nresults as the user types.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "forms" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Retrieve the value of a text field", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/retrieve-input.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/forms/retrieve-input.html", + "web": null, + "description": "In this recipe,\nlearn how to retrieve the text a user has entered into a text field\nusing the following steps:", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "forms" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Add Material touch ripples", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/ripples.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/gestures/ripples.html", + "web": null, + "description": "Widgets that follow the Material Design guidelines display\na ripple animation when tapped.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "gestures" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Handle taps", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/handling-taps.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/gestures/handling-taps.html", + "web": null, + "description": "You not only want to display information to users,\nyou want users to interact with your app.\nUse the GestureDetector widget to respond\nto fundamental actions, such as tapping and dragging.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "gestures" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Implement swipe to dismiss", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/dismissible.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/gestures/dismissible.html", + "web": null, + "description": "The “swipe to dismiss” pattern is common in many mobile apps.\nFor example, when writing an email app,\nyou might want to allow a user to swipe away\nemail messages to delete them from a list.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "gestures" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Display images from the internet", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/network-image.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/images/network-image.html", + "web": null, + "description": "Displaying images is fundamental for most mobile apps.\nFlutter provides the Image widget to\ndisplay different types of images.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "images" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Fade in images with a placeholder", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/fading-in-images.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/images/fading-in-images.html", + "web": null, + "description": "When displaying images using the default Image widget,\nyou might notice they simply pop onto the screen as they’re loaded.\nThis might feel visually jarring to your users.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "images" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Work with cached images", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/cached-images.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/images/cached-images.html", + "web": null, + "description": "In some cases, it’s handy to cache images as they’re downloaded from the\nweb, so they can be used offline. For this purpose,\nuse the cached_network_image package.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "images" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Create a grid list", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/grid-lists.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/lists/grid-lists.html", + "web": null, + "description": "In some cases, you might want to display your items as a grid rather than\na normal list of items that come one after the next.\nFor this task, use the GridView widget.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "lists" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Create a horizontal list", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/horizontal-list.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/lists/horizontal-list.html", + "web": null, + "description": "You might want to create a list that scrolls\nhorizontally rather than vertically.\nThe ListView widget supports horizontal lists.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "lists" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Create lists with different types of items", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/mixed-list.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/lists/mixed-list.html", + "web": null, + "description": "You might need to create lists that display different types of content.\nFor example, you might be working on a list that shows a heading\nfollowed by a few items related to the heading, followed by another heading,\nand so on.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "lists" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Place a floating app bar above a list", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/floating-app-bar.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/lists/floating-app-bar.html", + "web": null, + "description": "To make it easier for users to view a list of items,\nyou might want to hide the app bar as the user scrolls down the list.\nThis is especially true if your app displays a “tall”\napp bar that occupies a lot of vertical space.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "lists" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Use lists", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/basic-list.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/lists/basic-list.html", + "web": null, + "description": "Displaying lists of data is a fundamental pattern for mobile apps.\nFlutter includes the ListView\nwidget to make working with lists a breeze.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "lists" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Work with long lists", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/long-lists.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/lists/long-lists.html", + "web": null, + "description": "The standard ListView constructor works well\nfor small lists. To work with lists that contain\na large number of items, it’s best to use the\nListView.builder constructor.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "lists" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Report errors to a service", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/error-reporting.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/maintenance/error-reporting.html", + "web": null, + "description": "While one always tries to create apps that are free of bugs,\nthey’re sure to crop up from time to time.\nSince buggy apps lead to unhappy users and customers,\nit’s important to understand how often your users\nexperience bugs and where those bugs occur.\nThat way, you can prioritize the bugs with the\nhighest impact and work to fix them.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "maintenance" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Animate a widget across screens", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/hero-animations.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/navigation/hero-animations.html", + "web": null, + "description": "It’s often helpful to guide users through an app as they navigate from screen\nto screen. A common technique to lead users through an app is to animate a\nwidget from one screen to the next. This creates a visual anchor connecting\nthe two screens.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "navigation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Navigate to a new screen and back", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/navigation-basics.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/navigation/navigation-basics.html", + "web": null, + "description": "Most apps contain several screens for displaying different types of\ninformation.\nFor example, an app might have a screen that displays products.\nWhen the user taps the image of a product, a new screen displays\ndetails about the product.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "navigation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Navigate with named routes", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/named-routes.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/navigation/named-routes.html", + "web": null, + "description": "In the Navigate to a new screen and back recipe,\nyou learned how to navigate to a new screen by creating a new route and\npushing it to the Navigator.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "navigation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Pass arguments to a named route", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/navigate-with-arguments.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments.html", + "web": null, + "description": "The Navigator provides the ability to navigate\nto a named route from any part of an app using\na common identifier.\nIn some cases, you might also need to pass arguments to a\nnamed route. For example, you might wish to navigate to the /user route and\npass information about the user to that route.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "navigation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Return data from a screen", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/returning-data.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/navigation/returning-data.html", + "web": null, + "description": "In some cases, you might want to return data from a new screen.\nFor example, say you push a new screen that presents two options to a user.\nWhen the user taps an option, you want to inform the first screen\nof the user’s selection so that it can act on that information.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "navigation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Send data to a new screen", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/passing-data.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/navigation/passing-data.html", + "web": null, + "description": "Often, you not only want to navigate to a new screen,\nbut also pass data to the screen as well.\nFor example, you might want to pass information about\nthe item that’s been tapped.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "navigation" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Fetch data from the internet", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/fetch-data.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/networking/fetch-data.html", + "web": null, + "description": "Fetching data from the internet is necessary for most apps.\nLuckily, Dart and Flutter provide tools, such as the\nhttp package, for this type of work.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "networking" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Make authenticated requests", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/authenticated-requests.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/networking/authenticated-requests.html", + "web": null, + "description": "To fetch data from many web services, you need to provide\nauthorization. There are many ways to do this, but perhaps the most common\nuses the Authorization HTTP header.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "networking" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Parse JSON in the background", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/background-parsing.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/networking/background-parsing.html", + "web": null, + "description": "By default, Dart apps do all of their work on a single thread.\nIn many cases, this model simplifies coding and is fast enough\nthat it does not result in poor app performance or stuttering animations,\noften called “jank.”", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "networking" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Send data to the internet", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/send-data.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/networking/send-data.html", + "web": null, + "description": "Sending data to the internet is necessary for most apps.\nThe http package has got it covered too.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "networking" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Work with WebSockets", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/web-sockets.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/networking/web-sockets.html", + "web": null, + "description": "In addition to normal HTTP requests,\nyou can connect to servers using WebSockets.\nWebSockets allow for two-way communication with a server\nwithout polling.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "networking" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Persist data with SQLite", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/sqlite.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/persistence/sqlite.html", + "web": null, + "description": "If writing an app that needs to persist and query larger amounts of data on\nthe local device, consider using a database instead of a local file or\nkey-value store. In general, databases provide faster inserts, updates,\nand queries, compared to other local persistence solutions.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "persistence" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Read and write files", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/reading-writing-files.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/persistence/reading-writing-files.html", + "web": null, + "description": "In some cases, you need to read and write files to disk.\nFor example, you may need to persist data across app launches,\nor download data from the internet and save it for later offline use.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "persistence" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Store key-value data on disk", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/key-value.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/persistence/key-value.html", + "web": null, + "description": "If you have a relatively small collection of key-values\nto save, you can use the shared_preferences plugin.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "persistence" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Play and pause a video", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/play-video.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/plugins/play-video.html", + "web": null, + "description": "Playing videos is a common task in app development,\nand Flutter apps are no exception. To play videos,\nthe Flutter team provides the video_player plugin.\nYou can use the video_player plugin to play videos\nstored on the file system, as an asset, or from the internet.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "plugins" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Take a picture using the camera", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/picture-using-camera.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/plugins/picture-using-camera.html", + "web": null, + "description": "Many apps require working with the device’s cameras to\ntake photos and videos. Flutter provides the camera plugin\nfor this purpose. The camera plugin provides tools to get a list of the\navailable cameras, display a preview coming from a specific camera,\nand take photos or videos.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "plugins" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "An introduction to integration testing", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/introduction.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/testing/integration/introduction.html", + "web": null, + "description": "Unit tests and widget tests are handy for testing individual classes,\nfunctions, or widgets. However, they generally don’t test how\nindividual pieces work together as a whole or capture the performance\nof an application running on a real device. These tasks are performed\nwith integration tests.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "integration" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Handle scrolling", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/scrolling.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/testing/integration/scrolling.html", + "web": null, + "description": "Many apps feature lists of content,\nfrom email clients to music apps and beyond.\nTo verify that lists contain the expected content\nusing integration tests,\nyou need a way to scroll through lists to search for particular items.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "integration" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Performance profiling", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/profiling.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/testing/integration/profiling.html", + "web": null, + "description": "When it comes to mobile apps, performance is critical to user experience.\nUsers expect apps to have smooth scrolling and meaningful animations free of\nstuttering or skipped frames, known as “jank.” How to ensure that your app\nis free of jank on a wide variety of devices?", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "integration" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "An introduction to unit testing", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/introduction.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/testing/unit/introduction.html", + "web": null, + "description": "How can you ensure that your app continues to work as you add more features or\nchange existing functionality? By writing tests.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "unit" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Mock dependencies using Mockito", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/mocking.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/testing/unit/mocking.html", + "web": null, + "description": "Sometimes, unit tests might depend on classes that fetch data from live\nweb services or databases. This is inconvenient for a few reasons:", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "unit" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "An introduction to widget testing", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/introduction.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/testing/widget/introduction.html", + "web": null, + "description": "In the introduction to unit testing recipe,\nyou learned how to test Dart classes using the test package.\nTo test widget classes, you need a few additional tools provided by the\nflutter_test package, which ships with the Flutter SDK.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "widget" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Find widgets", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/finders.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/testing/widget/finders.html", + "web": null, + "description": "To locate widgets in a test environment, use the Finder\nclasses. While it’s possible to write your own Finder classes,\nit’s generally more convenient to locate widgets using the tools\nprovided by the flutter_test package.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "widget" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + }, + { + "name": "Tap, drag, and enter text", + "author": "Flutter", + "screenshots": [ + { + "url": "images/cookbook/tap-drag.png", + "alt": "Cookbook article" + } + ], + "source": "https://flutter.dev/docs/cookbook/testing/widget/tap-drag.html", + "web": null, + "description": "Many widgets not only display information, but also respond\nto user interaction. This includes buttons that can be tapped,\nand TextField for entering text.", + "difficulty": null, + "widgets": [], + "packages": [], + "tags": [ + "cookbook", + "widget" + ], + "platforms": [], + "links": [], + "type": "cookbook", + "date": null, + "channel": null + } + ] +} \ No newline at end of file diff --git a/web/samples_index/lib/src/data.dart b/web/samples_index/lib/src/data.dart new file mode 100644 index 000000000..351a6e034 --- /dev/null +++ b/web/samples_index/lib/src/data.dart @@ -0,0 +1,178 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file + +/// Defines the data types for this project. +library data; + +import 'package:json_annotation/json_annotation.dart'; +import 'package:samples_index/src/util.dart' as util; + +part 'data.g.dart'; + +/// The full list of samples +@JsonSerializable( + // Use anyMap and checked for more useful YAML parsing errors. See + // package:checked_yaml docs for details. + anyMap: true, + checked: true) +class Index { + final List samples; + + Index(this.samples); + + factory Index.fromJson(Map json) => _$IndexFromJson(json); + + Map toJson() => _$IndexToJson(this); +} + +/// A sample to be displayed in the app. +@JsonSerializable(anyMap: true, checked: true) +class Sample { + /// The name of the sample. + final String name; + + /// The author of the sample. Typically "Flutter" + final String author; + + /// Screenshots of the sample or cookbook article. At least 1 screenshot is + /// required. + final List screenshots; + + /// A link to the source code or cookbook article if type is 'cookbook'. + final String source; + + /// A link to this sample running in the browser. + final String web; + + /// 3-5 sentences describing the sample. + final String description; + + /// The difficulty level. Values are either 'beginner', 'intermediate', or + /// 'advanced'. + final String difficulty; + + /// List of widgets or Flutter APIs used by the sample. e.g. "AnimatedBuilder" + /// or "ChangeNotifier". + final List widgets; + + /// List of packages or Flutter libraries used by the sample. third-party + /// packages. + final List packages; + + /// Arbitrary tags to associate with this sample. + final List tags; + + /// Supported platforms. Values are either 'ios', 'android', 'desktop', and + /// 'web' + final List platforms; + + /// Links to display on the details page + final List links; + + /// The type of the sample. Supported values are either 'sample' or + /// 'cookbook'. + final String type; + + /// The date this sample was created. + final DateTime date; + + /// The Flutter channel this sample runs on. Either 'stable', 'dev' or + /// 'master'. + final String channel; + + Sample({ + this.name, + this.author, + this.screenshots, + this.source, + this.web, + this.description, + this.difficulty, + this.widgets = const [], + this.packages = const [], + this.tags = const [], + this.platforms = const [], + this.links = const [], + this.type, + this.date, + this.channel, + }); + + factory Sample.fromJson(Map json) => _$SampleFromJson(json); + + Map toJson() => _$SampleToJson(this); + + String get searchAttributes { + var buf = StringBuffer(); + buf.write(name.toLowerCase()); + buf.write(' '); + + for (var tag in tags) { + buf.write('tag:${tag.toLowerCase()} '); + + // Allow tags to be searched without the tag: prefix + buf.write('${tag.toLowerCase()} '); + } + + for (var platform in platforms) { + buf.write('platform:$platform '); + + // Allow platforms to be searched without the tag: prefix + buf.write('$platform '); + } + + for (var widget in widgets) { + buf.write('widget:$widget '); + } + + for (var package in packages) { + buf.write('package:$package '); + } + + buf.write('type:$type '); + + return buf.toString().trim(); + } + + String get filename { + var nameWithoutChars = name.replaceAll(RegExp(r'[^A-Za-z0-9\-\_\ ]'), ''); + var nameWithUnderscores = nameWithoutChars.replaceAll(' ', '_'); + var snake = util.snakeCase(nameWithUnderscores); + var s = snake.replaceAll('__', '_'); + return s; + } + + String get shortDescription { + if (description.length < 64) { + return description; + } + return description.substring(0, 64) + '...'; + } +} + +/// A screenshot of a sample +@JsonSerializable(anyMap: true, checked: true) +class Screenshot { + final String url; + final String alt; + + Screenshot(this.url, this.alt); + + factory Screenshot.fromJson(Map json) => _$ScreenshotFromJson(json); + + Map toJson() => _$ScreenshotToJson(this); +} + +/// An external link displayed next to a sample +@JsonSerializable(anyMap: true, checked: true) +class Link { + final String text; + final String href; + + Link(this.text, this.href); + + factory Link.fromJson(Map json) => _$LinkFromJson(json); + + Map toJson() => _$LinkToJson(this); +} diff --git a/web/samples_index/lib/src/data.g.dart b/web/samples_index/lib/src/data.g.dart new file mode 100644 index 000000000..465cbce68 --- /dev/null +++ b/web/samples_index/lib/src/data.g.dart @@ -0,0 +1,112 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of data; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Index _$IndexFromJson(Map json) { + return $checkedNew('Index', json, () { + final val = Index( + $checkedConvert( + json, + 'samples', + (v) => (v as List) + ?.map((e) => e == null ? null : Sample.fromJson(e as Map)) + ?.toList()), + ); + return val; + }); +} + +Map _$IndexToJson(Index instance) => { + 'samples': instance.samples, + }; + +Sample _$SampleFromJson(Map json) { + return $checkedNew('Sample', json, () { + final val = Sample( + name: $checkedConvert(json, 'name', (v) => v as String), + author: $checkedConvert(json, 'author', (v) => v as String), + screenshots: $checkedConvert( + json, + 'screenshots', + (v) => (v as List) + ?.map((e) => e == null ? null : Screenshot.fromJson(e as Map)) + ?.toList()), + source: $checkedConvert(json, 'source', (v) => v as String), + web: $checkedConvert(json, 'web', (v) => v as String), + description: $checkedConvert(json, 'description', (v) => v as String), + difficulty: $checkedConvert(json, 'difficulty', (v) => v as String), + widgets: $checkedConvert(json, 'widgets', + (v) => (v as List)?.map((e) => e as String)?.toList()), + packages: $checkedConvert(json, 'packages', + (v) => (v as List)?.map((e) => e as String)?.toList()), + tags: $checkedConvert( + json, 'tags', (v) => (v as List)?.map((e) => e as String)?.toList()), + platforms: $checkedConvert(json, 'platforms', + (v) => (v as List)?.map((e) => e as String)?.toList()), + links: $checkedConvert( + json, + 'links', + (v) => (v as List) + ?.map((e) => e == null ? null : Link.fromJson(e as Map)) + ?.toList()), + type: $checkedConvert(json, 'type', (v) => v as String), + date: $checkedConvert( + json, 'date', (v) => v == null ? null : DateTime.parse(v as String)), + channel: $checkedConvert(json, 'channel', (v) => v as String), + ); + return val; + }); +} + +Map _$SampleToJson(Sample instance) => { + 'name': instance.name, + 'author': instance.author, + 'screenshots': instance.screenshots, + 'source': instance.source, + 'web': instance.web, + 'description': instance.description, + 'difficulty': instance.difficulty, + 'widgets': instance.widgets, + 'packages': instance.packages, + 'tags': instance.tags, + 'platforms': instance.platforms, + 'links': instance.links, + 'type': instance.type, + 'date': instance.date?.toIso8601String(), + 'channel': instance.channel, + }; + +Screenshot _$ScreenshotFromJson(Map json) { + return $checkedNew('Screenshot', json, () { + final val = Screenshot( + $checkedConvert(json, 'url', (v) => v as String), + $checkedConvert(json, 'alt', (v) => v as String), + ); + return val; + }); +} + +Map _$ScreenshotToJson(Screenshot instance) => + { + 'url': instance.url, + 'alt': instance.alt, + }; + +Link _$LinkFromJson(Map json) { + return $checkedNew('Link', json, () { + final val = Link( + $checkedConvert(json, 'text', (v) => v as String), + $checkedConvert(json, 'href', (v) => v as String), + ); + return val; + }); +} + +Map _$LinkToJson(Link instance) => { + 'text': instance.text, + 'href': instance.href, + }; diff --git a/web/samples_index/lib/src/samples.yaml b/web/samples_index/lib/src/samples.yaml new file mode 100644 index 000000000..843231c2e --- /dev/null +++ b/web/samples_index/lib/src/samples.yaml @@ -0,0 +1,431 @@ +samples: + - name: Gallery + author: Flutter + screenshots: + - url: images/gallery1.png + alt: Gallery app screenshot + - url: images/gallery2.png + alt: Rally app screenshot + - url: images/gallery3.png + alt: Fortnightly app screenshot + - url: images/gallery4.png + alt: Crane app screenshot + - url: images/gallery5.png + alt: Shrine app screnshot + source: https://github.com/flutter/gallery + web: https://flutter.github.io/gallery + description: > + A collection of material design widgets, behaviors, and vignettes + implemented with Flutter. + difficulty: intermediate + widgets: + - AlertDialog + - AppBar + - BottomAppBar + - BottomNavigationBar + - BottomSheet + - Card + - Checkbox + - ChoiceChip + - CircularProgressIndicator + - Container + - CupertinoActivityIndicator + - CupertinoAlertDialog + - CupertinoButton + - CupertinoButton + - CupertinoDatePicker + - CupertinoDialogAction + - CupertinoNavigationBar + - CupertinoPageScaffold + - CupertinoSegmentedControl + - CupertinoSlider + - CupertinoSlidingSegmentedControl + - CupertinoSliverRefreshControl + - CupertinoSwitch + - CupertinoTabView + - CupertinoTextField + - CupertinoTheme + - DayPicker + - FilterChip + - FlatButton + - FloatingActionButton + - GridTile + - GridView + - Icon + - InputChip + - LayoutBuilder + - LinearProgressIndicator + - ListTile + - ListView + - MaterialBanner + - MonthPicker + - PaginatedDataTable + - PopupMenuButton + - PopupMenuItem + - Radio + - RaisedButton + - RangeSlider + - Scaffold + - SimpleDialog + - Slider + - SnackBar + - Switch + - TabBar + - TabBarView + - TextField + - TextFormField + - Tooltip + - YearPicker + packages: + - flutter/material + - flutter/cupertino + - google_fonts + - scoped_model + tags: ['intermediate', 'sample', 'gallery', 'material', 'design', 'vignettes'] + platforms: ['web', 'ios', 'android'] + type: demo + + - name: Add to App + author: Flutter + screenshots: + - url: images/add_to_app1.png + alt: Add_to_app screenshot + - url: images/add_to_app2.png + alt: Add_to_app screenshot + source: https://github.com/flutter/samples/tree/master/add_to_app + description: > + Android and iOS projects that each import a standalone Flutter module. + difficulty: advanced + widgets: + - WidgetsFlutterBinding + - MethodChannel + packages: + - flutter/material + - flutter/services + - provider + tags: ['advanced', 'sample', 'add-to-app', 'android', 'ios', 'native', 'embedding'] + platforms: ['ios', 'android'] + type: sample + + - name: Animations + author: Flutter + screenshots: + - url: images/animations1.png + alt: Animations sample screenshot + - url: images/animations2.png + alt: Animations sample screenshot + - url: images/animations3.png + alt: Animations sample screenshot + source: https://github.com/flutter/samples/tree/master/animations + description: > + Sample apps that showcasing Flutter's animation features. + difficulty: advanced + widgets: + - AnimatedContainer + - PageRouteBuilder + - AnimationController + - SingleTickerProviderStateMixin + - Tween + - AnimatedBuilder + - TweenSequence + - TweenSequenceItem + packages: + - flutter/material + tags: ['intermediate', 'sample', 'animation'] + platforms: ['ios', 'android', 'web'] + type: sample + web: web/animations + + - name: Flutter Maps Firestore + author: Flutter + screenshots: + - url: images/flutter_maps_firestore1.png + alt: Flutter maps firestore screenshot + - url: images/flutter_maps_firestore2.png + alt: Flutter maps firestore screenshot + source: https://github.com/flutter/samples/tree/master/flutter_maps_firestore + description: > + A Flutter sample app that shows the end product of the Cloud Next '19 talk + Build Mobile Apps With Flutter and Google Maps. + difficulty: advanced + widgets: + - GoogleMap + packages: + - flutter/material + - cloud_firestore + - google_maps_flutter + - google_maps_webservice + tags: ['intermediate', 'sample', 'firebase', 'maps'] + platforms: ['ios', 'android'] + type: sample + + - name: Isolate Example + author: Flutter + screenshots: + - url: images/isolate1.png + alt: Isolate example screenshot + - url: images/isolate2.png + alt: Isolate example screenshot + - url: images/isolate3.png + alt: Isolate example screenshot + source: https://github.com/flutter/samples/tree/master/isolate_example + description: > + A sample application that demonstrate best practices when using + isolates. + difficulty: intermediate + widgets: + - FutureBuilder + - AnimationController + packages: + - dart:isolate + - dart:math + tags: ['intermediate', 'sample', 'isolates', 'concurrency'] + platforms: ['ios', 'android'] + type: sample + + - name: jsonexample + author: Flutter + screenshots: + - url: images/jsonexample1.png + alt: JSON example screenshot + - url: images/jsonexample2.png + alt: JSON example screenshot + - url: images/jsonexample3.png + alt: JSON example screenshot + source: https://github.com/flutter/samples/tree/master/jsonexample + description: > + A Flutter sample app that deserializes a set of JSON strings using three + different libraries: dart:convert, json_serializable, and built_value. + difficulty: beginner + widgets: + - Table + - TableRow + packages: + - json_serializable + - built_value + - built_collection + - json_annotation + - build_runner + - built_value_generator + tags: ['beginner', 'sample'] + platforms: ['ios', 'android'] + type: sample + + - name: Place Tracker + author: Flutter + screenshots: + - url: images/place_tracker1.png + alt: Place Tracker screenshot + - url: images/place_tracker2.png + alt: Place Tracker screenshot + - url: images/place_tracker3.png + alt: Place Tracker screenshot + - url: images/place_tracker4.png + alt: Place Tracker screenshot + source: https://github.com/flutter/samples/tree/master/place_tracker + description: > + A sample place tracking app that uses the google_maps_flutter plugin. Keep + track of your favorite places, places you've visited, and places you want + to go. View details about these places, show them on a map, and get + directions to them. + difficulty: intermediate + widgets: + - GoogleMap + packages: + - google_maps_flutter + tags: ['intermediate', 'sample', 'json', 'serialization'] + platforms: ['android'] + type: sample + + - name: Platform Design + author: Flutter + screenshots: + - url: images/platform_design1.png + alt: Platform Design screenshot + - url: images/platform_design2.png + alt: Platform Design screenshot + - url: images/platform_design3.png + alt: Platform Design screenshot + - url: images/platform_design4.png + alt: Platform Design screenshot + - url: images/platform_design5.png + alt: Platform Design screenshot + - url: images/platform_design6.png + alt: Platform Design screenshot + - url: images/platform_design7.png + alt: Platform Design screenshot + source: https://github.com/flutter/samples/tree/master/platform_design + description: > + A Flutter app that maximizes application code reuse while adhering to + different design patterns on Android and iOS + difficulty: advanced + widgets: + - TargetPlatform + packages: + - flutter/material + - flutter/cupertino + tags: ['advanced', 'sample', 'ios'] + platforms: ['ios', 'android'] + type: sample + + - name: Platform View Swift + author: Flutter + screenshots: + - url: images/platform_view_swift1.png + alt: Platform View Swift screenshot + - url: images/platform_view_swift2.png + alt: Platform View Swift screenshot + source: https://github.com/flutter/samples/tree/master/platform_design + description: > + A Flutter sample app that combines a native iOS UIViewController with a + full-screen Flutter view. + difficulty: intermediate + widgets: + - MethodChannel + packages: + - flutter/material + - flutter/services + tags: ['advanced', 'sample', 'ios'] + platforms: ['ios'] + type: sample + + - name: Provider Shopper + author: Flutter + screenshots: + - url: images/provider_shopper1.png + alt: Provider Shopper screenshot + - url: images/provider_shopper2.png + alt: Provider Shopper screenshot + - url: images/provider_shopper3.png + alt: Provider Shopper screenshot + source: https://github.com/flutter/samples/tree/master/provider_shopper + description: > + A Flutter sample app that shows a state management approach using the Provider package. + difficulty: intermediate + widgets: + - Provider + - MultiProvider + - ChangeNotifier + packages: + - provider + tags: ['intermediate', 'sample', 'provider'] + platforms: ['ios', 'android', 'web'] + type: sample + web: web/provider_shopper + +################### +#### Web Demos #### +################### + + - name: Charts + author: Flutter + screenshots: + - url: images/charts1.png + source: https://github.com/google/charts + description: > + A general-purpose charting library. + difficulty: intermediate + widgets: [] + packages: [] + platforms: ['ios', 'android', 'web'] + tags: ['demo', 'charts'] + web: web/charts + type: demo + + - name: Filipino Cuisine + author: github.com/markgrancapal + screenshots: + - url: images/filipino_cuisine1.png + alt: Filipino Cuisine screenshot + source: https://github.com/markgrancapal/filipino_cuisine + description: > + Flutter Create challenge entry + difficulty: intermediate + widgets: [] + packages: [] + platforms: ['web'] + tags: ['demo', 'flutter create'] + web: web/filipino_cuisine + type: demo + + - name: GitHub Dataviz + author: Larva Labs + screenshots: + - url: images/github_dataviz1.png + alt: GitHub Dataviz screenshot + source: https://github.com/flutter/samples/tree/master/web/github_dataviz + description: > + A visualization for Flutter repository data + difficulty: intermediate + widgets: [] + packages: [] + platforms: ['web'] + tags: ['demo', 'data', 'visualization'] + web: web/github_dataviz + type: demo + + - name: Particle Background + author: Felix Blaschke + screenshots: + - url: images/particle_background1.png + alt: Particle Background screenshot + source: https://github.com/flutter/samples/tree/master/web/particle_background + description: > + Flutter app demonstrating package:simple_animations in action. + difficulty: intermediate + widgets: [] + packages: [] + platforms: ['web'] + tags: ['demo', 'animation'] + web: web/particle_background + type: demo + + - name: Slide Puzzle + author: Kevin Moore + screenshots: + - url: images/slide_puzzle1.png + alt: Slide Puzzle screenshot + source: https://github.com/kevmoo/slide_puzzle + description: > + A slide (15) puzzle implemented in Dart and Flutter. + difficulty: advanced + widgets: [] + packages: [] + platforms: ['web'] + tags: ['demo', 'game'] + web: web/slide_puzzle + type: demo + + - name: Timeflow + author: Fabian Stein + screenshots: + - url: images/timeflow1.png + alt: Timeflow screenshot + source: github.com/Fabian-Stein/timeflow + description: > + A gentle animation that provides a calming experience to stressed developers. + difficulty: advanced + widgets: [] + packages: [] + platforms: ['web'] + tags: ['demo', 'animation'] + web: web/timeflow + type: demo + + - name: Vision Challenge + author: Yukkei Choi + screenshots: + - url: images/vision_challenge1.png + alt: Vision Challenge screenshot + - url: images/vision_challenge2.png + alt: Vision Challenge screenshot + source: https://github.com/flutter/samples/tree/master/web/vision_challenge + description: > + A fun game to test your color perception abilities. + difficulty: advanced + widgets: [] + packages: [] + platforms: ['web'] + tags: ['demo', 'game'] + web: web/vision_challenge + type: demo diff --git a/web/samples_index/lib/src/search.dart b/web/samples_index/lib/src/search.dart new file mode 100644 index 000000000..3d2d6e834 --- /dev/null +++ b/web/samples_index/lib/src/search.dart @@ -0,0 +1,68 @@ +// Copyright 2020 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file + +bool matchesQuery(String query, String sampleAttributes) { + if (query == null || query.isEmpty) { + return true; + } + var queryWords = query.split(' ')..removeWhere((s) => s.isEmpty); + var attributes = sampleAttributes.split(' ')..removeWhere((s) => s.isEmpty); + + // Test for exact matches + if (attributes.contains(query)) { + return true; + } + + // Test for exact matches for keywords + var matches = 0; + for (var word in queryWords) { + if (attributes.contains(word)) { + matches++; + } + if (matches == queryWords.length) { + return true; + } + } + + // Test for queries whose keywords are a substring of any attribute + // e.g. searching "kitten tag:cats" is a match for a sample with the + // attributes "kittens tag:cats" + matches = 0; + for (var attribute in attributes) { + for (var queryWord in queryWords) { + if (attribute.startsWith(queryWord)) { + matches++; + } + } + // Only return true if each search term was matched + if (matches == queryWords.length) { + return true; + } + } + + return false; +} + +Map parseHash(String hash) => + Uri.parse(hash.substring(hash.indexOf('#') + 1)).queryParameters; + +String formatHash(Map parameters) => + Uri().replace(queryParameters: parameters).toString(); + +String searchQueryFromParams(Map params) { + var buf = StringBuffer(); + if (params.containsKey('search')) { + buf.write(params['search']); + } + if (params.containsKey('type')) { + if (buf.isNotEmpty) buf.write(' '); + buf.write('type:' + params['type']); + } + if (params.containsKey('platform')) { + if (buf.isNotEmpty) buf.write(' '); + buf.write('platform:' + params['platform']); + } + + return buf.toString(); +} diff --git a/web/samples_index/lib/src/templates.dart b/web/samples_index/lib/src/templates.dart new file mode 100644 index 000000000..e745bf7a6 --- /dev/null +++ b/web/samples_index/lib/src/templates.dart @@ -0,0 +1,222 @@ +// Copyright 2020 The Flutter team. 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:convert'; + +import 'data.dart'; +import 'util.dart' as util; + +String _escapeAttribute(String s) => + HtmlEscape(HtmlEscapeMode.attribute).convert(s); +String _escapeElement(String s) => HtmlEscape(HtmlEscapeMode.element).convert(s); + +String description(Sample sample) => ''' + + +$_descriptionHeader +${_descriptionPage(sample)} +$_footer + +'''; + +String index(List samples) => ''' + + +$_indexHeader +${_indexBody(samples)} +$_footer + + '''; + +String _indexHeader = ''' + + + Flutter samples + + + + + + + +'''; + +String _descriptionHeader = ''' + + + Flutter samples + + + + + + + +'''; + +String _navbar = ''' + +'''; + +String _footer = ''' + +'''; + +String _indexBody(List samples) => ''' + +
+ ${util.indent(_navbar, 4)} +
+
+

All Samples

+

A curated list of Flutter samples and apps

+
+
+ +
+
+
+
+
+
+ + All + +
+
+
+ + Sample + +
+
+
+ + Cookbook + +
+
+
+ + Web Demos + +
+
+
+
+
+
+ ${util.indent(_indexCards(samples), 6)} +
+
+
+ +'''; + +String _backgroundImage(String url) => + _escapeAttribute('background-image: url(\'$url\');'); +String _indexCards(List samples) => samples.map(_indexCard).join(); +String _indexCard(Sample sample) => ''' +
+
+
+
${_escapeElement(sample.type)}
+
+

${_escapeElement(sample.name)}

+
+
${sample.shortDescription}
+
+
+'''; + +String _descriptionPage(Sample sample) => ''' + +
+ ${util.indent(_navbar, 4)} +
+
+

${sample.name}

+
${sample.type}
+
+

By ${sample.author}

+
+
+ ${util.indent(_descriptionButtons(sample), 6)} +
+
+
+ local_offer + Tags +
+
+ ${util.indent(_tags(sample), 8)} +
+
+
+
+ ${util.indent(_descriptionScreenshots(sample), 4)} +
+
+ ${util.indent(_descriptionText(sample), 4)} +
+
+
+ +'''; + +String _descriptionButtons(Sample sample) { + var buf = StringBuffer(); + if (sample?.web?.isNotEmpty == true) { + buf.write(''''''); + } + + if (sample.type == 'app' || sample.type == 'sample' || sample.type == 'demo') { + buf.write(''''''); + } + + if (sample.type =='cookbook') { + buf.write(''''''); + } + return buf.toString(); +} + +String _tags(Sample sample) { + var buf = StringBuffer(); + for (var tag in sample.tags) { + buf.write('$tag\n'); + } + return buf.toString(); +} + +String _descriptionScreenshots(Sample sample) { + var buf = StringBuffer(); + for (var screenshot in sample.screenshots) { + buf.write('${screenshot.alt}\n'); + } + return buf.toString(); +} + +String _descriptionText(Sample sample) { + return '

${sample.description}

'; +} \ No newline at end of file diff --git a/web/samples_index/lib/src/util.dart b/web/samples_index/lib/src/util.dart new file mode 100644 index 000000000..b135879b6 --- /dev/null +++ b/web/samples_index/lib/src/util.dart @@ -0,0 +1,33 @@ +// Copyright 2020 The Flutter team. 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:convert'; + +String indent(String content, int spaces) => + LineSplitter.split(content).join('\n' + ' ' * spaces); + +String kebabCase(String input) => _fixCase(input, '-'); + +String snakeCase(String input) => _fixCase(input, '_'); + +final _upperCase = RegExp('[A-Z]'); + +String pascalCase(String input) { + if (input.isEmpty) { + return ''; + } + + return input[0].toUpperCase() + input.substring(1); +} + +String _fixCase(String input, String separator) => + input.replaceAllMapped(_upperCase, (match) { + var lower = match.group(0).toLowerCase(); + + if (match.start > 0) { + lower = '$separator$lower'; + } + + return lower; + }); \ No newline at end of file diff --git a/web/samples_index/pubspec.yaml b/web/samples_index/pubspec.yaml new file mode 100644 index 000000000..0d7be6144 --- /dev/null +++ b/web/samples_index/pubspec.yaml @@ -0,0 +1,27 @@ +name: samples_index +description: A visual index of Flutter samples +homepage: https://github.com/flutter/samples_index +author: Flutter Team +version: 0.0.1 +environment: + sdk: '>=2.5.0 <3.0.0' +dependencies: + json_annotation: ^3.0.0 + path: ^1.6.0 + resource: ^2.1.6 + yaml: ^2.2.0 + mdc_web: ^0.5.0-pre + sass_builder: ^2.1.0 + checked_yaml: ^1.0.0 + webdriver: ^2.1.0 + html: ^0.14.0 +dev_dependencies: + grinder: ^0.8.3 + pedantic: ^1.8.0 + test: ^1.6.0 + json_serializable: ^3.2.0 + build: ^1.2.0 + build_runner: ^1.7.0 + build_web_compilers: ^2.7.0 + tuneup: ^0.3.6 + diff --git a/web/samples_index/test/samples_index_test.dart b/web/samples_index/test/samples_index_test.dart new file mode 100644 index 000000000..e08e2e9a7 --- /dev/null +++ b/web/samples_index/test/samples_index_test.dart @@ -0,0 +1,165 @@ +// Copyright 2020 The Flutter team. 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:io'; + +import 'package:samples_index/samples_index.dart'; +import 'package:samples_index/browser.dart'; +import 'package:test/test.dart'; +import 'package:checked_yaml/checked_yaml.dart'; + +void main() { + group('YAML', () { + test('parsing', () async { + var file = File('test/yaml/single.yaml'); + var contents = await file.readAsString(); + expect(contents, isNotEmpty); + + var index = checkedYamlDecode(contents, (m) => Index.fromJson(m), + sourceUrl: file.uri); + expect(index.samples, isNotEmpty); + + var sample = index.samples.first; + expect(sample, isNotNull); + expect(sample.name, 'Kittens'); + expect(sample.screenshots, hasLength(2)); + expect(sample.source, 'http://github.com/johnpryan/kittens'); + expect(sample.description, 'A sample kitten app'); + expect(sample.difficulty, 'beginner'); + expect(sample.widgets, hasLength(2)); + expect(sample.widgets.first, 'AnimatedBuilder'); + expect(sample.packages, hasLength(2)); + expect(sample.packages.first, 'json_serializable'); + expect(sample.tags, hasLength(3)); + expect(sample.tags[1], 'kittens'); + expect(sample.platforms, hasLength(3)); + expect(sample.links, hasLength(2)); + expect(sample.links[1].text, 'author'); + expect(sample.links[1].href, 'http://jpryan.me'); + expect(sample.type, 'sample'); + expect(sample.date, DateTime.parse('2019-12-15T02:59:43.1Z')); + expect(sample.channel, 'stable'); + }); + + test('bad yaml', () async { + var file = File('test/yaml/bad.yaml'); + var contents = await file.readAsString(); + expect(contents, isNotEmpty); + + expect( + () => checkedYamlDecode(contents, (m) => Index.fromJson(m), + sourceUrl: file.uri), + throwsA(predicate((e) => + e is ParsedYamlException && + e.message.endsWith('Unsupported value for "name".')))); + }); + }); + + group('searching', () { + test('search attributes', () async { + var file = File('test/yaml/single.yaml'); + var contents = await file.readAsString(); + expect(contents, isNotEmpty); + + var index = checkedYamlDecode(contents, (m) => Index.fromJson(m), + sourceUrl: file.uri); + var sample = index.samples.first; + expect( + sample.searchAttributes.split(' '), + containsAll([ + 'kittens', + 'tag:beginner', + 'tag:kittens', + 'tag:cats', + + // Verify tags are searchable without the prefix + 'beginner', + 'kittens', + 'cats', + + 'platform:web', + 'platform:ios', + 'platform:android', + + // Verify platforms are searchable without the prefix + 'web', + 'ios', + 'android', + + 'widget:AnimatedBuilder', + 'widget:FutureBuilder', + 'package:json_serializable', + 'package:path', + + 'type:sample', + ])); + }); + + test('matchesQuery', () { + var attributes = 'kittens ' + 'tag:beginner ' + 'tag:kittens ' + 'tag:cats ' + 'platform:web ' + 'platform:ios ' + 'platform:android ' + 'widget:AnimatedBuilder ' + 'widget:FutureBuilder ' + 'package:json_serializable ' + 'package:path'; + + // Test if various queries match these attributes + expect(matchesQuery('foo', attributes), false); + expect(matchesQuery('kittens', attributes), true); + expect(matchesQuery('tag:cats', attributes), true); + expect(matchesQuery('tag:dogs', attributes), false); + expect(matchesQuery('package:path', attributes), true); + + // Test if partial queries match these attributes + expect(matchesQuery('kitten', attributes), true); + + // Test if multiple keywords match + expect(matchesQuery('kittens tag:cats', attributes), true); + expect(matchesQuery('kitten tag:cats', attributes), true); + expect(matchesQuery('tag:beginner dogs', attributes), false); + expect(matchesQuery('asdf ', attributes), false); + }); + }); + + group('Hash parameters', () { + test('can be parsed', () { + expect(parseHash('#?search=kittens&platform=web'), + containsPair('search', 'kittens')); + expect(parseHash('#?search=kittens&platform=web'), + containsPair('platform', 'web')); + expect(parseHash('#?type=sample'), containsPair('type', 'sample')); + expect(parseHash('#?type=cookbook'), containsPair('type', 'cookbook')); + }); + + test('can be set', () { + expect( + formatHash({ + 'search': 'kittens', + 'platform': 'web', + }), + equals('?search=kittens&platform=web')); + }); + + test('creates search attributes', () { + expect( + searchQueryFromParams({ + 'search': 'kittens', + 'platform': 'web', + 'type': 'sample', + }), + equals('kittens type:sample platform:web')); + expect( + searchQueryFromParams({ + 'search': 'kittens', + }), + equals('kittens')); + expect(searchQueryFromParams({}), equals('')); + }); + }); +} diff --git a/web/samples_index/test/yaml/bad.yaml b/web/samples_index/test/yaml/bad.yaml new file mode 100644 index 000000000..fe6e79638 --- /dev/null +++ b/web/samples_index/test/yaml/bad.yaml @@ -0,0 +1,27 @@ +samples: + # Bad type, should be string, but it's a number. + - name: 42 + screenshots: + - url: https://placekitten.com/500/500 + alt: a kitten + - url: https://placekitten.com/400/400 + alt: another kitten + source: http://github.com/johnpryan/kittens + description: A sample kitten app + difficulty: beginner + widgets: + - AnimatedBuilder + - FutureBuilder + packages: + - json_serializable + - path + tags: ['beginner', 'kittens', 'cats'] + platforms: ['web', 'ios', 'android'] + links: + - text: inspiration + href: https://apps.apple.com/us/app/neko-atsume-kitty-collector/id923917775 + - text: author + href: http://jpryan.me + type: sample # sample, app, or cookbook + date: 2019-12-15T02:59:43.1Z + channel: stable diff --git a/web/samples_index/test/yaml/single.yaml b/web/samples_index/test/yaml/single.yaml new file mode 100644 index 000000000..3affccb27 --- /dev/null +++ b/web/samples_index/test/yaml/single.yaml @@ -0,0 +1,26 @@ +samples: + - name: Kittens + screenshots: + - url: https://placekitten.com/500/500 + alt: a kitten + - url: https://placekitten.com/400/400 + alt: another kitten + source: http://github.com/johnpryan/kittens + description: A sample kitten app + difficulty: beginner + widgets: + - AnimatedBuilder + - FutureBuilder + packages: + - json_serializable + - path + tags: ['beginner', 'kittens', 'cats'] + platforms: ['web', 'ios', 'android'] + links: + - text: inspiration + href: https://apps.apple.com/us/app/neko-atsume-kitty-collector/id923917775 + - text: author + href: http://jpryan.me + type: sample # sample, app, or cookbook + date: 2019-12-15T02:59:43.1Z + channel: stable diff --git a/web/samples_index/tool/grind.dart b/web/samples_index/tool/grind.dart new file mode 100644 index 000000000..53f0188b9 --- /dev/null +++ b/web/samples_index/tool/grind.dart @@ -0,0 +1,94 @@ +// Copyright 2020 The Flutter team. 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:io'; +import 'dart:convert'; + +import 'package:grinder/grinder.dart'; +import 'package:path/path.dart' as path; +import 'package:samples_index/samples_index.dart'; +import 'package:samples_index/src/templates.dart' as templates; +import 'package:samples_index/cookbook.dart'; + +void main(args) => grind(args); + +@Task('Run tests in the VM') +void testCli() async => await TestRunner().testAsync(platformSelector: 'vm'); + +@Task() +void analyze() { + PubApp.local('tuneup')..run(['check']); +} + +@Task('deploy') +@Depends(analyze, testCli, generate, buildRelease) +void deploy() { + print('All tasks completed. To deploy to Firebase, run:'); + print(''); + print(' firebase deploy'); + print(''); +} + +@Task('Run build_runner to public/ directory') +Future buildRelease() async { + var app = PubApp.local('build_runner'); + await app.runAsync('build --release --output web:public'.split(' ').toList()); +} + +@DefaultTask('Build the project.') +@Depends(clean) +Future generate() async { + var samples = await getSamples(); + print('Generating index for ${samples.length} samples...'); + var outputFile = File('web/index.html'); + await outputFile.create(recursive: true); + await outputFile.writeAsString(templates.index(samples)); + var futures = []; + for (var sample in samples) { + var file = File('web/${sample.filename}.html'); + var future = file.create(recursive: true).then((_) async { + await file.writeAsString(templates.description(sample)); + }); + futures.add(future); + } + await Future.wait(futures); + print('Generated index for ${samples.length} samples.'); +} + +@Task('Scrape the cookbook for images and descriptions') +Future scrapeCookbook() async { + var driver = await Process.start( + 'chromedriver', ['--port=4444', '--url-base=wd/hub', '--verbose']); + driver.stdout.pipe(stdout); + driver.stderr.pipe(stderr); + var scraper = CookbookScraper(); + await scraper.init(); + var links = await scraper.fetchCookbookLinks(); + print('Scraping ${links.length} cookbook articles'); + var allSamples = []; + for (var link in links) { + allSamples.add(await scraper.getMetadata(link)); + await scraper.takeScreenshot(link); + } + var file = File('lib/src/cookbook.json'); + await file.create(); + var encoder = JsonEncoder.withIndent('\t'); + await file.writeAsString(encoder.convert(Index(allSamples))); + await scraper.dispose(); + var killed = driver.kill(); + if (!killed) { + print('failed to kill chromedriver process'); + } +} + +@Task('remove generated HTML files') +Future clean() async { + var tasks = []; + await for (var file in Directory('web').list(recursive: true)) { + if (path.extension(file.path) == '.html') { + tasks.add(file.delete()); + } + } + await Future.wait(tasks); +} diff --git a/web/samples_index/web/description.dart b/web/samples_index/web/description.dart new file mode 100644 index 000000000..39783ae32 --- /dev/null +++ b/web/samples_index/web/description.dart @@ -0,0 +1,10 @@ +import 'dart:html'; + +import 'package:mdc_web/mdc_web.dart'; + +InputElement searchInput; + +void main() { + querySelectorAll('.mdc-card__primary-action') + .forEach((el) => MDCRipple(el)); +} diff --git a/web/samples_index/web/images/add_to_app1.png b/web/samples_index/web/images/add_to_app1.png new file mode 100644 index 000000000..88a37e52e Binary files /dev/null and b/web/samples_index/web/images/add_to_app1.png differ diff --git a/web/samples_index/web/images/add_to_app2.png b/web/samples_index/web/images/add_to_app2.png new file mode 100644 index 000000000..941b5dd59 Binary files /dev/null and b/web/samples_index/web/images/add_to_app2.png differ diff --git a/web/samples_index/web/images/animations1.png b/web/samples_index/web/images/animations1.png new file mode 100644 index 000000000..8a8f55f26 Binary files /dev/null and b/web/samples_index/web/images/animations1.png differ diff --git a/web/samples_index/web/images/animations2.png b/web/samples_index/web/images/animations2.png new file mode 100644 index 000000000..851f53ce3 Binary files /dev/null and b/web/samples_index/web/images/animations2.png differ diff --git a/web/samples_index/web/images/animations3.png b/web/samples_index/web/images/animations3.png new file mode 100644 index 000000000..b2e0b088c Binary files /dev/null and b/web/samples_index/web/images/animations3.png differ diff --git a/web/samples_index/web/images/charts1.png b/web/samples_index/web/images/charts1.png new file mode 100644 index 000000000..4292241a7 Binary files /dev/null and b/web/samples_index/web/images/charts1.png differ diff --git a/web/samples_index/web/images/cookbook/animated-container.png b/web/samples_index/web/images/cookbook/animated-container.png new file mode 100644 index 000000000..7366eda86 Binary files /dev/null and b/web/samples_index/web/images/cookbook/animated-container.png differ diff --git a/web/samples_index/web/images/cookbook/authenticated-requests.png b/web/samples_index/web/images/cookbook/authenticated-requests.png new file mode 100644 index 000000000..e25452354 Binary files /dev/null and b/web/samples_index/web/images/cookbook/authenticated-requests.png differ diff --git a/web/samples_index/web/images/cookbook/background-parsing.png b/web/samples_index/web/images/cookbook/background-parsing.png new file mode 100644 index 000000000..cfcc8d1c4 Binary files /dev/null and b/web/samples_index/web/images/cookbook/background-parsing.png differ diff --git a/web/samples_index/web/images/cookbook/basic-list.png b/web/samples_index/web/images/cookbook/basic-list.png new file mode 100644 index 000000000..a040ec17c Binary files /dev/null and b/web/samples_index/web/images/cookbook/basic-list.png differ diff --git a/web/samples_index/web/images/cookbook/cached-images.png b/web/samples_index/web/images/cookbook/cached-images.png new file mode 100644 index 000000000..0da8cc0f2 Binary files /dev/null and b/web/samples_index/web/images/cookbook/cached-images.png differ diff --git a/web/samples_index/web/images/cookbook/dismissible.png b/web/samples_index/web/images/cookbook/dismissible.png new file mode 100644 index 000000000..4a29d1574 Binary files /dev/null and b/web/samples_index/web/images/cookbook/dismissible.png differ diff --git a/web/samples_index/web/images/cookbook/drawer.png b/web/samples_index/web/images/cookbook/drawer.png new file mode 100644 index 000000000..f818db8b7 Binary files /dev/null and b/web/samples_index/web/images/cookbook/drawer.png differ diff --git a/web/samples_index/web/images/cookbook/error-reporting.png b/web/samples_index/web/images/cookbook/error-reporting.png new file mode 100644 index 000000000..5ffba418c Binary files /dev/null and b/web/samples_index/web/images/cookbook/error-reporting.png differ diff --git a/web/samples_index/web/images/cookbook/fading-in-images.png b/web/samples_index/web/images/cookbook/fading-in-images.png new file mode 100644 index 000000000..1854dceab Binary files /dev/null and b/web/samples_index/web/images/cookbook/fading-in-images.png differ diff --git a/web/samples_index/web/images/cookbook/fetch-data.png b/web/samples_index/web/images/cookbook/fetch-data.png new file mode 100644 index 000000000..727f77668 Binary files /dev/null and b/web/samples_index/web/images/cookbook/fetch-data.png differ diff --git a/web/samples_index/web/images/cookbook/finders.png b/web/samples_index/web/images/cookbook/finders.png new file mode 100644 index 000000000..f03bab65e Binary files /dev/null and b/web/samples_index/web/images/cookbook/finders.png differ diff --git a/web/samples_index/web/images/cookbook/floating-app-bar.png b/web/samples_index/web/images/cookbook/floating-app-bar.png new file mode 100644 index 000000000..d1f0eccf0 Binary files /dev/null and b/web/samples_index/web/images/cookbook/floating-app-bar.png differ diff --git a/web/samples_index/web/images/cookbook/focus.png b/web/samples_index/web/images/cookbook/focus.png new file mode 100644 index 000000000..02710ba8b Binary files /dev/null and b/web/samples_index/web/images/cookbook/focus.png differ diff --git a/web/samples_index/web/images/cookbook/fonts.png b/web/samples_index/web/images/cookbook/fonts.png new file mode 100644 index 000000000..76c3ccf1c Binary files /dev/null and b/web/samples_index/web/images/cookbook/fonts.png differ diff --git a/web/samples_index/web/images/cookbook/grid-lists.png b/web/samples_index/web/images/cookbook/grid-lists.png new file mode 100644 index 000000000..c954c3e08 Binary files /dev/null and b/web/samples_index/web/images/cookbook/grid-lists.png differ diff --git a/web/samples_index/web/images/cookbook/handling-taps.png b/web/samples_index/web/images/cookbook/handling-taps.png new file mode 100644 index 000000000..d1e15486c Binary files /dev/null and b/web/samples_index/web/images/cookbook/handling-taps.png differ diff --git a/web/samples_index/web/images/cookbook/hero-animations.png b/web/samples_index/web/images/cookbook/hero-animations.png new file mode 100644 index 000000000..681ef2af9 Binary files /dev/null and b/web/samples_index/web/images/cookbook/hero-animations.png differ diff --git a/web/samples_index/web/images/cookbook/horizontal-list.png b/web/samples_index/web/images/cookbook/horizontal-list.png new file mode 100644 index 000000000..b1bb0a722 Binary files /dev/null and b/web/samples_index/web/images/cookbook/horizontal-list.png differ diff --git a/web/samples_index/web/images/cookbook/introduction.png b/web/samples_index/web/images/cookbook/introduction.png new file mode 100644 index 000000000..05e72bd03 Binary files /dev/null and b/web/samples_index/web/images/cookbook/introduction.png differ diff --git a/web/samples_index/web/images/cookbook/key-value.png b/web/samples_index/web/images/cookbook/key-value.png new file mode 100644 index 000000000..d9c654e74 Binary files /dev/null and b/web/samples_index/web/images/cookbook/key-value.png differ diff --git a/web/samples_index/web/images/cookbook/long-lists.png b/web/samples_index/web/images/cookbook/long-lists.png new file mode 100644 index 000000000..b152090ba Binary files /dev/null and b/web/samples_index/web/images/cookbook/long-lists.png differ diff --git a/web/samples_index/web/images/cookbook/mixed-list.png b/web/samples_index/web/images/cookbook/mixed-list.png new file mode 100644 index 000000000..1ca23b3ee Binary files /dev/null and b/web/samples_index/web/images/cookbook/mixed-list.png differ diff --git a/web/samples_index/web/images/cookbook/mocking.png b/web/samples_index/web/images/cookbook/mocking.png new file mode 100644 index 000000000..8c680aa8f Binary files /dev/null and b/web/samples_index/web/images/cookbook/mocking.png differ diff --git a/web/samples_index/web/images/cookbook/named-routes.png b/web/samples_index/web/images/cookbook/named-routes.png new file mode 100644 index 000000000..36bc5cd4b Binary files /dev/null and b/web/samples_index/web/images/cookbook/named-routes.png differ diff --git a/web/samples_index/web/images/cookbook/navigate-with-arguments.png b/web/samples_index/web/images/cookbook/navigate-with-arguments.png new file mode 100644 index 000000000..036521382 Binary files /dev/null and b/web/samples_index/web/images/cookbook/navigate-with-arguments.png differ diff --git a/web/samples_index/web/images/cookbook/navigation-basics.png b/web/samples_index/web/images/cookbook/navigation-basics.png new file mode 100644 index 000000000..c3bf151df Binary files /dev/null and b/web/samples_index/web/images/cookbook/navigation-basics.png differ diff --git a/web/samples_index/web/images/cookbook/network-image.png b/web/samples_index/web/images/cookbook/network-image.png new file mode 100644 index 000000000..b66adb2f5 Binary files /dev/null and b/web/samples_index/web/images/cookbook/network-image.png differ diff --git a/web/samples_index/web/images/cookbook/opacity-animation.png b/web/samples_index/web/images/cookbook/opacity-animation.png new file mode 100644 index 000000000..8d3e5ef67 Binary files /dev/null and b/web/samples_index/web/images/cookbook/opacity-animation.png differ diff --git a/web/samples_index/web/images/cookbook/orientation.png b/web/samples_index/web/images/cookbook/orientation.png new file mode 100644 index 000000000..6aa464e13 Binary files /dev/null and b/web/samples_index/web/images/cookbook/orientation.png differ diff --git a/web/samples_index/web/images/cookbook/package-fonts.png b/web/samples_index/web/images/cookbook/package-fonts.png new file mode 100644 index 000000000..c0143f526 Binary files /dev/null and b/web/samples_index/web/images/cookbook/package-fonts.png differ diff --git a/web/samples_index/web/images/cookbook/page-route-animation.png b/web/samples_index/web/images/cookbook/page-route-animation.png new file mode 100644 index 000000000..cc7524387 Binary files /dev/null and b/web/samples_index/web/images/cookbook/page-route-animation.png differ diff --git a/web/samples_index/web/images/cookbook/passing-data.png b/web/samples_index/web/images/cookbook/passing-data.png new file mode 100644 index 000000000..f4e049f18 Binary files /dev/null and b/web/samples_index/web/images/cookbook/passing-data.png differ diff --git a/web/samples_index/web/images/cookbook/physics-simulation.png b/web/samples_index/web/images/cookbook/physics-simulation.png new file mode 100644 index 000000000..487ce6e44 Binary files /dev/null and b/web/samples_index/web/images/cookbook/physics-simulation.png differ diff --git a/web/samples_index/web/images/cookbook/picture-using-camera.png b/web/samples_index/web/images/cookbook/picture-using-camera.png new file mode 100644 index 000000000..0b8736cca Binary files /dev/null and b/web/samples_index/web/images/cookbook/picture-using-camera.png differ diff --git a/web/samples_index/web/images/cookbook/play-video.png b/web/samples_index/web/images/cookbook/play-video.png new file mode 100644 index 000000000..f06f3fda4 Binary files /dev/null and b/web/samples_index/web/images/cookbook/play-video.png differ diff --git a/web/samples_index/web/images/cookbook/profiling.png b/web/samples_index/web/images/cookbook/profiling.png new file mode 100644 index 000000000..b8367660c Binary files /dev/null and b/web/samples_index/web/images/cookbook/profiling.png differ diff --git a/web/samples_index/web/images/cookbook/reading-writing-files.png b/web/samples_index/web/images/cookbook/reading-writing-files.png new file mode 100644 index 000000000..7a51105ca Binary files /dev/null and b/web/samples_index/web/images/cookbook/reading-writing-files.png differ diff --git a/web/samples_index/web/images/cookbook/retrieve-input.png b/web/samples_index/web/images/cookbook/retrieve-input.png new file mode 100644 index 000000000..1aac5df42 Binary files /dev/null and b/web/samples_index/web/images/cookbook/retrieve-input.png differ diff --git a/web/samples_index/web/images/cookbook/returning-data.png b/web/samples_index/web/images/cookbook/returning-data.png new file mode 100644 index 000000000..44b3f9e87 Binary files /dev/null and b/web/samples_index/web/images/cookbook/returning-data.png differ diff --git a/web/samples_index/web/images/cookbook/ripples.png b/web/samples_index/web/images/cookbook/ripples.png new file mode 100644 index 000000000..bd503ce64 Binary files /dev/null and b/web/samples_index/web/images/cookbook/ripples.png differ diff --git a/web/samples_index/web/images/cookbook/scrolling.png b/web/samples_index/web/images/cookbook/scrolling.png new file mode 100644 index 000000000..b77d09e52 Binary files /dev/null and b/web/samples_index/web/images/cookbook/scrolling.png differ diff --git a/web/samples_index/web/images/cookbook/send-data.png b/web/samples_index/web/images/cookbook/send-data.png new file mode 100644 index 000000000..219594132 Binary files /dev/null and b/web/samples_index/web/images/cookbook/send-data.png differ diff --git a/web/samples_index/web/images/cookbook/snackbars.png b/web/samples_index/web/images/cookbook/snackbars.png new file mode 100644 index 000000000..10874a6db Binary files /dev/null and b/web/samples_index/web/images/cookbook/snackbars.png differ diff --git a/web/samples_index/web/images/cookbook/sqlite.png b/web/samples_index/web/images/cookbook/sqlite.png new file mode 100644 index 000000000..8baf804e2 Binary files /dev/null and b/web/samples_index/web/images/cookbook/sqlite.png differ diff --git a/web/samples_index/web/images/cookbook/tabs.png b/web/samples_index/web/images/cookbook/tabs.png new file mode 100644 index 000000000..816a0d168 Binary files /dev/null and b/web/samples_index/web/images/cookbook/tabs.png differ diff --git a/web/samples_index/web/images/cookbook/tap-drag.png b/web/samples_index/web/images/cookbook/tap-drag.png new file mode 100644 index 000000000..959afb84d Binary files /dev/null and b/web/samples_index/web/images/cookbook/tap-drag.png differ diff --git a/web/samples_index/web/images/cookbook/text-field-changes.png b/web/samples_index/web/images/cookbook/text-field-changes.png new file mode 100644 index 000000000..af8d60914 Binary files /dev/null and b/web/samples_index/web/images/cookbook/text-field-changes.png differ diff --git a/web/samples_index/web/images/cookbook/text-input.png b/web/samples_index/web/images/cookbook/text-input.png new file mode 100644 index 000000000..e1ff883a5 Binary files /dev/null and b/web/samples_index/web/images/cookbook/text-input.png differ diff --git a/web/samples_index/web/images/cookbook/themes.png b/web/samples_index/web/images/cookbook/themes.png new file mode 100644 index 000000000..21345067b Binary files /dev/null and b/web/samples_index/web/images/cookbook/themes.png differ diff --git a/web/samples_index/web/images/cookbook/validation.png b/web/samples_index/web/images/cookbook/validation.png new file mode 100644 index 000000000..7f0d6bea6 Binary files /dev/null and b/web/samples_index/web/images/cookbook/validation.png differ diff --git a/web/samples_index/web/images/cookbook/web-sockets.png b/web/samples_index/web/images/cookbook/web-sockets.png new file mode 100644 index 000000000..f8b2d7a64 Binary files /dev/null and b/web/samples_index/web/images/cookbook/web-sockets.png differ diff --git a/web/samples_index/web/images/filipino_cuisine1.png b/web/samples_index/web/images/filipino_cuisine1.png new file mode 100644 index 000000000..145c40343 Binary files /dev/null and b/web/samples_index/web/images/filipino_cuisine1.png differ diff --git a/web/samples_index/web/images/flutter_maps_firestore1.png b/web/samples_index/web/images/flutter_maps_firestore1.png new file mode 100644 index 000000000..bfbaa960b Binary files /dev/null and b/web/samples_index/web/images/flutter_maps_firestore1.png differ diff --git a/web/samples_index/web/images/flutter_maps_firestore2.png b/web/samples_index/web/images/flutter_maps_firestore2.png new file mode 100644 index 000000000..971846164 Binary files /dev/null and b/web/samples_index/web/images/flutter_maps_firestore2.png differ diff --git a/web/samples_index/web/images/gallery1.png b/web/samples_index/web/images/gallery1.png new file mode 100644 index 000000000..7a4dec499 Binary files /dev/null and b/web/samples_index/web/images/gallery1.png differ diff --git a/web/samples_index/web/images/gallery2.png b/web/samples_index/web/images/gallery2.png new file mode 100644 index 000000000..b203fc3d7 Binary files /dev/null and b/web/samples_index/web/images/gallery2.png differ diff --git a/web/samples_index/web/images/gallery3.png b/web/samples_index/web/images/gallery3.png new file mode 100644 index 000000000..eeb092de6 Binary files /dev/null and b/web/samples_index/web/images/gallery3.png differ diff --git a/web/samples_index/web/images/gallery4.png b/web/samples_index/web/images/gallery4.png new file mode 100644 index 000000000..5113a51b1 Binary files /dev/null and b/web/samples_index/web/images/gallery4.png differ diff --git a/web/samples_index/web/images/gallery5.png b/web/samples_index/web/images/gallery5.png new file mode 100644 index 000000000..5b3e755d5 Binary files /dev/null and b/web/samples_index/web/images/gallery5.png differ diff --git a/web/samples_index/web/images/github_dataviz1.png b/web/samples_index/web/images/github_dataviz1.png new file mode 100644 index 000000000..5d63f68a4 Binary files /dev/null and b/web/samples_index/web/images/github_dataviz1.png differ diff --git a/web/samples_index/web/images/isolate1.png b/web/samples_index/web/images/isolate1.png new file mode 100644 index 000000000..520ebd307 Binary files /dev/null and b/web/samples_index/web/images/isolate1.png differ diff --git a/web/samples_index/web/images/isolate2.png b/web/samples_index/web/images/isolate2.png new file mode 100644 index 000000000..1b02db5a4 Binary files /dev/null and b/web/samples_index/web/images/isolate2.png differ diff --git a/web/samples_index/web/images/isolate3.png b/web/samples_index/web/images/isolate3.png new file mode 100644 index 000000000..2a44d493f Binary files /dev/null and b/web/samples_index/web/images/isolate3.png differ diff --git a/web/samples_index/web/images/jsonexample1.png b/web/samples_index/web/images/jsonexample1.png new file mode 100644 index 000000000..d5c8f854b Binary files /dev/null and b/web/samples_index/web/images/jsonexample1.png differ diff --git a/web/samples_index/web/images/jsonexample2.png b/web/samples_index/web/images/jsonexample2.png new file mode 100644 index 000000000..751d8ab7c Binary files /dev/null and b/web/samples_index/web/images/jsonexample2.png differ diff --git a/web/samples_index/web/images/jsonexample3.png b/web/samples_index/web/images/jsonexample3.png new file mode 100644 index 000000000..394251c42 Binary files /dev/null and b/web/samples_index/web/images/jsonexample3.png differ diff --git a/web/samples_index/web/images/logos/logo_flutter_1080px_clr.png b/web/samples_index/web/images/logos/logo_flutter_1080px_clr.png new file mode 100644 index 000000000..00357cb9c Binary files /dev/null and b/web/samples_index/web/images/logos/logo_flutter_1080px_clr.png differ diff --git a/web/samples_index/web/images/logos/logo_flutter_96px_clr.png b/web/samples_index/web/images/logos/logo_flutter_96px_clr.png new file mode 100644 index 000000000..d388a0370 Binary files /dev/null and b/web/samples_index/web/images/logos/logo_flutter_96px_clr.png differ diff --git a/web/samples_index/web/images/logos/logo_lockup_flutter_horizontal_wht.png b/web/samples_index/web/images/logos/logo_lockup_flutter_horizontal_wht.png new file mode 100644 index 000000000..08011179b Binary files /dev/null and b/web/samples_index/web/images/logos/logo_lockup_flutter_horizontal_wht.png differ diff --git a/web/samples_index/web/images/particle_background1.png b/web/samples_index/web/images/particle_background1.png new file mode 100644 index 000000000..1bf2637d7 Binary files /dev/null and b/web/samples_index/web/images/particle_background1.png differ diff --git a/web/samples_index/web/images/place_tracker1.png b/web/samples_index/web/images/place_tracker1.png new file mode 100644 index 000000000..17cdad10e Binary files /dev/null and b/web/samples_index/web/images/place_tracker1.png differ diff --git a/web/samples_index/web/images/place_tracker2.png b/web/samples_index/web/images/place_tracker2.png new file mode 100644 index 000000000..b378e3646 Binary files /dev/null and b/web/samples_index/web/images/place_tracker2.png differ diff --git a/web/samples_index/web/images/place_tracker3.png b/web/samples_index/web/images/place_tracker3.png new file mode 100644 index 000000000..9980e2511 Binary files /dev/null and b/web/samples_index/web/images/place_tracker3.png differ diff --git a/web/samples_index/web/images/place_tracker4.png b/web/samples_index/web/images/place_tracker4.png new file mode 100644 index 000000000..56bff3013 Binary files /dev/null and b/web/samples_index/web/images/place_tracker4.png differ diff --git a/web/samples_index/web/images/platform_design1.png b/web/samples_index/web/images/platform_design1.png new file mode 100644 index 000000000..cb73a5250 Binary files /dev/null and b/web/samples_index/web/images/platform_design1.png differ diff --git a/web/samples_index/web/images/platform_design2.png b/web/samples_index/web/images/platform_design2.png new file mode 100644 index 000000000..c78675f9b Binary files /dev/null and b/web/samples_index/web/images/platform_design2.png differ diff --git a/web/samples_index/web/images/platform_design3.png b/web/samples_index/web/images/platform_design3.png new file mode 100644 index 000000000..444dd429b Binary files /dev/null and b/web/samples_index/web/images/platform_design3.png differ diff --git a/web/samples_index/web/images/platform_design4.png b/web/samples_index/web/images/platform_design4.png new file mode 100644 index 000000000..87a8b0af6 Binary files /dev/null and b/web/samples_index/web/images/platform_design4.png differ diff --git a/web/samples_index/web/images/platform_design5.png b/web/samples_index/web/images/platform_design5.png new file mode 100644 index 000000000..08dfc7504 Binary files /dev/null and b/web/samples_index/web/images/platform_design5.png differ diff --git a/web/samples_index/web/images/platform_design6.png b/web/samples_index/web/images/platform_design6.png new file mode 100644 index 000000000..66d0ff31a Binary files /dev/null and b/web/samples_index/web/images/platform_design6.png differ diff --git a/web/samples_index/web/images/platform_design7.png b/web/samples_index/web/images/platform_design7.png new file mode 100644 index 000000000..7c3a674d8 Binary files /dev/null and b/web/samples_index/web/images/platform_design7.png differ diff --git a/web/samples_index/web/images/platform_view_swift1.png b/web/samples_index/web/images/platform_view_swift1.png new file mode 100644 index 000000000..f3fa9f4c9 Binary files /dev/null and b/web/samples_index/web/images/platform_view_swift1.png differ diff --git a/web/samples_index/web/images/platform_view_swift2.png b/web/samples_index/web/images/platform_view_swift2.png new file mode 100644 index 000000000..a8d15691a Binary files /dev/null and b/web/samples_index/web/images/platform_view_swift2.png differ diff --git a/web/samples_index/web/images/provider_shopper1.png b/web/samples_index/web/images/provider_shopper1.png new file mode 100644 index 000000000..13be39c11 Binary files /dev/null and b/web/samples_index/web/images/provider_shopper1.png differ diff --git a/web/samples_index/web/images/provider_shopper2.png b/web/samples_index/web/images/provider_shopper2.png new file mode 100644 index 000000000..cc4a446f1 Binary files /dev/null and b/web/samples_index/web/images/provider_shopper2.png differ diff --git a/web/samples_index/web/images/provider_shopper3.png b/web/samples_index/web/images/provider_shopper3.png new file mode 100644 index 000000000..9255e223a Binary files /dev/null and b/web/samples_index/web/images/provider_shopper3.png differ diff --git a/web/samples_index/web/images/slide_puzzle1.png b/web/samples_index/web/images/slide_puzzle1.png new file mode 100644 index 000000000..e8775b1e3 Binary files /dev/null and b/web/samples_index/web/images/slide_puzzle1.png differ diff --git a/web/samples_index/web/images/timeflow1.png b/web/samples_index/web/images/timeflow1.png new file mode 100644 index 000000000..654c606e9 Binary files /dev/null and b/web/samples_index/web/images/timeflow1.png differ diff --git a/web/samples_index/web/images/vision_challenge1.png b/web/samples_index/web/images/vision_challenge1.png new file mode 100644 index 000000000..6b4075c7f Binary files /dev/null and b/web/samples_index/web/images/vision_challenge1.png differ diff --git a/web/samples_index/web/images/vision_challenge2.png b/web/samples_index/web/images/vision_challenge2.png new file mode 100644 index 000000000..3977c2f53 Binary files /dev/null and b/web/samples_index/web/images/vision_challenge2.png differ diff --git a/web/samples_index/web/main.dart b/web/samples_index/web/main.dart new file mode 100644 index 000000000..39c885d44 --- /dev/null +++ b/web/samples_index/web/main.dart @@ -0,0 +1,162 @@ +import 'dart:html'; + +import 'package:mdc_web/mdc_web.dart'; +import 'package:samples_index/browser.dart'; + +/// The Material text input for searching +MDCTextField searchBar; +MDCChipSet chipSet; + +/// The current set of query parameters that determine how the cards are +/// filtered. e.g. {'search': 'kittens', 'platform': 'ios'} +final queryParams = {}; +const searchKey = 'search'; +const typeKey = 'type'; +const platformKey = 'platform'; + +void main() { + // Initialize Material components + MDCFloatingLabel(querySelector('.mdc-floating-label')); + searchBar = MDCTextField(querySelector('#search-bar')); + MDCRipple(querySelector('#clear-button')); + + // Listen for hash changes + window.onHashChange.listen((_) { + queryParams.clear(); + queryParams.addAll(parseHash(window.location.hash)); + setSearchBarText(); + setSelectedChips(); + filterCards(); + }); + + // Use a ripple effect on all cards + querySelectorAll('.mdc-card__primary-action').forEach((el) => MDCRipple(el) + // Navigate to the description page when tapped + ..listen('click', (e) { + window.location.href = el.attributes['href']; + })); + + // Filter cards on each keypress + searchBar.listen('keydown', (Event e) async { + await Future(() {}); + handleSearch(); + }); + + // Update the URL only when the user is done typing in the search bar + searchBar.listen('change', (Event e) { + queryParams[searchKey] = searchBar.value; + updateHash(); + }); + + // Update the hash, cards, and text input when the clear button is pressed + querySelector('#clear-button').onClick.listen((e) { + queryParams.remove('search'); + updateHash(); + setSearchBarText(); + filterCards(); + }); + + // Initialize chips + chipSet = MDCChipSet(querySelector('.mdc-chip-set')); + chipSet.listen('MDCChip:selection', (Event e) { + // Get the query parameters for this chip + var selectedChipIndex = chipSet.chips.indexWhere((chip) => chip.selected); + var chipParams = paramsForChip(selectedChipIndex); + + // Overwrite query parameters with new ones + queryParams.remove(typeKey); + queryParams.remove(platformKey); + queryParams.addAll(chipParams); + updateHash(); + filterCards(); + }); + + // Apply the search from the hash in the URL + queryParams.addAll(parseHash(window.location.hash)); + setSearchBarText(); + setSelectedChips(); + + // Filter cards if a filter is being applied + if (queryParams.isNotEmpty) { + filterCards(); + } +} + +void setSearchBarText() { + var search = queryParams[searchKey] ?? ''; + searchBar.value = search; +} + +void setSelectedChips() { + var type = queryParams.containsKey(typeKey) ? queryParams[typeKey] : ''; + if (type.isNotEmpty) { + if (type == 'sample') { + chipSet.chips[1].selected = true; + } + if (type == 'cookbook') { + chipSet.chips[2].selected = true; + } + } + + // Apply the platform from the hash in the URL + var platform = + queryParams.containsKey(platformKey) ? queryParams[platformKey] : ''; + if (platform.isNotEmpty) { + if (platform == 'web') { + chipSet.chips[3].selected = true; + } + } + if (platform.isEmpty && type.isEmpty) { + chipSet.chips[0].selected = true; + } +} + +void handleSearch() { + var search = searchBar.value; + queryParams[searchKey] = search; + filterCards(); +} + +void updateHash() { + if (queryParams.isEmpty) { + _replaceHash(''); + return; + } + _replaceHash(formatHash(queryParams)); +} + +void _replaceHash(String hash) { + var currentUri = Uri.parse(window.location.href); + window.history + .replaceState(null, '', currentUri.replace(fragment: hash).toString()); +} + +void filterCards() { + // The search query, e.g. 'kittens platform:web' + var searchQuery = searchQueryFromParams(queryParams); + + // Filter out all elements with non-matching search-attrs + var elements = querySelectorAll('[search-attrs]'); + for (var element in elements) { + var searchAttributes = element.attributes['search-attrs']; + if (matchesQuery(searchQuery, searchAttributes)) { + element.hidden = false; + } else { + element.hidden = true; + } + } +} + +Map paramsForChip(int index) { + switch (index) { + case 1: + return {typeKey: 'sample'}; + case 2: + return {typeKey: 'cookbook'}; + case 3: + return {platformKey: 'web'}; + case 0: + default: + return {}; + } +} diff --git a/web/samples_index/web/styles.scss b/web/samples_index/web/styles.scss new file mode 100644 index 000000000..f5d3d82ec --- /dev/null +++ b/web/samples_index/web/styles.scss @@ -0,0 +1,346 @@ +// Material Design Web theme colors. Must be imported before importing +// material-components-web.scss. +$mdc-theme-primary: #0175C2; +$mdc-theme-secondary: #FFC108; +//$mdc-theme-background: $playground-background-color; +//$mdc-theme-surface: $playground-background-color; +//$mdc-theme-error: $dark-red; + +@import 'package:mdc_web/material-components-web'; + +$dark: #202124; +$background-color: #F8F9FA; +$grey: var(--mdc-theme-text-secondary-on-background,rgba(0,0,0,.54)); +$font-size-small: 0.875rem; +$card-width: 300px; +$mobile-width: 960px; +$container-width: 960px; +$text-color: $dark; +$footer-height: 96px; +$font: Roboto, sans-serif; + +html, body { + height: 100%; + margin: 0; +} + +body { + background-color: $background-color; + margin: 0; +} + +.content { + min-height: 100%; + >.container { + padding-bottom: $footer-height; + } +} + +h1, h2, h3, h4, h5, h6 { + color: $text-color; + font-family: $font; + margin: 0; +} + +h1 { + font-size: 28px; + font-weight: bold; +} + +h2 { + font-size: 22px; + font-weight: bold; +} + +h3 { + font-size: 22px; +} + +h4 { + font-size: 22px; +} + +h5 { + font-size: 18px; +} + +h6 { + font-size: 14px; +} + +* { + font-family: $font; +} + +a { + text-decoration: none; +} + + +[hidden] { + display: none; +} + +.navbar { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + background-color: $dark; + color: white; + padding: 16px 125px 16px 125px; + margin-bottom: 48px; + + + @media screen and (max-width: $mobile-width) { + padding: 8px 16px 8px 16px; + margin-bottom: 16px; + } + + .leading { + &:hover { + opacity: 0.8; + } + } + + .leading, .nav-items { + display: flex; + flex-direction: row; + align-items: center; + > * { + margin: 0 8px 0 8px; + } + } + + .nav-items { + @media screen and (max-width: $mobile-width) { + display: none; + } + } + + span.title { + font-size: 28px; + font-weight: 300; + margin: 0; + color: #5DE0FD; + } + + img { + height: 36px; + } + + a { + color: white; + + &:active { + color: white; + } + } +} + +.footer { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + background-color: $dark; + color: white; + //padding: 36px 125px 36px 125px; + height: $footer-height; + margin-top: -$footer-height; +} + +.container { + width: 100%; + margin-left: auto; + margin-right: auto; + padding: 8px; + @media screen and (min-width: $mobile-width) { + max-width: $container-width; + } +} + +.toolbar { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: start; + margin-bottom: 12px; + + @media screen and (max-width: $mobile-width) { + flex-direction: column; + } + + .buttons { + button { + margin-right: 12px; + margin-bottom: 8px; + } + } +} + +.screenshots { + display: flex; + flex-direction: row; + height: 360px; + overflow-x: scroll; + + @media screen and (max-width: $mobile-width) { + height: 240px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + overflow-x: scroll; + img { + flex: 0 0 auto; + } + } + + img { + border-radius: 8px; + margin: 0 8px 0 8px; + max-width:100%; + max-height:100%; + } +} + +.index-header { + margin-left: 8px; + margin-right: 8px; +} + +.search-container { + display: flex; + flex-direction: row; + align-items: flex-start; + margin-bottom: 12px; + max-width: 640px; + margin-right: 8px; + margin-left: 8px; +} + +.mdc-chip-set { + justify-content: center; +} + +.mdc-card__media { + background-position: top; +} + +.mdc-chip__text, .mdc-text-field__icon { + outline: 0; +} + +#search-bar { + flex: 1; + @include mdc-text-field-fill-color($background-color); + @include mdc-states-hover-opacity(0.0); + @include mdc-states-focus-opacity(0.0); + +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, $card-width); + grid-gap: 8px; + @media screen and (max-width: $mobile-width) { + display: block; + } +} + +.demo-card { + width: $card-width; + margin: 12px; + @media screen and (max-width: $mobile-width) { + margin-left: auto; + margin-right: auto; + width: 100%; + max-width: $card-width; + } +} + +.demo-card__title { + font-family: $font; + margin: 0; +} + +.demo-card__primary { + padding: 1rem; +} + +.demo-card__primary-action { + height: 100%; +} + +.demo-card__secondary { + font-family: $font; + padding: 0 1rem 8px; + color: $grey; +} + +.demo-card__label { + position: absolute; + top: 0; + right: 0; +} + +.type-label { + font-family: $font; + font-size: $font-size-small; + text-transform: uppercase; + color: $dark; + background-color: rgba(255, 255, 255, .95); + border-radius: 8px; + padding: 7px; + margin: 4px; +} + +.type-label-bordered { + border: 1px solid $dark; +} + +.description-title-row { + display: flex; + flex-direction: row; + align-items: center; + h1 { + margin-right: 8px; + } +} + +.tags-container { + max-width: 400px; + .tags-label { + color: $dark; + display: flex; + flex-direction: row; + align-items: center; + justify-content: start; + text-transform: uppercase; + font-weight: bold; + >span{ + margin-left: 4px; + } + } + + .tags { + color: $grey; + a { + margin-left: 8px; + margin-right: 8px; + text-decoration: underline; + + color: $dark; + + &:active { + color: $dark; + } + + &:hover { + color: lighten($dark, 25%); + } + } + } +} diff --git a/web/samples_index/webimages/cookbook/animated-container.png b/web/samples_index/webimages/cookbook/animated-container.png new file mode 100644 index 000000000..4bc8ae9ac Binary files /dev/null and b/web/samples_index/webimages/cookbook/animated-container.png differ diff --git a/web/samples_index/webimages/cookbook/authenticated-requests.png b/web/samples_index/webimages/cookbook/authenticated-requests.png new file mode 100644 index 000000000..224ef5ed7 Binary files /dev/null and b/web/samples_index/webimages/cookbook/authenticated-requests.png differ diff --git a/web/samples_index/webimages/cookbook/background-parsing.png b/web/samples_index/webimages/cookbook/background-parsing.png new file mode 100644 index 000000000..48e44f68c Binary files /dev/null and b/web/samples_index/webimages/cookbook/background-parsing.png differ diff --git a/web/samples_index/webimages/cookbook/basic-list.png b/web/samples_index/webimages/cookbook/basic-list.png new file mode 100644 index 000000000..32c2a1159 Binary files /dev/null and b/web/samples_index/webimages/cookbook/basic-list.png differ diff --git a/web/samples_index/webimages/cookbook/cached-images.png b/web/samples_index/webimages/cookbook/cached-images.png new file mode 100644 index 000000000..8d90dfefd Binary files /dev/null and b/web/samples_index/webimages/cookbook/cached-images.png differ diff --git a/web/samples_index/webimages/cookbook/dismissible.png b/web/samples_index/webimages/cookbook/dismissible.png new file mode 100644 index 000000000..1b52a946a Binary files /dev/null and b/web/samples_index/webimages/cookbook/dismissible.png differ diff --git a/web/samples_index/webimages/cookbook/drawer.png b/web/samples_index/webimages/cookbook/drawer.png new file mode 100644 index 000000000..59372d6af Binary files /dev/null and b/web/samples_index/webimages/cookbook/drawer.png differ diff --git a/web/samples_index/webimages/cookbook/error-reporting.png b/web/samples_index/webimages/cookbook/error-reporting.png new file mode 100644 index 000000000..b29b5f54e Binary files /dev/null and b/web/samples_index/webimages/cookbook/error-reporting.png differ diff --git a/web/samples_index/webimages/cookbook/fading-in-images.png b/web/samples_index/webimages/cookbook/fading-in-images.png new file mode 100644 index 000000000..3802e2ddb Binary files /dev/null and b/web/samples_index/webimages/cookbook/fading-in-images.png differ diff --git a/web/samples_index/webimages/cookbook/fetch-data.png b/web/samples_index/webimages/cookbook/fetch-data.png new file mode 100644 index 000000000..b8e63db8c Binary files /dev/null and b/web/samples_index/webimages/cookbook/fetch-data.png differ diff --git a/web/samples_index/webimages/cookbook/finders.png b/web/samples_index/webimages/cookbook/finders.png new file mode 100644 index 000000000..2617fcd52 Binary files /dev/null and b/web/samples_index/webimages/cookbook/finders.png differ diff --git a/web/samples_index/webimages/cookbook/floating-app-bar.png b/web/samples_index/webimages/cookbook/floating-app-bar.png new file mode 100644 index 000000000..c28e16d22 Binary files /dev/null and b/web/samples_index/webimages/cookbook/floating-app-bar.png differ diff --git a/web/samples_index/webimages/cookbook/focus.png b/web/samples_index/webimages/cookbook/focus.png new file mode 100644 index 000000000..ab6c835c7 Binary files /dev/null and b/web/samples_index/webimages/cookbook/focus.png differ diff --git a/web/samples_index/webimages/cookbook/fonts.png b/web/samples_index/webimages/cookbook/fonts.png new file mode 100644 index 000000000..73f0cf288 Binary files /dev/null and b/web/samples_index/webimages/cookbook/fonts.png differ diff --git a/web/samples_index/webimages/cookbook/grid-lists.png b/web/samples_index/webimages/cookbook/grid-lists.png new file mode 100644 index 000000000..7c32c8b3e Binary files /dev/null and b/web/samples_index/webimages/cookbook/grid-lists.png differ diff --git a/web/samples_index/webimages/cookbook/handling-taps.png b/web/samples_index/webimages/cookbook/handling-taps.png new file mode 100644 index 000000000..1021374ed Binary files /dev/null and b/web/samples_index/webimages/cookbook/handling-taps.png differ diff --git a/web/samples_index/webimages/cookbook/hero-animations.png b/web/samples_index/webimages/cookbook/hero-animations.png new file mode 100644 index 000000000..711b2d643 Binary files /dev/null and b/web/samples_index/webimages/cookbook/hero-animations.png differ diff --git a/web/samples_index/webimages/cookbook/horizontal-list.png b/web/samples_index/webimages/cookbook/horizontal-list.png new file mode 100644 index 000000000..ae4062405 Binary files /dev/null and b/web/samples_index/webimages/cookbook/horizontal-list.png differ diff --git a/web/samples_index/webimages/cookbook/introduction.png b/web/samples_index/webimages/cookbook/introduction.png new file mode 100644 index 000000000..ff1dd1d97 Binary files /dev/null and b/web/samples_index/webimages/cookbook/introduction.png differ diff --git a/web/samples_index/webimages/cookbook/key-value.png b/web/samples_index/webimages/cookbook/key-value.png new file mode 100644 index 000000000..5bff847b1 Binary files /dev/null and b/web/samples_index/webimages/cookbook/key-value.png differ diff --git a/web/samples_index/webimages/cookbook/long-lists.png b/web/samples_index/webimages/cookbook/long-lists.png new file mode 100644 index 000000000..da85744fb Binary files /dev/null and b/web/samples_index/webimages/cookbook/long-lists.png differ diff --git a/web/samples_index/webimages/cookbook/mixed-list.png b/web/samples_index/webimages/cookbook/mixed-list.png new file mode 100644 index 000000000..15f1329e4 Binary files /dev/null and b/web/samples_index/webimages/cookbook/mixed-list.png differ diff --git a/web/samples_index/webimages/cookbook/mocking.png b/web/samples_index/webimages/cookbook/mocking.png new file mode 100644 index 000000000..393a5d891 Binary files /dev/null and b/web/samples_index/webimages/cookbook/mocking.png differ diff --git a/web/samples_index/webimages/cookbook/named-routes.png b/web/samples_index/webimages/cookbook/named-routes.png new file mode 100644 index 000000000..f6ce0080d Binary files /dev/null and b/web/samples_index/webimages/cookbook/named-routes.png differ diff --git a/web/samples_index/webimages/cookbook/navigate-with-arguments.png b/web/samples_index/webimages/cookbook/navigate-with-arguments.png new file mode 100644 index 000000000..0dce10dc5 Binary files /dev/null and b/web/samples_index/webimages/cookbook/navigate-with-arguments.png differ diff --git a/web/samples_index/webimages/cookbook/navigation-basics.png b/web/samples_index/webimages/cookbook/navigation-basics.png new file mode 100644 index 000000000..a9e73ed47 Binary files /dev/null and b/web/samples_index/webimages/cookbook/navigation-basics.png differ diff --git a/web/samples_index/webimages/cookbook/network-image.png b/web/samples_index/webimages/cookbook/network-image.png new file mode 100644 index 000000000..7d8beada8 Binary files /dev/null and b/web/samples_index/webimages/cookbook/network-image.png differ diff --git a/web/samples_index/webimages/cookbook/opacity-animation.png b/web/samples_index/webimages/cookbook/opacity-animation.png new file mode 100644 index 000000000..1de97874a Binary files /dev/null and b/web/samples_index/webimages/cookbook/opacity-animation.png differ diff --git a/web/samples_index/webimages/cookbook/orientation.png b/web/samples_index/webimages/cookbook/orientation.png new file mode 100644 index 000000000..8dedc2be2 Binary files /dev/null and b/web/samples_index/webimages/cookbook/orientation.png differ diff --git a/web/samples_index/webimages/cookbook/package-fonts.png b/web/samples_index/webimages/cookbook/package-fonts.png new file mode 100644 index 000000000..0f941212c Binary files /dev/null and b/web/samples_index/webimages/cookbook/package-fonts.png differ diff --git a/web/samples_index/webimages/cookbook/page-route-animation.png b/web/samples_index/webimages/cookbook/page-route-animation.png new file mode 100644 index 000000000..02dfa9409 Binary files /dev/null and b/web/samples_index/webimages/cookbook/page-route-animation.png differ diff --git a/web/samples_index/webimages/cookbook/passing-data.png b/web/samples_index/webimages/cookbook/passing-data.png new file mode 100644 index 000000000..862a87982 Binary files /dev/null and b/web/samples_index/webimages/cookbook/passing-data.png differ diff --git a/web/samples_index/webimages/cookbook/physics-simulation.png b/web/samples_index/webimages/cookbook/physics-simulation.png new file mode 100644 index 000000000..262f7ccf9 Binary files /dev/null and b/web/samples_index/webimages/cookbook/physics-simulation.png differ diff --git a/web/samples_index/webimages/cookbook/picture-using-camera.png b/web/samples_index/webimages/cookbook/picture-using-camera.png new file mode 100644 index 000000000..5ad2b6ef7 Binary files /dev/null and b/web/samples_index/webimages/cookbook/picture-using-camera.png differ diff --git a/web/samples_index/webimages/cookbook/play-video.png b/web/samples_index/webimages/cookbook/play-video.png new file mode 100644 index 000000000..ccf72c0e1 Binary files /dev/null and b/web/samples_index/webimages/cookbook/play-video.png differ diff --git a/web/samples_index/webimages/cookbook/profiling.png b/web/samples_index/webimages/cookbook/profiling.png new file mode 100644 index 000000000..4ecc9b919 Binary files /dev/null and b/web/samples_index/webimages/cookbook/profiling.png differ diff --git a/web/samples_index/webimages/cookbook/reading-writing-files.png b/web/samples_index/webimages/cookbook/reading-writing-files.png new file mode 100644 index 000000000..47ee879cd Binary files /dev/null and b/web/samples_index/webimages/cookbook/reading-writing-files.png differ diff --git a/web/samples_index/webimages/cookbook/retrieve-input.png b/web/samples_index/webimages/cookbook/retrieve-input.png new file mode 100644 index 000000000..958ca060f Binary files /dev/null and b/web/samples_index/webimages/cookbook/retrieve-input.png differ diff --git a/web/samples_index/webimages/cookbook/returning-data.png b/web/samples_index/webimages/cookbook/returning-data.png new file mode 100644 index 000000000..19ef97f2c Binary files /dev/null and b/web/samples_index/webimages/cookbook/returning-data.png differ diff --git a/web/samples_index/webimages/cookbook/ripples.png b/web/samples_index/webimages/cookbook/ripples.png new file mode 100644 index 000000000..2587f0729 Binary files /dev/null and b/web/samples_index/webimages/cookbook/ripples.png differ diff --git a/web/samples_index/webimages/cookbook/scrolling.png b/web/samples_index/webimages/cookbook/scrolling.png new file mode 100644 index 000000000..d6f84d36c Binary files /dev/null and b/web/samples_index/webimages/cookbook/scrolling.png differ diff --git a/web/samples_index/webimages/cookbook/send-data.png b/web/samples_index/webimages/cookbook/send-data.png new file mode 100644 index 000000000..6b1fce500 Binary files /dev/null and b/web/samples_index/webimages/cookbook/send-data.png differ diff --git a/web/samples_index/webimages/cookbook/snackbars.png b/web/samples_index/webimages/cookbook/snackbars.png new file mode 100644 index 000000000..3a5bc3226 Binary files /dev/null and b/web/samples_index/webimages/cookbook/snackbars.png differ diff --git a/web/samples_index/webimages/cookbook/sqlite.png b/web/samples_index/webimages/cookbook/sqlite.png new file mode 100644 index 000000000..6fb80c04c Binary files /dev/null and b/web/samples_index/webimages/cookbook/sqlite.png differ diff --git a/web/samples_index/webimages/cookbook/tabs.png b/web/samples_index/webimages/cookbook/tabs.png new file mode 100644 index 000000000..3fafaea62 Binary files /dev/null and b/web/samples_index/webimages/cookbook/tabs.png differ diff --git a/web/samples_index/webimages/cookbook/tap-drag.png b/web/samples_index/webimages/cookbook/tap-drag.png new file mode 100644 index 000000000..67ab3d088 Binary files /dev/null and b/web/samples_index/webimages/cookbook/tap-drag.png differ diff --git a/web/samples_index/webimages/cookbook/text-field-changes.png b/web/samples_index/webimages/cookbook/text-field-changes.png new file mode 100644 index 000000000..e2719c1e8 Binary files /dev/null and b/web/samples_index/webimages/cookbook/text-field-changes.png differ diff --git a/web/samples_index/webimages/cookbook/text-input.png b/web/samples_index/webimages/cookbook/text-input.png new file mode 100644 index 000000000..8776a58ca Binary files /dev/null and b/web/samples_index/webimages/cookbook/text-input.png differ diff --git a/web/samples_index/webimages/cookbook/themes.png b/web/samples_index/webimages/cookbook/themes.png new file mode 100644 index 000000000..cc39908e5 Binary files /dev/null and b/web/samples_index/webimages/cookbook/themes.png differ diff --git a/web/samples_index/webimages/cookbook/validation.png b/web/samples_index/webimages/cookbook/validation.png new file mode 100644 index 000000000..53dc4ef5a Binary files /dev/null and b/web/samples_index/webimages/cookbook/validation.png differ diff --git a/web/samples_index/webimages/cookbook/web-sockets.png b/web/samples_index/webimages/cookbook/web-sockets.png new file mode 100644 index 000000000..70cd48a50 Binary files /dev/null and b/web/samples_index/webimages/cookbook/web-sockets.png differ diff --git a/web/slide_puzzle/pubspec.lock b/web/slide_puzzle/pubspec.lock index 14b654847..fa7e49419 100644 --- a/web/slide_puzzle/pubspec.lock +++ b/web/slide_puzzle/pubspec.lock @@ -70,7 +70,7 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.13.5" + version: "0.13.8" crypto: dependency: transitive description: @@ -213,14 +213,7 @@ packages: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" + version: "1.9.1" path: dependency: transitive description: @@ -234,7 +227,7 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.8.0+1" + version: "1.9.0" petitparser: dependency: transitive description: @@ -255,7 +248,7 @@ packages: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.2" + version: "1.4.3" quiver: dependency: transitive description: @@ -276,7 +269,7 @@ packages: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.0" shelf_static: dependency: transitive description: @@ -302,14 +295,14 @@ packages: name: source_map_stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.1.5" + version: "2.0.0" source_maps: dependency: transitive description: name: source_maps url: "https://pub.dartlang.org" source: hosted - version: "0.10.8" + version: "0.10.9" source_span: dependency: transitive description: @@ -351,21 +344,21 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.9.4" + version: "1.14.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.2.15" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.3.2" typed_data: dependency: transitive description: @@ -386,14 +379,14 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "2.3.1" + version: "3.0.0+1" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+13" + version: "0.9.7+14" web_socket_channel: dependency: transitive description: @@ -401,6 +394,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0+1" xml: dependency: transitive description: @@ -416,4 +416,4 @@ packages: source: hosted version: "2.2.0" sdks: - dart: ">=2.6.0 <3.0.0" + dart: ">=2.7.0 <3.0.0"