Commit 3d6e36d0 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Updates Sample Catalog v0.0 (#11022)

parent 0f726490
...@@ -80,7 +80,7 @@ class Upload { ...@@ -80,7 +80,7 @@ class Upload {
throw new UploadError('upload of "$fromPath" to "$largeName" and "$smallName" failed after 2 retries'); throw new UploadError('upload of "$fromPath" to "$largeName" and "$smallName" failed after 2 retries');
largeImage ??= await new File(fromPath).readAsBytes(); largeImage ??= await new File(fromPath).readAsBytes();
smallImage ??= encodePng(copyResize(decodePng(largeImage), 400)); smallImage ??= encodePng(copyResize(decodePng(largeImage), 300));
if (!largeImageSaved) if (!largeImageSaved)
largeImageSaved = await save(client, largeName, largeImage); largeImageSaved = await save(client, largeName, largeImage);
......
---
layout: page
title: "@(class) Sample Apps"
permalink: /catalog/samples/@(link)/
---
All of the sample apps listed here use the Flutter @(class) class in an interesting way. The <a href="/catalog/samples/">Sample App Catalog</a> page lists all of the sample apps.
<div class="container-fluid">
@(entries)
</div>
<div class="row" style="margin-bottom: 32px">
<a href="/catalog/samples/@(link)/">
<div class="col-md-3">
<img style="border:1px solid #000000" src="@(android screenshot)" alt="Android screenshot" class="img-responsive">
</div>
</a>
<div class="col-md-9">
<p>
@(summary)
</p>
</div>
</div>
---
layout: page
title: "Sample App Catalog"
permalink: /catalog/samples/
---
Complete applications that demonstrate how to get things done with Flutter. Each sample app features a few classes or an animation, a layout, or other feature of Flutter. The samples are short, just one file and usually only one or two pages of code. They should easy to try out with your favorite IDE.
<div class="container-fluid">
@(entries)
</div>
...@@ -28,9 +28,6 @@ Directory outputDirectory; ...@@ -28,9 +28,6 @@ Directory outputDirectory;
Directory sampleDirectory; Directory sampleDirectory;
Directory testDirectory; Directory testDirectory;
Directory driverDirectory; Directory driverDirectory;
String sampleTemplate;
String screenshotTemplate;
String screenshotDriverTemplate;
void logMessage(String s) { print(s); } void logMessage(String s) { print(s); }
void logError(String s) { print(s); } void logError(String s) { print(s); }
...@@ -44,18 +41,11 @@ File outputFile(String name, [Directory directory]) { ...@@ -44,18 +41,11 @@ File outputFile(String name, [Directory directory]) {
} }
void initialize() { void initialize() {
final File sampleTemplateFile = inputFile('bin', 'sample_page.md.template');
final File screenshotTemplateFile = inputFile('bin', 'screenshot.dart.template');
final File screenshotDriverTemplateFile = inputFile('bin', 'screenshot_test.dart.template');
outputDirectory = new Directory('.generated'); outputDirectory = new Directory('.generated');
sampleDirectory = new Directory('lib'); sampleDirectory = new Directory('lib');
testDirectory = new Directory('test'); testDirectory = new Directory('test');
driverDirectory = new Directory('test_driver'); driverDirectory = new Directory('test_driver');
outputDirectory.createSync(); outputDirectory.createSync();
sampleTemplate = sampleTemplateFile.readAsStringSync();
screenshotTemplate = screenshotTemplateFile.readAsStringSync();
screenshotDriverTemplate = screenshotDriverTemplateFile.readAsStringSync();
} }
// Return a copy of template with each occurrence of @(foo) replaced // Return a copy of template with each occurrence of @(foo) replaced
...@@ -76,10 +66,11 @@ void writeExpandedTemplate(File output, String template, Map<String, String> val ...@@ -76,10 +66,11 @@ void writeExpandedTemplate(File output, String template, Map<String, String> val
logMessage('wrote $output'); logMessage('wrote $output');
} }
class SampleGenerator { class SampleInfo {
SampleGenerator(this.sourceFile); SampleInfo(this.sourceFile, this.commit);
final File sourceFile; final File sourceFile;
final String commit;
String sourceCode; String sourceCode;
Map<String, String> commentValues; Map<String, String> commentValues;
...@@ -87,8 +78,19 @@ class SampleGenerator { ...@@ -87,8 +78,19 @@ class SampleGenerator {
// is used to create derived filenames like foo.md or foo.png. // is used to create derived filenames like foo.md or foo.png.
String get sourceName => basenameWithoutExtension(sourceFile.path); String get sourceName => basenameWithoutExtension(sourceFile.path);
// The website's link to this page will be /catalog/samples/@(link)/.
String get link => sourceName.replaceAll('_', '-');
// The name of the widget class that defines this sample app, like 'FooSample'. // The name of the widget class that defines this sample app, like 'FooSample'.
String get sampleClass => commentValues["sample"]; String get sampleClass => commentValues['sample'];
// The value of the 'Classes:' comment as a list of class names.
Iterable<String> get highlightedClasses {
final String classNames = commentValues['classes'];
if (classNames == null)
return const <String>[];
return classNames.split(',').map((String s) => s.trim()).where((String s) => s.isNotEmpty);
}
// The relative import path for this sample, like '../lib/foo.dart'. // The relative import path for this sample, like '../lib/foo.dart'.
String get importPath => '..' + Platform.pathSeparator + sourceFile.path; String get importPath => '..' + Platform.pathSeparator + sourceFile.path;
...@@ -133,64 +135,123 @@ class SampleGenerator { ...@@ -133,64 +135,123 @@ class SampleGenerator {
commentValues['name'] = sourceName; commentValues['name'] = sourceName;
commentValues['path'] = 'examples/catalog/${sourceFile.path}'; commentValues['path'] = 'examples/catalog/${sourceFile.path}';
commentValues['source'] = sourceCode.trim(); commentValues['source'] = sourceCode.trim();
commentValues['link'] = link;
commentValues['android screenshot'] = 'https://storage.googleapis.com/flutter-catalog/$commit/${sourceName}_small.png';
return true; return true;
} }
} }
void generate() { void generate(String commit) {
initialize(); initialize();
final List<SampleGenerator> samples = <SampleGenerator>[]; final List<SampleInfo> samples = <SampleInfo>[];
sampleDirectory.listSync().forEach((FileSystemEntity entity) { sampleDirectory.listSync().forEach((FileSystemEntity entity) {
if (entity is File && entity.path.endsWith('.dart')) { if (entity is File && entity.path.endsWith('.dart')) {
final SampleGenerator sample = new SampleGenerator(entity); final SampleInfo sample = new SampleInfo(entity, commit);
if (sample.initialize()) { // skip files that lack the Sample Catalog comment if (sample.initialize()) // skip files that lack the Sample Catalog comment
writeExpandedTemplate(
outputFile(sample.sourceName + '.md'),
sampleTemplate,
sample.commentValues,
);
samples.add(sample); samples.add(sample);
} }
}
}); });
// Causes the generated imports to appear in alphabetical order. // Causes the generated imports to appear in alphabetical order.
// Avoid complaints from flutter lint. // Avoid complaints from flutter lint.
samples.sort((SampleGenerator a, SampleGenerator b) { samples.sort((SampleInfo a, SampleInfo b) {
return a.sourceName.compareTo(b.sourceName); return a.sourceName.compareTo(b.sourceName);
}); });
final String entryTemplate = inputFile('bin', 'entry.md.template').readAsStringSync();
// Write the sample catalog's home page: index.md
final Iterable<String> entries = samples.map((SampleInfo sample) {
return expandTemplate(entryTemplate, sample.commentValues);
});
writeExpandedTemplate(
outputFile('index.md'),
inputFile('bin', 'index.md.template').readAsStringSync(),
<String, String>{
'entries': entries.join('\n'),
},
);
// Write the sample app files, like animated_list.md
for (SampleInfo sample in samples) {
writeExpandedTemplate(
outputFile(sample.sourceName + '.md'),
inputFile('bin', 'sample_page.md.template').readAsStringSync(),
sample.commentValues,
);
}
// For each unique class listened in a sample app's "Classes:" list, generate
// a file that's structurally the same as index.md but only contains samples
// that feature one class. For example AnimatedList_index.md would only
// include samples that had AnimatedList in their "Classes:" list.
final Map<String, List<SampleInfo>> classToSamples = <String, List<SampleInfo>>{};
for (SampleInfo sample in samples) {
for (String className in sample.highlightedClasses) {
classToSamples[className] ??= <SampleInfo>[];
classToSamples[className].add(sample);
}
}
for (String className in classToSamples.keys) {
final Iterable<String> entries = classToSamples[className].map((SampleInfo sample) {
return expandTemplate(entryTemplate, sample.commentValues);
});
writeExpandedTemplate(
outputFile('${className}_index.md'),
inputFile('bin', 'class_index.md.template').readAsStringSync(),
<String, String>{
'class': '$className',
'entries': entries.join('\n'),
'link': '${className}_index',
},
);
}
// Write screenshot.dart, a "test" app that displays each sample
// app in turn when the app is tapped.
writeExpandedTemplate( writeExpandedTemplate(
outputFile('screenshot.dart', driverDirectory), outputFile('screenshot.dart', driverDirectory),
screenshotTemplate, inputFile('bin', 'screenshot.dart.template').readAsStringSync(),
<String, String>{ <String, String>{
'imports': samples.map((SampleGenerator page) { 'imports': samples.map((SampleInfo page) {
return "import '${page.importPath}' show ${page.sampleClass};\n"; return "import '${page.importPath}' show ${page.sampleClass};\n";
}).toList().join(), }).toList().join(),
'widgets': samples.map((SampleGenerator sample) { 'widgets': samples.map((SampleInfo sample) {
return 'new ${sample.sampleClass}(),\n'; return 'new ${sample.sampleClass}(),\n';
}).toList().join(), }).toList().join(),
}, },
); );
// Write screenshot_test.dart, a test driver for screenshot.dart
// that collects screenshots of each app and saves them.
writeExpandedTemplate( writeExpandedTemplate(
outputFile('screenshot_test.dart', driverDirectory), outputFile('screenshot_test.dart', driverDirectory),
screenshotDriverTemplate, inputFile('bin', 'screenshot_test.dart.template').readAsStringSync(),
<String, String>{ <String, String>{
'paths': samples.map((SampleGenerator sample) { 'paths': samples.map((SampleInfo sample) {
return "'${outputFile(sample.sourceName + '.png').path}'"; return "'${outputFile(sample.sourceName + '.png').path}'";
}).toList().join(',\n'), }).toList().join(',\n'),
}, },
); );
// To generate the screenshots: flutter drive test_driver/screenshot.dart // For now, the website's index.json file must be updated by hand.
logMessage('The following entries must appear in _data/catalog/widgets.json');
for (String className in classToSamples.keys)
logMessage('"sample": "${className}_index"');
} }
void main(List<String> args) { void main(List<String> args) {
if (args.length != 1) {
logError(
'Usage (cd examples/catalog/; dart bin/sample_page.dart commit)\n'
'The flutter commit hash locates screenshots on storage.googleapis.com/flutter-catalog/'
);
exit(255);
}
try { try {
generate(); generate(args[0]);
} catch (error) { } catch (error) {
logError( logError(
'Error: sample_page.dart failed: $error\n' 'Error: sample_page.dart failed: $error\n'
...@@ -199,6 +260,5 @@ void main(List<String> args) { ...@@ -199,6 +260,5 @@ void main(List<String> args) {
); );
exit(255); exit(255);
} }
exit(0); exit(0);
} }
--- ---
catalog: @(name) layout: page
title: "@(title)" title: "@(title)"
permalink: /catalog/samples/@(link)/
permalink: /catalog/@(name)/
--- ---
@(summary) @(summary)
<p>
<div class="container-fluid">
<div class="row">
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-body" style="padding: 16px 32px;">
<img style="border:1px solid #000000" src="@(android screenshot)" alt="Android screenshot" class="img-responsive">
</div>
<div class="panel-footer">
Android screenshot
</div>
</div>
</div>
</div>
</div>
</p>
@(description) @(description)
See also: Try this app out by creating a new project with `flutter create` and replacing the contents of `lib/main.dart` with the code that follows.
@(see also)
```dart ```dart
@(source) @(source)
``` ```
The source code is based on [@(path)](https://github.com/flutter/flutter/blob/master/@(path)).
<h2>See also:</h2>
@(see also)
- The source code in [@(path)](https://github.com/flutter/flutter/blob/master/@(path)).
// This file was generated using bin/screenshot_test.dart.template and // This file was generated using bin/screenshot_test.dart.template and
// bin/sample_page.dart. For more information see README.md. // bin/sample_page.dart. For more information see README.md.
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter_driver/flutter_driver.dart'; import 'package:flutter_driver/flutter_driver.dart';
...@@ -22,14 +23,15 @@ void main() { ...@@ -22,14 +23,15 @@ void main() {
final List<String> paths = <String>[ final List<String> paths = <String>[
@(paths) @(paths)
]; ];
await driver.waitUntilNoTransientCallbacks();
for (String path in paths) { for (String path in paths) {
await driver.waitUntilNoTransientCallbacks();
// TBD: when #11021 has been resolved, this shouldn't be necessary.
await new Future<Null>.delayed(const Duration(milliseconds: 500));
final List<int> pixels = await driver.screenshot(); final List<int> pixels = await driver.screenshot();
final File file = new File(path); final File file = new File(path);
await file.writeAsBytes(pixels); await file.writeAsBytes(pixels);
print('wrote $file'); print('wrote $file');
await driver.tap(find.byValueKey('screenshotGestureDetector')); await driver.tap(find.byValueKey('screenshotGestureDetector'));
await driver.waitUntilNoTransientCallbacks();
} }
}); });
}); });
......
...@@ -205,10 +205,9 @@ Sample Catalog ...@@ -205,10 +205,9 @@ Sample Catalog
Title: AnimatedList Title: AnimatedList
Summary: In this app an AnimatedList displays a list of cards which stays Summary: An AnimatedList that displays a list of cards which stay
in sync with an app-specific ListModel. When an item is added to or removed in sync with an app-specific ListModel. When an item is added to or removed
from the model, a corresponding card items animate in or out of view from the model, the corresponding card animates in or out of view.
in the animated list.
Description: Description:
Tap an item to select it, tap it again to unselect. Tap '+' to insert at the Tap an item to select it, tap it again to unselect. Tap '+' to insert at the
......
...@@ -123,14 +123,14 @@ Sample Catalog ...@@ -123,14 +123,14 @@ Sample Catalog
Title: AppBar with a custom bottom widget. Title: AppBar with a custom bottom widget.
Summary: The AppBar's bottom widget is often a TabBar however any widget with a Summary: Any widget with a PreferredSize can appear at the bottom of an AppBar.
PreferredSize can be used.
Description: Description:
In this app, the app bar's bottom widget is a TabPageSelector Typically an AppBar's bottom widget is a TabBar however any widget with a
that displays the relative position of the selected page in the app's PreferredSize can be used. In this app, the app bar's bottom widget is a
TabBarView. The arrow buttons in the toolbar part of the app bar select TabPageSelector that displays the relative position of the selected page
the previous or the next choice. in the app's TabBarView. The arrow buttons in the toolbar part of the app
bar and they select the previous or the next page.
Classes: AppBar, PreferredSize, TabBarView, TabController Classes: AppBar, PreferredSize, TabBarView, TabController
......
...@@ -104,8 +104,7 @@ Sample Catalog ...@@ -104,8 +104,7 @@ Sample Catalog
Title: AppBar Basics Title: AppBar Basics
Summary: An AppBar with a title, actions, and an overflow dropdown menu. Summary: A typcial AppBar with a title, actions, and an overflow dropdown menu.
One of the app's choices can be selected with an action button or the menu.
Description: Description:
An app that displays one of a half dozen choices with an icon and a title. An app that displays one of a half dozen choices with an icon and a title.
......
...@@ -98,9 +98,6 @@ Sample Catalog ...@@ -98,9 +98,6 @@ Sample Catalog
Title: ExpansionTile Title: ExpansionTile
Summary: ExpansionTiles can used to produce two-level or multi-level lists. Summary: ExpansionTiles can used to produce two-level or multi-level lists.
When displayed within a scrollable that creates its list items lazily,
like a scrollable list created with `ListView.builder()`, they can be quite
efficient, particularly for material design "expand/collapse" lists.
Description: Description:
This app displays hierarchical data with ExpansionTiles. Tapping a tile This app displays hierarchical data with ExpansionTiles. Tapping a tile
...@@ -108,6 +105,12 @@ expands or collapses the view of its children. When a tile is collapsed ...@@ -108,6 +105,12 @@ expands or collapses the view of its children. When a tile is collapsed
its children are disposed so that the widget footprint of the list only its children are disposed so that the widget footprint of the list only
reflects what's visible. reflects what's visible.
When displayed within a scrollable that creates its list items lazily,
like a scrollable list created with `ListView.builder()`, ExpansionTiles
can be quite efficient, particularly for material design "expand/collapse"
lists.
Classes: ExpansionTile, ListView Classes: ExpansionTile, ListView
Sample: ExpansionTileSample Sample: ExpansionTileSample
......
...@@ -85,11 +85,11 @@ Sample Catalog ...@@ -85,11 +85,11 @@ Sample Catalog
Title: Tabbed AppBar Title: Tabbed AppBar
Summary: An AppBar can include a TabBar as its bottom widget. Summary: An AppBar with a TabBar as its bottom widget.
Description: Description:
A TabBar can be used to navigate among the pages displayed in a TabBarView. A TabBar can be used to navigate among the pages displayed in a TabBarView.
Although a TabBar is an ordinary widget that can appear, it's most often Although a TabBar is an ordinary widget that can appear anywhere, it's most often
included in the application's AppBar. included in the application's AppBar.
Classes: AppBar, DefaultTabController, TabBar, Scaffold, TabBarView Classes: AppBar, DefaultTabController, TabBar, Scaffold, TabBarView
......
The screenshot_test.dart file was generated by ../bin/sample_page.dart. It should not be checked in. The screenshot_test.dart and screenshot_test.dart files were generated by ../bin/sample_page.dart. They should not be checked in.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment