New Flutter Gallery UI (#16936)
A new front-end for the Flutter Gallery example.
@ -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"
|
||||||
|
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 14 KiB |
83
examples/flutter_gallery/lib/gallery/about.dart
Normal 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: '.',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
330
examples/flutter_gallery/lib/gallery/backdrop.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
},
|
||||||
|
);
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -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(() {
|
||||||
|
50
examples/flutter_gallery/lib/gallery/icons.dart
Normal 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');
|
||||||
|
}
|
466
examples/flutter_gallery/lib/gallery/options.dart
Normal 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
37
examples/flutter_gallery/lib/gallery/scales.dart
Normal 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'),
|
||||||
|
];
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
65
examples/flutter_gallery/lib/gallery/themes.dart
Normal 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),
|
||||||
|
);
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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');
|
||||||
|
@ -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
|
||||||
|
@ -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));
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
|
@ -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'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)));
|
||||||
|