1
0
mirror of https://github.com/flutter/samples.git synced 2026-04-06 03:31:03 +00:00

Add flutter_web samples (#75)

This commit is contained in:
Kevin Moore
2019-05-07 13:32:08 -07:00
committed by Andrew Brogdon
parent 42f2dce01b
commit 3fe927cb29
697 changed files with 241026 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
// 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_web/gestures.dart';
import 'package:flutter_web/material.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: 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: '2018 Preview',
//applicationIcon: const FlutterLogo(),
applicationLegalese: '© 2018 The Chromium Authors',
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 24.0),
child: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
style: aboutTextStyle,
text: 'Flutter web is an early-stage, web framework. '
'This gallery is a preview of '
"Flutter's many widgets, behaviors, animations, layouts, "
'and more. Learn more about Flutter at '),
_LinkTextSpan(
style: linkStyle,
url: 'https://flutter.io',
),
TextSpan(
style: aboutTextStyle,
text: '.\n\nTo see the source code for flutter ',
),
_LinkTextSpan(
style: linkStyle,
url: 'https://goo.gl/iv1p4G',
text: 'flutter github repo',
),
TextSpan(
style: aboutTextStyle,
text: '.',
),
],
),
),
),
],
);
}

View File

@@ -0,0 +1,135 @@
// 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:async';
import 'package:flutter_web/foundation.dart' show defaultTargetPlatform;
import 'package:flutter_web/material.dart';
import 'package:flutter_web/scheduler.dart' show timeDilation;
import 'demos.dart';
import 'home.dart';
import 'options.dart';
import 'scales.dart';
import 'themes.dart';
class GalleryApp extends StatefulWidget {
const GalleryApp({
Key key,
this.enablePerformanceOverlay = true,
this.enableRasterCacheImagesCheckerboard = true,
this.enableOffscreenLayersCheckerboard = true,
this.onSendFeedback,
this.testMode = false,
}) : super(key: key);
final bool enablePerformanceOverlay;
final bool enableRasterCacheImagesCheckerboard;
final bool enableOffscreenLayersCheckerboard;
final VoidCallback onSendFeedback;
final bool testMode;
@override
_GalleryAppState createState() => _GalleryAppState();
}
class _GalleryAppState extends State<GalleryApp> {
GalleryOptions _options;
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 Map<String, WidgetBuilder>.fromIterable(
kAllGalleryDemos,
key: (dynamic demo) => '${demo.routeName}',
value: (dynamic demo) => demo.buildRoute,
);
}
@override
void initState() {
super.initState();
_options = GalleryOptions(
theme: kLightGalleryTheme,
textScaleFactor: kAllGalleryTextScaleValues[0],
timeDilation: timeDilation,
platform: defaultTargetPlatform,
);
}
@override
void dispose() {
_timeDilationTimer?.cancel();
_timeDilationTimer = null;
super.dispose();
}
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 = Timer(const Duration(milliseconds: 150), () {
timeDilation = newOptions.timeDilation;
});
} else {
timeDilation = newOptions.timeDilation;
}
}
_options = newOptions;
});
}
Widget _applyTextScaleFactor(Widget child) {
return Builder(
builder: (BuildContext context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaleFactor: _options.textScaleFactor.scale,
),
child: child,
);
},
);
}
@override
Widget build(BuildContext context) {
Widget home = GalleryHome(
testMode: widget.testMode,
optionsPage: GalleryOptionsPage(
options: _options,
onOptionsChanged: _handleOptionsChanged,
onSendFeedback: widget.onSendFeedback ??
() {
// TODO: launch('https://github.com/flutter/flutter/issues/new', forceSafariVC: false);
},
),
);
return MaterialApp(
theme: _options.theme.data.copyWith(platform: _options.platform),
title: 'Flutter Web Gallery',
color: Colors.grey,
showPerformanceOverlay: _options.showPerformanceOverlay,
checkerboardOffscreenLayers: _options.showOffscreenLayersCheckerboard,
checkerboardRasterCacheImages: _options.showRasterCacheImagesCheckerboard,
routes: _buildRoutes(),
builder: (BuildContext context, Widget child) {
return Directionality(
textDirection: _options.textDirection,
child: _applyTextScaleFactor(child),
);
},
home: home,
);
}
}

View File

