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 {
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);
......
---
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;
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<String, String> 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<String, String> 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<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 @@ 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<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 @@ void main(List<String> args) {
);
exit(255);
}
exit(0);
}
---
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)).
// 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<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();
}
});
});
......
......@@ -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
......
......@@ -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
......
......@@ -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.
......
......@@ -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
......
......@@ -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
......
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