New Flutter Gallery UI (#16936)

A new front-end for the Flutter Gallery example.
This commit is contained in:
Hans Muller 2018-04-25 14:15:34 -07:00 committed by GitHub
parent 76aa02875e
commit 7038597b02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 2092 additions and 1251 deletions

View File

@ -11,7 +11,7 @@ dependencies:
flutter_gallery_assets: flutter_gallery_assets:
git: git:
url: https://flutter.googlesource.com/gallery-assets url: https://flutter.googlesource.com/gallery-assets
ref: d318485f208376e06d7e330d9f191141d14722b8 ref: 43590e625ab1b07f6a5809287ce16f7e61d9e165
async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,83 @@
// Copyright 2018 The Chromium 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/gestures.dart';
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class _LinkTextSpan extends TextSpan {
// Beware!
//
// This class is only safe because the TapGestureRecognizer is not
// given a deadline and therefore never allocates any resources.
//
// In any other situation -- setting a deadline, using any of the less trivial
// recognizers, etc -- you would have to manage the gesture recognizer's
// lifetime and call dispose() when the TextSpan was no longer being rendered.
//
// Since TextSpan itself is @immutable, this means that you would have to
// manage the recognizer from outside the TextSpan, e.g. in the State of a
// stateful widget that then hands the recognizer to the TextSpan.
_LinkTextSpan({ TextStyle style, String url, String text }) : super(
style: style,
text: text ?? url,
recognizer: new TapGestureRecognizer()..onTap = () {
launch(url, forceSafariVC: false);
}
);
}
void showGalleryAboutDialog(BuildContext context) {
final ThemeData themeData = Theme.of(context);
final TextStyle aboutTextStyle = themeData.textTheme.body2;
final TextStyle linkStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor);
showAboutDialog(
context: context,
applicationVersion: 'April 2018 Preview',
applicationIcon: const FlutterLogo(),
applicationLegalese: '© 2017 The Chromium Authors',
children: <Widget>[
new Padding(
padding: const EdgeInsets.only(top: 24.0),
child: new RichText(
text: new TextSpan(
children: <TextSpan>[
new TextSpan(
style: aboutTextStyle,
text: 'Flutter is an early-stage, open-source project to help developers '
'build high-performance, high-fidelity, mobile apps for '
'${defaultTargetPlatform == TargetPlatform.iOS ? 'multiple platforms' : 'iOS and Android'} '
'from a single codebase. This gallery is a preview of '
"Flutter's many widgets, behaviors, animations, layouts, "
'and more. Learn more about Flutter at '
),
new _LinkTextSpan(
style: linkStyle,
url: 'https://flutter.io',
),
new TextSpan(
style: aboutTextStyle,
text: '.\n\nTo see the source code for this app, please visit the ',
),
new _LinkTextSpan(
style: linkStyle,
url: 'https://goo.gl/iv1p4G',
text: 'flutter github repo',
),
new TextSpan(
style: aboutTextStyle,
text: '.',
),
],
),
),
),
],
);
}

View File

@ -8,53 +8,79 @@ import 'package:flutter/foundation.dart' show defaultTargetPlatform;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation; import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:url_launcher/url_launcher.dart';
import 'demos.dart';
import 'home.dart'; import 'home.dart';
import 'item.dart'; import 'options.dart';
import 'theme.dart'; import 'scales.dart';
import 'updates.dart'; import 'themes.dart';
import 'updater.dart';
class GalleryApp extends StatefulWidget { class GalleryApp extends StatefulWidget {
const GalleryApp({ const GalleryApp({
Key key,
this.updateUrlFetcher, this.updateUrlFetcher,
this.enablePerformanceOverlay: true, this.enablePerformanceOverlay: true,
this.checkerboardRasterCacheImages: true, this.enableRasterCacheImagesCheckerboard: true,
this.checkerboardOffscreenLayers: true, this.enableOffscreenLayersCheckerboard: true,
this.onSendFeedback, this.onSendFeedback,
Key key} }) : super(key: key);
) : super(key: key);
final UpdateUrlFetcher updateUrlFetcher; final UpdateUrlFetcher updateUrlFetcher;
final bool enablePerformanceOverlay; final bool enablePerformanceOverlay;
final bool enableRasterCacheImagesCheckerboard;
final bool checkerboardRasterCacheImages; final bool enableOffscreenLayersCheckerboard;
final bool checkerboardOffscreenLayers;
final VoidCallback onSendFeedback; final VoidCallback onSendFeedback;
@override @override
GalleryAppState createState() => new GalleryAppState(); _GalleryAppState createState() => new _GalleryAppState();
} }
class GalleryAppState extends State<GalleryApp> { class _GalleryAppState extends State<GalleryApp> {
GalleryTheme _galleryTheme = kAllGalleryThemes[0]; GalleryOptions _options;
bool _showPerformanceOverlay = false;
bool _checkerboardRasterCacheImages = false;
bool _checkerboardOffscreenLayers = false;
TextDirection _overrideDirection = TextDirection.ltr;
double _timeDilation = 1.0;
TargetPlatform _platform;
// A null value indicates "use system default".
double _textScaleFactor;
Timer _timeDilationTimer; Timer _timeDilationTimer;
Map<String, WidgetBuilder> _buildRoutes() {
// For a different example of how to set up an application routing table
// using named routes, consider the example in the Navigator class documentation:
// https://docs.flutter.io/flutter/widgets/Navigator-class.html
return new Map<String, WidgetBuilder>.fromIterable(
kAllGalleryDemos,
key: (dynamic demo) => '${demo.routeName}',
value: (dynamic demo) => demo.buildRoute,
)..addAll(
new Map<String, WidgetBuilder>.fromIterable(
kAllGalleryDemoCategories,
key: (dynamic category) => '/${category.name}',
value: (dynamic category) {
return (BuildContext context) {
return new DemosPage(
category: category,
optionsPage: new GalleryOptionsPage(
options: _options,
onOptionsChanged: _handleOptionsChanged,
onSendFeedback: widget.onSendFeedback ?? () {
launch('https://github.com/flutter/flutter/issues/new', forceSafariVC: false);
},
),
);
};
},
),
);
}
@override @override
void initState() { void initState() {
_timeDilation = timeDilation;
super.initState(); super.initState();
_options = new GalleryOptions(
theme: kLightGalleryTheme,
textScaleFactor: kAllGalleryTextScaleValues[0],
timeDilation: timeDilation,
platform: defaultTargetPlatform,
);
} }
@override @override
@ -64,80 +90,50 @@ class GalleryAppState extends State<GalleryApp> {
super.dispose(); super.dispose();
} }
Widget _applyScaleFactor(Widget child) { void _handleOptionsChanged(GalleryOptions newOptions) {
setState(() {
if (_options.timeDilation != newOptions.timeDilation) {
_timeDilationTimer?.cancel();
_timeDilationTimer = null;
if (newOptions.timeDilation > 1.0) {
// We delay the time dilation change long enough that the user can see
// that UI has started reacting and then we slam on the brakes so that
// they see that the time is in fact now dilated.
_timeDilationTimer = new Timer(const Duration(milliseconds: 150), () {
timeDilation = newOptions.timeDilation;
});
} else {
timeDilation = newOptions.timeDilation;
}
}
_options = newOptions;
});
}
Widget _applyTextScaleFactor(Widget child) {
return new Builder( return new Builder(
builder: (BuildContext context) => new MediaQuery( builder: (BuildContext context) {
data: MediaQuery.of(context).copyWith( return new MediaQuery(
textScaleFactor: _textScaleFactor, data: MediaQuery.of(context).copyWith(
), textScaleFactor: _options.textScaleFactor.scale,
child: child, ),
), child: child,
);
},
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget home = new GalleryHome( Widget home = new GalleryHome(
galleryTheme: _galleryTheme, optionsPage: new GalleryOptionsPage(
onThemeChanged: (GalleryTheme value) { options: _options,
setState(() { onOptionsChanged: _handleOptionsChanged,
_galleryTheme = value; onSendFeedback: widget.onSendFeedback ?? () {
}); launch('https://github.com/flutter/flutter/issues/new');
}, },
showPerformanceOverlay: _showPerformanceOverlay, ),
onShowPerformanceOverlayChanged: widget.enablePerformanceOverlay ? (bool value) {
setState(() {
_showPerformanceOverlay = value;
});
} : null,
checkerboardRasterCacheImages: _checkerboardRasterCacheImages,
onCheckerboardRasterCacheImagesChanged: widget.checkerboardRasterCacheImages ? (bool value) {
setState(() {
_checkerboardRasterCacheImages = value;
});
} : null,
checkerboardOffscreenLayers: _checkerboardOffscreenLayers,
onCheckerboardOffscreenLayersChanged: widget.checkerboardOffscreenLayers ? (bool value) {
setState(() {
_checkerboardOffscreenLayers = value;
});
} : null,
onPlatformChanged: (TargetPlatform value) {
setState(() {
_platform = value == defaultTargetPlatform ? null : value;
});
},
timeDilation: _timeDilation,
onTimeDilationChanged: (double value) {
setState(() {
_timeDilationTimer?.cancel();
_timeDilationTimer = null;
_timeDilation = value;
if (_timeDilation > 1.0) {
// We delay the time dilation change long enough that the user can see
// that the checkbox in the drawer has started reacting, then we slam
// on the brakes so that they see that the time is in fact now dilated.
_timeDilationTimer = new Timer(const Duration(milliseconds: 150), () {
timeDilation = _timeDilation;
});
} else {
timeDilation = _timeDilation;
}
});
},
textScaleFactor: _textScaleFactor,
onTextScaleFactorChanged: (double value) {
setState(() {
_textScaleFactor = value;
});
},
overrideDirection: _overrideDirection,
onOverrideDirectionChanged: (TextDirection value) {
setState(() {
_overrideDirection = value;
});
},
onSendFeedback: widget.onSendFeedback,
); );
if (widget.updateUrlFetcher != null) { if (widget.updateUrlFetcher != null) {
@ -147,31 +143,21 @@ class GalleryAppState extends State<GalleryApp> {
); );
} }
final Map<String, WidgetBuilder> _kRoutes = <String, WidgetBuilder>{};
for (GalleryItem item in kAllGalleryItems) {
// For a different example of how to set up an application routing table
// using named routes, consider the example in the Navigator class documentation:
// https://docs.flutter.io/flutter/widgets/Navigator-class.html
_kRoutes[item.routeName] = (BuildContext context) {
return item.buildRoute(context);
};
}
return new MaterialApp( return new MaterialApp(
theme: _options.theme.data.copyWith(platform: _options.platform),
title: 'Flutter Gallery', title: 'Flutter Gallery',
color: Colors.grey, color: Colors.grey,
theme: _galleryTheme.theme.copyWith(platform: _platform ?? defaultTargetPlatform), showPerformanceOverlay: _options.showPerformanceOverlay,
showPerformanceOverlay: _showPerformanceOverlay, checkerboardOffscreenLayers: _options.showOffscreenLayersCheckerboard,
checkerboardRasterCacheImages: _checkerboardRasterCacheImages, checkerboardRasterCacheImages: _options.showRasterCacheImagesCheckerboard,
checkerboardOffscreenLayers: _checkerboardOffscreenLayers, routes: _buildRoutes(),
routes: _kRoutes,
home: home,
builder: (BuildContext context, Widget child) { builder: (BuildContext context, Widget child) {
return new Directionality( return new Directionality(
textDirection: _overrideDirection, textDirection: _options.textDirection,
child: _applyScaleFactor(child), child: _applyTextScaleFactor(child),
); );
}, },
home: home,
); );
} }
} }

View File

@ -0,0 +1,330 @@
// Copyright 2018 The Chromium 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 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';
const double _kFrontHeadingHeight = 32.0; // front layer beveled rectangle
const double _kFrontClosedHeight = 72.0; // front layer height when closed
const double _kBackAppBarHeight = 56.0; // back layer (options) appbar height
// The size of the front layer heading's left and right beveled corners.
final Tween<BorderRadius> _kFrontHeadingBevelRadius = new BorderRadiusTween(
begin: const BorderRadius.only(
topLeft: const Radius.circular(12.0),
topRight: const Radius.circular(12.0),
),
end: const BorderRadius.only(
topLeft: const Radius.circular(_kFrontHeadingHeight),
topRight: const Radius.circular(_kFrontHeadingHeight),
),
);
class _IgnorePointerWhileStatusIsNot extends StatefulWidget {
const _IgnorePointerWhileStatusIsNot(this.status, {
Key key,
this.controller,
this.child,
}) : super(key: key);
final AnimationController controller;
final AnimationStatus status;
final Widget child;
@override
_IgnorePointerWhileStatusIsNotState createState() => new _IgnorePointerWhileStatusIsNotState();
}
class _IgnorePointerWhileStatusIsNotState extends State<_IgnorePointerWhileStatusIsNot> {
bool _ignoring;
@override
void initState() {
super.initState();
widget.controller.addStatusListener(_handleStatusChange);
_ignoring = widget.controller.status != AnimationStatus.completed;
}
@override
void dispose() {
widget.controller.removeStatusListener(_handleStatusChange);
super.dispose();
}
void _handleStatusChange(AnimationStatus _) {
final bool value = widget.controller.status != widget.status;
if (_ignoring != value) {
setState(() {
_ignoring = value;
});
}
}
@override
Widget build(BuildContext context) {
return new IgnorePointer(
ignoring: _ignoring,
child: widget.child,
);
}
}
class _CrossFadeTransition extends AnimatedWidget {
const _CrossFadeTransition({
Key key,
this.alignment: Alignment.center,
Animation<double> progress,
this.child0,
this.child1,
}) : super(key: key, listenable: progress);
final AlignmentGeometry alignment;
final Widget child0;
final Widget child1;
@override
Widget build(BuildContext context) {
final Animation<double> progress = listenable;
final double opacity1 = new CurvedAnimation(
parent: new ReverseAnimation(progress),
curve: const Interval(0.5, 1.0),
).value;
final double opacity2 = new CurvedAnimation(
parent: progress,
curve: const Interval(0.5, 1.0),
).value;
return new Stack(
alignment: alignment,
children: <Widget>[
new IgnorePointer(
ignoring: opacity1 < 1.0,
child: new Opacity(
opacity: opacity1,
child: child1,
),
),
new IgnorePointer(
ignoring: opacity2 <1.0,
child: new Opacity(
opacity: opacity2,
child: child0,
),
),
],
);
}
}
class _BackAppBar extends StatelessWidget {
const _BackAppBar({
Key key,
this.leading: const SizedBox(width: 56.0),
@required this.title,
this.trailing,
}) : assert(leading != null), assert(title != null), super(key: key);
final Widget leading;
final Widget title;
final Widget trailing;
@override
Widget build(BuildContext context) {
final List<Widget> children = <Widget>[
new Container(
alignment: Alignment.center,
width: 56.0,
child: leading,
),
new Expanded(
child: title,
),
];
if (trailing != null) {
children.add(
new Container(
alignment: Alignment.center,
width: 56.0,
child: trailing,
),
);
}
final ThemeData theme = Theme.of(context);
return IconTheme.merge(
data: theme.primaryIconTheme,
child: new DefaultTextStyle(
style: theme.primaryTextTheme.title,
child: new SizedBox(
height: _kBackAppBarHeight,
child: new Row(children: children),
),
),
);
}
}
class Backdrop extends StatefulWidget {
const Backdrop({
this.frontAction,
this.frontTitle,
this.frontHeading,
this.frontLayer,
this.backTitle,
this.backLayer,
});
final Widget frontAction;
final Widget frontTitle;
final Widget frontLayer;
final Widget frontHeading;
final Widget backTitle;
final Widget backLayer;
@override
_BackdropState createState() => new _BackdropState();
}
class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin {
final GlobalKey _backdropKey = new GlobalKey(debugLabel: 'Backdrop');
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(milliseconds: 300),
value: 1.0,
vsync: this,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
double get _backdropHeight {
// Warning: this can be safely called from the event handlers but it may
// not be called at build time.
final RenderBox renderBox = _backdropKey.currentContext.findRenderObject();
return math.max(0.0, renderBox.size.height - _kBackAppBarHeight - _kFrontClosedHeight);
}
void _handleDragUpdate(DragUpdateDetails details) {
_controller.value -= details.primaryDelta / (_backdropHeight ?? details.primaryDelta);
}
void _handleDragEnd(DragEndDetails details) {
if (_controller.isAnimating || _controller.status == AnimationStatus.completed)
return;
final double flingVelocity = details.velocity.pixelsPerSecond.dy / _backdropHeight;
if (flingVelocity < 0.0)
_controller.fling(velocity: math.max(2.0, -flingVelocity));
else if (flingVelocity > 0.0)
_controller.fling(velocity: math.min(-2.0, -flingVelocity));
else
_controller.fling(velocity: _controller.value < 0.5 ? -2.0 : 2.0);
}
void _toggleFrontLayer() {
final AnimationStatus status = _controller.status;
final bool isOpen = status == AnimationStatus.completed || status == AnimationStatus.forward;
_controller.fling(velocity: isOpen ? -2.0 : 2.0);
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
final Animation<RelativeRect> frontRelativeRect = new RelativeRectTween(
begin: new RelativeRect.fromLTRB(0.0, constraints.biggest.height - _kFrontClosedHeight, 0.0, 0.0),
end: const RelativeRect.fromLTRB(0.0, _kBackAppBarHeight, 0.0, 0.0),
).animate(_controller);
return new Stack(
key: _backdropKey,
children: <Widget>[
new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
// Back layer
new _BackAppBar(
leading: widget.frontAction,
title: new _CrossFadeTransition(
progress: _controller,
alignment: AlignmentDirectional.centerStart,
child0: widget.frontTitle,
child1: widget.backTitle,
),
trailing: new IconButton(
onPressed: _toggleFrontLayer,
tooltip: 'Show options page',
icon: new AnimatedIcon(
icon: AnimatedIcons.close_menu,
progress: _controller,
),
),
),
new Expanded(
child: new _IgnorePointerWhileStatusIsNot(
AnimationStatus.dismissed,
controller: _controller,
child: widget.backLayer,
),
),
],
),
// Front layer
new PositionedTransition(
rect: frontRelativeRect,
child: new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return new PhysicalShape(
elevation: 12.0,
color: Theme.of(context).canvasColor,
clipper: new ShapeBorderClipper(
shape: new BeveledRectangleBorder(
borderRadius: _kFrontHeadingBevelRadius.lerp(_controller.value),
),
),
child: child,
);
},
child: new _IgnorePointerWhileStatusIsNot(
AnimationStatus.completed,
controller: _controller,
child: widget.frontLayer,
),
),
),
new PositionedTransition(
rect: frontRelativeRect,
child: new Container(
alignment: Alignment.topLeft,
child: new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _toggleFrontLayer,
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: widget.frontHeading,
),
),
),
],
);
}
@override
Widget build(BuildContext context) {
return new LayoutBuilder(builder: _buildStack);
}
}

View File

@ -1,19 +1,66 @@
// Copyright 2016 The Chromium Authors. All rights reserved. // Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:developer';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../demo/all.dart'; import '../demo/all.dart';
import 'icons.dart';
typedef Widget GalleryDemoBuilder(); class GalleryDemoCategory {
const GalleryDemoCategory._({ this.name, this.icon });
@required final String name;
@required final IconData icon;
class GalleryItem extends StatelessWidget { @override
const GalleryItem({ bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
final GalleryDemoCategory typedOther = other;
return typedOther.name == name && typedOther.icon == icon;
}
@override
int get hashCode => hashValues(name, icon);
@override
String toString() {
return '$runtimeType($name)';
}
}
const GalleryDemoCategory _kDemos = const GalleryDemoCategory._(
name: 'Vignettes',
icon: GalleryIcons.animation,
);
const GalleryDemoCategory _kStyle = const GalleryDemoCategory._(
name: 'Style',
icon: GalleryIcons.custom_typography,
);
const GalleryDemoCategory _kMaterialComponents = const GalleryDemoCategory._(
name: 'Material',
icon: GalleryIcons.category_mdc,
);
const GalleryDemoCategory _kCupertinoComponents = const GalleryDemoCategory._(
name: 'Cupertino',
icon: GalleryIcons.phone_iphone,
);
const GalleryDemoCategory _kMedia = const GalleryDemoCategory._(
name: 'Media',
icon: GalleryIcons.drive_video,
);
class GalleryDemo {
const GalleryDemo({
@required this.title, @required this.title,
@required this.icon,
this.subtitle, this.subtitle,
@required this.category, @required this.category,
@required this.routeName, @required this.routeName,
@ -24,363 +71,400 @@ class GalleryItem extends StatelessWidget {
assert(buildRoute != null); assert(buildRoute != null);
final String title; final String title;
final IconData icon;
final String subtitle; final String subtitle;
final String category; final GalleryDemoCategory category;
final String routeName; final String routeName;
final WidgetBuilder buildRoute; final WidgetBuilder buildRoute;
@override @override
Widget build(BuildContext context) { String toString() {
return new ListTile( return '$runtimeType($title $routeName)';
title: new Text(title),
subtitle: new Text(subtitle),
onTap: () {
if (routeName != null) {
Timeline.instantSync('Start Transition', arguments: <String, String>{
'from': '/',
'to': routeName
});
Navigator.pushNamed(context, routeName);
}
}
);
} }
} }
List<GalleryItem> _buildGalleryItems() { List<GalleryDemo> _buildGalleryDemos() {
// When editing this list, make sure you keep it in sync with final List<GalleryDemo> galleryDemos = <GalleryDemo>[
// the list in ../../test_driver/transitions_perf_test.dart
final List<GalleryItem> galleryItems = <GalleryItem>[
// Demos // Demos
new GalleryItem( new GalleryDemo(
title: 'Shrine', title: 'Shrine',
subtitle: 'Basic shopping app', subtitle: 'Basic shopping app',
category: 'Vignettes', icon: GalleryIcons.shrine,
category: _kDemos,
routeName: ShrineDemo.routeName, routeName: ShrineDemo.routeName,
buildRoute: (BuildContext context) => new ShrineDemo(), buildRoute: (BuildContext context) => new ShrineDemo(),
), ),
new GalleryItem( new GalleryDemo(
title: 'Contact profile', title: 'Contact profile',
subtitle: 'Address book entry with a flexible appbar', subtitle: 'Address book entry with a flexible appbar',
category: 'Vignettes', icon: GalleryIcons.account_box,
category: _kDemos,
routeName: ContactsDemo.routeName, routeName: ContactsDemo.routeName,
buildRoute: (BuildContext context) => new ContactsDemo(), buildRoute: (BuildContext context) => new ContactsDemo(),
), ),
new GalleryItem( new GalleryDemo(
title: 'Animation', title: 'Animation',
subtitle: 'Section organizer', subtitle: 'Section organizer',
category: 'Vignettes', icon: GalleryIcons.animation,
category: _kDemos,
routeName: AnimationDemo.routeName, routeName: AnimationDemo.routeName,
buildRoute: (BuildContext context) => const AnimationDemo(), buildRoute: (BuildContext context) => const AnimationDemo(),
), ),
new GalleryItem(
title: 'Video', // Style
subtitle: 'Video playback', new GalleryDemo(
category: 'Vignettes',
routeName: VideoDemo.routeName,
buildRoute: (BuildContext context) => const VideoDemo(),
),
// Material Components
new GalleryItem(
title: 'Backdrop',
subtitle: 'Select a front layer from back layer',
category: 'Material Components',
routeName: BackdropDemo.routeName,
buildRoute: (BuildContext context) => new BackdropDemo(),
),
new GalleryItem(
title: 'Bottom app bar',
subtitle: 'With repositionable floating action button',
category: 'Material Components',
routeName: BottomAppBarDemo.routeName,
buildRoute: (BuildContext context) => new BottomAppBarDemo(),
),
new GalleryItem(
title: 'Bottom navigation',
subtitle: 'Bottom navigation with cross-fading views',
category: 'Material Components',
routeName: BottomNavigationDemo.routeName,
buildRoute: (BuildContext context) => new BottomNavigationDemo(),
),
new GalleryItem(
title: 'Buttons',
subtitle: 'All kinds: flat, raised, dropdown, icon, etc',
category: 'Material Components',
routeName: ButtonsDemo.routeName,
buildRoute: (BuildContext context) => new ButtonsDemo(),
),
new GalleryItem(
title: 'Cards',
subtitle: 'Material with rounded corners and a drop shadow',
category: 'Material Components',
routeName: CardsDemo.routeName,
buildRoute: (BuildContext context) => new CardsDemo(),
),
new GalleryItem(
title: 'Chips',
subtitle: 'Label with an optional delete button and avatar',
category: 'Material Components',
routeName: ChipDemo.routeName,
buildRoute: (BuildContext context) => new ChipDemo(),
),
new GalleryItem(
title: 'Data tables',
subtitle: 'Rows and columns',
category: 'Material Components',
routeName: DataTableDemo.routeName,
buildRoute: (BuildContext context) => new DataTableDemo(),
),
new GalleryItem(
title: 'Date and time pickers',
subtitle: 'Date and time selection widgets',
category: 'Material Components',
routeName: DateAndTimePickerDemo.routeName,
buildRoute: (BuildContext context) => new DateAndTimePickerDemo(),
),
new GalleryItem(
title: 'Dialog',
subtitle: 'All kinds: simple, alert, fullscreen, etc',
category: 'Material Components',
routeName: DialogDemo.routeName,
buildRoute: (BuildContext context) => new DialogDemo(),
),
new GalleryItem(
title: 'Drawer',
subtitle: 'Navigation drawer with a standard header',
category: 'Material Components',
routeName: DrawerDemo.routeName,
buildRoute: (BuildContext context) => new DrawerDemo(),
),
new GalleryItem(
title: 'Expand/collapse list control',
subtitle: 'List with one level of sublists',
category: 'Material Components',
routeName: TwoLevelListDemo.routeName,
buildRoute: (BuildContext context) => new TwoLevelListDemo(),
),
new GalleryItem(
title: 'Expansion panels',
subtitle: 'List of expanding panels',
category: 'Material Components',
routeName: ExpansionPanelsDemo.routeName,
buildRoute: (BuildContext context) => new ExpansionPanelsDemo(),
),
new GalleryItem(
title: 'Floating action button',
subtitle: 'Action buttons with transitions',
category: 'Material Components',
routeName: TabsFabDemo.routeName,
buildRoute: (BuildContext context) => new TabsFabDemo(),
),
new GalleryItem(
title: 'Grid',
subtitle: 'Row and column layout',
category: 'Material Components',
routeName: GridListDemo.routeName,
buildRoute: (BuildContext context) => const GridListDemo(),
),
new GalleryItem(
title: 'Icons',
subtitle: 'Enabled and disabled icons with varying opacity',
category: 'Material Components',
routeName: IconsDemo.routeName,
buildRoute: (BuildContext context) => new IconsDemo(),
),
new GalleryItem(
title: 'Leave-behind list items',
subtitle: 'List items with hidden actions',
category: 'Material Components',
routeName: LeaveBehindDemo.routeName,
buildRoute: (BuildContext context) => const LeaveBehindDemo(),
),
new GalleryItem(
title: 'List',
subtitle: 'Layout variations for scrollable lists',
category: 'Material Components',
routeName: ListDemo.routeName,
buildRoute: (BuildContext context) => const ListDemo(),
),
new GalleryItem(
title: 'Menus',
subtitle: 'Menu buttons and simple menus',
category: 'Material Components',
routeName: MenuDemo.routeName,
buildRoute: (BuildContext context) => const MenuDemo(),
),
new GalleryItem(
title: 'Modal bottom sheet',
subtitle: 'Modal sheet that slides up from the bottom',
category: 'Material Components',
routeName: ModalBottomSheetDemo.routeName,
buildRoute: (BuildContext context) => new ModalBottomSheetDemo(),
),
new GalleryItem(
title: 'Page selector',
subtitle: 'PageView with indicator',
category: 'Material Components',
routeName: PageSelectorDemo.routeName,
buildRoute: (BuildContext context) => new PageSelectorDemo(),
),
new GalleryItem(
title: 'Persistent bottom sheet',
subtitle: 'Sheet that slides up from the bottom',
category: 'Material Components',
routeName: PersistentBottomSheetDemo.routeName,
buildRoute: (BuildContext context) => new PersistentBottomSheetDemo(),
),
new GalleryItem(
title: 'Progress indicators',
subtitle: 'All kinds: linear, circular, indeterminate, etc',
category: 'Material Components',
routeName: ProgressIndicatorDemo.routeName,
buildRoute: (BuildContext context) => new ProgressIndicatorDemo(),
),
new GalleryItem(
title: 'Pull to refresh',
subtitle: 'Refresh indicators',
category: 'Material Components',
routeName: OverscrollDemo.routeName,
buildRoute: (BuildContext context) => const OverscrollDemo(),
),
new GalleryItem(
title: 'Scrollable tabs',
subtitle: 'Tab bar that scrolls',
category: 'Material Components',
routeName: ScrollableTabsDemo.routeName,
buildRoute: (BuildContext context) => new ScrollableTabsDemo(),
),
new GalleryItem(
title: 'Selection controls',
subtitle: 'Checkboxes, radio buttons, and switches',
category: 'Material Components',
routeName: SelectionControlsDemo.routeName,
buildRoute: (BuildContext context) => new SelectionControlsDemo(),
),
new GalleryItem(
title: 'Sliders',
subtitle: 'Widgets that select a value by dragging the slider thumb',
category: 'Material Components',
routeName: SliderDemo.routeName,
buildRoute: (BuildContext context) => new SliderDemo(),
),
new GalleryItem(
title: 'Snackbar',
subtitle: 'Temporary message that appears at the bottom',
category: 'Material Components',
routeName: SnackBarDemo.routeName,
buildRoute: (BuildContext context) => const SnackBarDemo(),
),
new GalleryItem(
title: 'Tabs',
subtitle: 'Tabs with independently scrollable views',
category: 'Material Components',
routeName: TabsDemo.routeName,
buildRoute: (BuildContext context) => new TabsDemo(),
),
new GalleryItem(
title: 'Text fields',
subtitle: 'Single line of editable text and numbers',
category: 'Material Components',
routeName: TextFormFieldDemo.routeName,
buildRoute: (BuildContext context) => const TextFormFieldDemo(),
),
new GalleryItem(
title: 'Tooltips',
subtitle: 'Short message displayed after a long-press',
category: 'Material Components',
routeName: TooltipDemo.routeName,
buildRoute: (BuildContext context) => new TooltipDemo(),
),
// Cupertino Components
new GalleryItem(
title: 'Activity Indicator',
subtitle: 'Cupertino styled activity indicator',
category: 'Cupertino Components',
routeName: CupertinoProgressIndicatorDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoProgressIndicatorDemo(),
),
new GalleryItem(
title: 'Buttons',
subtitle: 'Cupertino styled buttons',
category: 'Cupertino Components',
routeName: CupertinoButtonsDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoButtonsDemo(),
),
new GalleryItem(
title: 'Dialogs',
subtitle: 'Cupertino styled dialogs',
category: 'Cupertino Components',
routeName: CupertinoDialogDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoDialogDemo(),
),
new GalleryItem(
title: 'Navigation',
subtitle: 'Cupertino styled navigation patterns',
category: 'Cupertino Components',
routeName: CupertinoNavigationDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoNavigationDemo(),
),
new GalleryItem(
title: 'Pickers',
subtitle: 'Cupertino styled pickers',
category: 'Cupertino Components',
routeName: CupertinoPickerDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoPickerDemo(),
),
new GalleryItem(
title: 'Pull to refresh',
subtitle: 'Cupertino styled refresh controls',
category: 'Cupertino Components',
routeName: CupertinoRefreshControlDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoRefreshControlDemo(),
),
new GalleryItem(
title: 'Sliders',
subtitle: 'Cupertino styled sliders',
category: 'Cupertino Components',
routeName: CupertinoSliderDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoSliderDemo(),
),
new GalleryItem(
title: 'Switches',
subtitle: 'Cupertino styled switches',
category: 'Cupertino Components',
routeName: CupertinoSwitchDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoSwitchDemo(),
),
// Media
new GalleryItem(
title: 'Animated images',
subtitle: 'GIF and WebP animations',
category: 'Media',
routeName: ImagesDemo.routeName,
buildRoute: (BuildContext context) => new ImagesDemo(),
),
// Styles
new GalleryItem(
title: 'Colors', title: 'Colors',
subtitle: 'All of the predefined colors', subtitle: 'All of the predefined colors',
category: 'Style', icon: GalleryIcons.colors,
category: _kStyle,
routeName: ColorsDemo.routeName, routeName: ColorsDemo.routeName,
buildRoute: (BuildContext context) => new ColorsDemo(), buildRoute: (BuildContext context) => new ColorsDemo(),
), ),
new GalleryItem( new GalleryDemo(
title: 'Typography', title: 'Typography',
subtitle: 'All of the predefined text styles', subtitle: 'All of the predefined text styles',
category: 'Style', icon: GalleryIcons.custom_typography,
category: _kStyle,
routeName: TypographyDemo.routeName, routeName: TypographyDemo.routeName,
buildRoute: (BuildContext context) => new TypographyDemo(), buildRoute: (BuildContext context) => new TypographyDemo(),
) ),
// Material Components
new GalleryDemo(
title: 'Backdrop',
subtitle: 'Select a front layer from back layer',
icon: GalleryIcons.backdrop,
category: _kMaterialComponents,
routeName: BackdropDemo.routeName,
buildRoute: (BuildContext context) => new BackdropDemo(),
),
new GalleryDemo(
title: 'Bottom app bar',
subtitle: 'With repositionable floating action button',
icon: GalleryIcons.bottom_app_bar,
category: _kMaterialComponents,
routeName: BottomAppBarDemo.routeName,
buildRoute: (BuildContext context) => new BottomAppBarDemo(),
),
new GalleryDemo(
title: 'Bottom navigation',
subtitle: 'Bottom navigation with cross-fading views',
icon: GalleryIcons.bottom_navigation,
category: _kMaterialComponents,
routeName: BottomNavigationDemo.routeName,
buildRoute: (BuildContext context) => new BottomNavigationDemo(),
),
new GalleryDemo(
title: 'Buttons',
subtitle: 'All kinds: flat, raised, dropdown, icon, etc',
icon: GalleryIcons.generic_buttons,
category: _kMaterialComponents,
routeName: ButtonsDemo.routeName,
buildRoute: (BuildContext context) => new ButtonsDemo(),
),
new GalleryDemo(
title: 'Cards',
subtitle: 'Material with rounded corners and a drop shadow',
icon: GalleryIcons.cards,
category: _kMaterialComponents,
routeName: CardsDemo.routeName,
buildRoute: (BuildContext context) => new CardsDemo(),
),
new GalleryDemo(
title: 'Chips',
subtitle: 'Label with an optional delete button and avatar',
icon: GalleryIcons.chips,
category: _kMaterialComponents,
routeName: ChipDemo.routeName,
buildRoute: (BuildContext context) => new ChipDemo(),
),
new GalleryDemo(
title: 'Data tables',
subtitle: 'Rows and columns',
icon: GalleryIcons.data_table,
category: _kMaterialComponents,
routeName: DataTableDemo.routeName,
buildRoute: (BuildContext context) => new DataTableDemo(),
),
new GalleryDemo(
title: 'Date and time pickers',
subtitle: 'Date and time selection widgets',
icon: GalleryIcons.event,
category: _kMaterialComponents,
routeName: DateAndTimePickerDemo.routeName,
buildRoute: (BuildContext context) => new DateAndTimePickerDemo(),
),
new GalleryDemo(
title: 'Dialog',
subtitle: 'All kinds: simple, alert, fullscreen, etc',
icon: GalleryIcons.dialogs,
category: _kMaterialComponents,
routeName: DialogDemo.routeName,
buildRoute: (BuildContext context) => new DialogDemo(),
),
new GalleryDemo(
title: 'Drawer',
subtitle: 'Navigation drawer with a standard header',
icon: GalleryIcons.menu,
category: _kMaterialComponents,
routeName: DrawerDemo.routeName,
buildRoute: (BuildContext context) => new DrawerDemo(),
),
new GalleryDemo(
title: 'Expand/collapse list control',
subtitle: 'List with one level of sublists',
icon: GalleryIcons.expand_all,
category: _kMaterialComponents,
routeName: TwoLevelListDemo.routeName,
buildRoute: (BuildContext context) => new TwoLevelListDemo(),
),
new GalleryDemo(
title: 'Expansion panels',
subtitle: 'List of expanding panels',
icon: GalleryIcons.expand_all,
category: _kMaterialComponents,
routeName: ExpansionPanelsDemo.routeName,
buildRoute: (BuildContext context) => new ExpansionPanelsDemo(),
),
new GalleryDemo(
title: 'Floating action button',
subtitle: 'Action buttons with transitions',
icon: GalleryIcons.buttons,
category: _kMaterialComponents,
routeName: TabsFabDemo.routeName,
buildRoute: (BuildContext context) => new TabsFabDemo(),
),
new GalleryDemo(
title: 'Grid',
subtitle: 'Row and column layout',
icon: GalleryIcons.grid_on,
category: _kMaterialComponents,
routeName: GridListDemo.routeName,
buildRoute: (BuildContext context) => const GridListDemo(),
),
new GalleryDemo(
title: 'Icons',
subtitle: 'Enabled and disabled icons with varying opacity',
icon: GalleryIcons.sentiment_very_satisfied,
category: _kMaterialComponents,
routeName: IconsDemo.routeName,
buildRoute: (BuildContext context) => new IconsDemo(),
),
new GalleryDemo(
title: 'Leave-behind list items',
subtitle: 'List items with hidden actions',
icon: GalleryIcons.lists_leave_behind,
category: _kMaterialComponents,
routeName: LeaveBehindDemo.routeName,
buildRoute: (BuildContext context) => const LeaveBehindDemo(),
),
new GalleryDemo(
title: 'List',
subtitle: 'Layout variations for scrollable lists',
icon: GalleryIcons.list_alt,
category: _kMaterialComponents,
routeName: ListDemo.routeName,
buildRoute: (BuildContext context) => const ListDemo(),
),
new GalleryDemo(
title: 'Menus',
subtitle: 'Menu buttons and simple menus',
icon: GalleryIcons.more_vert,
category: _kMaterialComponents,
routeName: MenuDemo.routeName,
buildRoute: (BuildContext context) => const MenuDemo(),
),
new GalleryDemo(
title: 'Modal bottom sheet',
subtitle: 'Modal sheet that slides up from the bottom',
icon: GalleryIcons.bottom_sheets,
category: _kMaterialComponents,
routeName: ModalBottomSheetDemo.routeName,
buildRoute: (BuildContext context) => new ModalBottomSheetDemo(),
),
new GalleryDemo(
title: 'Page selector',
subtitle: 'PageView with indicator',
icon: GalleryIcons.page_control,
category: _kMaterialComponents,
routeName: PageSelectorDemo.routeName,
buildRoute: (BuildContext context) => new PageSelectorDemo(),
),
new GalleryDemo(
title: 'Persistent bottom sheet',
subtitle: 'Sheet that slides up from the bottom',
icon: GalleryIcons.bottom_sheet_persistent,
category: _kMaterialComponents,
routeName: PersistentBottomSheetDemo.routeName,
buildRoute: (BuildContext context) => new PersistentBottomSheetDemo(),
),
new GalleryDemo(
title: 'Progress indicators',
subtitle: 'All kinds: linear, circular, indeterminate, etc',
icon: GalleryIcons.progress_activity,
category: _kMaterialComponents,
routeName: ProgressIndicatorDemo.routeName,
buildRoute: (BuildContext context) => new ProgressIndicatorDemo(),
),
new GalleryDemo(
title: 'Pull to refresh',
subtitle: 'Refresh indicators',
icon: GalleryIcons.refresh,
category: _kMaterialComponents,
routeName: OverscrollDemo.routeName,
buildRoute: (BuildContext context) => const OverscrollDemo(),
),
new GalleryDemo(
title: 'Scrollable tabs',
subtitle: 'Tab bar that scrolls',
category: _kMaterialComponents,
icon: GalleryIcons.tabs,
routeName: ScrollableTabsDemo.routeName,
buildRoute: (BuildContext context) => new ScrollableTabsDemo(),
),
new GalleryDemo(
title: 'Selection controls',
subtitle: 'Checkboxes, radio buttons, and switches',
icon: GalleryIcons.check_box,
category: _kMaterialComponents,
routeName: SelectionControlsDemo.routeName,
buildRoute: (BuildContext context) => new SelectionControlsDemo(),
),
new GalleryDemo(
title: 'Sliders',
subtitle: 'Widgets that select a value by dragging the slider thumb',
icon: GalleryIcons.sliders,
category: _kMaterialComponents,
routeName: SliderDemo.routeName,
buildRoute: (BuildContext context) => new SliderDemo(),
),
new GalleryDemo(
title: 'Snackbar',
subtitle: 'Temporary message that appears at the bottom',
icon: GalleryIcons.snackbar,
category: _kMaterialComponents,
routeName: SnackBarDemo.routeName,
buildRoute: (BuildContext context) => const SnackBarDemo(),
),
new GalleryDemo(
title: 'Tabs',
subtitle: 'Tabs with independently scrollable views',
icon: GalleryIcons.tabs,
category: _kMaterialComponents,
routeName: TabsDemo.routeName,
buildRoute: (BuildContext context) => new TabsDemo(),
),
new GalleryDemo(
title: 'Text fields',
subtitle: 'Single line of editable text and numbers',
icon: GalleryIcons.text_fields_alt,
category: _kMaterialComponents,
routeName: TextFormFieldDemo.routeName,
buildRoute: (BuildContext context) => const TextFormFieldDemo(),
),
new GalleryDemo(
title: 'Tooltips',
subtitle: 'Short message displayed after a long-press',
icon: GalleryIcons.tooltip,
category: _kMaterialComponents,
routeName: TooltipDemo.routeName,
buildRoute: (BuildContext context) => new TooltipDemo(),
),
// Cupertino Components
new GalleryDemo(
title: 'Activity Indicator',
subtitle: 'Cupertino styled activity indicator',
icon: GalleryIcons.cupertino_progress,
category: _kCupertinoComponents,
routeName: CupertinoProgressIndicatorDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoProgressIndicatorDemo(),
),
new GalleryDemo(
title: 'Buttons',
subtitle: 'Cupertino styled buttons',
icon: GalleryIcons.generic_buttons,
category: _kCupertinoComponents,
routeName: CupertinoButtonsDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoButtonsDemo(),
),
new GalleryDemo(
title: 'Dialogs',
subtitle: 'Cupertino styled dialogs',
icon: GalleryIcons.dialogs,
category: _kCupertinoComponents,
routeName: CupertinoDialogDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoDialogDemo(),
),
new GalleryDemo(
title: 'Navigation',
subtitle: 'Cupertino styled navigation patterns',
icon: GalleryIcons.bottom_navigation,
category: _kCupertinoComponents,
routeName: CupertinoNavigationDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoNavigationDemo(),
),
new GalleryDemo(
title: 'Pickers',
subtitle: 'Cupertino styled pickers',
icon: GalleryIcons.event,
category: _kCupertinoComponents,
routeName: CupertinoPickerDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoPickerDemo(),
),
new GalleryDemo(
title: 'Pull to refresh',
subtitle: 'Cupertino styled refresh controls',
icon: GalleryIcons.cupertino_pull_to_refresh,
category: _kCupertinoComponents,
routeName: CupertinoRefreshControlDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoRefreshControlDemo(),
),
new GalleryDemo(
title: 'Sliders',
subtitle: 'Cupertino styled sliders',
icon: GalleryIcons.sliders,
category: _kCupertinoComponents,
routeName: CupertinoSliderDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoSliderDemo(),
),
new GalleryDemo(
title: 'Switches',
subtitle: 'Cupertino styled switches',
icon: GalleryIcons.cupertino_switch,
category: _kCupertinoComponents,
routeName: CupertinoSwitchDemo.routeName,
buildRoute: (BuildContext context) => new CupertinoSwitchDemo(),
),
// Media
new GalleryDemo(
title: 'Animated images',
subtitle: 'GIF and WebP animations',
icon: GalleryIcons.animation,
category: _kMedia,
routeName: ImagesDemo.routeName,
buildRoute: (BuildContext context) => new ImagesDemo(),
),
new GalleryDemo(
title: 'Video',
subtitle: 'Video playback',
icon: GalleryIcons.drive_video,
category: _kMedia,
routeName: VideoDemo.routeName,
buildRoute: (BuildContext context) => const VideoDemo(),
),
]; ];
// Keep Pesto around for its regression test value. It is not included // Keep Pesto around for its regression test value. It is not included
// in (release builds) the performance tests. // in (release builds) the performance tests.
assert(() { assert(() {
galleryItems.insert(0, galleryDemos.insert(0,
new GalleryItem( new GalleryDemo(
title: 'Pesto', title: 'Pesto',
subtitle: 'Simple recipe browser', subtitle: 'Simple recipe browser',
category: 'Vignettes', icon: Icons.adjust,
category: _kDemos,
routeName: PestoDemo.routeName, routeName: PestoDemo.routeName,
buildRoute: (BuildContext context) => const PestoDemo(), buildRoute: (BuildContext context) => const PestoDemo(),
), ),
@ -388,7 +472,18 @@ List<GalleryItem> _buildGalleryItems() {
return true; return true;
}()); }());
return galleryItems; return galleryDemos;
} }
final List<GalleryItem> kAllGalleryItems = _buildGalleryItems(); final List<GalleryDemo> kAllGalleryDemos = _buildGalleryDemos();
final Set<GalleryDemoCategory> kAllGalleryDemoCategories =
kAllGalleryDemos.map<GalleryDemoCategory>((GalleryDemo demo) => demo.category).toSet();
final Map<GalleryDemoCategory, List<GalleryDemo>> kGalleryCategoryToDemos =
new Map<GalleryDemoCategory, List<GalleryDemo>>.fromIterable(
kAllGalleryDemoCategories,
value: (dynamic category) {
return kAllGalleryDemos.where((GalleryDemo demo) => demo.category == category).toList();
},
);

View File

@ -1,349 +0,0 @@
// Copyright 2016 The Chromium 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 'dart:math' as math;
import 'package:flutter/foundation.dart' show defaultTargetPlatform, required;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'theme.dart';
class LinkTextSpan extends TextSpan {
// Beware!
//
// This class is only safe because the TapGestureRecognizer is not
// given a deadline and therefore never allocates any resources.
//
// In any other situation -- setting a deadline, using any of the less trivial
// recognizers, etc -- you would have to manage the gesture recognizer's
// lifetime and call dispose() when the TextSpan was no longer being rendered.
//
// Since TextSpan itself is @immutable, this means that you would have to
// manage the recognizer from outside the TextSpan, e.g. in the State of a
// stateful widget that then hands the recognizer to the TextSpan.
LinkTextSpan({ TextStyle style, String url, String text }) : super(
style: style,
text: text ?? url,
recognizer: new TapGestureRecognizer()..onTap = () {
launch(url, forceSafariVC: false);
}
);
}
class GalleryDrawerHeader extends StatefulWidget {
const GalleryDrawerHeader({ Key key, this.light }) : super(key: key);
final bool light;
@override
_GalleryDrawerHeaderState createState() => new _GalleryDrawerHeaderState();
}
class _GalleryDrawerHeaderState extends State<GalleryDrawerHeader> {
bool _logoHasName = true;
bool _logoHorizontal = true;
MaterialColor _logoColor = Colors.blue;
@override
Widget build(BuildContext context) {
final double systemTopPadding = MediaQuery.of(context).padding.top;
return new Semantics(
label: 'Flutter',
child: new DrawerHeader(
decoration: new FlutterLogoDecoration(
margin: new EdgeInsets.fromLTRB(12.0, 12.0 + systemTopPadding, 12.0, 12.0),
style: _logoHasName ? _logoHorizontal ? FlutterLogoStyle.horizontal
: FlutterLogoStyle.stacked
: FlutterLogoStyle.markOnly,
lightColor: _logoColor.shade400,
darkColor: _logoColor.shade900,
textColor: widget.light ? const Color(0xFF616161) : const Color(0xFF9E9E9E),
),
duration: const Duration(milliseconds: 750),
child: new GestureDetector(
onLongPress: () {
setState(() {
_logoHorizontal = !_logoHorizontal;
if (!_logoHasName)
_logoHasName = true;
});
},
onTap: () {
setState(() {
_logoHasName = !_logoHasName;
});
},
onDoubleTap: () {
setState(() {
final List<MaterialColor> options = <MaterialColor>[];
if (_logoColor != Colors.blue)
options.addAll(<MaterialColor>[Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue]);
if (_logoColor != Colors.amber)
options.addAll(<MaterialColor>[Colors.amber, Colors.amber, Colors.amber]);
if (_logoColor != Colors.red)
options.addAll(<MaterialColor>[Colors.red, Colors.red, Colors.red]);
if (_logoColor != Colors.indigo)
options.addAll(<MaterialColor>[Colors.indigo, Colors.indigo, Colors.indigo]);
if (_logoColor != Colors.pink)
options.addAll(<MaterialColor>[Colors.pink]);
if (_logoColor != Colors.purple)
options.addAll(<MaterialColor>[Colors.purple]);
if (_logoColor != Colors.cyan)
options.addAll(<MaterialColor>[Colors.cyan]);
_logoColor = options[new math.Random().nextInt(options.length)];
});
}
),
),
);
}
}
class GalleryDrawer extends StatelessWidget {
const GalleryDrawer({
Key key,
this.galleryTheme,
@required this.onThemeChanged,
this.timeDilation,
@required this.onTimeDilationChanged,
this.textScaleFactor,
this.onTextScaleFactorChanged,
this.showPerformanceOverlay,
this.onShowPerformanceOverlayChanged,
this.checkerboardRasterCacheImages,
this.onCheckerboardRasterCacheImagesChanged,
this.checkerboardOffscreenLayers,
this.onCheckerboardOffscreenLayersChanged,
this.onPlatformChanged,
this.overrideDirection: TextDirection.ltr,
this.onOverrideDirectionChanged,
this.onSendFeedback,
}) : assert(onThemeChanged != null),
assert(onTimeDilationChanged != null),
super(key: key);
final GalleryTheme galleryTheme;
final ValueChanged<GalleryTheme> onThemeChanged;
final double timeDilation;
final ValueChanged<double> onTimeDilationChanged;
final double textScaleFactor;
final ValueChanged<double> onTextScaleFactorChanged;
final bool showPerformanceOverlay;
final ValueChanged<bool> onShowPerformanceOverlayChanged;
final bool checkerboardRasterCacheImages;
final ValueChanged<bool> onCheckerboardRasterCacheImagesChanged;
final bool checkerboardOffscreenLayers;
final ValueChanged<bool> onCheckerboardOffscreenLayersChanged;
final ValueChanged<TargetPlatform> onPlatformChanged;
final TextDirection overrideDirection;
final ValueChanged<TextDirection> onOverrideDirectionChanged;
final VoidCallback onSendFeedback;
@override
Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context);
final TextStyle aboutTextStyle = themeData.textTheme.body2;
final TextStyle linkStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor);
final List<Widget> themeItems = kAllGalleryThemes.map<Widget>((GalleryTheme theme) {
return new RadioListTile<GalleryTheme>(
title: new Text(theme.name),
secondary: new Icon(theme.icon),
value: theme,
groupValue: galleryTheme,
onChanged: onThemeChanged,
selected: galleryTheme == theme,
);
}).toList();
final Widget mountainViewItem = new RadioListTile<TargetPlatform>(
// on iOS, we don't want to show an Android phone icon
secondary: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star : Icons.phone_android),
title: new Text(defaultTargetPlatform == TargetPlatform.iOS ? 'Mountain View' : 'Android'),
value: TargetPlatform.android,
groupValue: Theme.of(context).platform,
onChanged: onPlatformChanged,
selected: Theme.of(context).platform == TargetPlatform.android,
);
final Widget cupertinoItem = new RadioListTile<TargetPlatform>(
// on iOS, we don't want to show the iPhone icon
secondary: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star_border : Icons.phone_iphone),
title: new Text(defaultTargetPlatform == TargetPlatform.iOS ? 'Cupertino' : 'iOS'),
value: TargetPlatform.iOS,
groupValue: Theme.of(context).platform,
onChanged: onPlatformChanged,
selected: Theme.of(context).platform == TargetPlatform.iOS,
);
final List<Widget> textSizeItems = <Widget>[];
final Map<double, String> textSizes = <double, String>{
null: 'System Default',
0.8: 'Small',
1.0: 'Normal',
1.3: 'Large',
2.0: 'Huge',
};
for (double size in textSizes.keys) {
textSizeItems.add(new RadioListTile<double>(
secondary: const Icon(Icons.text_fields),
title: new Text(textSizes[size]),
value: size,
groupValue: textScaleFactor,
onChanged: onTextScaleFactorChanged,
selected: textScaleFactor == size,
));
}
final Widget animateSlowlyItem = new CheckboxListTile(
title: const Text('Animate Slowly'),
value: timeDilation != 1.0,
onChanged: (bool value) {
onTimeDilationChanged(value ? 20.0 : 1.0);
},
secondary: const Icon(Icons.hourglass_empty),
selected: timeDilation != 1.0,
);
final Widget overrideDirectionItem = new CheckboxListTile(
title: const Text('Force RTL'),
value: overrideDirection == TextDirection.rtl,
onChanged: (bool value) {
onOverrideDirectionChanged(value ? TextDirection.rtl : TextDirection.ltr);
},
secondary: const Icon(Icons.format_textdirection_r_to_l),
selected: overrideDirection == TextDirection.rtl,
);
final Widget sendFeedbackItem = new ListTile(
leading: const Icon(Icons.report),
title: const Text('Send feedback'),
onTap: onSendFeedback ?? () {
launch('https://github.com/flutter/flutter/issues/new');
},
);
final Widget aboutItem = new AboutListTile(
icon: const FlutterLogo(),
applicationVersion: 'April 2018 Preview',
applicationIcon: const FlutterLogo(),
applicationLegalese: '© 2017 The Chromium Authors',
aboutBoxChildren: <Widget>[
new Padding(
padding: const EdgeInsets.only(top: 24.0),
child: new RichText(
text: new TextSpan(
children: <TextSpan>[
new TextSpan(
style: aboutTextStyle,
text: 'Flutter is an early-stage, open-source project to help developers '
'build high-performance, high-fidelity, mobile apps for '
'${defaultTargetPlatform == TargetPlatform.iOS ? 'multiple platforms' : 'iOS and Android'} '
'from a single codebase. This gallery is a preview of '
"Flutter's many widgets, behaviors, animations, layouts, "
'and more. Learn more about Flutter at '
),
new LinkTextSpan(
style: linkStyle,
url: 'https://flutter.io'
),
new TextSpan(
style: aboutTextStyle,
text: '.\n\nTo see the source code for this app, please visit the '
),
new LinkTextSpan(
style: linkStyle,
url: 'https://goo.gl/iv1p4G',
text: 'flutter github repo'
),
new TextSpan(
style: aboutTextStyle,
text: '.'
)
]
)
)
)
]
);
final List<Widget> allDrawerItems = <Widget>[
new GalleryDrawerHeader(
light: galleryTheme.theme.brightness == Brightness.light,
),
]
..addAll(themeItems)
..addAll(<Widget>[
const Divider(),
mountainViewItem,
cupertinoItem,
const Divider(),
])
..addAll(textSizeItems)
..addAll(<Widget>[
overrideDirectionItem,
const Divider(),
animateSlowlyItem,
const Divider(),
]);
bool addedOptionalItem = false;
if (onCheckerboardOffscreenLayersChanged != null) {
allDrawerItems.add(new CheckboxListTile(
title: const Text('Checkerboard Offscreen Layers'),
value: checkerboardOffscreenLayers,
onChanged: onCheckerboardOffscreenLayersChanged,
secondary: const Icon(Icons.assessment),
selected: checkerboardOffscreenLayers,
));
addedOptionalItem = true;
}
if (onCheckerboardRasterCacheImagesChanged != null) {
allDrawerItems.add(new CheckboxListTile(
title: const Text('Checkerboard Raster Cache Images'),
value: checkerboardRasterCacheImages,
onChanged: onCheckerboardRasterCacheImagesChanged,
secondary: const Icon(Icons.assessment),
selected: checkerboardRasterCacheImages,
));
addedOptionalItem = true;
}
if (onShowPerformanceOverlayChanged != null) {
allDrawerItems.add(new CheckboxListTile(
title: const Text('Performance Overlay'),
value: showPerformanceOverlay,
onChanged: onShowPerformanceOverlayChanged,
secondary: const Icon(Icons.assessment),
selected: showPerformanceOverlay,
));
addedOptionalItem = true;
}
if (addedOptionalItem)
allDrawerItems.add(const Divider());
allDrawerItems.addAll(<Widget>[
sendFeedbackItem,
aboutItem,
]);
return new Drawer(child: new ListView(primary: false, children: allDrawerItems));
}
}

View File

@ -1,126 +1,286 @@
// Copyright 2016 The Chromium Authors. All rights reserved. // Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart'; import 'dart:developer';
import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'drawer.dart'; import 'backdrop.dart';
import 'item.dart'; import 'demos.dart';
import 'theme.dart';
const double _kFlexibleSpaceMaxHeight = 256.0;
const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; const String _kGalleryAssetsPackage = 'flutter_gallery_assets';
const Color _kFlutterBlue = const Color(0xFF003D75);
const double _kDemoItemHeight = 64.0;
class _BackgroundLayer { class _FlutterLogo extends StatelessWidget {
_BackgroundLayer({ int level, double parallax }) const _FlutterLogo({ Key key }) : super(key: key);
: assetName = 'appbar/appbar_background_layer$level.png',
assetPackage = _kGalleryAssetsPackage,
parallaxTween = new Tween<double>(begin: 0.0, end: parallax);
final String assetName;
final String assetPackage;
final Tween<double> parallaxTween;
}
final List<_BackgroundLayer> _kBackgroundLayers = <_BackgroundLayer>[
new _BackgroundLayer(level: 0, parallax: _kFlexibleSpaceMaxHeight),
new _BackgroundLayer(level: 1, parallax: _kFlexibleSpaceMaxHeight),
new _BackgroundLayer(level: 2, parallax: _kFlexibleSpaceMaxHeight / 2.0),
new _BackgroundLayer(level: 3, parallax: _kFlexibleSpaceMaxHeight / 4.0),
new _BackgroundLayer(level: 4, parallax: _kFlexibleSpaceMaxHeight / 2.0),
new _BackgroundLayer(level: 5, parallax: _kFlexibleSpaceMaxHeight)
];
class _AppBarBackground extends StatelessWidget {
const _AppBarBackground({ Key key, this.animation }) : super(key: key);
final Animation<double> animation;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new AnimatedBuilder( return new Center(
animation: animation, child: new Container(
builder: (BuildContext context, Widget child) { width: 34.0,
return new Stack( height: 34.0,
children: _kBackgroundLayers.map((_BackgroundLayer layer) { decoration: const BoxDecoration(
return new Positioned( image: const DecorationImage(
top: -layer.parallaxTween.evaluate(animation), image: const AssetImage(
left: 0.0, 'white_logo/logo.png',
right: 0.0, package: _kGalleryAssetsPackage,
bottom: 0.0, ),
child: new Image.asset( ),
layer.assetName, ),
package: layer.assetPackage, ),
fit: BoxFit.cover, );
height: _kFlexibleSpaceMaxHeight }
) }
);
}).toList() class _CategoryItem extends StatelessWidget {
); const _CategoryItem({
} Key key,
this.category,
this.onTap,
}) : super (key: key);
final GalleryDemoCategory category;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final bool isDark = theme.brightness == Brightness.dark;
return new RawMaterialButton(
padding: EdgeInsets.zero,
splashColor: theme.primaryColor.withOpacity(0.12),
highlightColor: Colors.transparent,
onPressed: onTap,
child: new Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Padding(
padding: const EdgeInsets.all(6.0),
child: new Icon(
category.icon,
size: 60.0,
color: isDark ? Colors.white : _kFlutterBlue,
),
),
const SizedBox(height: 10.0),
new Container(
height: 48.0,
alignment: Alignment.center,
child: new Text(
category.name,
textAlign: TextAlign.center,
style: theme.textTheme.subhead.copyWith(
fontFamily: 'GoogleSans',
color: isDark ? Colors.white : _kFlutterBlue,
),
),
),
],
),
);
}
}
class _CategoriesPage extends StatelessWidget {
const _CategoriesPage({
Key key,
this.categories,
this.onCategoryTap,
}) : super(key: key);
final Iterable<GalleryDemoCategory> categories;
final ValueChanged<GalleryDemoCategory> onCategoryTap;
@override
Widget build(BuildContext context) {
const double aspectRatio = 160.0 / 180.0;
final List<GalleryDemoCategory> categoriesList = categories.toList();
final int columnCount = (MediaQuery.of(context).orientation == Orientation.portrait) ? 2 : 3;
return new SingleChildScrollView(
child: new LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final double columnWidth = constraints.biggest.width / columnCount.toDouble();
final double rowHeight = columnWidth * aspectRatio;
final int rowCount = (categories.length + columnCount - 1) ~/ columnCount;
return new Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: new List<Widget>.generate(rowCount, (int rowIndex) {
final int columnCountForRow = rowIndex == rowCount - 1
? categories.length - columnCount * math.max(0, rowCount - 1)
: columnCount;
return new Row(
children: new List<Widget>.generate(columnCountForRow, (int columnIndex) {
final int index = rowIndex * columnCount + columnIndex;
final GalleryDemoCategory category = categoriesList[index];
return new SizedBox(
width: columnWidth,
height: rowHeight,
child: new _CategoryItem(
category: category,
onTap: () {
Navigator.pushNamed(context, '/${category.name}');
},
),
);
}),
);
}),
);
},
),
);
}
}
class _DemoItem extends StatelessWidget {
const _DemoItem({ Key key, this.demo }) : super(key: key);
final GalleryDemo demo;
void _launchDemo(BuildContext context) {
if (demo.routeName != null) {
Timeline.instantSync('Start Transition', arguments: <String, String>{
'from': '/',
'to': demo.routeName,
});
Navigator.pushNamed(context, demo.routeName);
}
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final bool isDark = theme.brightness == Brightness.dark;
final double textScaleFactor = MediaQuery.of(context)?.textScaleFactor ?? 1.0;
return new RawMaterialButton(
padding: EdgeInsets.zero,
splashColor: theme.primaryColor.withOpacity(0.12),
highlightColor: Colors.transparent,
onPressed: () {
_launchDemo(context);
},
child: new Container(
constraints: new BoxConstraints(minHeight: _kDemoItemHeight * textScaleFactor),
child: new Row(
children: <Widget>[
new Container(
width: 56.0,
height: 56.0,
alignment: Alignment.center,
child: new Icon(
demo.icon,
size: 24.0,
color: isDark ? Colors.white : _kFlutterBlue,
),
),
new Expanded(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Text(
demo.title,
style: theme.textTheme.subhead.copyWith(
color: isDark ? Colors.white : const Color(0xFF202124),
),
),
new Text(
demo.subtitle,
style: theme.textTheme.body1.copyWith(
color: isDark ? Colors.white : const Color(0xFF60646B)),
),
],
),
),
const SizedBox(width: 44.0),
],
),
),
);
}
}
class DemosPage extends StatelessWidget {
const DemosPage({
Key key,
this.category,
this.optionsPage,
}) : super(key: key);
final GalleryDemoCategory category;
final Widget optionsPage;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final bool isDark = theme.brightness == Brightness.dark;
return new Scaffold(
backgroundColor: isDark ? _kFlutterBlue : theme.primaryColor,
body: new SafeArea(
child: new SizedBox.expand(
child: new Backdrop(
backTitle: const Text('Options'),
backLayer: optionsPage,
frontAction: const BackButton(),
frontTitle: new Text(category.name),
frontHeading: new Container(
height: 40.0,
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: const Divider(
color: const Color(0xFFD5D7DA),
height: 1.0
),
),
frontLayer: new Padding(
padding: const EdgeInsets.only(top: 40.0),
child: new ListView(
key: const ValueKey<String>('GalleryDemoList'), // So tests can find it.
padding: const EdgeInsets.only(top: 8.0),
children: kGalleryCategoryToDemos[category].map<Widget>((GalleryDemo demo) {
return new _DemoItem(demo: demo);
}).toList(),
),
),
),
),
),
); );
} }
} }
class GalleryHome extends StatefulWidget { class GalleryHome extends StatefulWidget {
const GalleryHome({
Key key,
this.galleryTheme,
@required this.onThemeChanged,
this.timeDilation,
@required this.onTimeDilationChanged,
this.textScaleFactor,
this.onTextScaleFactorChanged,
this.showPerformanceOverlay,
this.onShowPerformanceOverlayChanged,
this.checkerboardRasterCacheImages,
this.onCheckerboardRasterCacheImagesChanged,
this.checkerboardOffscreenLayers,
this.onCheckerboardOffscreenLayersChanged,
this.onPlatformChanged,
this.overrideDirection: TextDirection.ltr,
this.onOverrideDirectionChanged,
this.onSendFeedback,
}) : assert(onThemeChanged != null),
assert(onTimeDilationChanged != null),
super(key: key);
// In checked mode our MaterialApp will show the default "debug" banner. // In checked mode our MaterialApp will show the default "debug" banner.
// Otherwise show the "preview" banner. // Otherwise show the "preview" banner.
static bool showPreviewBanner = true; static bool showPreviewBanner = true;
final GalleryTheme galleryTheme; const GalleryHome({
final ValueChanged<GalleryTheme> onThemeChanged; Key key,
this.optionsPage,
}) : super(key: key);
final double timeDilation; final Widget optionsPage;
final ValueChanged<double> onTimeDilationChanged;
final double textScaleFactor;
final ValueChanged<double> onTextScaleFactorChanged;
final bool showPerformanceOverlay;
final ValueChanged<bool> onShowPerformanceOverlayChanged;
final bool checkerboardRasterCacheImages;
final ValueChanged<bool> onCheckerboardRasterCacheImagesChanged;
final bool checkerboardOffscreenLayers;
final ValueChanged<bool> onCheckerboardOffscreenLayersChanged;
final ValueChanged<TargetPlatform> onPlatformChanged;
final TextDirection overrideDirection;
final ValueChanged<TextDirection> onOverrideDirectionChanged;
final VoidCallback onSendFeedback;
@override @override
GalleryHomeState createState() => new GalleryHomeState(); _GalleryHomeState createState() => new _GalleryHomeState();
} }
class GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStateMixin { class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStateMixin {
static final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); static final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
AnimationController _controller; AnimationController _controller;
@override @override
@ -139,75 +299,27 @@ class GalleryHomeState extends State<GalleryHome> with SingleTickerProviderState
super.dispose(); super.dispose();
} }
List<Widget> _galleryListItems() {
final List<Widget> listItems = <Widget>[];
final ThemeData themeData = Theme.of(context);
final TextStyle headerStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor);
String category;
for (GalleryItem galleryItem in kAllGalleryItems) {
if (category != galleryItem.category) {
if (category != null)
listItems.add(const Divider());
listItems.add(
new MergeSemantics(
child: new Container(
height: 48.0,
padding: const EdgeInsetsDirectional.only(start: 16.0),
alignment: AlignmentDirectional.centerStart,
child: new SafeArea(
top: false,
bottom: false,
child: new Semantics(
header: true,
child: new Text(galleryItem.category, style: headerStyle),
),
),
),
)
);
category = galleryItem.category;
}
listItems.add(galleryItem);
}
return listItems;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final bool isDark = theme.brightness == Brightness.dark;
Widget home = new Scaffold( Widget home = new Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
drawer: new GalleryDrawer( backgroundColor: isDark ? _kFlutterBlue : theme.primaryColor,
galleryTheme: widget.galleryTheme, body: new SafeArea(
onThemeChanged: widget.onThemeChanged, bottom: false,
timeDilation: widget.timeDilation, child: new Backdrop(
onTimeDilationChanged: widget.onTimeDilationChanged, backTitle: const Text('Options'),
textScaleFactor: widget.textScaleFactor, backLayer: widget.optionsPage,
onTextScaleFactorChanged: widget.onTextScaleFactorChanged, frontAction: const _FlutterLogo(),
showPerformanceOverlay: widget.showPerformanceOverlay, frontTitle: const Text('Flutter gallery'),
onShowPerformanceOverlayChanged: widget.onShowPerformanceOverlayChanged, frontHeading: new Container(height: 24.0),
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, frontLayer: new _CategoriesPage(
onCheckerboardRasterCacheImagesChanged: widget.onCheckerboardRasterCacheImagesChanged, categories: kAllGalleryDemoCategories,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
onCheckerboardOffscreenLayersChanged: widget.onCheckerboardOffscreenLayersChanged,
onPlatformChanged: widget.onPlatformChanged,
overrideDirection: widget.overrideDirection,
onOverrideDirectionChanged: widget.onOverrideDirectionChanged,
onSendFeedback: widget.onSendFeedback,
),
body: new CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: _kFlexibleSpaceMaxHeight,
flexibleSpace: const FlexibleSpaceBar(
title: const Text('Flutter Gallery'),
// TODO(abarth): Wire up to the parallax in a way that doesn't pop during hero transition.
background: const _AppBarBackground(animation: kAlwaysDismissedAnimation),
),
), ),
new SliverList(delegate: new SliverChildListDelegate(_galleryListItems())), ),
], ),
)
); );
assert(() { assert(() {

View File

@ -0,0 +1,50 @@
// Copyright 2018 The Chromium 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 GalleryIcons {
GalleryIcons._();
static const IconData tooltip = const IconData(0xe900, fontFamily: 'GalleryIcons');
static const IconData text_fields_alt = const IconData(0xe901, fontFamily: 'GalleryIcons');
static const IconData tabs = const IconData(0xe902, fontFamily: 'GalleryIcons');
static const IconData switches = const IconData(0xe903, fontFamily: 'GalleryIcons');
static const IconData sliders = const IconData(0xe904, fontFamily: 'GalleryIcons');
static const IconData shrine = const IconData(0xe905, fontFamily: 'GalleryIcons');
static const IconData sentiment_very_satisfied = const IconData(0xe906, fontFamily: 'GalleryIcons');
static const IconData refresh = const IconData(0xe907, fontFamily: 'GalleryIcons');
static const IconData progress_activity = const IconData(0xe908, fontFamily: 'GalleryIcons');
static const IconData phone_iphone = const IconData(0xe909, fontFamily: 'GalleryIcons');
static const IconData page_control = const IconData(0xe90a, fontFamily: 'GalleryIcons');
static const IconData more_vert = const IconData(0xe90b, fontFamily: 'GalleryIcons');
static const IconData menu = const IconData(0xe90c, fontFamily: 'GalleryIcons');
static const IconData list_alt = const IconData(0xe90d, fontFamily: 'GalleryIcons');
static const IconData grid_on = const IconData(0xe90e, fontFamily: 'GalleryIcons');
static const IconData expand_all = const IconData(0xe90f, fontFamily: 'GalleryIcons');
static const IconData event = const IconData(0xe910, fontFamily: 'GalleryIcons');
static const IconData drive_video = const IconData(0xe911, fontFamily: 'GalleryIcons');
static const IconData dialogs = const IconData(0xe912, fontFamily: 'GalleryIcons');
static const IconData data_table = const IconData(0xe913, fontFamily: 'GalleryIcons');
static const IconData custom_typography = const IconData(0xe914, fontFamily: 'GalleryIcons');
static const IconData colors = const IconData(0xe915, fontFamily: 'GalleryIcons');
static const IconData chips = const IconData(0xe916, fontFamily: 'GalleryIcons');
static const IconData check_box = const IconData(0xe917, fontFamily: 'GalleryIcons');
static const IconData cards = const IconData(0xe918, fontFamily: 'GalleryIcons');
static const IconData buttons = const IconData(0xe919, fontFamily: 'GalleryIcons');
static const IconData bottom_sheets = const IconData(0xe91a, fontFamily: 'GalleryIcons');
static const IconData bottom_navigation = const IconData(0xe91b, fontFamily: 'GalleryIcons');
static const IconData animation = const IconData(0xe91c, fontFamily: 'GalleryIcons');
static const IconData account_box = const IconData(0xe91d, fontFamily: 'GalleryIcons');
static const IconData snackbar = const IconData(0xe91e, fontFamily: 'GalleryIcons');
static const IconData category_mdc = const IconData(0xe91f, fontFamily: 'GalleryIcons');
static const IconData cupertino_progress = const IconData(0xe920, fontFamily: 'GalleryIcons');
static const IconData cupertino_pull_to_refresh = const IconData(0xe921, fontFamily: 'GalleryIcons');
static const IconData cupertino_switch = const IconData(0xe922, fontFamily: 'GalleryIcons');
static const IconData generic_buttons = const IconData(0xe923, fontFamily: 'GalleryIcons');
static const IconData backdrop = const IconData(0xe924, fontFamily: 'GalleryIcons');
static const IconData bottom_app_bar = const IconData(0xe925, fontFamily: 'GalleryIcons');
static const IconData bottom_sheet_persistent = const IconData(0xe926, fontFamily: 'GalleryIcons');
static const IconData lists_leave_behind = const IconData(0xe927, fontFamily: 'GalleryIcons');
}

View File

@ -0,0 +1,466 @@
// Copyright 2018 The Chromium 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';
import 'about.dart';
import 'scales.dart';
import 'themes.dart';
class GalleryOptions {
GalleryOptions({
this.theme,
this.textScaleFactor,
this.textDirection: TextDirection.ltr,
this.timeDilation: 1.0,
this.platform,
this.showOffscreenLayersCheckerboard: false,
this.showRasterCacheImagesCheckerboard: false,
this.showPerformanceOverlay: false,
});
final GalleryTheme theme;
final GalleryTextScaleValue textScaleFactor;
final TextDirection textDirection;
final double timeDilation;
final TargetPlatform platform;
final bool showPerformanceOverlay;
final bool showRasterCacheImagesCheckerboard;
final bool showOffscreenLayersCheckerboard;
GalleryOptions copyWith({
GalleryTheme theme,
GalleryTextScaleValue textScaleFactor,
TextDirection textDirection,
double timeDilation,
TargetPlatform platform,
bool showPerformanceOverlay,
bool showRasterCacheImagesCheckerboard,
bool showOffscreenLayersCheckerboard,
}) {
return new GalleryOptions(
theme: theme ?? this.theme,
textScaleFactor: textScaleFactor ?? this.textScaleFactor,
textDirection: textDirection ?? this.textDirection,
timeDilation: timeDilation ?? this.timeDilation,
platform: platform ?? this.platform,
showPerformanceOverlay: showPerformanceOverlay ?? this.showPerformanceOverlay,
showOffscreenLayersCheckerboard: showOffscreenLayersCheckerboard ?? this.showOffscreenLayersCheckerboard,
showRasterCacheImagesCheckerboard: showRasterCacheImagesCheckerboard ?? this.showRasterCacheImagesCheckerboard,
);
}
@override
bool operator ==(dynamic other) {
if (runtimeType != other.runtimeType)
return false;
final GalleryOptions typedOther = other;
return theme == typedOther.theme
&& textScaleFactor == typedOther.textScaleFactor
&& textDirection == typedOther.textDirection
&& platform == typedOther.platform
&& showPerformanceOverlay == typedOther.showPerformanceOverlay
&& showRasterCacheImagesCheckerboard == typedOther.showRasterCacheImagesCheckerboard
&& showOffscreenLayersCheckerboard == typedOther.showRasterCacheImagesCheckerboard;
}
@override
int get hashCode => hashValues(
theme,
textScaleFactor,
textDirection,
timeDilation,
platform,
showPerformanceOverlay,
showRasterCacheImagesCheckerboard,
showOffscreenLayersCheckerboard,
);
@override
String toString() {
return '$runtimeType($theme)';
}
}
const double _kItemHeight = 48.0;
const EdgeInsetsDirectional _kItemPadding = const EdgeInsetsDirectional.only(start: 56.0);
class _OptionsItem extends StatelessWidget {
const _OptionsItem({ Key key, this.child }) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) {
final double textScaleFactor = MediaQuery.of(context)?.textScaleFactor ?? 1.0;
return new Container(
constraints: new BoxConstraints(minHeight: _kItemHeight * textScaleFactor),
padding: _kItemPadding,
alignment: AlignmentDirectional.centerStart,
child: new DefaultTextStyle(
style: DefaultTextStyle.of(context).style,
maxLines: 2,
overflow: TextOverflow.fade,
child: new IconTheme(
data: Theme.of(context).primaryIconTheme,
child: child,
),
),
);
}
}
class _BooleanItem extends StatelessWidget {
const _BooleanItem(this.title, this.value, this.onChanged);
final String title;
final bool value;
final ValueChanged<bool> onChanged;
@override
Widget build(BuildContext context) {
final bool isDark = Theme.of(context).brightness == Brightness.dark;
return new _OptionsItem(
child: new Row(
children: <Widget>[
new Expanded(child: new Text(title)),
new Switch(
value: value,
onChanged: onChanged,
activeColor: const Color(0xFF39CEFD),
activeTrackColor: isDark ? Colors.white30 : Colors.black26,
),
],
),
);
}
}
class _ActionItem extends StatelessWidget {
const _ActionItem(this.text, this.onTap);
final String text;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return new _OptionsItem(
child: new _FlatButton(
onPressed: onTap,
child: new Text(text),
),
);
}
}
class _FlatButton extends StatelessWidget {
const _FlatButton({ Key key, this.onPressed, this.child }) : super(key: key);
final VoidCallback onPressed;
final Widget child;
@override
Widget build(BuildContext context) {
return new FlatButton(
padding: EdgeInsets.zero,
onPressed: onPressed,
child: new DefaultTextStyle(
style: Theme.of(context).primaryTextTheme.subhead,
child: child,
),
);
}
}
class _Heading extends StatelessWidget {
const _Heading(this.text);
final String text;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return new Semantics(
header: true,
child: new _OptionsItem(
child: new DefaultTextStyle(
style: theme.textTheme.body1.copyWith(
fontFamily: 'GoogleSans',
color: theme.accentColor,
),
child: new Text(text),
),
),
);
}
}
class _ThemeItem extends StatelessWidget {
const _ThemeItem(this.options, this.onOptionsChanged);
final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged;
@override
Widget build(BuildContext context) {
return new _BooleanItem(
'Dark Theme',
options.theme == kDarkGalleryTheme,
(bool value) {
onOptionsChanged(
options.copyWith(
theme: value ? kDarkGalleryTheme : kLightGalleryTheme,
),
);
},
);
}
}
class _TextScaleFactorItem extends StatelessWidget {
const _TextScaleFactorItem(this.options, this.onOptionsChanged);
final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged;
@override
Widget build(BuildContext context) {
return new _OptionsItem(
child: new Row(
children: <Widget>[
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Text size'),
new Text(
'${options.textScaleFactor.label}',
style: Theme.of(context).primaryTextTheme.body1,
),
],
),
),
new PopupMenuButton<GalleryTextScaleValue>(
padding: const EdgeInsetsDirectional.only(end: 16.0),
icon: const Icon(Icons.arrow_drop_down),
itemBuilder: (BuildContext context) {
return kAllGalleryTextScaleValues.map((GalleryTextScaleValue scaleValue) {
return new PopupMenuItem<GalleryTextScaleValue>(
value: scaleValue,
child: new Text(scaleValue.label),
);
}).toList();
},
onSelected: (GalleryTextScaleValue scaleValue) {
onOptionsChanged(
options.copyWith(textScaleFactor: scaleValue),
);
},
),
],
),
);
}
}
class _TextDirectionItem extends StatelessWidget {
const _TextDirectionItem(this.options, this.onOptionsChanged);
final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged;
@override
Widget build(BuildContext context) {
return new _BooleanItem(
'Force RTL',
options.textDirection == TextDirection.rtl,
(bool value) {
onOptionsChanged(
options.copyWith(
textDirection: value ? TextDirection.rtl : TextDirection.ltr,
),
);
},
);
}
}
class _TimeDilationItem extends StatelessWidget {
const _TimeDilationItem(this.options, this.onOptionsChanged);
final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged;
@override
Widget build(BuildContext context) {
return new _BooleanItem(
'Slow motion',
options.timeDilation != 1.0,
(bool value) {
onOptionsChanged(
options.copyWith(
timeDilation: value ? 20.0 : 1.0,
),
);
},
);
}
}
class _PlatformItem extends StatelessWidget {
const _PlatformItem(this.options, this.onOptionsChanged);
final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged;
String _platformLabel(TargetPlatform platform) {
switch(platform) {
case TargetPlatform.android:
return 'Mountain View';
case TargetPlatform.fuchsia:
return 'Fuchsia';
case TargetPlatform.iOS:
return 'Cupertino';
}
assert(false);
return null;
}
@override
Widget build(BuildContext context) {
return new _OptionsItem(
child: new Row(
children: <Widget>[
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Platform mechanics'),
new Text(
'${_platformLabel(options.platform)}',
style: Theme.of(context).primaryTextTheme.body1,
),
],
),
),
new PopupMenuButton<TargetPlatform>(
padding: const EdgeInsetsDirectional.only(end: 16.0),
icon: const Icon(Icons.arrow_drop_down),
itemBuilder: (BuildContext context) {
return TargetPlatform.values.map((TargetPlatform platform) {
return new PopupMenuItem<TargetPlatform>(
value: platform,
child: new Text(_platformLabel(platform)),
);
}).toList();
},
onSelected: (TargetPlatform platform) {
onOptionsChanged(
options.copyWith(platform: platform),
);
},
),
],
),
);
}
}
class GalleryOptionsPage extends StatelessWidget {
const GalleryOptionsPage({
Key key,
this.options,
this.onOptionsChanged,
this.onSendFeedback,
}) : super(key: key);
final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged;
final VoidCallback onSendFeedback;
List<Widget> _enabledDiagnosticItems() {
// Boolean showFoo options with a value of null: don't display
// the showFoo option at all.
if (null == options.showOffscreenLayersCheckerboard
?? options.showRasterCacheImagesCheckerboard
?? options.showPerformanceOverlay)
return const <Widget>[];
final List<Widget> items = <Widget>[
const Divider(),
const _Heading('Diagnostics'),
];
if (options.showOffscreenLayersCheckerboard != null) {
items.add(
new _BooleanItem(
'Highlight offscreen layers',
options.showOffscreenLayersCheckerboard,
(bool value) {
onOptionsChanged(options.copyWith(showOffscreenLayersCheckerboard: value));
}
),
);
}
if (options.showRasterCacheImagesCheckerboard != null) {
items.add(
new _BooleanItem(
'Highlight raster cache images',
options.showRasterCacheImagesCheckerboard,
(bool value) {
onOptionsChanged(options.copyWith(showRasterCacheImagesCheckerboard: value));
},
),
);
}
if (options.showPerformanceOverlay != null) {
items.add(
new _BooleanItem(
'Show performance overlay',
options.showPerformanceOverlay,
(bool value) {
onOptionsChanged(options.copyWith(showPerformanceOverlay: value));
},
),
);
}
return items;
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return new DefaultTextStyle(
style: theme.primaryTextTheme.subhead,
child: new ListView(
padding: const EdgeInsets.only(bottom: 124.0),
children: <Widget>[
const _Heading('Display'),
new _ThemeItem(options, onOptionsChanged),
new _TextScaleFactorItem(options, onOptionsChanged),
new _TextDirectionItem(options, onOptionsChanged),
new _TimeDilationItem(options, onOptionsChanged),
const Divider(),
const _Heading('Platform mechanics'),
new _PlatformItem(options, onOptionsChanged),
]..addAll(
_enabledDiagnosticItems(),
)..addAll(
<Widget>[
const Divider(),
const _Heading('Flutter gallery'),
new _ActionItem('About Flutter Gallery', () {
showGalleryAboutDialog(context);
}),
new _ActionItem('Send feedback', onSendFeedback),
],
),
),
);
}
}