@@ -0,0 +1,366 @@
// 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_web/rendering.dart';
import 'package:flutter_web/material.dart';
const double _kFrontHeadingHeight = 32.0; // front layer beveled rectangle
const double _kFrontClosedHeight = 92.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 Animatable<BorderRadius> _kFrontHeadingBevelRadius = BorderRadiusTween(
begin: const BorderRadius.only(
topLeft: Radius.circular(12.0),
topRight: Radius.circular(12.0),
),
end: const BorderRadius.only(
topLeft: Radius.circular(_kFrontHeadingHeight),
topRight: Radius.circular(_kFrontHeadingHeight),
),
);
class _TappableWhileStatusIs extends StatefulWidget {
const _TappableWhileStatusIs(
this.status, {
Key key,
this.controller,
this.child,
}) : super(key: key);
final AnimationController controller;
final AnimationStatus status;
final Widget child;
@override
_TappableWhileStatusIsState createState() => _TappableWhileStatusIsState();
}
class _TappableWhileStatusIsState extends State<_TappableWhileStatusIs> {
bool _active;
@override
void initState() {
super.initState();
widget.controller.addStatusListener(_handleStatusChange);
_active = widget.controller.status == widget.status;
}
@override
void dispose() {
widget.controller.removeStatusListener(_handleStatusChange);
super.dispose();
}
void _handleStatusChange(AnimationStatus status) {
final bool value = widget.controller.status == widget.status;
if (_active != value) {
setState(() {
_active = value;
});
}
}
@override
Widget build(BuildContext context) {
return AbsorbPointer(
absorbing: !_active,
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 = CurvedAnimation(
parent: ReverseAnimation(progress),
curve: const Interval(0.5, 1.0),
).value;
final double opacity2 = CurvedAnimation(
parent: progress,
curve: const Interval(0.5, 1.0),
).value;
return Stack(
alignment: alignment,
children: <Widget>[
Opacity(
opacity: opacity1,
child: Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: child1,
),
),
Opacity(
opacity: opacity2,
child: Semantics(
scopesRoute: true,
explicitChildNodes: true,
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>[
Container(
alignment: Alignment.center,
width: 56.0,
child: leading,
),
Expanded(
child: title,
),
];
if (trailing != null) {
children.add(
Container(
alignment: Alignment.center,
width: 56.0,
child: trailing,
),
);
}
final ThemeData theme = Theme.of(context);
return IconTheme.merge(
data: theme.primaryIconTheme,
child: DefaultTextStyle(
style: theme.primaryTextTheme.title,
child: SizedBox(
height: _kBackAppBarHeight,
child: 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() => _BackdropState();
}
class _BackdropState extends State<Backdrop>
with SingleTickerProviderStateMixin {
final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');
AnimationController _controller;
Animation<double> _frontOpacity;
static final Animatable<double> _frontOpacityTween =
Tween<double>(begin: 0.2, end: 1.0).chain(
CurveTween(curve: const Interval(0.0, 0.4, curve: Curves.easeInOut)));
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 300),
value: 1.0,
vsync: this,
);
_frontOpacity = _controller.drive(_frontOpacityTween);
}
@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 =
_controller.drive(RelativeRectTween(
begin: RelativeRect.fromLTRB(
0.0, constraints.biggest.height - _kFrontClosedHeight, 0.0, 0.0),
end: const RelativeRect.fromLTRB(0.0, _kBackAppBarHeight, 0.0, 0.0),
));
final List<Widget> layers = <Widget>[
// Back layer
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
_BackAppBar(
leading: widget.frontAction,
title: _CrossFadeTransition(
progress: _controller,
alignment: AlignmentDirectional.centerStart,
child0: Semantics(namesRoute: true, child: widget.frontTitle),
child1: Semantics(namesRoute: true, child: widget.backTitle),
),
trailing: IconButton(
onPressed: _toggleFrontLayer,
tooltip: 'Toggle options page',
icon: AnimatedIcon(
icon: AnimatedIcons.close_menu,
progress: _controller,
),
),
),
Expanded(
child: Visibility(
child: widget.backLayer,
visible: _controller.status != AnimationStatus.completed,
maintainState: true,
)),
],
),
// Front layer
PositionedTransition(
rect: frontRelativeRect,
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return PhysicalShape(
elevation: 12.0,
color: Theme.of(context).canvasColor,
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius:
_kFrontHeadingBevelRadius.transform(_controller.value)),
// BeveledRectangleBorder(
// borderRadius: _kFrontHeadingBevelRadius.transform(_controller.value),
// ),
),
clipBehavior: Clip.antiAlias,
child: child,
);
},
child: _TappableWhileStatusIs(
AnimationStatus.completed,
controller: _controller,
child: FadeTransition(
opacity: _frontOpacity,
child: widget.frontLayer,
),
),
),
),
];
// The front "heading" is a (typically transparent) widget that's stacked on
// top of, and at the top of, the front layer. It adds support for dragging
// the front layer up and down and for opening and closing the front layer
// with a tap. It may obscure part of the front layer's topmost child.
if (widget.frontHeading != null) {
layers.add(
PositionedTransition(
rect: frontRelativeRect,
child: ExcludeSemantics(
child: Container(
alignment: Alignment.topLeft,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _toggleFrontLayer,
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: widget.frontHeading,
),
),
),
),
);
}
return Stack(
key: _backdropKey,
children: layers,
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: _buildStack);
}
}

View File

@@ -0,0 +1,201 @@
// 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_web/material.dart';
import 'package:flutter_web/cupertino.dart';
class ComponentDemoTabData {
ComponentDemoTabData({
this.demoWidget,
this.exampleCodeTag,
this.description,
this.tabName,
this.documentationUrl,
});
final Widget demoWidget;
final String exampleCodeTag;
final String description;
final String tabName;
final String documentationUrl;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
final ComponentDemoTabData typedOther = other;
return typedOther.tabName == tabName &&
typedOther.description == description &&
typedOther.documentationUrl == documentationUrl;
}
@override
int get hashCode => hashValues(tabName, description, documentationUrl);
}
class TabbedComponentDemoScaffold extends StatelessWidget {
const TabbedComponentDemoScaffold({
this.title,
this.demos,
this.actions,
});
final List<ComponentDemoTabData> demos;
final String title;
final List<Widget> actions;
void _showExampleCode(BuildContext context) {
final String tag =
demos[DefaultTabController.of(context).index].exampleCodeTag;
if (tag != null) {
throw new UnimplementedError();
// TODO:
// Navigator.push(context, MaterialPageRoute<FullScreenCodeDialog>(
// builder: (BuildContext context) => FullScreenCodeDialog(exampleCodeTag: tag)
// ));
}
}
void _showApiDocumentation(BuildContext context) {
final String url =
demos[DefaultTabController.of(context).index].documentationUrl;
if (url != null) {
// launch(url, forceWebView: true);
}
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: demos.length,
child: Scaffold(
appBar: AppBar(
title: Text(title),
actions: (actions ?? <Widget>[])
..addAll(
<Widget>[
Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.library_books),
onPressed: () => _showApiDocumentation(context),
);
},
),
Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.code),
tooltip: 'Show example code',
onPressed: () => _showExampleCode(context),
);
},
)
],
),
bottom: TabBar(
isScrollable: true,
tabs: demos
.map<Widget>(
(ComponentDemoTabData data) => Tab(text: data.tabName))
.toList(),
),
),
body: TabBarView(
children: demos.map<Widget>((ComponentDemoTabData demo) {
return SafeArea(
top: false,
bottom: false,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(demo.description,
style: Theme.of(context).textTheme.subhead)),
Expanded(child: demo.demoWidget)
],
),
);
}).toList(),
),
),
);
}
}
class MaterialDemoDocumentationButton extends StatelessWidget {
MaterialDemoDocumentationButton(String routeName, {Key key})
: documentationUrl = 'todo',
assert(
'todo' != null,
'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos',
),
super(key: key);
final String documentationUrl;
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.library_books),
tooltip: 'API documentation',
// TODO(flutter_web): launch(documentationUrl, forceWebView: true)
onPressed: () => {});
}
}
Widget wrapScaffold(String title, BuildContext context, Key key, Widget child,
String routeName) {
IconData _backIcon() {
switch (Theme.of(context).platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return Icons.arrow_back;
case TargetPlatform.iOS:
return Icons.arrow_back_ios;
}
assert(false);
return null;
}
return Scaffold(
key: key,
appBar: AppBar(
leading: IconButton(
icon: Icon(_backIcon()),
alignment: Alignment.centerLeft,
tooltip: 'Back',
onPressed: () {
Navigator.pop(context);
},
),
title: Text(title),
actions: <Widget>[MaterialDemoDocumentationButton(routeName)],
),
body: Material(child: Center(child: child)),
);
}
class CupertinoDemoDocumentationButton extends StatelessWidget {
CupertinoDemoDocumentationButton(String routeName, {Key key})
: documentationUrl = 'todo',
assert(
'todo' != null,
'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos',
),
super(key: key);
final String documentationUrl;
@override
Widget build(BuildContext context) {
return CupertinoButton(
padding: EdgeInsets.zero,
child: Semantics(
label: 'API documentation',
child: const Icon(CupertinoIcons.book),
),
// TODO(flutter_web): launch(documentationUrl, forceWebView: true)
onPressed: () => {});
}
}

