Updates Sample Catalog v0.0 (#11022)
This commit is contained in:
parent
0f726490e1
commit
3d6e36d056
@ -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);
|
||||
|
11
examples/catalog/bin/class_index.md.template
Normal file
11
examples/catalog/bin/class_index.md.template
Normal file
@ -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>
|
12
examples/catalog/bin/entry.md.template
Normal file
12
examples/catalog/bin/entry.md.template
Normal file
@ -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>
|
11
examples/catalog/bin/index.md.template
Normal file
11
examples/catalog/bin/index.md.template
Normal file
@ -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>
|
@ -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);
|
||||
}
|
||||
|
@ -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)).
|
||||
|
@ -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<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
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user