Updates Sample Catalog v0.0 (#11022)
diff --git a/dev/devicelab/lib/tasks/save_catalog_screenshots.dart b/dev/devicelab/lib/tasks/save_catalog_screenshots.dart
index 6612666..f2646de 100644
--- a/dev/devicelab/lib/tasks/save_catalog_screenshots.dart
+++ b/dev/devicelab/lib/tasks/save_catalog_screenshots.dart
@@ -80,7 +80,7 @@
throw new UploadError('upload of "$fromPath" to "$largeName" and "$smallName" failed after 2 retries');
largeImage ??= await new File(fromPath).readAsBytes();
- smallImage ??= encodePng(copyResize(decodePng(largeImage), 400));
+ smallImage ??= encodePng(copyResize(decodePng(largeImage), 300));
if (!largeImageSaved)
largeImageSaved = await save(client, largeName, largeImage);
diff --git a/examples/catalog/bin/class_index.md.template b/examples/catalog/bin/class_index.md.template
new file mode 100644
index 0000000..7411af3
--- /dev/null
+++ b/examples/catalog/bin/class_index.md.template
@@ -0,0 +1,11 @@
+---
+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>
diff --git a/examples/catalog/bin/entry.md.template b/examples/catalog/bin/entry.md.template
new file mode 100644
index 0000000..5875ec3
--- /dev/null
+++ b/examples/catalog/bin/entry.md.template
@@ -0,0 +1,12 @@
+ <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>
diff --git a/examples/catalog/bin/index.md.template b/examples/catalog/bin/index.md.template
new file mode 100644
index 0000000..67e382f
--- /dev/null
+++ b/examples/catalog/bin/index.md.template
@@ -0,0 +1,11 @@
+---
+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>
diff --git a/examples/catalog/bin/sample_page.dart b/examples/catalog/bin/sample_page.dart
index b790330..5096376 100644
--- a/examples/catalog/bin/sample_page.dart
+++ b/examples/catalog/bin/sample_page.dart
@@ -28,9 +28,6 @@
Directory sampleDirectory;
Directory testDirectory;
Directory driverDirectory;
-String sampleTemplate;
-String screenshotTemplate;
-String screenshotDriverTemplate;
void logMessage(String s) { print(s); }
void logError(String s) { print(s); }
@@ -44,18 +41,11 @@
}
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');
sampleDirectory = new Directory('lib');
testDirectory = new Directory('test');
driverDirectory = new Directory('test_driver');
outputDirectory.createSync();
- sampleTemplate = sampleTemplateFile.readAsStringSync();
- screenshotTemplate = screenshotTemplateFile.readAsStringSync();
- screenshotDriverTemplate = screenshotDriverTemplateFile.readAsStringSync();
}
// Return a copy of template with each occurrence of @(foo) replaced
@@ -76,10 +66,11 @@
logMessage('wrote $output');
}
-class SampleGenerator {
- SampleGenerator(this.sourceFile);
+class SampleInfo {
+ SampleInfo(this.sourceFile, this.commit);
final File sourceFile;
+ final String commit;
String sourceCode;
Map<String, String> commentValues;
@@ -87,8 +78,19 @@
// is used to create derived filenames like foo.md or foo.png.
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'.
- 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'.
String get importPath => '..' + Platform.pathSeparator + sourceFile.path;
@@ -133,64 +135,123 @@
commentValues['name'] = sourceName;
commentValues['path'] = 'examples/catalog/${sourceFile.path}';
commentValues['source'] = sourceCode.trim();
+ commentValues['link'] = link;
+ commentValues['android screenshot'] = 'https://storage.googleapis.com/flutter-catalog/$commit/${sourceName}_small.png';
return true;
}
}
-void generate() {
+void generate(String commit) {
initialize();
- final List<SampleGenerator> samples = <SampleGenerator>[];
+ final List<SampleInfo> samples = <SampleInfo>[];
sampleDirectory.listSync().forEach((FileSystemEntity entity) {
if (entity is File && entity.path.endsWith('.dart')) {
- final SampleGenerator sample = new SampleGenerator(entity);
- if (sample.initialize()) { // skip files that lack the Sample Catalog comment
- writeExpandedTemplate(
- outputFile(sample.sourceName + '.md'),
- sampleTemplate,
- sample.commentValues,
- );
+ final SampleInfo sample = new SampleInfo(entity, commit);
+ if (sample.initialize()) // skip files that lack the Sample Catalog comment
samples.add(sample);
- }
}
});
// Causes the generated imports to appear in alphabetical order.
// Avoid complaints from flutter lint.
- samples.sort((SampleGenerator a, SampleGenerator b) {
+ samples.sort((SampleInfo a, SampleInfo b) {
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(
outputFile('screenshot.dart', driverDirectory),
- screenshotTemplate,
+ inputFile('bin', 'screenshot.dart.template').readAsStringSync(),
<String, String>{
- 'imports': samples.map((SampleGenerator page) {
+ 'imports': samples.map((SampleInfo page) {
return "import '${page.importPath}' show ${page.sampleClass};\n";
}).toList().join(),
- 'widgets': samples.map((SampleGenerator sample) {
+ 'widgets': samples.map((SampleInfo sample) {
return 'new ${sample.sampleClass}(),\n';
}).toList().join(),
},
);
+ // Write screenshot_test.dart, a test driver for screenshot.dart
+ // that collects screenshots of each app and saves them.
writeExpandedTemplate(
outputFile('screenshot_test.dart', driverDirectory),
- screenshotDriverTemplate,
+ inputFile('bin', 'screenshot_test.dart.template').readAsStringSync(),
<String, String>{
- 'paths': samples.map((SampleGenerator sample) {
+ 'paths': samples.map((SampleInfo sample) {
return "'${outputFile(sample.sourceName + '.png').path}'";
}).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) {
+ 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 {
- generate();
+ generate(args[0]);
} catch (error) {
logError(
'Error: sample_page.dart failed: $error\n'
@@ -199,6 +260,5 @@
);
exit(255);
}
-
exit(0);
}
diff --git a/examples/catalog/bin/sample_page.md.template b/examples/catalog/bin/sample_page.md.template
index c3ccc78..f9470ca 100644
--- a/examples/catalog/bin/sample_page.md.template
+++ b/examples/catalog/bin/sample_page.md.template
@@ -1,18 +1,36 @@
---
-catalog: @(name)
+layout: page
title: "@(title)"
-
-permalink: /catalog/@(name)/
+permalink: /catalog/samples/@(link)/
---
@(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)
-See also:
-@(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.
```dart
@(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)).
diff --git a/examples/catalog/bin/screenshot_test.dart.template b/examples/catalog/bin/screenshot_test.dart.template
index a924cd0..6492439 100644
--- a/examples/catalog/bin/screenshot_test.dart.template
+++ b/examples/catalog/bin/screenshot_test.dart.template
@@ -1,6 +1,7 @@
// This file was generated using bin/screenshot_test.dart.template and
// bin/sample_page.dart. For more information see README.md.
+import 'dart:async';
import 'dart:io';
import 'package:flutter_driver/flutter_driver.dart';
@@ -22,14 +23,15 @@
final List<String> paths = <String>[
@(paths)
];
- await driver.waitUntilNoTransientCallbacks();
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 File file = new File(path);
await file.writeAsBytes(pixels);
print('wrote $file');
await driver.tap(find.byValueKey('screenshotGestureDetector'));
- await driver.waitUntilNoTransientCallbacks();
}
});
});
diff --git a/examples/catalog/lib/animated_list.dart b/examples/catalog/lib/animated_list.dart
index 85101a7..91281ec 100644
--- a/examples/catalog/lib/animated_list.dart
+++ b/examples/catalog/lib/animated_list.dart
@@ -205,10 +205,9 @@
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
-from the model, a corresponding card items animate in or out of view
-in the animated list.
+from the model, the corresponding card animates in or out of view.
Description:
Tap an item to select it, tap it again to unselect. Tap '+' to insert at the
diff --git a/examples/catalog/lib/app_bar_bottom.dart b/examples/catalog/lib/app_bar_bottom.dart
index 308ec837..89f8434 100644
--- a/examples/catalog/lib/app_bar_bottom.dart
+++ b/examples/catalog/lib/app_bar_bottom.dart
@@ -123,14 +123,14 @@
Title: AppBar with a custom bottom widget.
-Summary: The AppBar's bottom widget is often a TabBar however any widget with a
-PreferredSize can be used.
+Summary: Any widget with a PreferredSize can appear at the bottom of an AppBar.
Description:
-In this app, the app bar's bottom widget is a TabPageSelector
-that displays the relative position of the selected page in the app's
-TabBarView. The arrow buttons in the toolbar part of the app bar select
-the previous or the next choice.
+Typically an AppBar's bottom widget is a TabBar however any widget with a
+PreferredSize can be used. In this app, the app bar's bottom widget is a
+TabPageSelector that displays the relative position of the selected page
+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
diff --git a/examples/catalog/lib/basic_app_bar.dart b/examples/catalog/lib/basic_app_bar.dart
index ed15fd9..5075d66 100644
--- a/examples/catalog/lib/basic_app_bar.dart
+++ b/examples/catalog/lib/basic_app_bar.dart
@@ -104,8 +104,7 @@
Title: AppBar Basics
-Summary: An 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.
+Summary: A typcial AppBar with a title, actions, and an overflow dropdown menu.
Description:
An app that displays one of a half dozen choices with an icon and a title.
diff --git a/examples/catalog/lib/expansion_tile_sample.dart b/examples/catalog/lib/expansion_tile_sample.dart
index 01b75c7..a261217 100644
--- a/examples/catalog/lib/expansion_tile_sample.dart
+++ b/examples/catalog/lib/expansion_tile_sample.dart
@@ -98,9 +98,6 @@
Title: ExpansionTile
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:
This app displays hierarchical data with ExpansionTiles. Tapping a tile
@@ -108,6 +105,12 @@
its children are disposed so that the widget footprint of the list only
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
Sample: ExpansionTileSample
diff --git a/examples/catalog/lib/tabbed_app_bar.dart b/examples/catalog/lib/tabbed_app_bar.dart
index ab0cb6d..0b0ef58 100644
--- a/examples/catalog/lib/tabbed_app_bar.dart
+++ b/examples/catalog/lib/tabbed_app_bar.dart
@@ -85,11 +85,11 @@
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:
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.
Classes: AppBar, DefaultTabController, TabBar, Scaffold, TabBarView
diff --git a/examples/catalog/test_driver/README.md b/examples/catalog/test_driver/README.md
index fe96af3..36263a9 100644
--- a/examples/catalog/test_driver/README.md
+++ b/examples/catalog/test_driver/README.md
@@ -1 +1 @@
-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.