View File

@@ -0,0 +1,640 @@
// 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_web/material.dart';
import '../demo/all.dart';
import 'icons.dart';
// TODO: As Demos are added and complete, uncomment _buildGalleryDemos sections.
class GalleryDemoCategory {
const GalleryDemoCategory._({this.name, this.icon});
@required
final String name;
@required
final IconData icon;
@override
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 = GalleryDemoCategory._(
name: 'Studies',
icon: GalleryIcons.animation,
);
const GalleryDemoCategory _kStyle = GalleryDemoCategory._(
name: 'Style',
icon: GalleryIcons.custom_typography,
);
//const GalleryDemoCategory _kCupertinoComponents = GalleryDemoCategory._(
// name: 'Cupertino',
// icon: GalleryIcons.phone_iphone,
//);
const GalleryDemoCategory _kMaterialComponents = GalleryDemoCategory._(
name: 'Material',
icon: GalleryIcons.category_mdc,
);
//const GalleryDemoCategory _kMedia = GalleryDemoCategory._(
// name: 'Media',
// icon: GalleryIcons.drive_video,
//);
class GalleryDemo {
const GalleryDemo({
@required this.title,
@required this.icon,
this.subtitle,
@required this.category,
@required this.routeName,
this.documentationUrl,
@required this.buildRoute,
}) : assert(title != null),
assert(category != null),
assert(routeName != null),
assert(buildRoute != null);
final String title;
final IconData icon;
final String subtitle;
final GalleryDemoCategory category;
final String routeName;
final WidgetBuilder buildRoute;
final String documentationUrl;
@override
String toString() {
return '$runtimeType($title $routeName)';
}
}
List<GalleryDemo> _buildGalleryDemos() {
final List<GalleryDemo> galleryDemos = <GalleryDemo>[
// Demos
GalleryDemo(
title: 'Contact profile',
subtitle: 'Address book entry with a flexible appbar',
icon: GalleryIcons.account_box,
category: _kDemos,
routeName: ContactsDemo.routeName,
buildRoute: (BuildContext context) => ContactsDemo(),
),
GalleryDemo(
title: 'Shrine',
subtitle: 'Basic shopping app',
icon: GalleryIcons.shrine,
category: _kDemos,
routeName: ShrineDemo.routeName,
buildRoute: (BuildContext context) => ShrineDemo(),
),
GalleryDemo(
title: 'Animation',
subtitle: 'Section organizer',
icon: GalleryIcons.animation,
category: _kDemos,
routeName: AnimationDemo.routeName,
buildRoute: (BuildContext context) => const AnimationDemo(),
),
// Style
GalleryDemo(
title: 'Colors',
subtitle: 'All of the predefined colors',
icon: GalleryIcons.colors,
category: _kStyle,
routeName: ColorsDemo.routeName,
buildRoute: (BuildContext context) => ColorsDemo(),
),
GalleryDemo(
title: 'Typography',
subtitle: 'All of the predefined text styles',
icon: GalleryIcons.custom_typography,
category: _kStyle,
routeName: TypographyDemo.routeName,
buildRoute: (BuildContext context) => TypographyDemo(),
),
// Material Components
GalleryDemo(
title: 'Backdrop',
subtitle: 'Select a front layer from back layer',
icon: GalleryIcons.backdrop,
category: _kMaterialComponents,
routeName: BackdropDemo.routeName,
buildRoute: (BuildContext context) => BackdropDemo(),
),
GalleryDemo(
title: 'Bottom app bar',
subtitle: 'Optional floating action button notch',
icon: GalleryIcons.bottom_app_bar,
category: _kMaterialComponents,
routeName: BottomAppBarDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/BottomAppBar-class.html',
buildRoute: (BuildContext context) => BottomAppBarDemo(),
),
GalleryDemo(
title: 'Bottom navigation',
subtitle: 'Bottom navigation with cross-fading views',
icon: GalleryIcons.bottom_navigation,
category: _kMaterialComponents,
routeName: BottomNavigationDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/BottomNavigationBar-class.html',
buildRoute: (BuildContext context) => BottomNavigationDemo(),
),
GalleryDemo(
title: 'Bottom sheet: Modal',
subtitle: 'A dismissable bottom sheet',
icon: GalleryIcons.bottom_sheets,
category: _kMaterialComponents,
routeName: ModalBottomSheetDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/showModalBottomSheet.html',
buildRoute: (BuildContext context) => ModalBottomSheetDemo(),
),
GalleryDemo(
title: 'Bottom sheet: Persistent',
subtitle: 'A bottom sheet that sticks around',
icon: GalleryIcons.bottom_sheet_persistent,
category: _kMaterialComponents,
routeName: PersistentBottomSheetDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/ScaffoldState/showBottomSheet.html',
buildRoute: (BuildContext context) => PersistentBottomSheetDemo(),
),
GalleryDemo(
title: 'Buttons',
subtitle: 'Flat, raised, dropdown, and more',
icon: GalleryIcons.generic_buttons,
category: _kMaterialComponents,
routeName: ButtonsDemo.routeName,
buildRoute: (BuildContext context) => ButtonsDemo(),
),
GalleryDemo(
title: 'Buttons: Floating Action Button',
subtitle: 'FAB with transitions',
icon: GalleryIcons.buttons,
category: _kMaterialComponents,
routeName: TabsFabDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/FloatingActionButton-class.html',
buildRoute: (BuildContext context) => TabsFabDemo(),
),
GalleryDemo(
title: 'Cards',
subtitle: 'Baseline cards with rounded corners',
icon: GalleryIcons.cards,
category: _kMaterialComponents,
routeName: CardsDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/Card-class.html',
buildRoute: (BuildContext context) => CardsDemo(),
),
GalleryDemo(
title: 'Chips',
subtitle: 'Labeled with delete buttons and avatars',
icon: GalleryIcons.chips,
category: _kMaterialComponents,
routeName: ChipDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/Chip-class.html',
buildRoute: (BuildContext context) => ChipDemo(),
),
GalleryDemo(
title: 'Data tables',
subtitle: 'Rows and columns',
icon: GalleryIcons.data_table,
category: _kMaterialComponents,
routeName: DataTableDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/PaginatedDataTable-class.html',
buildRoute: (BuildContext context) => DataTableDemo(),
),
GalleryDemo(
title: 'Dialogs',
subtitle: 'Simple, alert, and fullscreen',
icon: GalleryIcons.dialogs,
category: _kMaterialComponents,
routeName: DialogDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/showDialog.html',
buildRoute: (BuildContext context) => DialogDemo(),
),
GalleryDemo(
title: 'Elevations',
subtitle: 'Shadow values on cards',
// TODO(larche): Change to custom icon for elevations when one exists.
icon: GalleryIcons.cupertino_progress,
category: _kMaterialComponents,
routeName: ElevationDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/Material/elevation.html',
buildRoute: (BuildContext context) => ElevationDemo(),
),
GalleryDemo(
title: 'Expand/collapse list control',
subtitle: 'A list with one sub-list level',
icon: GalleryIcons.expand_all,
category: _kMaterialComponents,
routeName: TwoLevelListDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/ExpansionTile-class.html',
buildRoute: (BuildContext context) => TwoLevelListDemo(),
),
GalleryDemo(
title: 'Expansion panels',
subtitle: 'List of expanding panels',
icon: GalleryIcons.expand_all,
category: _kMaterialComponents,
routeName: ExpansionPanelsDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/ExpansionPanel-class.html',
buildRoute: (BuildContext context) => ExpansionPanelsDemo(),
),
GalleryDemo(
title: 'Grid',
subtitle: 'Row and column layout',
icon: GalleryIcons.grid_on,
category: _kMaterialComponents,
routeName: GridListDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/widgets/GridView-class.html',
buildRoute: (BuildContext context) => const GridListDemo(),
),
GalleryDemo(
title: 'Icons',
subtitle: 'Enabled and disabled icons with opacity',
icon: GalleryIcons.sentiment_very_satisfied,
category: _kMaterialComponents,
routeName: IconsDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/IconButton-class.html',
buildRoute: (BuildContext context) => IconsDemo(),
),
GalleryDemo(
title: 'Lists',
subtitle: 'Scrolling list layouts',
icon: GalleryIcons.list_alt,
category: _kMaterialComponents,
routeName: ListDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/ListTile-class.html',
buildRoute: (BuildContext context) => const ListDemo(),
),
GalleryDemo(
title: 'Lists: leave-behind list items',
subtitle: 'List items with hidden actions',
icon: GalleryIcons.lists_leave_behind,
category: _kMaterialComponents,
routeName: LeaveBehindDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/widgets/Dismissible-class.html',
buildRoute: (BuildContext context) => const LeaveBehindDemo(),
),
GalleryDemo(
title: 'Lists: reorderable',
subtitle: 'Reorderable lists',
icon: GalleryIcons.list_alt,
category: _kMaterialComponents,
routeName: ReorderableListDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/ReorderableListView-class.html',
buildRoute: (BuildContext context) => const ReorderableListDemo(),
),
GalleryDemo(
title: 'Menus',
subtitle: 'Menu buttons and simple menus',
icon: GalleryIcons.more_vert,
category: _kMaterialComponents,
routeName: MenuDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/PopupMenuButton-class.html',
buildRoute: (BuildContext context) => const MenuDemo(),
),
GalleryDemo(
title: 'Navigation drawer',
subtitle: 'Navigation drawer with standard header',
icon: GalleryIcons.menu,
category: _kMaterialComponents,
routeName: DrawerDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/Drawer-class.html',
buildRoute: (BuildContext context) => DrawerDemo(),
),
GalleryDemo(
title: 'Pagination',
subtitle: 'PageView with indicator',
icon: GalleryIcons.page_control,
category: _kMaterialComponents,
routeName: PageSelectorDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/TabBarView-class.html',
buildRoute: (BuildContext context) => PageSelectorDemo(),
),
GalleryDemo(
title: 'Pickers',
subtitle: 'Date and time selection widgets',
icon: GalleryIcons.event,
category: _kMaterialComponents,
routeName: DateAndTimePickerDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/showDatePicker.html',
buildRoute: (BuildContext context) => DateAndTimePickerDemo(),
),
GalleryDemo(
title: 'Progress indicators',
subtitle: 'Linear, circular, indeterminate',
icon: GalleryIcons.progress_activity,
category: _kMaterialComponents,
routeName: ProgressIndicatorDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/LinearProgressIndicator-class.html',
buildRoute: (BuildContext context) => ProgressIndicatorDemo(),
),
GalleryDemo(
title: 'Pull to refresh',
subtitle: 'Refresh indicators',
icon: GalleryIcons.refresh,
category: _kMaterialComponents,
routeName: OverscrollDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/RefreshIndicator-class.html',
buildRoute: (BuildContext context) => const OverscrollDemo(),
),
GalleryDemo(
title: 'Search',
subtitle: 'Expandable search',
icon: Icons.search,
category: _kMaterialComponents,
routeName: SearchDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/showSearch.html',
buildRoute: (BuildContext context) => SearchDemo(),
),
GalleryDemo(
title: 'Selection controls',
subtitle: 'Checkboxes, radio buttons, and switches',
icon: GalleryIcons.check_box,
category: _kMaterialComponents,
routeName: SelectionControlsDemo.routeName,
buildRoute: (BuildContext context) => SelectionControlsDemo(),
),
GalleryDemo(
title: 'Sliders',
subtitle: 'Widgets for selecting a value by swiping',
icon: GalleryIcons.sliders,
category: _kMaterialComponents,
routeName: SliderDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/Slider-class.html',
buildRoute: (BuildContext context) => SliderDemo(),
),
GalleryDemo(
title: 'Snackbar',
subtitle: 'Temporary messaging',
icon: GalleryIcons.snackbar,
category: _kMaterialComponents,
routeName: SnackBarDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/ScaffoldState/showSnackBar.html',
buildRoute: (BuildContext context) => const SnackBarDemo(),
),
GalleryDemo(
title: 'Tabs',
subtitle: 'Tabs with independently scrollable views',
icon: GalleryIcons.tabs,
category: _kMaterialComponents,
routeName: TabsDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/TabBarView-class.html',
buildRoute: (BuildContext context) => TabsDemo(),
),
GalleryDemo(
title: 'Text',
subtitle: 'Single-line text and multiline paragraphs',
icon: Icons.text_fields,
category: _kMaterialComponents,
routeName: TextDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/widgets/Text-class.html',
buildRoute: (BuildContext context) => TextDemo(),
),
GalleryDemo(
title: 'Text Editing',
subtitle: 'EditableText with a TextEditingController',
icon: Icons.text_fields,
category: _kMaterialComponents,
routeName: EditableTextDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/widgets/EditableText-class.html',
buildRoute: (BuildContext context) => EditableTextDemo(),
),
GalleryDemo(
title: 'Tabs: Scrolling',
subtitle: 'Tab bar that scrolls',
category: _kMaterialComponents,
icon: GalleryIcons.tabs,
routeName: ScrollableTabsDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/TabBar-class.html',
buildRoute: (BuildContext context) => ScrollableTabsDemo(),
),
GalleryDemo(
title: 'Text fields',
subtitle: 'Single line of editable text and numbers',
icon: GalleryIcons.text_fields_alt,
category: _kMaterialComponents,
routeName: TextFormFieldDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/TextFormField-class.html',
buildRoute: (BuildContext context) => const TextFormFieldDemo(),
),
GalleryDemo(
title: 'Tooltips',
subtitle: 'Short message displayed on long-press',
icon: GalleryIcons.tooltip,
category: _kMaterialComponents,
routeName: TooltipDemo.routeName,
documentationUrl:
'https://docs.flutter.io/flutter/material/Tooltip-class.html',
buildRoute: (BuildContext context) => TooltipDemo(),
),
// Media
// GalleryDemo(
// title: 'Animated images',
// subtitle: 'GIF and WebP animations',
// icon: GalleryIcons.animation,
// category: _kMedia,
// routeName: ImagesDemo.routeName,
// buildRoute: (BuildContext context) => ImagesDemo(),
// ),
// GalleryDemo(
// title: 'Video',
// subtitle: 'Video playback',
// icon: GalleryIcons.drive_video,
// category: _kMedia,
// routeName: VideoDemo.routeName,
// buildRoute: (BuildContext context) => const VideoDemo(),
// ),
// Cupertino Components
// GalleryDemo(
// title: 'Activity Indicator',
// icon: GalleryIcons.cupertino_progress,
// category: _kCupertinoComponents,
// routeName: CupertinoProgressIndicatorDemo.routeName,
// documentationUrl:
// 'https://docs.flutter.io/flutter/cupertino/CupertinoActivityIndicator-class.html',
// buildRoute: (BuildContext context) => CupertinoProgressIndicatorDemo(),
// ),
// GalleryDemo(
// title: 'Alerts',
// icon: GalleryIcons.dialogs,
// category: _kCupertinoComponents,
// routeName: CupertinoAlertDemo.routeName,
// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/showCupertinoDialog.html',
// buildRoute: (BuildContext context) => CupertinoAlertDemo(),
// ),
// GalleryDemo(
// title: 'Buttons',
// icon: GalleryIcons.generic_buttons,
// category: _kCupertinoComponents,
// routeName: CupertinoButtonsDemo.routeName,
// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoButton-class.html',
// buildRoute: (BuildContext context) => CupertinoButtonsDemo(),
// ),
// GalleryDemo(
// title: 'Navigation',
// icon: GalleryIcons.bottom_navigation,
// category: _kCupertinoComponents,
// routeName: CupertinoNavigationDemo.routeName,
// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoTabScaffold-class.html',
// buildRoute: (BuildContext context) => CupertinoNavigationDemo(),
// ),
// GalleryDemo(
// title: 'Pickers',
// icon: GalleryIcons.event,
// category: _kCupertinoComponents,
// routeName: CupertinoPickerDemo.routeName,
// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoPicker-class.html',
// buildRoute: (BuildContext context) => CupertinoPickerDemo(),
// ),
// GalleryDemo(
// title: 'Pull to refresh',
// icon: GalleryIcons.cupertino_pull_to_refresh,
// category: _kCupertinoComponents,
// routeName: CupertinoRefreshControlDemo.routeName,
// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSliverRefreshControl-class.html',
// buildRoute: (BuildContext context) => CupertinoRefreshControlDemo(),
// ),
// GalleryDemo(
// title: 'Segmented Control',
// icon: GalleryIcons.tabs,
// category: _kCupertinoComponents,
// routeName: CupertinoSegmentedControlDemo.routeName,
// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSegmentedControl-class.html',
// buildRoute: (BuildContext context) => CupertinoSegmentedControlDemo(),
// ),
// GalleryDemo(
// title: 'Sliders',
// icon: GalleryIcons.sliders,
// category: _kCupertinoComponents,
// routeName: CupertinoSliderDemo.routeName,
// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSlider-class.html',
// buildRoute: (BuildContext context) => CupertinoSliderDemo(),
// ),
// GalleryDemo(
// title: 'Switches',
// icon: GalleryIcons.cupertino_switch,
// category: _kCupertinoComponents,
// routeName: CupertinoSwitchDemo.routeName,
// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSwitch-class.html',
// buildRoute: (BuildContext context) => CupertinoSwitchDemo(),
// ),
// GalleryDemo(
// title: 'Text Fields',
// icon: GalleryIcons.text_fields_alt,
// category: _kCupertinoComponents,
// routeName: CupertinoTextFieldDemo.routeName,
// buildRoute: (BuildContext context) => CupertinoTextFieldDemo(),
// ),
//
// // Media
// GalleryDemo(
// title: 'Animated images',
// subtitle: 'GIF and WebP animations',
// icon: GalleryIcons.animation,
// category: _kMedia,
// routeName: ImagesDemo.routeName,
// buildRoute: (BuildContext context) => ImagesDemo(),
// ),
// 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
// in (release builds) the performance tests.
assert(() {
galleryDemos.insert(
0,
GalleryDemo(
title: 'Pesto',
subtitle: 'Simple recipe browser',
icon: Icons.adjust,
category: _kDemos,
routeName: PestoDemo.routeName,
buildRoute: (BuildContext context) => const PestoDemo(),
),
);
return true;
}());
return galleryDemos;
}
final List<GalleryDemo> kAllGalleryDemos = _buildGalleryDemos();
final Set<GalleryDemoCategory> kAllGalleryDemoCategories = kAllGalleryDemos
.map<GalleryDemoCategory>((GalleryDemo demo) => demo.category)
.toSet();
final Map<GalleryDemoCategory, List<GalleryDemo>> kGalleryCategoryToDemos =
Map<GalleryDemoCategory, List<GalleryDemo>>.fromIterable(
kAllGalleryDemoCategories,
value: (dynamic category) {
return kAllGalleryDemos
.where((GalleryDemo demo) => demo.category == category)
.toList();
},
);
final Map<String, String> kDemoDocumentationUrl =
Map<String, String>.fromIterable(
kAllGalleryDemos.where((GalleryDemo demo) => demo.documentationUrl != null),
key: (dynamic demo) => demo.routeName,
value: (dynamic demo) => demo.documentationUrl,
);

View File

@@ -0,0 +1,418 @@
// 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 'dart:developer';
import 'package:flutter_web/material.dart';
import 'backdrop.dart';
import 'demos.dart';
const Color _kFlutterBlue = Color(0xFF003D75);
const double _kDemoItemHeight = 64.0;
const Duration _kFrontLayerSwitchDuration = Duration(milliseconds: 300);
class _FlutterLogo extends StatelessWidget {
const _FlutterLogo({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 34.0,
height: 34.0,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
'logos/flutter_white/logo.png',
//package: _kGalleryAssetsPackage,
),
),
),
),
);
}
}
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;
// This repaint boundary prevents the entire _CategoriesPage from being
// repainted when the button's ink splash animates.
return RepaintBoundary(
child: RawMaterialButton(
padding: EdgeInsets.zero,
splashColor: theme.primaryColor.withOpacity(0.12),
highlightColor: Colors.transparent,
onPressed: onTap,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(6.0),
child: Icon(
category.icon,
size: 60.0,
color: isDark ? Colors.white : _kFlutterBlue,
),
),
const SizedBox(height: 10.0),
Container(
height: 48.0,
alignment: Alignment.center,
child: 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 Semantics(
scopesRoute: true,
namesRoute: true,
label: 'categories',
explicitChildNodes: true,
child: SingleChildScrollView(
key: const PageStorageKey<String>('categories'),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final double columnWidth =
constraints.biggest.width / columnCount.toDouble();
final double rowHeight = math.min(225.0, columnWidth * aspectRatio);
final int rowCount =
(categories.length + columnCount - 1) ~/ columnCount;
// This repaint boundary prevents the inner contents of the front layer
// from repainting when the backdrop toggle triggers a repaint on the
// LayoutBuilder.
return RepaintBoundary(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: List<Widget>.generate(rowCount, (int rowIndex) {
final int columnCountForRow = rowIndex == rowCount - 1
? categories.length -
columnCount * math.max(0, rowCount - 1)
: columnCount;
return Row(
children: List<Widget>.generate(columnCountForRow,
(int columnIndex) {
final int index = rowIndex * columnCount + columnIndex;
final GalleryDemoCategory category =
categoriesList[index];
return SizedBox(
width: columnWidth,
height: rowHeight,
child: _CategoryItem(
category: category,
onTap: () {
onCategoryTap(category);
},
),
);
}),
);
}),
),
);
},
),
),
);
}
}
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.textScaleFactorOf(context);
final List<Widget> titleChildren = <Widget>[
Text(
demo.title,
style: theme.textTheme.subhead.copyWith(
color: isDark ? Colors.white : const Color(0xFF202124),
),
),
];
if (demo.subtitle != null) {
titleChildren.add(
Text(
demo.subtitle,
style: theme.textTheme.body1
.copyWith(color: isDark ? Colors.white : const Color(0xFF60646B)),
),
);
}
return RawMaterialButton(
padding: EdgeInsets.zero,
splashColor: theme.primaryColor.withOpacity(0.12),
highlightColor: Colors.transparent,
onPressed: () {
_launchDemo(context);
},
child: Container(
constraints:
BoxConstraints(minHeight: _kDemoItemHeight * textScaleFactor),
child: Row(
children: <Widget>[
Container(
width: 56.0,
height: 56.0,
alignment: Alignment.center,
child: Icon(
demo.icon,
size: 24.0,
color: isDark ? Colors.white : _kFlutterBlue,
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: titleChildren,
),
),
const SizedBox(width: 44.0),
],
),
),
);
}
}
class _DemosPage extends StatelessWidget {
const _DemosPage(this.category);
final GalleryDemoCategory category;
@override
Widget build(BuildContext context) {
// When overriding ListView.padding, it is necessary to manually handle
// safe areas.
final double windowBottomPadding = MediaQuery.of(context).padding.bottom;
return KeyedSubtree(
key: const ValueKey<String>(
'GalleryDemoList'), // So the tests can find this ListView
child: Semantics(
scopesRoute: true,
namesRoute: true,
label: category.name,
explicitChildNodes: true,
child: ListView(
key: PageStorageKey<String>(category.name),
padding: EdgeInsets.only(top: 8.0, bottom: windowBottomPadding),
children:
kGalleryCategoryToDemos[category].map<Widget>((GalleryDemo demo) {
return _DemoItem(demo: demo);
}).toList(),
),
),
);
}
}
class GalleryHome extends StatefulWidget {
const GalleryHome({
Key key,
this.testMode = false,
this.optionsPage,
}) : super(key: key);
final Widget optionsPage;
final bool testMode;
// In checked mode our MaterialApp will show the default "debug" banner.
// Otherwise show the "preview" banner.
static bool showPreviewBanner = true;
@override
_GalleryHomeState createState() => _GalleryHomeState();
}
class _GalleryHomeState extends State<GalleryHome>
with SingleTickerProviderStateMixin {
static final GlobalKey<ScaffoldState> _scaffoldKey =
GlobalKey<ScaffoldState>();
AnimationController _controller;
GalleryDemoCategory _category;
static Widget _topHomeLayout(
Widget currentChild, List<Widget> previousChildren) {
List<Widget> children = previousChildren;
if (currentChild != null) children = children.toList()..add(currentChild);
return Stack(
children: children,
alignment: Alignment.topCenter,
);
}
static const AnimatedSwitcherLayoutBuilder _centerHomeLayout =
AnimatedSwitcher.defaultLayoutBuilder;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 600),
debugLabel: 'preview banner',
vsync: this,
)..forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final bool isDark = theme.brightness == Brightness.dark;
final MediaQueryData media = MediaQuery.of(context);
final bool centerHome =
media.orientation == Orientation.portrait && media.size.height < 800.0;
const Curve switchOutCurve =
Interval(0.4, 1.0, curve: Curves.fastOutSlowIn);
const Curve switchInCurve = Interval(0.4, 1.0, curve: Curves.fastOutSlowIn);
Widget home = Scaffold(
key: _scaffoldKey,
backgroundColor: isDark ? _kFlutterBlue : theme.primaryColor,
body: SafeArea(
bottom: false,
child: WillPopScope(
onWillPop: () {
// Pop the category page if Android back button is pressed.
if (_category != null) {
setState(() => _category = null);
return Future<bool>.value(false);
}
return Future<bool>.value(true);
},
child: Backdrop(
backTitle: const Text('Options'),
backLayer: widget.optionsPage,
frontAction: AnimatedSwitcher(
duration: _kFrontLayerSwitchDuration,
switchOutCurve: switchOutCurve,
switchInCurve: switchInCurve,
child: _category == null
? const _FlutterLogo()
: IconButton(
icon: const BackButtonIcon(),
tooltip: 'Back',
onPressed: () => setState(() => _category = null),
),
),
frontTitle: AnimatedSwitcher(
duration: _kFrontLayerSwitchDuration,
child: _category == null
? const Text('Flutter web gallery')
: Text(_category.name),
),
frontHeading: widget.testMode ? null : Container(height: 24.0),
frontLayer: AnimatedSwitcher(
duration: _kFrontLayerSwitchDuration,
switchOutCurve: switchOutCurve,
switchInCurve: switchInCurve,
layoutBuilder: centerHome ? _centerHomeLayout : _topHomeLayout,
child: _category != null
? _DemosPage(_category)
: _CategoriesPage(
categories: kAllGalleryDemoCategories,
onCategoryTap: (GalleryDemoCategory category) {
setState(() => _category = category);
},
),
),
),
),
),
);
assert(() {
GalleryHome.showPreviewBanner = false;
return true;
}());
if (GalleryHome.showPreviewBanner) {
home = Stack(fit: StackFit.expand, children: <Widget>[
home,
FadeTransition(
opacity:
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
child: const Banner(
message: 'PREVIEW',
location: BannerLocation.topEnd,
)),
]);
}
home = AnnotatedRegion<SystemUiOverlayStyle>(
child: home, value: SystemUiOverlayStyle.light);
return home;
}
}