View File

@ -0,0 +1,37 @@
// Copyright 2018 The Chromium 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 GalleryTextScaleValue {
const GalleryTextScaleValue(this.scale, this.label);
final double scale;
final String label;
@override
bool operator ==(dynamic other) {
if (runtimeType != other.runtimeType)
return false;
final GalleryTextScaleValue typedOther = other;
return scale == typedOther.scale && label == typedOther.label;
}
@override
int get hashCode => hashValues(scale, label);
@override
String toString() {
return '$runtimeType($label)';
}
}
const List<GalleryTextScaleValue> kAllGalleryTextScaleValues = const <GalleryTextScaleValue>[
const GalleryTextScaleValue(null, 'System Default'),
const GalleryTextScaleValue(0.8, 'Small'),
const GalleryTextScaleValue(1.0, 'Normal'),
const GalleryTextScaleValue(1.3, 'Large'),
const GalleryTextScaleValue(2.0, 'Huge'),
];

View File

@ -1,62 +0,0 @@
// Copyright 2018 The Chromium 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 GalleryTheme {
const GalleryTheme({ this.name, this.icon, this.theme });
final String name;
final IconData icon;
final ThemeData theme;
}
const int _kPurplePrimaryValue = 0xFF6200EE;
const MaterialColor _kPurpleSwatch = const MaterialColor(
_kPurplePrimaryValue,
const <int, Color> {
50: const Color(0xFFF2E7FE),
100: const Color(0xFFD7B7FD),
200: const Color(0xFFBB86FC),
300: const Color(0xFF9E55FC),
400: const Color(0xFF7F22FD),
500: const Color(_kPurplePrimaryValue),
700: const Color(0xFF3700B3),
800: const Color(0xFF270096),
900: const Color(0xFF190078),
}
);
final List<GalleryTheme> kAllGalleryThemes = <GalleryTheme>[
new GalleryTheme(
name: 'Light',
icon: Icons.brightness_5,
theme: new ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
),
),
new GalleryTheme(
name: 'Dark',
icon: Icons.brightness_7,
theme: new ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
),
),
new GalleryTheme(
name: 'Purple',
icon: Icons.brightness_6,
theme: new ThemeData(
brightness: Brightness.light,
primarySwatch: _kPurpleSwatch,
buttonColor: _kPurpleSwatch[500],
splashColor: Colors.white24,
splashFactory: InkRipple.splashFactory,
errorColor: const Color(0xFFFF1744),
buttonTheme: const ButtonThemeData(
textTheme: ButtonTextTheme.primary,
),
),
),
];

