From 3d6e36d056f3f1ff118d4da253fff57aaefd40ef Mon Sep 17 00:00:00 2001
From: Hans Muller
Date: Thu, 29 Jun 2017 10:44:45 -0700
Subject: [PATCH] Updates Sample Catalog v0.0 (#11022)
---
.../lib/tasks/save_catalog_screenshots.dart | 2 +-
examples/catalog/bin/class_index.md.template | 11 ++
examples/catalog/bin/entry.md.template | 12 ++
examples/catalog/bin/index.md.template | 11 ++
examples/catalog/bin/sample_page.dart | 124 +++++++++++++-----
examples/catalog/bin/sample_page.md.template | 30 ++++-
.../catalog/bin/screenshot_test.dart.template | 6 +-
examples/catalog/lib/animated_list.dart | 5 +-
examples/catalog/lib/app_bar_bottom.dart | 12 +-
examples/catalog/lib/basic_app_bar.dart | 3 +-
.../catalog/lib/expansion_tile_sample.dart | 9 +-
examples/catalog/lib/tabbed_app_bar.dart | 4 +-
examples/catalog/test_driver/README.md | 2 +-
13 files changed, 173 insertions(+), 58 deletions(-)
create mode 100644 examples/catalog/bin/class_index.md.template
create mode 100644 examples/catalog/bin/entry.md.template
create mode 100644 examples/catalog/bin/index.md.template
diff --git a/dev/devicelab/lib/tasks/save_catalog_screenshots.dart b/dev/devicelab/lib/tasks/save_catalog_screenshots.dart
index 661266699f2..f2646de0d71 100644
--- a/dev/devicelab/lib/tasks/save_catalog_screenshots.dart
+++ b/dev/devicelab/lib/tasks/save_catalog_screenshots.dart
@@ -80,7 +80,7 @@ class Upload {
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 00000000000..7411af3ba01
--- /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 Sample App Catalog page lists all of the sample apps.
+
+
+@(entries)
+
diff --git a/examples/catalog/bin/entry.md.template b/examples/catalog/bin/entry.md.template
new file mode 100644
index 00000000000..5875ec34cb5
--- /dev/null
+++ b/examples/catalog/bin/entry.md.template
@@ -0,0 +1,12 @@
+
diff --git a/examples/catalog/bin/index.md.template b/examples/catalog/bin/index.md.template
new file mode 100644
index 00000000000..67e382f0576
--- /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.
+
+
+@(entries)
+
diff --git a/examples/catalog/bin/sample_page.dart b/examples/catalog/bin/sample_page.dart
index b7903307379..5096376e8aa 100644
--- a/examples/catalog/bin/sample_page.dart
+++ b/examples/catalog/bin/sample_page.dart
@@ -28,9 +28,6 @@ Directory outputDirectory;
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 @@ File outputFile(String name, [Directory directory]) {
}
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 @@ void writeExpandedTemplate(File output, String template, Map val
logMessage('wrote $output');
}
-class SampleGenerator {
- SampleGenerator(this.sourceFile);
+class SampleInfo {
+ SampleInfo(this.sourceFile, this.commit);
final File sourceFile;
+ final String commit;
String sourceCode;
Map commentValues;
@@ -87,8 +78,19 @@ class SampleGenerator {
// 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 get highlightedClasses {
+ final String classNames = commentValues['classes'];
+ if (classNames == null)
+ return const [];
+ 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 @@ class SampleGenerator {
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 samples = [];
+ final List samples = [];
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 entries = samples.map((SampleInfo sample) {
+ return expandTemplate(entryTemplate, sample.commentValues);
+ });
+ writeExpandedTemplate(
+ outputFile('index.md'),
+ inputFile('bin', 'index.md.template').readAsStringSync(),
+ {
+ '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> classToSamples = >{};
+ for (SampleInfo sample in samples) {
+ for (String className in sample.highlightedClasses) {
+ classToSamples[className] ??= [];
+ classToSamples[className].add(sample);
+ }
+ }
+ for (String className in classToSamples.keys) {
+ final Iterable entries = classToSamples[className].map((SampleInfo sample) {
+ return expandTemplate(entryTemplate, sample.commentValues);
+ });
+ writeExpandedTemplate(
+ outputFile('${className}_index.md'),
+ inputFile('bin', 'class_index.md.template').readAsStringSync(),
+ {
+ '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(),
{
- '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(),
{
- '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 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 @@ void main(List args) {
);
exit(255);
}
-
exit(0);
}
diff --git a/examples/catalog/bin/sample_page.md.template b/examples/catalog/bin/sample_page.md.template
index c3ccc783a2c..f9470caf653 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)
+
+
+
+
+
+
+
)
+
+
+
+
+
+
+
+
@(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)).
+
+See also:
+@(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 a924cd06b56..6492439d6f3 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 @@ void main() {
final List paths = [
@(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.delayed(const Duration(milliseconds: 500));
final List 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 85101a74292..91281ec63dc 100644
--- a/examples/catalog/lib/animated_list.dart
+++ b/examples/catalog/lib/animated_list.dart
@@ -205,10 +205,9 @@ Sample Catalog
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 308ec837b52..89f8434666d 100644
--- a/examples/catalog/lib/app_bar_bottom.dart
+++ b/examples/catalog/lib/app_bar_bottom.dart
@@ -123,14 +123,14 @@ Sample Catalog
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 ed15fd9f904..5075d66bb2e 100644
--- a/examples/catalog/lib/basic_app_bar.dart
+++ b/examples/catalog/lib/basic_app_bar.dart
@@ -104,8 +104,7 @@ Sample Catalog
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 01b75c7917f..a261217124c 100644
--- a/examples/catalog/lib/expansion_tile_sample.dart
+++ b/examples/catalog/lib/expansion_tile_sample.dart
@@ -98,9 +98,6 @@ Sample Catalog
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 @@ 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
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 ab0cb6d7caf..0b0ef58dbbb 100644
--- a/examples/catalog/lib/tabbed_app_bar.dart
+++ b/examples/catalog/lib/tabbed_app_bar.dart
@@ -85,11 +85,11 @@ Sample Catalog
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 fe96af377aa..36263a95f6c 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.