Skip to content

Commit 3d6e36d

Browse files
author
Hans Muller
authored
Updates Sample Catalog v0.0 (flutter#11022)
1 parent 0f72649 commit 3d6e36d

13 files changed

+173
-58
lines changed

dev/devicelab/lib/tasks/save_catalog_screenshots.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class Upload {
8080
throw new UploadError('upload of "$fromPath" to "$largeName" and "$smallName" failed after 2 retries');
8181

8282
largeImage ??= await new File(fromPath).readAsBytes();
83-
smallImage ??= encodePng(copyResize(decodePng(largeImage), 400));
83+
smallImage ??= encodePng(copyResize(decodePng(largeImage), 300));
8484

8585
if (!largeImageSaved)
8686
largeImageSaved = await save(client, largeName, largeImage);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
layout: page
3+
title: "@(class) Sample Apps"
4+
permalink: /catalog/samples/@(link)/
5+
---
6+
7+
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.
8+
9+
<div class="container-fluid">
10+
@(entries)
11+
</div>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<div class="row" style="margin-bottom: 32px">
2+
<a href="/catalog/samples/@(link)/">
3+
<div class="col-md-3">
4+
<img style="border:1px solid #000000" src="@(android screenshot)" alt="Android screenshot" class="img-responsive">
5+
</div>
6+
</a>
7+
<div class="col-md-9">
8+
<p>
9+
@(summary)
10+
</p>
11+
</div>
12+
</div>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
layout: page
3+
title: "Sample App Catalog"
4+
permalink: /catalog/samples/
5+
---
6+
7+
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.
8+
9+
<div class="container-fluid">
10+
@(entries)
11+
</div>

examples/catalog/bin/sample_page.dart

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ Directory outputDirectory;
2828
Directory sampleDirectory;
2929
Directory testDirectory;
3030
Directory driverDirectory;
31-
String sampleTemplate;
32-
String screenshotTemplate;
33-
String screenshotDriverTemplate;
3431

3532
void logMessage(String s) { print(s); }
3633
void logError(String s) { print(s); }
@@ -44,18 +41,11 @@ File outputFile(String name, [Directory directory]) {
4441
}
4542

4643
void initialize() {
47-
final File sampleTemplateFile = inputFile('bin', 'sample_page.md.template');
48-
final File screenshotTemplateFile = inputFile('bin', 'screenshot.dart.template');
49-
final File screenshotDriverTemplateFile = inputFile('bin', 'screenshot_test.dart.template');
50-
5144
outputDirectory = new Directory('.generated');
5245
sampleDirectory = new Directory('lib');
5346
testDirectory = new Directory('test');
5447
driverDirectory = new Directory('test_driver');
5548
outputDirectory.createSync();
56-
sampleTemplate = sampleTemplateFile.readAsStringSync();
57-
screenshotTemplate = screenshotTemplateFile.readAsStringSync();
58-
screenshotDriverTemplate = screenshotDriverTemplateFile.readAsStringSync();
5949
}
6050

6151
// Return a copy of template with each occurrence of @(foo) replaced
@@ -76,19 +66,31 @@ void writeExpandedTemplate(File output, String template, Map<String, String> val
7666
logMessage('wrote $output');
7767
}
7868

79-
class SampleGenerator {
80-
SampleGenerator(this.sourceFile);
69+
class SampleInfo {
70+
SampleInfo(this.sourceFile, this.commit);
8171

8272
final File sourceFile;
73+
final String commit;
8374
String sourceCode;
8475
Map<String, String> commentValues;
8576

8677
// If sourceFile is lib/foo.dart then sourceName is foo. The sourceName
8778
// is used to create derived filenames like foo.md or foo.png.
8879
String get sourceName => basenameWithoutExtension(sourceFile.path);
8980

81+
// The website's link to this page will be /catalog/samples/@(link)/.
82+
String get link => sourceName.replaceAll('_', '-');
83+
9084
// The name of the widget class that defines this sample app, like 'FooSample'.
91-
String get sampleClass => commentValues["sample"];
85+
String get sampleClass => commentValues['sample'];
86+
87+
// The value of the 'Classes:' comment as a list of class names.
88+
Iterable<String> get highlightedClasses {
89+
final String classNames = commentValues['classes'];
90+
if (classNames == null)
91+
return const <String>[];
92+
return classNames.split(',').map((String s) => s.trim()).where((String s) => s.isNotEmpty);
93+
}
9294

9395
// The relative import path for this sample, like '../lib/foo.dart'.
9496
String get importPath => '..' + Platform.pathSeparator + sourceFile.path;
@@ -133,64 +135,123 @@ class SampleGenerator {
133135
commentValues['name'] = sourceName;
134136
commentValues['path'] = 'examples/catalog/${sourceFile.path}';
135137
commentValues['source'] = sourceCode.trim();
138+
commentValues['link'] = link;
139+
commentValues['android screenshot'] = 'https://storage.googleapis.com/flutter-catalog/$commit/${sourceName}_small.png';
136140

137141
return true;
138142
}
139143
}
140144

141-
void generate() {
145+
void generate(String commit) {
142146
initialize();
143147

144-
final List<SampleGenerator> samples = <SampleGenerator>[];
148+
final List<SampleInfo> samples = <SampleInfo>[];
145149
sampleDirectory.listSync().forEach((FileSystemEntity entity) {
146150
if (entity is File && entity.path.endsWith('.dart')) {
147-
final SampleGenerator sample = new SampleGenerator(entity);
148-
if (sample.initialize()) { // skip files that lack the Sample Catalog comment
149-
writeExpandedTemplate(
150-
outputFile(sample.sourceName + '.md'),
151-
sampleTemplate,
152-
sample.commentValues,
153-
);
151+
final SampleInfo sample = new SampleInfo(entity, commit);
152+
if (sample.initialize()) // skip files that lack the Sample Catalog comment
154153
samples.add(sample);
155-
}
156154
}
157155
});
158156

159157
// Causes the generated imports to appear in alphabetical order.
160158
// Avoid complaints from flutter lint.
161-
samples.sort((SampleGenerator a, SampleGenerator b) {
159+
samples.sort((SampleInfo a, SampleInfo b) {
162160
return a.sourceName.compareTo(b.sourceName);
163161
});
164162

163+
final String entryTemplate = inputFile('bin', 'entry.md.template').readAsStringSync();
164+
165+
// Write the sample catalog's home page: index.md
166+
final Iterable<String> entries = samples.map((SampleInfo sample) {
167+
return expandTemplate(entryTemplate, sample.commentValues);
168+
});
169+
writeExpandedTemplate(
170+
outputFile('index.md'),
171+
inputFile('bin', 'index.md.template').readAsStringSync(),
172+
<String, String>{
173+
'entries': entries.join('\n'),
174+
},
175+
);
176+
177+
// Write the sample app files, like animated_list.md
178+
for (SampleInfo sample in samples) {
179+
writeExpandedTemplate(
180+
outputFile(sample.sourceName + '.md'),
181+
inputFile('bin', 'sample_page.md.template').readAsStringSync(),
182+
sample.commentValues,
183+
);
184+
}
185+
186+
// For each unique class listened in a sample app's "Classes:" list, generate
187+
// a file that's structurally the same as index.md but only contains samples
188+
// that feature one class. For example AnimatedList_index.md would only
189+
// include samples that had AnimatedList in their "Classes:" list.
190+
final Map<String, List<SampleInfo>> classToSamples = <String, List<SampleInfo>>{};
191+
for (SampleInfo sample in samples) {
192+
for (String className in sample.highlightedClasses) {
193+
classToSamples[className] ??= <SampleInfo>[];
194+
classToSamples[className].add(sample);
195+
}
196+
}
197+
for (String className in classToSamples.keys) {
198+
final Iterable<String> entries = classToSamples[className].map((SampleInfo sample) {
199+
return expandTemplate(entryTemplate, sample.commentValues);
200+
});
201+
writeExpandedTemplate(
202+
outputFile('${className}_index.md'),
203+
inputFile('bin', 'class_index.md.template').readAsStringSync(),
204+
<String, String>{
205+
'class': '$className',
206+
'entries': entries.join('\n'),
207+
'link': '${className}_index',
208+
},
209+
);
210+
}
211+
212+
// Write screenshot.dart, a "test" app that displays each sample
213+
// app in turn when the app is tapped.
165214
writeExpandedTemplate(
166215
outputFile('screenshot.dart', driverDirectory),
167-
screenshotTemplate,
216+
inputFile('bin', 'screenshot.dart.template').readAsStringSync(),
168217
<String, String>{
169-
'imports': samples.map((SampleGenerator page) {
218+
'imports': samples.map((SampleInfo page) {
170219
return "import '${page.importPath}' show ${page.sampleClass};\n";
171220
}).toList().join(),
172-
'widgets': samples.map((SampleGenerator sample) {
221+
'widgets': samples.map((SampleInfo sample) {
173222
return 'new ${sample.sampleClass}(),\n';
174223
}).toList().join(),
175224
},
176225
);
177226

227+
// Write screenshot_test.dart, a test driver for screenshot.dart
228+
// that collects screenshots of each app and saves them.
178229
writeExpandedTemplate(
179230
outputFile('screenshot_test.dart', driverDirectory),
180-
screenshotDriverTemplate,
231+
inputFile('bin', 'screenshot_test.dart.template').readAsStringSync(),
181232
<String, String>{
182-
'paths': samples.map((SampleGenerator sample) {
233+
'paths': samples.map((SampleInfo sample) {
183234
return "'${outputFile(sample.sourceName + '.png').path}'";
184235
}).toList().join(',\n'),
185236
},
186237
);
187238

188-
// To generate the screenshots: flutter drive test_driver/screenshot.dart
239+
// For now, the website's index.json file must be updated by hand.
240+
logMessage('The following entries must appear in _data/catalog/widgets.json');
241+
for (String className in classToSamples.keys)
242+
logMessage('"sample": "${className}_index"');
189243
}
190244

191245
void main(List<String> args) {
246+
if (args.length != 1) {
247+
logError(
248+
'Usage (cd examples/catalog/; dart bin/sample_page.dart commit)\n'
249+
'The flutter commit hash locates screenshots on storage.googleapis.com/flutter-catalog/'
250+
);
251+
exit(255);
252+
}
192253
try {
193-
generate();
254+
generate(args[0]);
194255
} catch (error) {
195256
logError(
196257
'Error: sample_page.dart failed: $error\n'
@@ -199,6 +260,5 @@ void main(List<String> args) {
199260
);
200261
exit(255);
201262
}
202-
203263
exit(0);
204264
}
Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,36 @@
11
---
2-
catalog: @(name)
2+
layout: page
33
title: "@(title)"
4-
5-
permalink: /catalog/@(name)/
4+
permalink: /catalog/samples/@(link)/
65
---
76

87
@(summary)
98

9+
<p>
10+
<div class="container-fluid">
11+
<div class="row">
12+
<div class="col-md-4">
13+
<div class="panel panel-default">
14+
<div class="panel-body" style="padding: 16px 32px;">
15+
<img style="border:1px solid #000000" src="@(android screenshot)" alt="Android screenshot" class="img-responsive">
16+
</div>
17+
<div class="panel-footer">
18+
Android screenshot
19+
</div>
20+
</div>
21+
</div>
22+
</div>
23+
</div>
24+
</p>
25+
1026
@(description)
1127

12-
See also:
13-
@(see also)
28+
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.
1429

1530
```dart
1631
@(source)
1732
```
18-
The source code is based on [@(path)](https://github.com/flutter/flutter/blob/master/@(path)).
33+
34+
<h2>See also:</h2>
35+
@(see also)
36+
- The source code in [@(path)](https://github.com/flutter/flutter/blob/master/@(path)).

examples/catalog/bin/screenshot_test.dart.template

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This file was generated using bin/screenshot_test.dart.template and
22
// bin/sample_page.dart. For more information see README.md.
33

4+
import 'dart:async';
45
import 'dart:io';
56

67
import 'package:flutter_driver/flutter_driver.dart';
@@ -22,14 +23,15 @@ void main() {
2223
final List<String> paths = <String>[
2324
@(paths)
2425
];
25-
await driver.waitUntilNoTransientCallbacks();
2626
for (String path in paths) {
27+
await driver.waitUntilNoTransientCallbacks();
28+
// TBD: when #11021 has been resolved, this shouldn't be necessary.
29+
await new Future<Null>.delayed(const Duration(milliseconds: 500));
2730
final List<int> pixels = await driver.screenshot();
2831
final File file = new File(path);
2932
await file.writeAsBytes(pixels);
3033
print('wrote $file');
3134
await driver.tap(find.byValueKey('screenshotGestureDetector'));
32-
await driver.waitUntilNoTransientCallbacks();
3335
}
3436
});
3537
});

examples/catalog/lib/animated_list.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,9 @@ Sample Catalog
205205
206206
Title: AnimatedList
207207
208-
Summary: In this app an AnimatedList displays a list of cards which stays
208+
Summary: An AnimatedList that displays a list of cards which stay
209209
in sync with an app-specific ListModel. When an item is added to or removed
210-
from the model, a corresponding card items animate in or out of view
211-
in the animated list.
210+
from the model, the corresponding card animates in or out of view.
212211
213212
Description:
214213
Tap an item to select it, tap it again to unselect. Tap '+' to insert at the

examples/catalog/lib/app_bar_bottom.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,14 @@ Sample Catalog
123123
124124
Title: AppBar with a custom bottom widget.
125125
126-
Summary: The AppBar's bottom widget is often a TabBar however any widget with a
127-
PreferredSize can be used.
126+
Summary: Any widget with a PreferredSize can appear at the bottom of an AppBar.
128127
129128
Description:
130-
In this app, the app bar's bottom widget is a TabPageSelector
131-
that displays the relative position of the selected page in the app's
132-
TabBarView. The arrow buttons in the toolbar part of the app bar select
133-
the previous or the next choice.
129+
Typically an AppBar's bottom widget is a TabBar however any widget with a
130+
PreferredSize can be used. In this app, the app bar's bottom widget is a
131+
TabPageSelector that displays the relative position of the selected page
132+
in the app's TabBarView. The arrow buttons in the toolbar part of the app
133+
bar and they select the previous or the next page.
134134
135135
Classes: AppBar, PreferredSize, TabBarView, TabController
136136

examples/catalog/lib/basic_app_bar.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,7 @@ Sample Catalog
104104
105105
Title: AppBar Basics
106106
107-
Summary: An AppBar with a title, actions, and an overflow dropdown menu.
108-
One of the app's choices can be selected with an action button or the menu.
107+
Summary: A typcial AppBar with a title, actions, and an overflow dropdown menu.
109108
110109
Description:
111110
An app that displays one of a half dozen choices with an icon and a title.

0 commit comments

Comments
 (0)