View File

@ -0,0 +1,65 @@
// Copyright 2018 The Chromium 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 GalleryTheme {
const GalleryTheme._(this.name, this.data);
final String name;
final ThemeData data;
}
final GalleryTheme kDarkGalleryTheme = new GalleryTheme._('Dark', _buildDarkTheme());
final GalleryTheme kLightGalleryTheme = new GalleryTheme._('Light', _buildLightTheme());
TextTheme _buildTextTheme(TextTheme base) {
return base.copyWith(
title: base.title.copyWith(
fontFamily: 'GoogleSans',
),
);
}
ThemeData _buildDarkTheme() {
const Color primaryColor = const Color(0xFF0175c2);
final ThemeData base = new ThemeData.dark();
return base.copyWith(
primaryColor: primaryColor,
buttonColor: primaryColor,
indicatorColor: Colors.white,
accentColor: const Color(0xFF13B9FD),
canvasColor: const Color(0xFF202124),
scaffoldBackgroundColor: const Color(0xFF202124),
backgroundColor: const Color(0xFF202124),
buttonTheme: const ButtonThemeData(
textTheme: ButtonTextTheme.primary,
),
textTheme: _buildTextTheme(base.textTheme),
primaryTextTheme: _buildTextTheme(base.primaryTextTheme),
accentTextTheme: _buildTextTheme(base.accentTextTheme),
);
}
ThemeData _buildLightTheme() {
const Color primaryColor = const Color(0xFF0175c2);
final ThemeData base = new ThemeData.light();
return base.copyWith(
primaryColor: primaryColor,
buttonColor: primaryColor,
indicatorColor: Colors.white,
splashColor: Colors.white24,
splashFactory: InkRipple.splashFactory,
accentColor: const Color(0xFF13B9FD),
canvasColor: Colors.white,
scaffoldBackgroundColor: Colors.white,
backgroundColor: Colors.white,
buttonTheme: const ButtonThemeData(
textTheme: ButtonTextTheme.primary,
),
textTheme: _buildTextTheme(base.textTheme),
primaryTextTheme: _buildTextTheme(base.primaryTextTheme),
accentTextTheme: _buildTextTheme(base.accentTextTheme),
);
}

