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:
committed by
Andrew Brogdon
parent
42f2dce01b
commit
3fe927cb29
78
web/gallery/lib/gallery/about.dart
Normal file
78
web/gallery/lib/gallery/about.dart
Normal 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: '.',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
135
web/gallery/lib/gallery/app.dart
Normal file
135
web/gallery/lib/gallery/app.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
366
web/gallery/lib/gallery/backdrop.dart
Normal file
366
web/gallery/lib/gallery/backdrop.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
201
web/gallery/lib/gallery/demo.dart
Normal file
201
web/gallery/lib/gallery/demo.dart
Normal 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: () => {});
|
||||
}
|
||||
}
|
||||
640
web/gallery/lib/gallery/demos.dart
Normal file
640
web/gallery/lib/gallery/demos.dart
Normal 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,
|
||||
);
|
||||
418
web/gallery/lib/gallery/home.dart
Normal file
418
web/gallery/lib/gallery/home.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
73
web/gallery/lib/gallery/icons.dart
Normal file
73
web/gallery/lib/gallery/icons.dart
Normal 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');
|
||||
}
|
||||
480
web/gallery/lib/gallery/options.dart
Normal file
480
web/gallery/lib/gallery/options.dart
Normal 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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
36
web/gallery/lib/gallery/scales.dart
Normal file
36
web/gallery/lib/gallery/scales.dart
Normal 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'),
|
||||
];
|
||||
82
web/gallery/lib/gallery/themes.dart
Normal file
82
web/gallery/lib/gallery/themes.dart
Normal 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),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user