View File

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

View File

@@ -0,0 +1,480 @@
// 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_web/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 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 =
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.textScaleFactorOf(context);
return MergeSemantics(
child: Container(
constraints: BoxConstraints(minHeight: _kItemHeight * textScaleFactor),
padding: _kItemPadding,
alignment: AlignmentDirectional.centerStart,
child: DefaultTextStyle(
style: DefaultTextStyle.of(context).style,
maxLines: 2,
overflow: TextOverflow.fade,
child: IconTheme(
data: Theme.of(context).primaryIconTheme,
child: child,
),
),
),
);
}
}
class _BooleanItem extends StatelessWidget {
const _BooleanItem(this.title, this.value, this.onChanged, {this.switchKey});
final String title;
final bool value;
final ValueChanged<bool> onChanged;
// [switchKey] is used for accessing the switch from driver tests.
final Key switchKey;
@override
Widget build(BuildContext context) {
final bool isDark = Theme.of(context).brightness == Brightness.dark;
return _OptionsItem(
child: Row(
children: <Widget>[
Expanded(child: Text(title)),
Switch(
key: switchKey,
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 _OptionsItem(
child: _FlatButton(
onPressed: onTap,
child: 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 FlatButton(
padding: EdgeInsets.zero,
onPressed: onPressed,
child: 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 _OptionsItem(
child: DefaultTextStyle(
style: theme.textTheme.body1.copyWith(
fontFamily: 'GoogleSans',
color: theme.accentColor,
),
child: Semantics(
child: Text(text),
header: true,
),
),
);
}
}
class _ThemeItem extends StatelessWidget {
const _ThemeItem(this.options, this.onOptionsChanged);
final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged;
@override
Widget build(BuildContext context) {
return _BooleanItem(
'Dark Theme',
options.theme == kDarkGalleryTheme,
(bool value) {
onOptionsChanged(
options.copyWith(
theme: value ? kDarkGalleryTheme : kLightGalleryTheme,
),
);
},
switchKey: const Key('dark_theme'),
);
}
}
class _TextScaleFactorItem extends StatelessWidget {
const _TextScaleFactorItem(this.options, this.onOptionsChanged);
final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged;
@override
Widget build(BuildContext context) {
return _OptionsItem(
child: Row(
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Text size'),
Text(
'${options.textScaleFactor.label}',
style: Theme.of(context).primaryTextTheme.body1,
),
],
),
),
PopupMenuButton<GalleryTextScaleValue>(
padding: const EdgeInsetsDirectional.only(end: 16.0),
icon: const Icon(Icons.arrow_drop_down),
itemBuilder: (BuildContext context) {
return kAllGalleryTextScaleValues
.map<PopupMenuItem<GalleryTextScaleValue>>(
(GalleryTextScaleValue scaleValue) {
return PopupMenuItem<GalleryTextScaleValue>(
value: scaleValue,
child: 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 _BooleanItem(
'Force RTL',
options.textDirection == TextDirection.rtl,
(bool value) {
onOptionsChanged(
options.copyWith(
textDirection: value ? TextDirection.rtl : TextDirection.ltr,
),
);
},
switchKey: const Key('text_direction'),
);
}
}
class _TimeDilationItem extends StatelessWidget {
const _TimeDilationItem(this.options, this.onOptionsChanged);
final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged;
@override
Widget build(BuildContext context) {
return _BooleanItem(
'Slow motion',
options.timeDilation != 1.0,
(bool value) {
onOptionsChanged(
options.copyWith(
timeDilation: value ? 20.0 : 1.0,
),
);
},
switchKey: const Key('slow_motion'),
);
}
}
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 _OptionsItem(
child: Row(
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Platform mechanics'),
Text(
'${_platformLabel(options.platform)}',
style: Theme.of(context).primaryTextTheme.body1,
),
],
),
),
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 PopupMenuItem<TargetPlatform>(
value: platform,
child: 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(
_BooleanItem('Highlight offscreen layers',
options.showOffscreenLayersCheckerboard, (bool value) {
onOptionsChanged(
options.copyWith(showOffscreenLayersCheckerboard: value));
}),
);
}
if (options.showRasterCacheImagesCheckerboard != null) {
items.add(
_BooleanItem(
'Highlight raster cache images',
options.showRasterCacheImagesCheckerboard,
(bool value) {
onOptionsChanged(
options.copyWith(showRasterCacheImagesCheckerboard: value));
},
),
);
}
if (options.showPerformanceOverlay != null) {
items.add(
_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 DefaultTextStyle(
style: theme.primaryTextTheme.subhead,
child: ListView(
padding: const EdgeInsets.only(bottom: 124.0),
children: <Widget>[
const _Heading('Display'),
_ThemeItem(options, onOptionsChanged),
_TextScaleFactorItem(options, onOptionsChanged),
_TextDirectionItem(options, onOptionsChanged),
_TimeDilationItem(options, onOptionsChanged),
const Divider(),
const _Heading('Platform mechanics'),
_PlatformItem(options, onOptionsChanged),
]
..addAll(
_enabledDiagnosticItems(),
)
..addAll(
<Widget>[
const Divider(),
const _Heading('Flutter Web gallery'),
_ActionItem('About Flutter Web Gallery', () {
showGalleryAboutDialog(context);
}),
_ActionItem('Send feedback', onSendFeedback),
],
),
),
);
}
}

View File

@@ -0,0 +1,36 @@
// 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_web/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 =
<GalleryTextScaleValue>[
GalleryTextScaleValue(null, 'System Default'),
GalleryTextScaleValue(0.8, 'Small'),
GalleryTextScaleValue(1.0, 'Normal'),
GalleryTextScaleValue(1.3, 'Large'),
GalleryTextScaleValue(2.0, 'Huge'),
];

View File

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