View File

@ -15,7 +15,7 @@ dependencies:
flutter_gallery_assets: flutter_gallery_assets:
git: git:
url: https://flutter.googlesource.com/gallery-assets url: https://flutter.googlesource.com/gallery-assets
ref: d318485f208376e06d7e330d9f191141d14722b8 ref: 43590e625ab1b07f6a5809287ce16f7e61d9e165
charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
meta: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@ -79,6 +79,11 @@ flutter:
uses-material-design: true uses-material-design: true
assets: assets:
- lib/gallery/example_code.dart - lib/gallery/example_code.dart
- packages/flutter_gallery_assets/white_logo/logo.png
- packages/flutter_gallery_assets/white_logo/1.5x/logo.png
- packages/flutter_gallery_assets/white_logo/2.5x/logo.png
- packages/flutter_gallery_assets/white_logo/3.0x/logo.png
- packages/flutter_gallery_assets/white_logo/4.0x/logo.png
- packages/flutter_gallery_assets/videos/butterfly.mp4 - packages/flutter_gallery_assets/videos/butterfly.mp4
- packages/flutter_gallery_assets/animated_flutter_lgtm.gif - packages/flutter_gallery_assets/animated_flutter_lgtm.gif
- packages/flutter_gallery_assets/animated_flutter_stickers.webp - packages/flutter_gallery_assets/animated_flutter_stickers.webp
@ -166,5 +171,42 @@ flutter:
- family: AbrilFatface - family: AbrilFatface
fonts: fonts:
- asset: packages/flutter_gallery_assets/shrine/fonts/abrilfatface/AbrilFatface-Regular.ttf - asset: packages/flutter_gallery_assets/shrine/fonts/abrilfatface/AbrilFatface-Regular.ttf
- family: GalleryIcons
fonts:
- asset: packages/flutter_gallery_assets/fonts/GalleryIcons.ttf
- family: GoogleSans
fonts:
- asset: packages/flutter_gallery_assets/fonts/GoogleSans-BoldItalic.ttf
weight: 700
style: italic
- asset: packages/flutter_gallery_assets/fonts/GoogleSans-Bold.ttf
weight: 700
- asset: packages/flutter_gallery_assets/fonts/GoogleSans-Italic.ttf
weight: 400
style: italic
- asset: packages/flutter_gallery_assets/fonts/GoogleSans-MediumItalic.ttf
weight: 500
style: italic
- asset: packages/flutter_gallery_assets/fonts/GoogleSans-Medium.ttf
weight: 500
- asset: packages/flutter_gallery_assets/fonts/GoogleSans-Regular.ttf
weight: 400
- family: GoogleSansDisplay
fonts:
- asset: packages/flutter_gallery_assets/fonts/GoogleSansDisplay-BoldItalic.ttf
weight: 700
style: italic
- asset: packages/flutter_gallery_assets/fonts/GoogleSansDisplay-Bold.ttf
weight: 700
- asset: packages/flutter_gallery_assets/fonts/GoogleSansDisplay-Italic.ttf
weight: 400
style: italic
- asset: packages/flutter_gallery_assets/fonts/GoogleSansDisplay-MediumItalic.ttf
style: italic
weight: 500
- asset: packages/flutter_gallery_assets/fonts/GoogleSansDisplay-Medium.ttf
weight: 500
- asset: packages/flutter_gallery_assets/fonts/GoogleSansDisplay-Regular.ttf
weight: 400
# PUBSPEC CHECKSUM: 50c7 # PUBSPEC CHECKSUM: 50c7

