Unverified Commit 2462f696 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Remove catalog (#67470)

parent b77753b9
Samples Catalog
=======
A collection of sample apps that demonstrate how Flutter can be used.
Each sample app is contained in a single `.dart` file located in the `lib`
directory. To run each sample app, specify the corresponding file on the
`flutter run` command line, for example:
```
flutter run lib/animated_list.dart
flutter run lib/app_bar_bottom.dart
flutter run lib/basic_app_bar.dart
...
```
The apps are intended to be short and easily understood. Classes that represent
the sample's focus are at the top of the file; data and support classes follow.
Each sample app contains a comment (usually at the end) which provides some
standard documentation that also appears in the web view of the catalog.
See the "Generating..." section below.
Generating the web view of the catalog
---------
Markdown and a screenshot of each app are produced by `bin/sample_page.dart`
and saved in the `.generated` directory. The markdown file contains
the text taken from the Sample Catalog comment found in the app's source
file, followed by the source code itself.
This `sample_page.dart` command-line app must be run from the `examples/catalog`
directory. It relies on templates also found in the bin directory, and it
generates and executes `test_driver` apps to collect the screenshots:
```
cd examples/catalog
dart bin/sample_page.dart
```
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
applicationId "io.flutter.examples.catalog"
minSdkVersion 16
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourcompany.animated_list">
<!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<application android:label="animated_list" android:icon="@mipmap/ic_launcher">
<activity android:name="io.flutter.embedding.android.FlutterActivity"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
---
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>
<p>
This app features the following classes: @(classes).
</p>
<p>
<a href="/catalog/samples/@(link)/">Learn more</a>.
</p>
</div>
</div>
---
layout: page
title: "Sample App Catalog"
permalink: /catalog/samples/
---
This catalog lists applications that demonstrate how to implement common mobile design patterns with Flutter. Each sample demonstrates how a few Flutter widgets can be put together to implement a meaningful user interface. The samples are short - just one Dart file - but they're complete applications. They should be easy to try out and tweak with your favorite IDE/code editor.
If there are other sample apps that you'd like to see we'd appreciate hearing from you on our [Gitter channel](https://gitter.im/flutter/flutter) or [mailing list](https://groups.google.com/d/forum/flutter-dev).
<div class="container-fluid">
@(entries)
</div>
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This application generates markdown pages and screenshots for each
// sample app. For more information see ../README.md.
import 'dart:io';
import 'package:path/path.dart';
class SampleError extends Error {
SampleError(this.message);
final String message;
@override
String toString() => 'SampleError($message)';
}
// Sample apps are .dart files in the lib directory which contain a block
// comment that begins with a '/* Sample Catalog' line, and ends with a line
// that just contains '*/'. The following keywords may appear at the
// beginning of lines within the comment. A keyword's value is all of
// the following text up to the next keyword or the end of the comment,
// sans leading and trailing whitespace.
const String sampleCatalogKeywords = r'^Title:|^Summary:|^Description:|^Classes:|^Sample:|^See also:';
Directory outputDirectory;
Directory sampleDirectory;
Directory testDirectory;
Directory driverDirectory;
void logMessage(String s) { print(s); }
void logError(String s) { print(s); }
File inputFile(String dir, String name) {
return File(dir + Platform.pathSeparator + name);
}
File outputFile(String name, [Directory directory]) {
return File((directory ?? outputDirectory).path + Platform.pathSeparator + name);
}
void initialize() {
outputDirectory = Directory('.generated');
sampleDirectory = Directory('lib');
testDirectory = Directory('test');
driverDirectory = Directory('test_driver');
outputDirectory.createSync();
}
// Return a copy of template with each occurrence of @(foo) replaced
// by values[foo].
String expandTemplate(String template, Map<String, String> values) {
// Matches @(foo), match[1] == 'foo'
final RegExp tokenRE = RegExp(r'@\(([\w ]+)\)', multiLine: true);
return template.replaceAllMapped(tokenRE, (Match match) {
if (match.groupCount != 1)
throw SampleError('bad template keyword $match[0]');
final String keyword = match[1];
return values[keyword] ?? '';
});
}
void writeExpandedTemplate(File output, String template, Map<String, String> values) {
output.writeAsStringSync(expandTemplate(template, values));
logMessage('wrote $output');
}
class SampleInfo {
SampleInfo(this.sourceFile, this.commit);
final File sourceFile;
final String commit;
String sourceCode;
Map<String, String> commentValues;
// If sourceFile is lib/foo.dart then sourceName is foo. The sourceName
// 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'];
// 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>((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;
// Return true if we're able to find the "Sample Catalog" comment in the
// sourceFile, and we're able to load its keyword/value pairs into
// the commentValues Map. The rest of the file's contents are saved
// in sourceCode.
bool initialize() {
final String contents = sourceFile.readAsStringSync();
final RegExp startRE = RegExp(r'^/\*\s+^Sample\s+Catalog', multiLine: true);
final RegExp endRE = RegExp(r'^\*/', multiLine: true);
final Match startMatch = startRE.firstMatch(contents);
if (startMatch == null)
return false;
final int startIndex = startMatch.end;
final Match endMatch = endRE.firstMatch(contents.substring(startIndex));
if (endMatch == null)
return false;
final String comment = contents.substring(startIndex, startIndex + endMatch.start);
sourceCode = contents.substring(0, startMatch.start) + contents.substring(startIndex + endMatch.end);
if (sourceCode.trim().isEmpty)
throw SampleError('did not find any source code in $sourceFile');
final RegExp keywordsRE = RegExp(sampleCatalogKeywords, multiLine: true);
final List<Match> keywordMatches = keywordsRE.allMatches(comment).toList();
if (keywordMatches.isEmpty)
throw SampleError('did not find any keywords in the Sample Catalog comment in $sourceFile');
commentValues = <String, String>{};
for (int i = 0; i < keywordMatches.length; i += 1) {
final String keyword = comment.substring(keywordMatches[i].start, keywordMatches[i].end - 1);
final String value = comment.substring(
keywordMatches[i].end,
i == keywordMatches.length - 1 ? null : keywordMatches[i + 1].start,
);
commentValues[keyword.toLowerCase()] = value.trim();
}
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(String commit) {
initialize();
final List<SampleInfo> samples = <SampleInfo>[];
for (final FileSystemEntity entity in sampleDirectory.listSync()) {
if (entity is File && entity.path.endsWith('.dart')) {
final SampleInfo sample = 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((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<String>((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 (final 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 (final SampleInfo sample in samples) {
for (final String className in sample.highlightedClasses) {
classToSamples[className] ??= <SampleInfo>[];
classToSamples[className].add(sample);
}
}
for (final String className in classToSamples.keys) {
final Iterable<String> entries = classToSamples[className].map<String>((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),
inputFile('bin', 'screenshot.dart.template').readAsStringSync(),
<String, String>{
'imports': samples.map<String>((SampleInfo page) {
return "import '${page.importPath}' show ${page.sampleClass};\n";
}).join(),
'widgets': samples.map<String>((SampleInfo sample) {
return 'new ${sample.sampleClass}(),\n';
}).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),
inputFile('bin', 'screenshot_test.dart.template').readAsStringSync(),
<String, String>{
'paths': samples.map<String>((SampleInfo sample) {
return "'${outputFile(sample.sourceName + '.png').path}'";
}).join(',\n'),
},
);
// 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 (final 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(args[0]);
} catch (error) {
logError(
'Error: sample_page.dart failed: $error\n'
'This sample_page.dart app expects to be run from the examples/catalog directory. '
'More information can be found in examples/catalog/README.md.'
);
exit(255);
}
exit(0);
}
---
layout: page
title: "@(title)"
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)
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)
```
<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.dart.template and
// bin/sample_page.dart. For more information see README.md.
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter/material.dart';
@(imports)
class SampleScreenshots extends StatefulWidget {
@override
SampleScreenshotsState createState() => new SampleScreenshotsState();
}
class SampleScreenshotsState extends State<SampleScreenshots> {
final List<Widget> samples = <Widget>[
@(widgets)
];
int sampleIndex = 0;
@override
Widget build(BuildContext context) {
return new GestureDetector(
key: const ValueKey<String>('screenshotGestureDetector'),
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() {
sampleIndex += 1;
});
},
child: new IgnorePointer(
child: samples[sampleIndex % samples.length],
),
);
}
}
void main() {
enableFlutterDriverExtension();
WidgetsApp.debugAllowBannerOverride = false; // No "debug" banner.
runApp(new SampleScreenshots());
}
// 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';
import 'package:test/test.dart';
void main() {
group('sample screenshots', () {
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
await driver?.close();
});
test('take sample screenshots', () async {
final List<String> paths = <String>[
@(paths)
];
for (String path in paths) {
await driver.waitUntilNoTransientCallbacks();
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'));
}
});
});
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
</dict>
</plist>
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate
@end
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>animated_list</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([AppDelegate class]));
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AnimatedListSample extends StatefulWidget {
@override
_AnimatedListSampleState createState() => _AnimatedListSampleState();
}
class _AnimatedListSampleState extends State<AnimatedListSample> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
ListModel<int> _list;
int _selectedItem;
int _nextItem; // The next item inserted when the user presses the '+' button.
@override
void initState() {
super.initState();
_list = ListModel<int>(
listKey: _listKey,
initialItems: <int>[0, 1, 2],
removedItemBuilder: _buildRemovedItem,
);
_nextItem = 3;
}
// Used to build list items that haven't been removed.
Widget _buildItem(BuildContext context, int index, Animation<double> animation) {
return CardItem(
animation: animation,
item: _list[index],
selected: _selectedItem == _list[index],
onTap: () {
setState(() {
_selectedItem = _selectedItem == _list[index] ? null : _list[index];
});
},
);
}
// Used to build an item after it has been removed from the list. This method is
// needed because a removed item remains visible until its animation has
// completed (even though it's gone as far this ListModel is concerned).
// The widget will be used by the [AnimatedListState.removeItem] method's
// [AnimatedListRemovedItemBuilder] parameter.
Widget _buildRemovedItem(int item, BuildContext context, Animation<double> animation) {
return CardItem(
animation: animation,
item: item,
selected: false,
// No gesture detector here: we don't want removed items to be interactive.
);
}
// Insert the "next item" into the list model.
void _insert() {
final int index = _selectedItem == null ? _list.length : _list.indexOf(_selectedItem);
_list.insert(index, _nextItem++);
}
// Remove the selected item from the list model.
void _remove() {
if (_selectedItem != null) {
_list.removeAt(_list.indexOf(_selectedItem));
setState(() {
_selectedItem = null;
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('AnimatedList'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.add_circle),
onPressed: _insert,
tooltip: 'insert a new item',
),
IconButton(
icon: const Icon(Icons.remove_circle),
onPressed: _remove,
tooltip: 'remove the selected item',
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: AnimatedList(
key: _listKey,
initialItemCount: _list.length,
itemBuilder: _buildItem,
),
),
),
);
}
}
/// Keeps a Dart List in sync with an AnimatedList.
///
/// The [insert] and [removeAt] methods apply to both the internal list and the
/// animated list that belongs to [listKey].
///
/// This class only exposes as much of the Dart List API as is needed by the
/// sample app. More list methods are easily added, however methods that mutate the
/// list must make the same changes to the animated list in terms of
/// [AnimatedListState.insertItem] and [AnimatedList.removeItem].
class ListModel<E> {
ListModel({
@required this.listKey,
@required this.removedItemBuilder,
Iterable<E> initialItems,
}) : assert(listKey != null),
assert(removedItemBuilder != null),
_items = initialItems?.toList() ?? <E>[];
final GlobalKey<AnimatedListState> listKey;
final Widget Function(E item, BuildContext context, Animation<double> animation) removedItemBuilder;
final List<E> _items;
AnimatedListState get _animatedList => listKey.currentState;
void insert(int index, E item) {
_items.insert(index, item);
_animatedList.insertItem(index);
}
E removeAt(int index) {
final E removedItem = _items.removeAt(index);
if (removedItem != null) {
_animatedList.removeItem(index, (BuildContext context, Animation<double> animation) {
return removedItemBuilder(removedItem, context, animation);
});
}
return removedItem;
}
int get length => _items.length;
E operator [](int index) => _items[index];
int indexOf(E item) => _items.indexOf(item);
}
/// Displays its integer item as 'item N' on a Card whose color is based on
/// the item's value. The text is displayed in bright green if selected is true.
/// This widget's height is based on the animation parameter, it varies
/// from 0 to 128 as the animation varies from 0.0 to 1.0.
class CardItem extends StatelessWidget {
const CardItem({
Key key,
@required this.animation,
this.onTap,
@required this.item,
this.selected = false,
}) : assert(animation != null),
assert(item != null && item >= 0),
assert(selected != null),
super(key: key);
final Animation<double> animation;
final VoidCallback onTap;
final int item;
final bool selected;
@override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.headline4;
if (selected)
textStyle = textStyle.copyWith(color: Colors.lightGreenAccent[400]);
return Padding(
padding: const EdgeInsets.all(2.0),
child: SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: SizedBox(
height: 128.0,
child: Card(
color: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
),
),
);
}
}
void main() {
runApp(AnimatedListSample());
}
/*
Sample Catalog
Title: AnimatedList
Summary: An AnimatedList for displaying a list of cards that stay
in sync with an app-specific ListModel. When an item is added to or removed
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
selected item, '-' to remove the selected item. The tap handlers add or
remove items from a `ListModel<E>`, a simple encapsulation of `List<E>`
that keeps the AnimatedList in sync. The list model has a GlobalKey for
its animated list. It uses the key to call the insertItem and removeItem
methods defined by AnimatedListState.
Classes: AnimatedList, AnimatedListState
Sample: AnimatedListSample
See also:
- The "Components-Lists: Controls" section of the material design specification:
<https://material.io/guidelines/components/lists-controls.html#>
*/
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class AppBarBottomSample extends StatefulWidget {
@override
_AppBarBottomSampleState createState() => _AppBarBottomSampleState();
}
class _AppBarBottomSampleState extends State<AppBarBottomSample> with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: choices.length);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _nextPage(int delta) {
final int newIndex = _tabController.index + delta;
if (newIndex < 0 || newIndex >= _tabController.length)
return;
_tabController.animateTo(newIndex);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('AppBar Bottom Widget'),
leading: IconButton(
tooltip: 'Previous choice',
icon: const Icon(Icons.arrow_back),
onPressed: () { _nextPage(-1); },
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_forward),
tooltip: 'Next choice',
onPressed: () { _nextPage(1); },
),
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(48.0),
child: Theme(
data: Theme.of(context).copyWith(accentColor: Colors.white),
child: Container(
height: 48.0,
alignment: Alignment.center,
child: TabPageSelector(controller: _tabController),
),
),
),
),
body: TabBarView(
controller: _tabController,
children: choices.map<Widget>((Choice choice) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: ChoiceCard(choice: choice),
);
}).toList(),
),
),
);
}
}
class Choice {
const Choice({ this.title, this.icon });
final String title;
final IconData icon;
}
const List<Choice> choices = <Choice>[
Choice(title: 'CAR', icon: Icons.directions_car),
Choice(title: 'BICYCLE', icon: Icons.directions_bike),
Choice(title: 'BOAT', icon: Icons.directions_boat),
Choice(title: 'BUS', icon: Icons.directions_bus),
Choice(title: 'TRAIN', icon: Icons.directions_railway),
Choice(title: 'WALK', icon: Icons.directions_walk),
];
class ChoiceCard extends StatelessWidget {
const ChoiceCard({ Key key, this.choice }) : super(key: key);
final Choice choice;
@override
Widget build(BuildContext context) {
final TextStyle textStyle = Theme.of(context).textTheme.headline4;
return Card(
color: Colors.white,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(choice.icon, size: 128.0, color: textStyle.color),
Text(choice.title, style: textStyle),
],
),
),
);
}
}
void main() {
runApp(AppBarBottomSample());
}
/*
Sample Catalog
Title: AppBar with a custom bottom widget.
Summary: An AppBar that includes a bottom widget. Any widget
with a PreferredSize can appear at the bottom of an AppBar.
Summary: Any widget with a PreferredSize can appear at the bottom of an AppBar.
Description:
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
Sample: AppBarBottomSample
See also:
- The "Components-Tabs" section of the material design specification:
<https://material.io/go/design-tabs>
*/
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
// This app is a stateful, it tracks the user's current choice.
class BasicAppBarSample extends StatefulWidget {
@override
_BasicAppBarSampleState createState() => _BasicAppBarSampleState();
}
class _BasicAppBarSampleState extends State<BasicAppBarSample> {
Choice _selectedChoice = choices[0]; // The app's "state".
void _select(Choice choice) {
setState(() { // Causes the app to rebuild with the new _selectedChoice.
_selectedChoice = choice;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Basic AppBar'),
actions: <Widget>[
IconButton( // action button
icon: Icon(choices[0].icon),
onPressed: () { _select(choices[0]); },
),
IconButton( // action button
icon: Icon(choices[1].icon),
onPressed: () { _select(choices[1]); },
),
PopupMenuButton<Choice>( // overflow menu
onSelected: _select,
itemBuilder: (BuildContext context) {
return choices.skip(2).map<PopupMenuItem<Choice>>((Choice choice) {
return PopupMenuItem<Choice>(
value: choice,
child: Text(choice.title),
);
}).toList();
},
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: ChoiceCard(choice: _selectedChoice),
),
),
);
}
}
class Choice {
const Choice({ this.title, this.icon });
final String title;
final IconData icon;
}
const List<Choice> choices = <Choice>[
Choice(title: 'Car', icon: Icons.directions_car),
Choice(title: 'Bicycle', icon: Icons.directions_bike),
Choice(title: 'Boat', icon: Icons.directions_boat),
Choice(title: 'Bus', icon: Icons.directions_bus),
Choice(title: 'Train', icon: Icons.directions_railway),
Choice(title: 'Walk', icon: Icons.directions_walk),
];
class ChoiceCard extends StatelessWidget {
const ChoiceCard({ Key key, this.choice }) : super(key: key);
final Choice choice;
@override
Widget build(BuildContext context) {
final TextStyle textStyle = Theme.of(context).textTheme.headline4;
return Card(
color: Colors.white,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(choice.icon, size: 128.0, color: textStyle.color),
Text(choice.title, style: textStyle),
],
),
),
);
}
}
void main() {
runApp(BasicAppBarSample());
}
/*
Sample Catalog
Title: AppBar Basics
Summary: A basic 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.
The two most common choices are available as action buttons and the remaining
choices are included in the overflow dropdown menu.
Classes: AppBar, IconButton, PopupMenuButton, Scaffold
Sample: BasicAppBarSample
See also:
- The "Layout-Structure" section of the material design specification:
<https://material.io/guidelines/layout/structure.html#structure-app-bar>
*/
This diff is collapsed.
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
/// A [ListTile] containing a dropdown menu that exposes itself as an
/// "Adjustable" to screen readers (e.g. TalkBack on Android and VoiceOver on
/// iOS).
///
/// This allows screen reader users to swipe up/down (on iOS) or use the volume
/// keys (on Android) to switch between the values in the dropdown menu.
/// Depending on what the values in the dropdown menu are this can be a more
/// intuitive way of switching values compared to exposing the content of the
/// drop down menu as a screen overlay from which the user can select.
///
/// Users that do not use a screen reader will just see a regular dropdown menu.
class AdjustableDropdownListTile extends StatelessWidget {
const AdjustableDropdownListTile({
this.label,
this.value,
this.items,
this.onChanged,
});
final String label;
final String value;
final List<String> items;
final ValueChanged<String> onChanged;
@override
Widget build(BuildContext context) {
final int indexOfValue = items.indexOf(value);
assert(indexOfValue != -1);
final bool canIncrease = indexOfValue < items.length - 1;
final bool canDecrease = indexOfValue > 0;
return Semantics(
container: true,
label: label,
value: value,
increasedValue: canIncrease ? _increasedValue : null,
decreasedValue: canDecrease ? _decreasedValue : null,
onIncrease: canIncrease ? _performIncrease : null,
onDecrease: canDecrease ? _performDecrease : null,
child: ExcludeSemantics(
child: ListTile(
title: Text(label),
trailing: DropdownButton<String>(
value: value,
onChanged: onChanged,
items: items.map<DropdownMenuItem<String>>((String item) {
return DropdownMenuItem<String>(
value: item,
child: Text(item),
);
}).toList(),
),
),
),
);
}
String get _increasedValue {
final int indexOfValue = items.indexOf(value);
assert(indexOfValue < items.length - 1);
return items[indexOfValue + 1];
}
String get _decreasedValue {
final int indexOfValue = items.indexOf(value);
assert(indexOfValue > 0);
return items[indexOfValue - 1];
}
void _performIncrease() => onChanged(_increasedValue);
void _performDecrease() => onChanged(_decreasedValue);
}
class AdjustableDropdownExample extends StatefulWidget {
@override
AdjustableDropdownExampleState createState() => AdjustableDropdownExampleState();
}
class AdjustableDropdownExampleState extends State<AdjustableDropdownExample> {
final List<String> items = <String>[
'1 second',
'5 seconds',
'15 seconds',
'30 seconds',
'1 minute',
];
String timeout;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Adjustable DropDown'),
),
body: ListView(
children: <Widget>[
AdjustableDropdownListTile(
label: 'Timeout',
value: timeout ?? items[2],
items: items,
onChanged: (String value) {
setState(() {
timeout = value;
});
},
),
],
),
),
);
}
}
void main() {
runApp(AdjustableDropdownExample());
}
/*
Sample Catalog
Title: AdjustableDropdownListTile
Summary: A dropdown menu that exposes itself as an "Adjustable" to screen
readers.
Description:
This app presents a dropdown menu to the user that exposes itself as an
"Adjustable" to screen readers (e.g. TalkBack on Android and VoiceOver on iOS).
This allows users of screen readers to cycle through the values of the dropdown
menu by swiping up or down on the screen with one finger (on iOS) or by using
the volume keys (on Android). Depending on the values in the dropdown this
behavior may be more intuitive to screen reader users compared to showing the
classical dropdown overlay on screen to choose a value.
When the screen reader is turned off, the dropdown menu behaves like any
dropdown menu would.
Classes: Semantics
Sample: AdjustableDropdownListTile
*/
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class ExpansionTileSample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('ExpansionTile'),
),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) => EntryItem(data[index]),
itemCount: data.length,
),
),
);
}
}
// One entry in the multilevel list displayed by this app.
class Entry {
Entry(this.title, [this.children = const <Entry>[]]);
final String title;
final List<Entry> children;
}
// The entire multilevel list displayed by this app.
final List<Entry> data = <Entry>[
Entry('Chapter A',
<Entry>[
Entry('Section A0',
<Entry>[
Entry('Item A0.1'),
Entry('Item A0.2'),
Entry('Item A0.3'),
],
),
Entry('Section A1'),
Entry('Section A2'),
],
),
Entry('Chapter B',
<Entry>[
Entry('Section B0'),
Entry('Section B1'),
],
),
Entry('Chapter C',
<Entry>[
Entry('Section C0'),
Entry('Section C1'),
Entry('Section C2',
<Entry>[
Entry('Item C2.0'),
Entry('Item C2.1'),
Entry('Item C2.2'),
Entry('Item C2.3'),
],
),
],
),
];
// Displays one Entry. If the entry has children then it's displayed
// with an ExpansionTile.
class EntryItem extends StatelessWidget {
const EntryItem(this.entry);
final Entry entry;
Widget _buildTiles(Entry root) {
if (root.children.isEmpty)
return ListTile(title: Text(root.title));
return ExpansionTile(
key: PageStorageKey<Entry>(root),
title: Text(root.title),
children: root.children.map<Widget>(_buildTiles).toList(),
);
}
@override
Widget build(BuildContext context) {
return _buildTiles(entry);
}
}
void main() {
runApp(ExpansionTileSample());
}
/*
Sample Catalog
Title: ExpansionTile
Summary: An ExpansionTile for building nested lists, with two or more levels.
Description:
This app displays hierarchical data with ExpansionTiles. Tapping a tile
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
See also:
- The "expand/collapse" part of the material design specification:
<https://material.io/guidelines/components/lists-controls.html#lists-controls-types-of-list-controls>
*/
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
void main() {
runApp(
const Center(
child: Text(
'Instead run:\nflutter run lib/xxx.dart',
textDirection: TextDirection.ltr,
),
),
);
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class TabbedAppBarSample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: choices.length,
child: Scaffold(
appBar: AppBar(
title: const Text('Tabbed AppBar'),
bottom: TabBar(
isScrollable: true,
tabs: choices.map<Widget>((Choice choice) {
return Tab(
text: choice.title,
icon: Icon(choice.icon),
);
}).toList(),
),
),
body: TabBarView(
children: choices.map<Widget>((Choice choice) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: ChoiceCard(choice: choice),
);
}).toList(),
),
),
),
);
}
}
class Choice {
const Choice({ this.title, this.icon });
final String title;
final IconData icon;
}
const List<Choice> choices = <Choice>[
Choice(title: 'CAR', icon: Icons.directions_car),
Choice(title: 'BICYCLE', icon: Icons.directions_bike),
Choice(title: 'BOAT', icon: Icons.directions_boat),
Choice(title: 'BUS', icon: Icons.directions_bus),
Choice(title: 'TRAIN', icon: Icons.directions_railway),
Choice(title: 'WALK', icon: Icons.directions_walk),
];
class ChoiceCard extends StatelessWidget {
const ChoiceCard({ Key key, this.choice }) : super(key: key);
final Choice choice;
@override
Widget build(BuildContext context) {
final TextStyle textStyle = Theme.of(context).textTheme.headline4;
return Card(
color: Colors.white,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(choice.icon, size: 128.0, color: textStyle.color),
Text(choice.title, style: textStyle),
],
),
),
);
}
}
void main() {
runApp(TabbedAppBarSample());
}
/*
Sample Catalog
Title: Tabbed AppBar
Summary: An AppBar with a TabBar for navigating pages just below it.
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 anywhere, it's most often
included in the application's AppBar.
Classes: AppBar, DefaultTabController, TabBar, Scaffold, TabBarView
Sample: TabbedAppBarSample
See also:
- The "Components-Tabs" section of the material design specification:
<https://material.io/go/design-tabs>
*/
name: sample_catalog
description: A collection of Flutter sample apps
environment:
# The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
path: 1.8.0-nullsafety.1
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
meta: 1.3.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
typed_data: 1.3.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vector_math: 2.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
dev_dependencies:
flutter_test:
sdk: flutter
flutter_driver:
sdk: flutter
test: 1.16.0-nullsafety.5
_fe_analyzer_shared: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
analyzer: 0.39.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
archive: 2.0.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
args: 1.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
async: 2.5.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
boolean_selector: 2.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
charcode: 1.2.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
cli_util: 0.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
clock: 1.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
coverage: 0.14.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
crypto: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
csslib: 0.16.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
fake_async: 1.2.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
file: 6.0.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http: 0.12.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http_multi_server: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http_parser: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
js: 0.6.3-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
json_rpc_2: 2.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
matcher: 0.12.10-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
mime: 0.9.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
node_interop: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
node_io: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
node_preamble: 1.4.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pedantic: 1.10.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pool: 1.5.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pub_semver: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_packages_handler: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_web_socket: 0.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_map_stack_trace: 2.1.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_maps: 0.10.10-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_span: 1.8.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stack_trace: 1.10.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stream_channel: 2.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
string_scanner: 1.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
sync_http: 0.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
term_glyph: 1.2.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test_api: 0.2.19-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test_core: 0.3.12-nullsafety.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service: 4.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
webkit_inspection_protocol: 0.7.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
yaml: 2.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
flutter:
uses-material-design: true
# PUBSPEC CHECKSUM: 5ffe
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sample_catalog/animated_list.dart' as animated_list_sample;
void main() {
testWidgets('animated_list sample app smoke test', (WidgetTester tester) async {
animated_list_sample.main();
await tester.pump();
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 1'), findsOneWidget);
expect(find.text('Item 2'), findsOneWidget);
final Finder insertButton = find.byTooltip('insert a new item');
final Finder removeButton = find.byTooltip('remove the selected item');
expect(insertButton, findsOneWidget);
expect(removeButton, findsOneWidget);
// Remove items 0, 1, 2.
await tester.tap(find.text('Item 0'));
await tester.tap(removeButton);
await tester.pumpAndSettle();
await tester.tap(find.text('Item 1'));
await tester.tap(removeButton);
await tester.pumpAndSettle();
await tester.tap(find.text('Item 2'));
await tester.tap(removeButton);
await tester.pumpAndSettle();
// Append items 3, 4, 5, 6.
await tester.tap(insertButton);
await tester.tap(insertButton);
await tester.tap(insertButton);
await tester.tap(insertButton);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsNothing);
expect(find.text('Item 1'), findsNothing);
expect(find.text('Item 2'), findsNothing);
expect(find.text('Item 3'), findsOneWidget);
expect(find.text('Item 4'), findsOneWidget);
expect(find.text('Item 5'), findsOneWidget);
expect(find.text('Item 6'), findsOneWidget);
// Insert items 7, 8 at item 3's position (at the top)
await tester.tap(find.text('Item 3'));
await tester.tap(insertButton);
await tester.tap(insertButton);
await tester.pumpAndSettle();
expect(find.text('Item 7'), findsOneWidget);
expect(find.text('Item 8'), findsOneWidget);
// Scroll to the end.
await tester.fling(find.text('Item 7'), const Offset(0.0, -200.0), 1000.0);
await tester.pumpAndSettle();
expect(find.text('Item 6'), findsOneWidget);
expect(find.text('Item 8'), findsNothing);
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sample_catalog/app_bar_bottom.dart' as app_bar_bottom_sample;
final int choiceCount = app_bar_bottom_sample.choices.length;
IconData iconAt(int index) => app_bar_bottom_sample.choices[index].icon;
Finder findChoiceCard(IconData icon) {
return find.descendant(of: find.byType(Card), matching: find.byIcon(icon));
}
void main() {
testWidgets('app_bar_bottom sample smoke test', (WidgetTester tester) async {
app_bar_bottom_sample.main();
await tester.pump();
// Cycle through the choices using the forward and backwards arrows.
final Finder nextChoice = find.byTooltip('Next choice');
for (int i = 0; i < choiceCount; i += 1) {
expect(findChoiceCard(iconAt(i)), findsOneWidget);
await tester.tap(nextChoice);
await tester.pumpAndSettle();
}
final Finder previousChoice = find.byTooltip('Previous choice');
for (int i = choiceCount - 1; i >= 0; i -= 1) {
expect(findChoiceCard(iconAt(i)), findsOneWidget);
await tester.tap(previousChoice);
await tester.pumpAndSettle();
}
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sample_catalog/basic_app_bar.dart' as basic_app_bar_sample;
int choiceCount = basic_app_bar_sample.choices.length;
IconData iconAt(int index) => basic_app_bar_sample.choices[index].icon;
String titleAt(int index) => basic_app_bar_sample.choices[index].title;
Finder findAppBarIcon(IconData icon) {
return find.descendant(of: find.byType(AppBar), matching: find.byIcon(icon));
}
Finder findChoiceCard(IconData icon) {
return find.descendant(of: find.byType(Card), matching: find.byIcon(icon));
}
void main() {
testWidgets('basic_app_bar sample smoke test', (WidgetTester tester) async {
basic_app_bar_sample.main();
await tester.pump();
// Tap on the two action buttons and all of the overflow menu items.
// Verify that a Card with the expected icon appears.
await tester.tap(findAppBarIcon(iconAt(0)));
await tester.pumpAndSettle();
expect(findChoiceCard(iconAt(0)), findsOneWidget);
await tester.tap(findAppBarIcon(iconAt(1)));
await tester.pumpAndSettle();
expect(findChoiceCard(iconAt(1)), findsOneWidget);
for (int i = 2; i < choiceCount; i += 1) {
await tester.tap(findAppBarIcon(Icons.more_vert));
await tester.pumpAndSettle();
await tester.tap(find.text(titleAt(i)));
await tester.pumpAndSettle();
expect(findChoiceCard(iconAt(i)), findsOneWidget);
}
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sample_catalog/custom_semantics.dart' as custom_semantics show main;
import 'package:sample_catalog/custom_semantics.dart';
void main() {
testWidgets('custom_semantics sample smoke test', (WidgetTester tester) async {
// Turn on Semantics
final SemanticsHandle semanticsHandler = tester.binding.pipelineOwner.ensureSemantics();
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner;
// Build the sample app
custom_semantics.main();
await tester.pump();
// Verify it correctly exposes its semantics.
final SemanticsNode semantics = tester.getSemantics(find.byType(AdjustableDropdownListTile));
expectAdjustable(semantics,
hasIncreaseAction: true,
hasDecreaseAction: true,
label: 'Timeout',
decreasedValue: '5 seconds',
value: '15 seconds',
increasedValue: '30 seconds',
);
// Increase
semanticsOwner.performAction(semantics.id, SemanticsAction.increase);
await tester.pump();
expectAdjustable(semantics,
hasIncreaseAction: true,
hasDecreaseAction: true,
label: 'Timeout',
decreasedValue: '15 seconds',
value: '30 seconds',
increasedValue: '1 minute',
);
// Increase all the way to highest value
semanticsOwner.performAction(semantics.id, SemanticsAction.increase);
await tester.pump();
expectAdjustable(semantics,
hasIncreaseAction: false,
hasDecreaseAction: true,
label: 'Timeout',
decreasedValue: '30 seconds',
value: '1 minute',
);
// Decrease
semanticsOwner.performAction(semantics.id, SemanticsAction.decrease);
await tester.pump();
expectAdjustable(semantics,
hasIncreaseAction: true,
hasDecreaseAction: true,
label: 'Timeout',
decreasedValue: '15 seconds',
value: '30 seconds',
increasedValue: '1 minute',
);
// Decrease all the way to lowest value
semanticsOwner.performAction(semantics.id, SemanticsAction.decrease);
await tester.pump();
semanticsOwner.performAction(semantics.id, SemanticsAction.decrease);
await tester.pump();
semanticsOwner.performAction(semantics.id, SemanticsAction.decrease);
await tester.pump();
expectAdjustable(semantics,
hasIncreaseAction: true,
hasDecreaseAction: false,
label: 'Timeout',
value: '1 second',
increasedValue: '5 seconds',
);
// Clean-up
semanticsHandler.dispose();
});
}
void expectAdjustable(
SemanticsNode node, {
bool hasIncreaseAction = true,
bool hasDecreaseAction = true,
String label = '',
String decreasedValue = '',
String value = '',
String increasedValue = '',
}) {
final SemanticsData semanticsData = node.getSemanticsData();
int actions = 0;
if (hasIncreaseAction)
actions |= SemanticsAction.increase.index;
if (hasDecreaseAction)
actions |= SemanticsAction.decrease.index;
expect(semanticsData.actions, actions);
expect(semanticsData.label, label);
expect(semanticsData.decreasedValue, decreasedValue);
expect(semanticsData.value, value);
expect(semanticsData.increasedValue, increasedValue);
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sample_catalog/expansion_tile_sample.dart' as expansion_tile_sample;
import 'package:sample_catalog/expansion_tile_sample.dart' show Entry;
void main() {
testWidgets('expansion_tile sample smoke test', (WidgetTester tester) async {
expansion_tile_sample.main();
await tester.pump();
// Initially only the top level EntryItems (the "chapters") are present.
for (final Entry chapter in expansion_tile_sample.data) {
expect(find.text(chapter.title), findsOneWidget);
for (final Entry section in chapter.children) {
expect(find.text(section.title), findsNothing);
for (final Entry item in section.children)
expect(find.text(item.title), findsNothing);
}
}
Future<void> scrollUpOneEntry() async {
await tester.dragFrom(const Offset(200.0, 200.0), const Offset(0.0, -88.00));
await tester.pumpAndSettle();
}
Future<void> tapEntry(String title) async {
await tester.tap(find.text(title));
await tester.pumpAndSettle();
}
// Expand the chapters. Now the chapter and sections, but not the
// items, should be present.
for (final Entry chapter in expansion_tile_sample.data.reversed)
await tapEntry(chapter.title);
for (final Entry chapter in expansion_tile_sample.data) {
expect(find.text(chapter.title), findsOneWidget);
for (final Entry section in chapter.children) {
expect(find.text(section.title), findsOneWidget);
await scrollUpOneEntry();
for (final Entry item in section.children)
expect(find.text(item.title), findsNothing);
}
await scrollUpOneEntry();
}
// - scroll to the top -
await tester.flingFrom(const Offset(200.0, 200.0), const Offset(0.0, 100.0), 5000.0);
await tester.pumpAndSettle();
// Expand the sections. Now Widgets for all three levels should be present.
for (final Entry chapter in expansion_tile_sample.data) {
for (final Entry section in chapter.children) {
await tapEntry(section.title);
await scrollUpOneEntry();
}
await scrollUpOneEntry();
}
// We're scrolled to the bottom so the very last item is visible.
// Working in reverse order, so we don't need to do anymore scrolling,
// check that everything is visible and close the sections and
// chapters as we go up.
for (final Entry chapter in expansion_tile_sample.data.reversed) {
expect(find.text(chapter.title), findsOneWidget);
for (final Entry section in chapter.children.reversed) {
expect(find.text(section.title), findsOneWidget);
for (final Entry item in section.children.reversed)
expect(find.text(item.title), findsOneWidget);
await tapEntry(section.title); // close the section
}
await tapEntry(chapter.title); // close the chapter
}
// Finally only the top level EntryItems (the "chapters") are present.
for (final Entry chapter in expansion_tile_sample.data) {
expect(find.text(chapter.title), findsOneWidget);
for (final Entry section in chapter.children) {
expect(find.text(section.title), findsNothing);
for (final Entry item in section.children)
expect(find.text(item.title), findsNothing);
}
}
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sample_catalog/tabbed_app_bar.dart' as tabbed_app_bar_sample;
final int choiceCount = tabbed_app_bar_sample.choices.length;
IconData iconAt(int index) => tabbed_app_bar_sample.choices[index].icon;
Finder findChoiceCard(IconData icon) {
return find.descendant(of: find.byType(Card), matching: find.byIcon(icon));
}
Finder findTab(IconData icon) {
return find.descendant(of: find.byType(Tab), matching: find.byIcon(icon));
}
void main() {
testWidgets('tabbed_app_bar sample smoke test', (WidgetTester tester) async {
tabbed_app_bar_sample.main();
await tester.pump();
// Tap on each tab, verify that a Card with the expected icon appears.
for (int i = 0; i < choiceCount; i += 1) {
await tester.tap(findTab(iconAt(i)));
await tester.pumpAndSettle();
expect(findChoiceCard(iconAt(i)), findsOneWidget);
// Scroll the tabBar by about one tab width
await tester.drag(find.byType(TabBar), const Offset(-24.0, 0.0));
await tester.pumpAndSettle();
}
});
}
The screenshot.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