View File

@ -14,87 +14,82 @@ void main() {
testWidgets('Flutter Gallery drawer item test', (WidgetTester tester) async { testWidgets('Flutter Gallery drawer item test', (WidgetTester tester) async {
bool hasFeedback = false; bool hasFeedback = false;
void mockOnSendFeedback() {
hasFeedback = true;
}
await tester.pumpWidget(new GalleryApp(onSendFeedback: mockOnSendFeedback)); await tester.pumpWidget(
new GalleryApp(
onSendFeedback: () {
hasFeedback = true;
},
),
);
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865 await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
await tester.pump(); // triggers a frame await tester.pump(); // triggers a frame
final Finder finder = find.byWidgetPredicate((Widget widget) { // Show the options page
return widget is Tooltip && widget.message == 'Open navigation menu'; await tester.tap(find.byTooltip('Show options page'));
}); await tester.pumpAndSettle();
expect(finder, findsOneWidget);
// Open drawer
await tester.tap(finder);
await tester.pump(); // start animation
await tester.pump(const Duration(seconds: 1)); // end animation
MaterialApp app = find.byType(MaterialApp).evaluate().first.widget; MaterialApp app = find.byType(MaterialApp).evaluate().first.widget;
expect(app.theme.brightness, equals(Brightness.light)); expect(app.theme.brightness, equals(Brightness.light));
// Change theme // Switch to the dark theme: first switch control
await tester.tap(find.text('Dark')); await tester.tap(find.byType(Switch).first);
await tester.pump(); // start animation await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // end animation
app = find.byType(MaterialApp).evaluate().first.widget; app = find.byType(MaterialApp).evaluate().first.widget;
expect(app.theme.brightness, equals(Brightness.dark)); expect(app.theme.brightness, equals(Brightness.dark));
expect(app.theme.platform, equals(TargetPlatform.android)); expect(app.theme.platform, equals(TargetPlatform.android));
// Change platform // Popup the platform menu: second menu button, choose 'Cupertino'
await tester.tap(find.text('iOS')); await tester.tap(find.byIcon(Icons.arrow_drop_down).at(1));
await tester.pump(); // start animation await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // end animation await tester.tap(find.text('Cupertino').at(1));
await tester.pumpAndSettle();
app = find.byType(MaterialApp).evaluate().first.widget; app = find.byType(MaterialApp).evaluate().first.widget;
expect(app.theme.platform, equals(TargetPlatform.iOS)); expect(app.theme.platform, equals(TargetPlatform.iOS));
// Verify the font scale. // Verify the font scale.
final Size origTextSize = tester.getSize(find.text('Small')); final Size origTextSize = tester.getSize(find.text('Text size'));
expect(origTextSize, equals(const Size(176.0, 14.0))); expect(origTextSize, equals(const Size(144.0, 16.0)));
// Switch font scale. // Popup the text size menu: first menu button, choose 'Small'
await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
await tester.pumpAndSettle();
await tester.tap(find.text('Small')); await tester.tap(find.text('Small'));
await tester.pump(); await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. Size textSize = tester.getSize(find.text('Text size'));
final Size textSize = tester.getSize(find.text('Small')); expect(textSize, equals(const Size(116.0, 13.0)));
expect(textSize, equals(const Size(176.0, 11.0)));
// Set font scale back to default. // Set font scale back to the default.
await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
await tester.pumpAndSettle();
await tester.tap(find.text('System Default')); await tester.tap(find.text('System Default'));
await tester.pump(); await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. textSize = tester.getSize(find.text('Text size'));
final Size newTextSize = tester.getSize(find.text('Small')); expect(textSize, origTextSize);
expect(newTextSize, equals(origTextSize));
// Scroll to the bottom of the menu. // Switch to slow animation: third switch control
await tester.drag(find.text('Small'), const Offset(0.0, -1000.0)); expect(timeDilation, 1.0);
await tester.pump(); await tester.tap(find.byType(Switch).at(2));
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. await tester.pumpAndSettle();
// Test slow animations.
expect(timeDilation, equals(1.0));
await tester.tap(find.text('Animate Slowly'));
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
expect(timeDilation, greaterThan(1.0)); expect(timeDilation, greaterThan(1.0));
// Put back time dilation (so as not to throw off tests after this one). // Restore normal animation: third switch control
await tester.tap(find.text('Animate Slowly')); await tester.tap(find.byType(Switch).at(2));
await tester.pump(); await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. expect(timeDilation, 1.0);
expect(timeDilation, equals(1.0));
// Send feedback. // Send feedback.
expect(hasFeedback, false); expect(hasFeedback, false);
// Scroll to the end
await tester.drag(find.text('Text size'), const Offset(0.0, -1000.0));
await tester.pumpAndSettle();
await tester.tap(find.text('Send feedback')); await tester.tap(find.text('Send feedback'));
await tester.pump(); await tester.pumpAndSettle();
expect(hasFeedback, true); expect(hasFeedback, true);
// Close drawer // Hide the options page
await tester.tap(find.byType(DrawerController)); await tester.tap(find.byTooltip('Show options page'));
await tester.pump(); // start animation await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // end animation
}); });
} }

View File

@ -18,25 +18,16 @@ void main() {
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865 await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
await tester.pump(); // triggers a frame await tester.pump(); // triggers a frame
Scrollable.ensureVisible(tester.element(find.text('Material')), alignment: 0.5);
// Scroll the Buttons demo into view so that a tap will succeed await tester.pumpAndSettle();
final Offset allDemosOrigin = tester.getTopRight(find.text('Vignettes')); await tester.tap(find.text('Material'));
final Finder button = find.text('Buttons'); await tester.pumpAndSettle();
while (button.evaluate().isEmpty) {
await tester.dragFrom(allDemosOrigin, const Offset(0.0, -200.0));
await tester.pumpAndSettle();
}
// Launch the buttons demo and then prove that showing the example // Launch the buttons demo and then prove that showing the example
// code dialog does not crash. // code dialog does not crash.
await tester.tap(find.text('Buttons')); await tester.tap(find.text('Buttons'));
await tester.pump(); // start animation await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // end animation
await tester.tap(find.text('RAISED'));
await tester.pump(); // start animation
await tester.pump(const Duration(seconds: 1)); // end animation
await tester.tap(find.byTooltip('Show example code')); await tester.tap(find.byTooltip('Show example code'));
await tester.pump(); // start animation await tester.pump(); // start animation

View File

@ -10,18 +10,15 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gallery/gallery/app.dart'; import 'package:flutter_gallery/gallery/demos.dart';
import 'package:flutter_gallery/gallery/item.dart'; import 'package:flutter_gallery/gallery/app.dart' show GalleryApp;
// Reports success or failure to the native code. // Reports success or failure to the native code.
const MethodChannel _kTestChannel = const MethodChannel('io.flutter.demo.gallery/TestLifecycleListener'); const MethodChannel _kTestChannel = const MethodChannel('io.flutter.demo.gallery/TestLifecycleListener');
// The titles for all of the Gallery demos.
final List<String> _kAllDemos = kAllGalleryItems.map((GalleryItem item) => item.title).toList();
// We don't want to wait for animations to complete before tapping the // We don't want to wait for animations to complete before tapping the
// back button in the demos with these titles. // back button in the demos with these titles.
const List<String> _kUnsynchronizedDemos = const <String>[ const List<String> _kUnsynchronizedDemoTitles = const <String>[
'Progress indicators', 'Progress indicators',
'Activity Indicator', 'Activity Indicator',
'Video', 'Video',
@ -29,38 +26,45 @@ const List<String> _kUnsynchronizedDemos = const <String>[
// These demos can't be backed out of by tapping a button whose // These demos can't be backed out of by tapping a button whose
// tooltip is 'Back'. // tooltip is 'Back'.
const List<String> _kSkippedDemos = const <String>[ const List<String> _kSkippedDemoTitles = const <String>[
'Pull to refresh', 'Pull to refresh',
'Progress indicators',
'Activity Indicator',
'Video',
]; ];
Future<Null> main() async { Future<Null> main() async {
try { try {
// Verify that _kUnsynchronizedDemos and _kSkippedDemos identify // Verify that _kUnsynchronizedDemos and _kSkippedDemos identify
// demos that actually exist. // demos that actually exist.
if (!new Set<String>.from(_kAllDemos).containsAll(_kUnsynchronizedDemos)) final List<String> allDemoTitles = kAllGalleryDemos.map((GalleryDemo demo) => demo.title).toList();
fail('Unrecognized demo names in _kUnsynchronizedDemos: $_kUnsynchronizedDemos'); if (!new Set<String>.from(allDemoTitles).containsAll(_kUnsynchronizedDemoTitles))
if (!new Set<String>.from(_kAllDemos).containsAll(_kSkippedDemos)) fail('Unrecognized demo titles in _kUnsynchronizedDemosTitles: $_kUnsynchronizedDemoTitles');
fail('Unrecognized demo names in _kSkippedDemos: $_kSkippedDemos'); if (!new Set<String>.from(allDemoTitles).containsAll(_kSkippedDemoTitles))
fail('Unrecognized demo names in _kSkippedDemoTitles: $_kSkippedDemoTitles');
runApp(const GalleryApp()); runApp(const GalleryApp());
final _LiveWidgetController controller = new _LiveWidgetController(); final _LiveWidgetController controller = new _LiveWidgetController();
for (String demo in _kAllDemos) { for (GalleryDemoCategory category in kAllGalleryDemoCategories) {
print('Testing "$demo" demo'); await controller.tap(find.text(category.name));
final Finder menuItem = find.text(demo); for (GalleryDemo demo in kGalleryCategoryToDemos[category]) {
await controller.scrollIntoView(menuItem, alignment: 0.5); final Finder demoItem = find.text(demo.title);
await controller.scrollIntoView(demoItem, alignment: 0.5);
if (_kSkippedDemos.contains(demo)) { if (_kSkippedDemoTitles.contains(demo.title)) {
print('> skipped $demo'); print('> skipped $demo');
continue; continue;
} }
for (int i = 0; i < 2; i += 1) { for (int i = 0; i < 2; i += 1) {
await controller.tap(menuItem); // Launch the demo await controller.tap(demoItem); // Launch the demo
controller.frameSync = !_kUnsynchronizedDemos.contains(demo); controller.frameSync = !_kUnsynchronizedDemoTitles.contains(demo.title);
await controller.tap(find.byTooltip('Back')); await controller.tap(find.byTooltip('Back'));
controller.frameSync = true; controller.frameSync = true;
}
print('Success');
} }
print('Success'); await controller.tap(find.byTooltip('Back'));
} }
_kTestChannel.invokeMethod('success'); _kTestChannel.invokeMethod('success');

View File

@ -17,7 +17,7 @@ void main() {
// The bug only manifests itself when the screen's orientation is portrait // The bug only manifests itself when the screen's orientation is portrait
const Center( const Center(
child: const SizedBox( child: const SizedBox(
width: 400.0, width: 450.0,
height: 800.0, height: 800.0,
child: const GalleryApp() child: const GalleryApp()
) )
@ -26,29 +26,32 @@ void main() {
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865 await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
await tester.pump(); // triggers a frame await tester.pump(); // triggers a frame
await tester.tap(find.text('Vignettes'));
await tester.pumpAndSettle();
await tester.tap(find.text('Pesto')); await tester.tap(find.text('Pesto'));
await tester.pump(); // Launch pesto await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // transition is complete
await tester.tap(find.text('Pesto Bruschetta')); await tester.tap(find.text('Pesto Bruschetta'));
await tester.pump(); // Launch the recipe page await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // transition is complete
await tester.drag(find.text('Pesto Bruschetta'), const Offset(0.0, -300.0)); await tester.drag(find.text('Pesto Bruschetta'), const Offset(0.0, -300.0));
await tester.pump(); await tester.pumpAndSettle();
Navigator.pop(find.byType(Scaffold).evaluate().single); Navigator.pop(find.byType(Scaffold).evaluate().single);
await tester.pump(); await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // transition is complete
}); });
testWidgets('Pesto can be scrolled all the way down', (WidgetTester tester) async { testWidgets('Pesto can be scrolled all the way down', (WidgetTester tester) async {
await tester.pumpWidget(const GalleryApp()); await tester.pumpWidget(const GalleryApp());
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865 await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
await tester.pump(); // triggers a frame
await tester.tap(find.text('Vignettes'));
await tester.pumpAndSettle();
await tester.tap(find.text('Pesto')); await tester.tap(find.text('Pesto'));
await tester.pump(); // Launch pesto await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // transition is complete
await tester.fling(find.text('Pesto Bruschetta'), const Offset(0.0, -200.0), 10000.0); await tester.fling(find.text('Pesto Bruschetta'), const Offset(0.0, -200.0), 10000.0);
await tester.pumpAndSettle(); // start and finish fling await tester.pumpAndSettle(); // start and finish fling

View File

@ -16,37 +16,29 @@ void main() {
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865 await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
await tester.pump(); // triggers a frame await tester.pump(); // triggers a frame
final Finder finder = find.byWidgetPredicate((Widget widget) { final Finder showOptionsPageButton = find.byTooltip('Show options page');
return widget is Tooltip && widget.message == 'Open navigation menu';
});
expect(finder, findsOneWidget);
// Open drawer // Show the options page
await tester.tap(finder); await tester.tap(showOptionsPageButton);
await tester.pump(); // start animation await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // end animation
// Change theme // Switch to the dark theme: the first switch control
await tester.tap(find.text('Dark')); await tester.tap(find.byType(Switch).first);
await tester.pump(); // start animation await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // end animation
// Close drawer // Close the options page
await tester.tap(find.byType(DrawerController)); expect(showOptionsPageButton, findsOneWidget);
await tester.pump(); // start animation await tester.tap(showOptionsPageButton);
await tester.pump(const Duration(seconds: 1)); // end animation await tester.pumpAndSettle();
// Open Demos // Show the vignettes
await tester.tap(find.text('Vignettes')); await tester.tap(find.text('Vignettes'));
await tester.pump(); // start animation await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // end animation
// Open Flexible space toolbar // Show the Contact profile demo and scroll it upwards
await tester.tap(find.text('Contact profile')); await tester.tap(find.text('Contact profile'));
await tester.pump(); // start animation await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1)); // end animation
// Scroll it up
await tester.drag(find.text('(650) 555-1234'), const Offset(0.0, -50.0)); await tester.drag(find.text('(650) 555-1234'), const Offset(0.0, -50.0));
await tester.pump(const Duration(milliseconds: 200)); await tester.pump(const Duration(milliseconds: 200));
await tester.drag(find.text('(650) 555-1234'), const Offset(0.0, -50.0)); await tester.drag(find.text('(650) 555-1234'), const Offset(0.0, -50.0));

View File

@ -2,31 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:collection' show LinkedHashSet;
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gallery/gallery/item.dart' show GalleryItem, kAllGalleryItems; import 'package:flutter_gallery/gallery/demos.dart';
import 'package:flutter_gallery/gallery/app.dart' show GalleryApp; import 'package:flutter_gallery/gallery/app.dart' show GalleryApp;
const String kCaption = 'Flutter Gallery'; // This title is visible on the home and demo category pages. It's
// not visible when the demos are running.
const String kGalleryTitle = 'Flutter gallery';
final List<String> demoCategories = new LinkedHashSet<String>.from( // All of the classes printed by debugDump etc, must have toString()
kAllGalleryItems.map<String>((GalleryItem item) => item.category) // values approved by verityToStringOutput().
).toList(); int toStringErrors = 0;
final List<String> routeNames =
kAllGalleryItems.map((GalleryItem item) => item.routeName).toList();
Finder findGalleryItemByRouteName(WidgetTester tester, String routeName) {
return find.byWidgetPredicate((Widget widget) {
return widget is GalleryItem && widget.routeName == routeName;
});
}
int errors = 0;
void reportToStringError(String name, String route, int lineNumber, List<String> lines, String message) { void reportToStringError(String name, String route, int lineNumber, List<String> lines, String message) {
// If you're on line 12, then it has index 11. // If you're on line 12, then it has index 11.
@ -36,7 +26,7 @@ void reportToStringError(String name, String route, int lineNumber, List<String>
final int firstLine = math.max(0, lineNumber - margin); final int firstLine = math.max(0, lineNumber - margin);
final int lastLine = math.min(lines.length, lineNumber + margin); final int lastLine = math.min(lines.length, lineNumber + margin);
print('$name : $route : line $lineNumber of ${lines.length} : $message; nearby lines were:\n ${lines.sublist(firstLine, lastLine).join("\n ")}'); print('$name : $route : line $lineNumber of ${lines.length} : $message; nearby lines were:\n ${lines.sublist(firstLine, lastLine).join("\n ")}');
errors += 1; toStringErrors += 1;
} }
void verifyToStringOutput(String name, String route, String testString) { void verifyToStringOutput(String name, String route, String testString) {
@ -56,22 +46,16 @@ void verifyToStringOutput(String name, String route, String testString) {
} }
} }
// Start a gallery demo and then go back. This function assumes that the Future<Null> smokeDemo(WidgetTester tester, GalleryDemo demo) async {
// we're starting on the home route and that the submenu that contains print(demo);
// the item for a demo that pushes route 'routeName' is already open.
Future<Null> smokeDemo(WidgetTester tester, String routeName) async {
// Ensure that we're (likely to be) on the home page
final Finder menuItem = findGalleryItemByRouteName(tester, routeName);
expect(menuItem, findsOneWidget);
// Don't use pumpUntilNoTransientCallbacks in this function, because some of // Don't use pumpUntilNoTransientCallbacks in this function, because some of
// the smoketests have infinitely-running animations (e.g. the progress // the smoketests have infinitely-running animations (e.g. the progress
// indicators demo). // indicators demo).
await tester.tap(menuItem); await tester.tap(find.text(demo.title));
await tester.pump(); // Launch the demo. await tester.pump(); // Launch the demo.
await tester.pump(const Duration(milliseconds: 400)); // Wait until the demo has opened. await tester.pump(const Duration(milliseconds: 400)); // Wait until the demo has opened.
expect(find.text(kCaption), findsNothing); expect(find.text(kGalleryTitle), findsNothing);
// Leave the demo on the screen briefly for manual testing. // Leave the demo on the screen briefly for manual testing.
await tester.pump(const Duration(milliseconds: 400)); await tester.pump(const Duration(milliseconds: 400));
@ -85,6 +69,7 @@ Future<Null> smokeDemo(WidgetTester tester, String routeName) async {
await tester.pump(const Duration(milliseconds: 400)); await tester.pump(const Duration(milliseconds: 400));
// Verify that the dumps are pretty. // Verify that the dumps are pretty.
final String routeName = demo.routeName;
verifyToStringOutput('debugDumpApp', routeName, WidgetsBinding.instance.renderViewElement.toStringDeep()); verifyToStringOutput('debugDumpApp', routeName, WidgetsBinding.instance.renderViewElement.toStringDeep());
verifyToStringOutput('debugDumpRenderTree', routeName, RendererBinding.instance?.renderView?.toStringDeep()); verifyToStringOutput('debugDumpRenderTree', routeName, RendererBinding.instance?.renderView?.toStringDeep());
verifyToStringOutput('debugDumpLayerTree', routeName, RendererBinding.instance?.renderView?.debugLayer?.toStringDeep()); verifyToStringOutput('debugDumpLayerTree', routeName, RendererBinding.instance?.renderView?.debugLayer?.toStringDeep());
@ -108,74 +93,85 @@ Future<Null> smokeDemo(WidgetTester tester, String routeName) async {
await tester.pump(); // Start the pop "back" operation. await tester.pump(); // Start the pop "back" operation.
await tester.pump(); // Complete the willPop() Future. await tester.pump(); // Complete the willPop() Future.
await tester.pump(const Duration(milliseconds: 400)); // Wait until it has finished. await tester.pump(const Duration(milliseconds: 400)); // Wait until it has finished.
return null;
} }
Future<Null> runSmokeTest(WidgetTester tester) async { Future<Null> smokeOptionsPage(WidgetTester tester) async {
bool hasFeedback = false; final Finder showOptionsPageButton = find.byTooltip('Show options page');
void mockOnSendFeedback() {
hasFeedback = true;
}
await tester.pumpWidget(new GalleryApp(onSendFeedback: mockOnSendFeedback)); // Show the options page
await tester.tap(showOptionsPageButton);
await tester.pumpAndSettle();
// Switch to the dark theme: first switch control
await tester.tap(find.byType(Switch).first);
await tester.pumpAndSettle();
// Switch back to the light theme: first switch control again
await tester.tap(find.byType(Switch).first);
await tester.pumpAndSettle();
// Popup the text size menu: first menu button, choose 'Small'
await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
await tester.pumpAndSettle();
await tester.tap(find.text('Small'));
await tester.pumpAndSettle();
// Popup the text size menu: first menu button, choose 'Normal'
await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
await tester.pumpAndSettle();
await tester.tap(find.text('Normal'));
await tester.pumpAndSettle();
// Scroll the 'Send feedback' item into view
await tester.drag(find.text('Normal'), const Offset(0.0, -1000.0));
await tester.pumpAndSettle();
await tester.tap(find.text('Send feedback'));
await tester.pumpAndSettle();
// Close the options page
expect(showOptionsPageButton, findsOneWidget);
await tester.tap(showOptionsPageButton);
await tester.pumpAndSettle();
}
Future<Null> smokeGallery(WidgetTester tester) async {
bool sendFeedbackButtonPressed = false;
await tester.pumpWidget(
new GalleryApp(
onSendFeedback: () {
sendFeedbackButtonPressed = true; // see smokeOptionsPage()
},
),
);
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865 await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
await tester.pump(); // triggers a frame await tester.pump(); // triggers a frame
expect(find.text(kCaption), findsOneWidget); expect(find.text(kGalleryTitle), findsOneWidget);
for (String routeName in routeNames) { for (GalleryDemoCategory category in kAllGalleryDemoCategories) {
final Finder finder = findGalleryItemByRouteName(tester, routeName); await tester.tap(find.text(category.name));
Scrollable.ensureVisible(tester.element(finder), alignment: 0.5); await tester.pumpAndSettle();
for (GalleryDemo demo in kGalleryCategoryToDemos[category]) {
Scrollable.ensureVisible(tester.element(find.text(demo.title)), alignment: 0.5);
await smokeDemo(tester, demo);
tester.binding.debugAssertNoTransientCallbacks('A transient callback was still active after running $demo');
}
await tester.pageBack();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await smokeDemo(tester, routeName);
tester.binding.debugAssertNoTransientCallbacks('A transient callback was still active after leaving route $routeName');
} }
expect(errors, 0); expect(toStringErrors, 0);
final Finder navigationMenuButton = find.byTooltip('Open navigation menu'); await smokeOptionsPage(tester);
expect(navigationMenuButton, findsOneWidget); expect(sendFeedbackButtonPressed, true);
await tester.tap(navigationMenuButton);
await tester.pump(); // Start opening drawer.
await tester.pump(const Duration(seconds: 1)); // Wait until it's really opened.
// Switch theme.
await tester.tap(find.text('Dark'));
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
// Switch theme.
await tester.tap(find.text('Light'));
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
// Switch font scale.
await tester.tap(find.text('Small'));
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
// Switch font scale back to default.
await tester.tap(find.text('System Default'));
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
// Scroll the 'Send feedback' item into view.
await tester.drag(find.text('Small'), const Offset(0.0, -1000.0));
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
// Send feedback.
expect(hasFeedback, false);
await tester.tap(find.text('Send feedback'));
await tester.pump();
expect(hasFeedback, true);
} }
void main() { void main() {
testWidgets('Flutter Gallery app smoke test', runSmokeTest); testWidgets('Flutter Gallery app smoke test', smokeGallery);
testWidgets('Flutter Gallery app smoke test with semantics', (WidgetTester tester) async { testWidgets('Flutter Gallery app smoke test with semantics', (WidgetTester tester) async {
RendererBinding.instance.setSemanticsEnabled(true); RendererBinding.instance.setSemanticsEnabled(true);
await runSmokeTest(tester); await smokeGallery(tester);
RendererBinding.instance.setSemanticsEnabled(false); RendererBinding.instance.setSemanticsEnabled(false);
}); });
} }

View File

@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gallery/gallery/app.dart'; import 'package:flutter_gallery/gallery/app.dart' show GalleryApp;
Future<String> mockUpdateUrlFetcher() { Future<String> mockUpdateUrlFetcher() {
// A real implementation would connect to the network to retrieve this value // A real implementation would connect to the network to retrieve this value
@ -26,8 +26,8 @@ void main() {
await tester.tap(find.text('NO THANKS')); await tester.tap(find.text('NO THANKS'));
await tester.pump(); await tester.pump();
await tester.tap(find.text('Shrine')); await tester.tap(find.text('Vignettes'));
await tester.pump(); // Launch shrine await tester.pump(); // Launch
await tester.pump(const Duration(seconds: 1)); // transition is complete await tester.pump(const Duration(seconds: 1)); // transition is complete
final Finder backButton = find.byTooltip('Back'); final Finder backButton = find.byTooltip('Back');

View File

@ -14,14 +14,17 @@ void main() {
}); });
test('navigation', () async { test('navigation', () async {
final SerializableFinder menuItem = find.text('Text fields'); await driver.tap(find.text('Material'));
await driver.scrollUntilVisible(find.byType('CustomScrollView'), menuItem,
final SerializableFinder demoList = find.byValueKey('GalleryDemoList');
final SerializableFinder demoItem = find.text('Text fields');
await driver.scrollUntilVisible(demoList, demoItem,
dyScroll: -300.0, dyScroll: -300.0,
alignment: 0.5, alignment: 0.5,
timeout: const Duration(minutes: 1), timeout: const Duration(minutes: 1),
); );
for (int i = 0; i < 15; i++) { for (int i = 0; i < 15; i++) {
await driver.tap(menuItem); await driver.tap(demoItem);
await driver.tap(find.byTooltip('Back')); await driver.tap(find.byTooltip('Back'));
} }
}); });

View File

@ -22,24 +22,21 @@ void main() {
test('measure', () async { test('measure', () async {
final Timeline timeline = await driver.traceAction(() async { final Timeline timeline = await driver.traceAction(() async {
final SerializableFinder home = find.byValueKey('Gallery List'); await driver.tap(find.text('Material'));
expect(home, isNotNull);
await driver.tap(find.text('Vignettes')); final SerializableFinder demoList = find.byValueKey('GalleryDemoList');
await driver.tap(find.text('Components'));
await driver.tap(find.text('Style'));
// TODO(eseidel): These are very artificial scrolls, we should use better // TODO(eseidel): These are very artificial scrolls, we should use better
// https://github.com/flutter/flutter/issues/3316 // https://github.com/flutter/flutter/issues/3316
// Scroll down // Scroll down
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
await driver.scroll(home, 0.0, -300.0, const Duration(milliseconds: 300)); await driver.scroll(demoList, 0.0, -300.0, const Duration(milliseconds: 300));
await new Future<Null>.delayed(const Duration(milliseconds: 500)); await new Future<Null>.delayed(const Duration(milliseconds: 500));
} }
// Scroll up // Scroll up
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
await driver.scroll(home, 0.0, 300.0, const Duration(milliseconds: 300)); await driver.scroll(demoList, 0.0, 300.0, const Duration(milliseconds: 300));
await new Future<Null>.delayed(const Duration(milliseconds: 500)); await new Future<Null>.delayed(const Duration(milliseconds: 500));
} }
}); });

View File

@ -6,13 +6,13 @@ import 'dart:async';
import 'dart:convert' show JsonEncoder; import 'dart:convert' show JsonEncoder;
import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_gallery/gallery/item.dart'; import 'package:flutter_gallery/gallery/demos.dart';
import 'package:flutter_gallery/main.dart' as app; import 'package:flutter_gallery/main.dart' as app;
Future<String> _handleMessages(String message) async { Future<String> _handleMessages(String message) async {
assert(message == 'demoNames'); assert(message == 'demoNames');
return const JsonEncoder.withIndent(' ').convert( return const JsonEncoder.withIndent(' ').convert(
kAllGalleryItems.map((GalleryItem item) => item.title).toList(), kAllGalleryDemos.map((GalleryDemo demo) => '${demo.title}@${demo.category.name}').toList(),
); );
} }

View File

@ -45,8 +45,7 @@ const List<String> kUnsynchronizedDemos = const <String>[
'Video', 'Video',
]; ];
// All of the gallery demo titles in the order they appear on the // All of the gallery demos, identified as "title@category".
// gallery home page.
// //
// These names are reported by the test app, see _handleMessages() // These names are reported by the test app, see _handleMessages()
// in transitions_perf.dart. // in transitions_perf.dart.
@ -121,20 +120,26 @@ Future<Null> saveDurationsHistogram(List<Map<String, dynamic>> events, String ou
/// Scrolls each demo menu item into view, launches it, then returns to the /// Scrolls each demo menu item into view, launches it, then returns to the
/// home screen twice. /// home screen twice.
Future<Null> runDemos(List<String> demos, FlutterDriver driver) async { Future<Null> runDemos(List<String> demos, FlutterDriver driver) async {
final SerializableFinder demoList = find.byValueKey('GalleryDemoList');
String currentDemoCategory;
for (String demo in demos) { for (String demo in demos) {
print('Testing "$demo" demo'); final String demoAtCategory = _allDemos.firstWhere((String s) => s.startsWith(demo));
final SerializableFinder menuItem = find.text(demo); final String demoCategory = demoAtCategory.substring(demoAtCategory.indexOf('@') + 1);
await driver.scrollUntilVisible(find.byType('CustomScrollView'), menuItem,
dyScroll: -48.0, if (currentDemoCategory == null) {
alignment: 0.5, await driver.tap(find.text(demoCategory));
); } else if (currentDemoCategory != demoCategory) {
await driver.tap(find.byTooltip('Back'));
await driver.tap(find.text(demoCategory));
}
currentDemoCategory = demoCategory;
final SerializableFinder demoItem = find.text(demo);
await driver.scrollUntilVisible(demoList, demoItem, dyScroll: -48.0, alignment: 0.5);
for (int i = 0; i < 2; i += 1) { for (int i = 0; i < 2; i += 1) {
await driver.tap(menuItem); // Launch the demo await driver.tap(demoItem); // Launch the demo
// This demo's back button isn't initially visible.
if (demo == 'Backdrop')
await driver.tap(find.byTooltip('Tap to dismiss'));
if (kUnsynchronizedDemos.contains(demo)) { if (kUnsynchronizedDemos.contains(demo)) {
await driver.runUnsynchronized<Future<Null>>(() async { await driver.runUnsynchronized<Future<Null>>(() async {
@ -144,8 +149,12 @@ Future<Null> runDemos(List<String> demos, FlutterDriver driver) async {
await driver.tap(find.byTooltip('Back')); await driver.tap(find.byTooltip('Back'));
} }
} }
print('Success'); print('Success');
} }
// Return to the home screen
await driver.tap(find.byTooltip('Back'));
} }
void main([List<String> args = const <String>[]]) { void main([List<String> args = const <String>[]]) {
@ -171,6 +180,7 @@ void main([List<String> args = const <String>[]]) {
}); });
test('all demos', () async { test('all demos', () async {
// Collect timeline data for just a limited set of demos to avoid OOMs. // Collect timeline data for just a limited set of demos to avoid OOMs.
final Timeline timeline = await driver.traceAction( final Timeline timeline = await driver.traceAction(
() async { () async {
@ -190,14 +200,9 @@ void main([List<String> args = const <String>[]]) {
final String histogramPath = path.join(testOutputsDirectory, 'transition_durations.timeline.json'); final String histogramPath = path.join(testOutputsDirectory, 'transition_durations.timeline.json');
await saveDurationsHistogram(timeline.json['traceEvents'], histogramPath); await saveDurationsHistogram(timeline.json['traceEvents'], histogramPath);
// Scroll back to the top
await driver.scrollUntilVisible(find.byType('CustomScrollView'), find.text(_allDemos[0]),
dyScroll: 200.0,
alignment: 0.0
);
// Execute the remaining tests. // Execute the remaining tests.
final Set<String> unprofiledDemos = new Set<String>.from(_allDemos)..removeAll(kProfiledDemos); final List<String> allDemoNames = _allDemos.map((String s) => s.substring(0, s.indexOf('@')));
final Set<String> unprofiledDemos = new Set<String>.from(allDemoNames)..removeAll(kProfiledDemos);
await runDemos(unprofiledDemos.toList(), driver); await runDemos(unprofiledDemos.toList(), driver);
}, timeout: const Timeout(const Duration(minutes: 5))); }, timeout: const Timeout(const Duration(minutes: 5)));