1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-11 15:28:44 +00:00

Update web/ samples to work with Flutter SDK (#134)

* add package:http dependency in dad_jokes

* add package:http dependency in filipino_cuisine

* don't build package:http demos until flutter/flutter#34858 is resolved

* update gallery

* update github_dataviz

* update particle_background

* don't build github_dataviz (uses package:http)

* update slide_puzzle

* update spinning_square

* update timeflow

* update vision_challenge

* update charts

* update dad_jokes

* update filipino cuisine

* ignore build output

* update timeflow and vision_challenge

* update slide_puzzle

* don't commit build/ directory

* move preview.png images to assets

* fix path url join

* update readme

* update web/readme.md
This commit is contained in:
John Ryan
2019-09-10 09:49:58 -07:00
committed by GitHub
parent 16fa475ff8
commit 317d459a58
746 changed files with 14607 additions and 61610 deletions

View File

@@ -4,14 +4,14 @@
import 'dart:math' as math;
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
// This demo displays one Category at a time. The backdrop show a list
// of all of the categories and the selected category is displayed
// (CategoryView) on top of the backdrop.
class Category {
const Category({this.title, this.assets});
const Category({ this.title, this.assets });
final String title;
final List<String> assets;
@override
@@ -95,49 +95,52 @@ const List<Category> allCategories = <Category>[
];
class CategoryView extends StatelessWidget {
const CategoryView({Key key, this.category}) : super(key: key);
const CategoryView({ Key key, this.category }) : super(key: key);
final Category category;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return ListView(
key: PageStorageKey<Category>(category),
padding: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 64.0,
),
children: category.assets.map<Widget>((String asset) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Card(
child: Container(
width: 144.0,
alignment: Alignment.center,
child: Column(
children: <Widget>[
Image.asset(
'$asset',
fit: BoxFit.contain,
),
Container(
padding: const EdgeInsets.only(bottom: 16.0),
alignment: AlignmentDirectional.center,
child: Text(
return Scrollbar(
child: ListView(
key: PageStorageKey<Category>(category),
padding: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 64.0,
),
children: category.assets.map<Widget>((String asset) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Card(
child: Container(
width: 144.0,
alignment: Alignment.center,
child: Column(
children: <Widget>[
Image.asset(
asset,
style: theme.textTheme.caption,
package: 'flutter_gallery_assets',
fit: BoxFit.contain,
),
),
],
Container(
padding: const EdgeInsets.only(bottom: 16.0),
alignment: AlignmentDirectional.center,
child: Text(
asset,
style: theme.textTheme.caption,
),
),
],
),
),
),
),
const SizedBox(height: 24.0),
],
);
}).toList(),
const SizedBox(height: 24.0),
],
);
}).toList(),
)
);
}
}
@@ -242,8 +245,7 @@ class BackdropDemo extends StatefulWidget {
_BackdropDemoState createState() => _BackdropDemoState();
}
class _BackdropDemoState extends State<BackdropDemo>
with SingleTickerProviderStateMixin {
class _BackdropDemoState extends State<BackdropDemo> with SingleTickerProviderStateMixin {
final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');
AnimationController _controller;
Category _category = allCategories[0];
@@ -273,8 +275,7 @@ class _BackdropDemoState extends State<BackdropDemo>
bool get _backdropPanelVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
return status == AnimationStatus.completed || status == AnimationStatus.forward;
}
void _toggleBackdropPanelVisibility() {
@@ -290,19 +291,17 @@ class _BackdropDemoState extends State<BackdropDemo>
// the user must either tap its heading or the backdrop's menu icon.
void _handleDragUpdate(DragUpdateDetails details) {
if (_controller.isAnimating ||
_controller.status == AnimationStatus.completed) return;
if (_controller.isAnimating || _controller.status == AnimationStatus.completed)
return;
_controller.value -=
details.primaryDelta / (_backdropHeight ?? details.primaryDelta);
_controller.value -= details.primaryDelta / (_backdropHeight ?? details.primaryDelta);
}
void _handleDragEnd(DragEndDetails details) {
if (_controller.isAnimating ||
_controller.status == AnimationStatus.completed) return;
if (_controller.isAnimating || _controller.status == AnimationStatus.completed)
return;
final double flingVelocity =
details.velocity.pixelsPerSecond.dy / _backdropHeight;
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)
@@ -334,14 +333,15 @@ class _BackdropDemoState extends State<BackdropDemo>
);
final ThemeData theme = Theme.of(context);
final List<Widget> backdropItems =
allCategories.map<Widget>((Category category) {
final List<Widget> backdropItems = allCategories.map<Widget>((Category category) {
final bool selected = category == _category;
return Material(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
color: selected ? Colors.white.withOpacity(0.25) : Colors.transparent,
color: selected
? Colors.white.withOpacity(0.25)
: Colors.transparent,
child: ListTile(
title: Text(category.title),
selected: selected,

View File

@@ -0,0 +1,109 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
enum BannerDemoAction {
reset,
showMultipleActions,
showLeading,
}
class BannerDemo extends StatefulWidget {
const BannerDemo({ Key key }) : super(key: key);
static const String routeName = '/material/banner';
@override
_BannerDemoState createState() => _BannerDemoState();
}
class _BannerDemoState extends State<BannerDemo> {
static const int _numItems = 20;
bool _displayBanner = true;
bool _showMultipleActions = true;
bool _showLeading = true;
void handleDemoAction(BannerDemoAction action) {
setState(() {
switch (action) {
case BannerDemoAction.reset:
_displayBanner = true;
_showMultipleActions = true;
_showLeading = true;
break;
case BannerDemoAction.showMultipleActions:
_showMultipleActions = !_showMultipleActions;
break;
case BannerDemoAction.showLeading:
_showLeading = !_showLeading;
break;
}
});
}
@override
Widget build(BuildContext context) {
final Widget banner = MaterialBanner(
content: const Text('Your password was updated on your other device. Please sign in again.'),
leading: _showLeading ? const CircleAvatar(child: Icon(Icons.access_alarm)) : null,
actions: <Widget>[
FlatButton(
child: const Text('SIGN IN'),
onPressed: () {
setState(() {
_displayBanner = false;
});
}
),
if (_showMultipleActions)
FlatButton(
child: const Text('DISMISS'),
onPressed: () {
setState(() {
_displayBanner = false;
});
}
),
],
);
return Scaffold(
appBar: AppBar(
title: const Text('Banner'),
actions: <Widget>[
MaterialDemoDocumentationButton(BannerDemo.routeName),
PopupMenuButton<BannerDemoAction>(
onSelected: handleDemoAction,
itemBuilder: (BuildContext context) => <PopupMenuEntry<BannerDemoAction>>[
const PopupMenuItem<BannerDemoAction>(
value: BannerDemoAction.reset,
child: Text('Reset the banner'),
),
const PopupMenuDivider(),
CheckedPopupMenuItem<BannerDemoAction>(
value: BannerDemoAction.showMultipleActions,
checked: _showMultipleActions,
child: const Text('Multiple actions'),
),
CheckedPopupMenuItem<BannerDemoAction>(
value: BannerDemoAction.showLeading,
checked: _showLeading,
child: const Text('Leading icon'),
),
],
),
],
),
body: ListView.builder(itemCount: _displayBanner ? _numItems + 1 : _numItems, itemBuilder: (BuildContext context, int index) {
if (index == 0 && _displayBanner) {
return banner;
}
return ListTile(title: Text('Item ${_displayBanner ? index : index + 1}'),);
}),
);
}
}

View File

@@ -2,7 +2,7 @@
// 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/material.dart';
import '../../gallery/demo.dart';
@@ -18,8 +18,7 @@ class BottomAppBarDemo extends StatefulWidget {
// for bottom application bar.
class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
static final GlobalKey<ScaffoldState> _scaffoldKey =
GlobalKey<ScaffoldState>();
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
// FAB shape
@@ -64,41 +63,35 @@ class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
// FAB Position
static const _ChoiceValue<FloatingActionButtonLocation> kFabEndDocked =
_ChoiceValue<FloatingActionButtonLocation>(
static const _ChoiceValue<FloatingActionButtonLocation> kFabEndDocked = _ChoiceValue<FloatingActionButtonLocation>(
title: 'Attached - End',
label: 'floating action button is docked at the end of the bottom app bar',
value: FloatingActionButtonLocation.endDocked,
);
static const _ChoiceValue<FloatingActionButtonLocation> kFabCenterDocked =
_ChoiceValue<FloatingActionButtonLocation>(
static const _ChoiceValue<FloatingActionButtonLocation> kFabCenterDocked = _ChoiceValue<FloatingActionButtonLocation>(
title: 'Attached - Center',
label:
'floating action button is docked at the center of the bottom app bar',
label: 'floating action button is docked at the center of the bottom app bar',
value: FloatingActionButtonLocation.centerDocked,
);
static const _ChoiceValue<FloatingActionButtonLocation> kFabEndFloat =
_ChoiceValue<FloatingActionButtonLocation>(
static const _ChoiceValue<FloatingActionButtonLocation> kFabEndFloat= _ChoiceValue<FloatingActionButtonLocation>(
title: 'Free - End',
label: 'floating action button floats above the end of the bottom app bar',
value: FloatingActionButtonLocation.endFloat,
);
static const _ChoiceValue<FloatingActionButtonLocation> kFabCenterFloat =
_ChoiceValue<FloatingActionButtonLocation>(
static const _ChoiceValue<FloatingActionButtonLocation> kFabCenterFloat = _ChoiceValue<FloatingActionButtonLocation>(
title: 'Free - Center',
label:
'floating action button is floats above the center of the bottom app bar',
label: 'floating action button is floats above the center of the bottom app bar',
value: FloatingActionButtonLocation.centerFloat,
);
static void _showSnackbar() {
const String text =
"When the Scaffold's floating action button location changes, "
'the floating action button animates to its new position.'
'The BottomAppBar adapts its shape appropriately.';
"When the Scaffold's floating action button location changes, "
'the floating action button animates to its new position.'
'The BottomAppBar adapts its shape appropriately.';
_scaffoldKey.currentState.showSnackBar(
const SnackBar(content: Text(text)),
);
@@ -154,42 +147,45 @@ class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
actions: <Widget>[
MaterialDemoDocumentationButton(BottomAppBarDemo.routeName),
IconButton(
icon: const Icon(Icons.sentiment_very_satisfied,
semanticLabel: 'Update shape'),
icon: const Icon(Icons.sentiment_very_satisfied, semanticLabel: 'Update shape'),
onPressed: () {
setState(() {
_fabShape =
_fabShape == kCircularFab ? kDiamondFab : kCircularFab;
_fabShape = _fabShape == kCircularFab ? kDiamondFab : kCircularFab;
});
},
),
],
),
body: ListView(
padding: const EdgeInsets.only(bottom: 88.0),
children: <Widget>[
const _Heading('FAB Shape'),
_RadioItem<Widget>(kCircularFab, _fabShape, _onFabShapeChanged),
_RadioItem<Widget>(kDiamondFab, _fabShape, _onFabShapeChanged),
_RadioItem<Widget>(kNoFab, _fabShape, _onFabShapeChanged),
const Divider(),
const _Heading('Notch'),
_RadioItem<bool>(kShowNotchTrue, _showNotch, _onShowNotchChanged),
_RadioItem<bool>(kShowNotchFalse, _showNotch, _onShowNotchChanged),
const Divider(),
const _Heading('FAB Position'),
_RadioItem<FloatingActionButtonLocation>(
kFabEndDocked, _fabLocation, _onFabLocationChanged),
_RadioItem<FloatingActionButtonLocation>(
kFabCenterDocked, _fabLocation, _onFabLocationChanged),
_RadioItem<FloatingActionButtonLocation>(
kFabEndFloat, _fabLocation, _onFabLocationChanged),
_RadioItem<FloatingActionButtonLocation>(
kFabCenterFloat, _fabLocation, _onFabLocationChanged),
const Divider(),
const _Heading('App bar color'),
_ColorsItem(kBabColors, _babColor, _onBabColorChanged),
],
body: Scrollbar(
child: ListView(
padding: const EdgeInsets.only(bottom: 88.0),
children: <Widget>[
const _Heading('FAB Shape'),
_RadioItem<Widget>(kCircularFab, _fabShape, _onFabShapeChanged),
_RadioItem<Widget>(kDiamondFab, _fabShape, _onFabShapeChanged),
_RadioItem<Widget>(kNoFab, _fabShape, _onFabShapeChanged),
const Divider(),
const _Heading('Notch'),
_RadioItem<bool>(kShowNotchTrue, _showNotch, _onShowNotchChanged),
_RadioItem<bool>(kShowNotchFalse, _showNotch, _onShowNotchChanged),
const Divider(),
const _Heading('FAB Position'),
_RadioItem<FloatingActionButtonLocation>(kFabEndDocked, _fabLocation, _onFabLocationChanged),
_RadioItem<FloatingActionButtonLocation>(kFabCenterDocked, _fabLocation, _onFabLocationChanged),
_RadioItem<FloatingActionButtonLocation>(kFabEndFloat, _fabLocation, _onFabLocationChanged),
_RadioItem<FloatingActionButtonLocation>(kFabCenterFloat, _fabLocation, _onFabLocationChanged),
const Divider(),
const _Heading('App bar color'),
_ColorsItem(kBabColors, _babColor, _onBabColorChanged),
],
),
),
floatingActionButton: _fabShape.value,
floatingActionButtonLocation: _fabLocation.value,
@@ -202,15 +198,18 @@ class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
}
NotchedShape _selectNotch() {
if (!_showNotch.value) return null;
if (_fabShape == kCircularFab) return const CircularNotchedRectangle();
if (_fabShape == kDiamondFab) return const _DiamondNotchedRectangle();
if (!_showNotch.value)
return null;
if (_fabShape == kCircularFab)
return const CircularNotchedRectangle();
if (_fabShape == kDiamondFab)
return const _DiamondNotchedRectangle();
return null;
}
}
class _ChoiceValue<T> {
const _ChoiceValue({this.value, this.title, this.label});
const _ChoiceValue({ this.value, this.title, this.label });
final T value;
final String title;
@@ -235,30 +234,32 @@ class _RadioItem<T> extends StatelessWidget {
padding: const EdgeInsetsDirectional.only(start: 16.0),
alignment: AlignmentDirectional.centerStart,
child: MergeSemantics(
child: Row(children: <Widget>[
Radio<_ChoiceValue<T>>(
value: value,
groupValue: groupValue,
onChanged: onChanged,
),
Expanded(
child: Semantics(
container: true,
button: true,
label: value.label,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
onChanged(value);
},
child: Text(
value.title,
style: theme.textTheme.subhead,
child: Row(
children: <Widget>[
Radio<_ChoiceValue<T>>(
value: value,
groupValue: groupValue,
onChanged: onChanged,
),
Expanded(
child: Semantics(
container: true,
button: true,
label: value.label,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
onChanged(value);
},
child: Text(
value.title,
style: theme.textTheme.subhead,
),
),
),
),
),
]),
],
),
),
);
}
@@ -294,9 +295,7 @@ class _ColorsItem extends StatelessWidget {
fillColor: namedColor.color,
shape: CircleBorder(
side: BorderSide(
color: namedColor.color == selectedColor
? Colors.black
: const Color(0xFFD5D7DA),
color: namedColor.color == selectedColor ? Colors.black : const Color(0xFFD5D7DA),
width: 2.0,
),
),
@@ -333,69 +332,59 @@ class _Heading extends StatelessWidget {
}
class _DemoBottomAppBar extends StatelessWidget {
const _DemoBottomAppBar({this.color, this.fabLocation, this.shape});
const _DemoBottomAppBar({
this.color,
this.fabLocation,
this.shape,
});
final Color color;
final FloatingActionButtonLocation fabLocation;
final NotchedShape shape;
static final List<FloatingActionButtonLocation> kCenterLocations =
<FloatingActionButtonLocation>[
static final List<FloatingActionButtonLocation> kCenterLocations = <FloatingActionButtonLocation>[
FloatingActionButtonLocation.centerDocked,
FloatingActionButtonLocation.centerFloat,
];
@override
Widget build(BuildContext context) {
final List<Widget> rowContents = <Widget>[
IconButton(
icon: const Icon(Icons.menu, semanticLabel: 'Show bottom sheet'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) => const _DemoDrawer(),
);
},
),
];
if (kCenterLocations.contains(fabLocation)) {
rowContents.add(
const Expanded(child: SizedBox()),
);
}
rowContents.addAll(<Widget>[
IconButton(
icon: const Icon(
Icons.search,
semanticLabel: 'show search action',
),
onPressed: () {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text('This is a dummy search action.')),
);
},
),
IconButton(
icon: Icon(
Theme.of(context).platform == TargetPlatform.iOS
? Icons.more_horiz
: Icons.more_vert,
semanticLabel: 'Show menu actions',
),
onPressed: () {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text('This is a dummy menu action.')),
);
},
),
]);
return BottomAppBar(
color: color,
child: Row(children: rowContents),
shape: shape,
child: Row(children: <Widget>[
IconButton(
icon: const Icon(Icons.menu, semanticLabel: 'Show bottom sheet'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) => const _DemoDrawer(),
);
},
),
if (kCenterLocations.contains(fabLocation)) const Expanded(child: SizedBox()),
IconButton(
icon: const Icon(Icons.search, semanticLabel: 'show search action',),
onPressed: () {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text('This is a dummy search action.')),
);
},
),
IconButton(
icon: Icon(
Theme.of(context).platform == TargetPlatform.iOS
? Icons.more_horiz
: Icons.more_vert,
semanticLabel: 'Show menu actions',
),
onPressed: () {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text('This is a dummy menu action.')),
);
},
),
]),
);
}
}
@@ -459,7 +448,8 @@ class _DiamondNotchedRectangle implements NotchedShape {
@override
Path getOuterPath(Rect host, Rect guest) {
if (!host.overlaps(guest)) return Path()..addRect(host);
if (!host.overlaps(guest))
return Path()..addRect(host);
assert(guest.width > 0.0);
final Rect intersection = guest.intersect(host);
@@ -476,7 +466,8 @@ class _DiamondNotchedRectangle implements NotchedShape {
// the host's top edge where the notch starts (marked with "*").
// We compute notchToCenter by similar triangles:
final double notchToCenter =
intersection.height * (guest.height / 2.0) / (guest.width / 2.0);
intersection.height * (guest.height / 2.0)
/ (guest.width / 2.0);
return Path()
..moveTo(host.left, host.top)
@@ -499,22 +490,22 @@ class _DiamondBorder extends ShapeBorder {
}
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
return getOuterPath(rect, textDirection: textDirection);
}
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
return Path()
..moveTo(rect.left + rect.width / 2.0, rect.top)
..lineTo(rect.right, rect.top + rect.height / 2.0)
..lineTo(rect.left + rect.width / 2.0, rect.bottom)
..lineTo(rect.left + rect.width / 2.0, rect.bottom)
..lineTo(rect.left, rect.top + rect.height / 2.0)
..close();
}
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) { }
// This border doesn't support scaling.
@override

View File

@@ -1,8 +1,8 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
@@ -13,19 +13,19 @@ class NavigationIconView {
String title,
Color color,
TickerProvider vsync,
}) : _icon = icon,
_color = color,
_title = title,
item = BottomNavigationBarItem(
icon: icon,
activeIcon: activeIcon,
title: Text(title),
backgroundColor: color,
),
controller = AnimationController(
duration: kThemeAnimationDuration,
vsync: vsync,
) {
}) : _icon = icon,
_color = color,
_title = title,
item = BottomNavigationBarItem(
icon: icon,
activeIcon: activeIcon,
title: Text(title),
backgroundColor: color,
),
controller = AnimationController(
duration: kThemeAnimationDuration,
vsync: vsync,
) {
_animation = controller.drive(CurveTween(
curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
));
@@ -38,8 +38,7 @@ class NavigationIconView {
final AnimationController controller;
Animation<double> _animation;
FadeTransition transition(
BottomNavigationBarType type, BuildContext context) {
FadeTransition transition(BottomNavigationBarType type, BuildContext context) {
Color iconColor;
if (type == BottomNavigationBarType.shifting) {
iconColor = _color;
@@ -92,12 +91,13 @@ class CustomInactiveIcon extends StatelessWidget {
Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context);
return Container(
margin: const EdgeInsets.all(4.0),
width: iconTheme.size - 8.0,
height: iconTheme.size - 8.0,
decoration: BoxDecoration(
border: Border.all(color: iconTheme.color, width: 2.0),
));
margin: const EdgeInsets.all(4.0),
width: iconTheme.size - 8.0,
height: iconTheme.size - 8.0,
decoration: BoxDecoration(
border: Border.all(color: iconTheme.color, width: 2.0),
),
);
}
}
@@ -150,27 +150,19 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo>
title: 'Event',
color: Colors.pink,
vsync: this,
)
),
];
for (NavigationIconView view in _navigationViews)
view.controller.addListener(_rebuild);
_navigationViews[_currentIndex].controller.value = 1.0;
}
@override
void dispose() {
for (NavigationIconView view in _navigationViews) view.controller.dispose();
for (NavigationIconView view in _navigationViews)
view.controller.dispose();
super.dispose();
}
void _rebuild() {
setState(() {
// Rebuild in order to animate views.
});
}
Widget _buildTransitionsStack() {
final List<FadeTransition> transitions = <FadeTransition>[];
@@ -193,8 +185,7 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo>
Widget build(BuildContext context) {
final BottomNavigationBar botNavBar = BottomNavigationBar(
items: _navigationViews
.map<BottomNavigationBarItem>(
(NavigationIconView navigationView) => navigationView.item)
.map<BottomNavigationBarItem>((NavigationIconView navigationView) => navigationView.item)
.toList(),
currentIndex: _currentIndex,
type: _type,
@@ -218,8 +209,7 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo>
_type = value;
});
},
itemBuilder: (BuildContext context) =>
<PopupMenuItem<BottomNavigationBarType>>[
itemBuilder: (BuildContext context) => <PopupMenuItem<BottomNavigationBarType>>[
const PopupMenuItem<BottomNavigationBarType>(
value: BottomNavigationBarType.fixed,
child: Text('Fixed'),
@@ -227,12 +217,14 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo>
const PopupMenuItem<BottomNavigationBarType>(
value: BottomNavigationBarType.shifting,
child: Text('Shifting'),
)
),
],
)
),
],
),
body: Center(child: _buildTransitionsStack()),
body: Center(
child: _buildTransitionsStack(),
),
bottomNavigationBar: botNavBar,
);
}

View File

@@ -0,0 +1,386 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
const String _raisedText =
'Raised buttons add dimension to mostly flat layouts. They emphasize '
'functions on busy or wide spaces.';
const String _raisedCode = 'buttons_raised';
const String _flatText = 'A flat button displays an ink splash on press '
'but does not lift. Use flat buttons on toolbars, in dialogs and '
'inline with padding';
const String _flatCode = 'buttons_flat';
const String _outlineText =
'Outline buttons become opaque and elevate when pressed. They are often '
'paired with raised buttons to indicate an alternative, secondary action.';
const String _outlineCode = 'buttons_outline';
const String _dropdownText =
'A dropdown button displays a menu that\'s used to select a value from a '
'small set of values. The button displays the current value and a down '
'arrow.';
const String _dropdownCode = 'buttons_dropdown';
const String _iconText =
'IconButtons are appropriate for toggle buttons that allow a single choice '
'to be selected or deselected, such as adding or removing an item\'s star.';
const String _iconCode = 'buttons_icon';
const String _actionText =
'Floating action buttons are used for a promoted action. They are '
'distinguished by a circled icon floating above the UI and can have motion '
'behaviors that include morphing, launching, and a transferring anchor '
'point.';
const String _actionCode = 'buttons_action';
class ButtonsDemo extends StatefulWidget {
static const String routeName = '/material/buttons';
@override
_ButtonsDemoState createState() => _ButtonsDemoState();
}
class _ButtonsDemoState extends State<ButtonsDemo> {
ShapeBorder _buttonShape;
@override
Widget build(BuildContext context) {
final ButtonThemeData buttonTheme = ButtonTheme.of(context).copyWith(
shape: _buttonShape
);
final List<ComponentDemoTabData> demos = <ComponentDemoTabData>[
ComponentDemoTabData(
tabName: 'RAISED',
description: _raisedText,
demoWidget: ButtonTheme.fromButtonThemeData(
data: buttonTheme,
child: buildRaisedButton(),
),
exampleCodeTag: _raisedCode,
documentationUrl: 'https://docs.flutter.io/flutter/material/RaisedButton-class.html',
),
ComponentDemoTabData(
tabName: 'FLAT',
description: _flatText,
demoWidget: ButtonTheme.fromButtonThemeData(
data: buttonTheme,
child: buildFlatButton(),
),
exampleCodeTag: _flatCode,
documentationUrl: 'https://docs.flutter.io/flutter/material/FlatButton-class.html',
),
ComponentDemoTabData(
tabName: 'OUTLINE',
description: _outlineText,
demoWidget: ButtonTheme.fromButtonThemeData(
data: buttonTheme,
child: buildOutlineButton(),
),
exampleCodeTag: _outlineCode,
documentationUrl: 'https://docs.flutter.io/flutter/material/OutlineButton-class.html',
),
ComponentDemoTabData(
tabName: 'DROPDOWN',
description: _dropdownText,
demoWidget: buildDropdownButton(),
exampleCodeTag: _dropdownCode,
documentationUrl: 'https://docs.flutter.io/flutter/material/DropdownButton-class.html',
),
ComponentDemoTabData(
tabName: 'ICON',
description: _iconText,
demoWidget: buildIconButton(),
exampleCodeTag: _iconCode,
documentationUrl: 'https://docs.flutter.io/flutter/material/IconButton-class.html',
),
ComponentDemoTabData(
tabName: 'ACTION',
description: _actionText,
demoWidget: buildActionButton(),
exampleCodeTag: _actionCode,
documentationUrl: 'https://docs.flutter.io/flutter/material/FloatingActionButton-class.html',
),
];
return TabbedComponentDemoScaffold(
title: 'Buttons',
demos: demos,
actions: <Widget>[
IconButton(
icon: const Icon(Icons.sentiment_very_satisfied, semanticLabel: 'Update shape'),
onPressed: () {
setState(() {
_buttonShape = _buttonShape == null ? const StadiumBorder() : null;
});
},
),
],
);
}
Widget buildRaisedButton() {
return Align(
alignment: const Alignment(0.0, -0.2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ButtonBar(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
child: const Text('RAISED BUTTON', semanticsLabel: 'RAISED BUTTON 1'),
onPressed: () {
// Perform some action
},
),
const RaisedButton(
child: Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 1'),
onPressed: null,
),
],
),
ButtonBar(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton.icon(
icon: const Icon(Icons.add, size: 18.0),
label: const Text('RAISED BUTTON', semanticsLabel: 'RAISED BUTTON 2'),
onPressed: () {
// Perform some action
},
),
RaisedButton.icon(
icon: const Icon(Icons.add, size: 18.0),
label: const Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 2'),
onPressed: null,
),
],
),
],
),
);
}
Widget buildFlatButton() {
return Align(
alignment: const Alignment(0.0, -0.2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ButtonBar(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FlatButton(
child: const Text('FLAT BUTTON', semanticsLabel: 'FLAT BUTTON 1'),
onPressed: () {
// Perform some action
},
),
const FlatButton(
child: Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 3',),
onPressed: null,
),
],
),
ButtonBar(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FlatButton.icon(
icon: const Icon(Icons.add_circle_outline, size: 18.0),
label: const Text('FLAT BUTTON', semanticsLabel: 'FLAT BUTTON 2'),
onPressed: () {
// Perform some action
},
),
FlatButton.icon(
icon: const Icon(Icons.add_circle_outline, size: 18.0),
label: const Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 4'),
onPressed: null,
),
],
),
],
),
);
}
Widget buildOutlineButton() {
return Align(
alignment: const Alignment(0.0, -0.2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ButtonBar(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
OutlineButton(
child: const Text('OUTLINE BUTTON', semanticsLabel: 'OUTLINE BUTTON 1'),
onPressed: () {
// Perform some action
},
),
const OutlineButton(
child: Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 5'),
onPressed: null,
),
],
),
ButtonBar(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
OutlineButton.icon(
icon: const Icon(Icons.add, size: 18.0),
label: const Text('OUTLINE BUTTON', semanticsLabel: 'OUTLINE BUTTON 2'),
onPressed: () {
// Perform some action
},
),
OutlineButton.icon(
icon: const Icon(Icons.add, size: 18.0),
label: const Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 6'),
onPressed: null,
),
],
),
],
),
);
}
// https://en.wikipedia.org/wiki/Free_Four
String dropdown1Value = 'Free';
String dropdown2Value;
String dropdown3Value = 'Four';
Widget buildDropdownButton() {
return Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
ListTile(
title: const Text('Simple dropdown:'),
trailing: DropdownButton<String>(
value: dropdown1Value,
onChanged: (String newValue) {
setState(() {
dropdown1Value = newValue;
});
},
items: <String>['One', 'Two', 'Free', 'Four'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
const SizedBox(
height: 24.0,
),
ListTile(
title: const Text('Dropdown with a hint:'),
trailing: DropdownButton<String>(
value: dropdown2Value,
hint: const Text('Choose'),
onChanged: (String newValue) {
setState(() {
dropdown2Value = newValue;
});
},
items: <String>['One', 'Two', 'Free', 'Four'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
const SizedBox(
height: 24.0,
),
ListTile(
title: const Text('Scrollable dropdown:'),
trailing: DropdownButton<String>(
value: dropdown3Value,
onChanged: (String newValue) {
setState(() {
dropdown3Value = newValue;
});
},
items: <String>[
'One', 'Two', 'Free', 'Four', 'Can', 'I', 'Have', 'A', 'Little',
'Bit', 'More', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten',
]
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
})
.toList(),
),
),
],
),
);
}
bool iconButtonToggle = false;
Widget buildIconButton() {
return Align(
alignment: const Alignment(0.0, -0.2),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: const Icon(
Icons.thumb_up,
semanticLabel: 'Thumbs up',
),
onPressed: () {
setState(() => iconButtonToggle = !iconButtonToggle);
},
color: iconButtonToggle ? Theme.of(context).primaryColor : null,
),
const IconButton(
icon: Icon(
Icons.thumb_up,
semanticLabel: 'Thumbs not up',
),
onPressed: null,
),
]
.map<Widget>((Widget button) => SizedBox(width: 64.0, height: 64.0, child: button))
.toList(),
),
);
}
Widget buildActionButton() {
return Align(
alignment: const Alignment(0.0, -0.2),
child: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// Perform some action
},
tooltip: 'floating action button',
),
);
}
}

View File

@@ -1,142 +1,101 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/foundation.dart';
import 'package:flutter_web/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
const String _kGalleryAssetsPackage = 'flutter_gallery_assets';
enum CardDemoType {
standard,
tappable,
selectable,
}
class TravelDestination {
const TravelDestination({
this.assetName,
this.assetPackage,
this.title,
this.description,
});
@required this.assetName,
@required this.assetPackage,
@required this.title,
@required this.description,
@required this.city,
@required this.location,
this.type = CardDemoType.standard,
}) : assert(assetName != null),
assert(assetPackage != null),
assert(title != null),
assert(description != null),
assert(city != null),
assert(location != null);
final String assetName;
final String assetPackage;
final String title;
final List<String> description;
bool get isValid =>
assetName != null && title != null && description?.length == 3;
final String description;
final String city;
final String location;
final CardDemoType type;
}
final List<TravelDestination> destinations = <TravelDestination>[
const TravelDestination(
const List<TravelDestination> destinations = <TravelDestination>[
TravelDestination(
assetName: 'places/india_thanjavur_market.png',
assetPackage: _kGalleryAssetsPackage,
title: 'Top 10 Cities to Visit in Tamil Nadu',
description: <String>[
'Number 10',
'Thanjavur',
'Thanjavur, Tamil Nadu',
],
description: 'Number 10',
city: 'Thanjavur',
location: 'Thanjavur, Tamil Nadu',
),
const TravelDestination(
TravelDestination(
assetName: 'places/india_chettinad_silk_maker.png',
assetPackage: _kGalleryAssetsPackage,
title: 'Artisans of Southern India',
description: <String>[
'Silk Spinners',
'Chettinad',
'Sivaganga, Tamil Nadu',
],
)
description: 'Silk Spinners',
city: 'Chettinad',
location: 'Sivaganga, Tamil Nadu',
type: CardDemoType.tappable,
),
TravelDestination(
assetName: 'places/india_tanjore_thanjavur_temple.png',
assetPackage: _kGalleryAssetsPackage,
title: 'Brihadisvara Temple',
description: 'Temples',
city: 'Thanjavur',
location: 'Thanjavur, Tamil Nadu',
type: CardDemoType.selectable,
),
];
class TravelDestinationItem extends StatelessWidget {
TravelDestinationItem({Key key, @required this.destination, this.shape})
: assert(destination != null && destination.isValid),
super(key: key);
const TravelDestinationItem({ Key key, @required this.destination, this.shape })
: assert(destination != null),
super(key: key);
static const double height = 366.0;
// This height will allow for all the Card's content to fit comfortably within the card.
static const double height = 338.0;
final TravelDestination destination;
final ShapeBorder shape;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextStyle titleStyle =
theme.textTheme.headline.copyWith(color: Colors.white);
final TextStyle descriptionStyle = theme.textTheme.subhead;
return Container(
padding: const EdgeInsets.all(8.0),
height: height,
child: Card(
shape: shape,
return SafeArea(
top: false,
bottom: false,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// photo and title
const SectionTitle(title: 'Normal'),
SizedBox(
height: 184.0,
child: Stack(
children: <Widget>[
Positioned(
bottom: 16.0,
left: 16.0,
right: 16.0,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
destination.title,
style: titleStyle,
),
),
),
],
),
),
// description and share/explore buttons
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
child: DefaultTextStyle(
softWrap: false,
overflow: TextOverflow.ellipsis,
style: descriptionStyle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// three line description
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
destination.description[0],
style:
descriptionStyle.copyWith(color: Colors.black54),
),
),
Text(destination.description[1]),
Text(destination.description[2]),
],
),
),
),
),
// share, explore buttons
ButtonTheme.bar(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
FlatButton(
child: const Text('SHARE'),
textColor: Colors.amber.shade500,
onPressed: () {/* do nothing */},
),
FlatButton(
child: const Text('EXPLORE'),
textColor: Colors.amber.shade500,
onPressed: () {/* do nothing */},
),
],
height: height,
child: Card(
// This ensures that the Card's children are clipped correctly.
clipBehavior: Clip.antiAlias,
shape: shape,
child: TravelDestinationContent(destination: destination),
),
),
],
@@ -146,6 +105,250 @@ class TravelDestinationItem extends StatelessWidget {
}
}
class TappableTravelDestinationItem extends StatelessWidget {
const TappableTravelDestinationItem({ Key key, @required this.destination, this.shape })
: assert(destination != null),
super(key: key);
// This height will allow for all the Card's content to fit comfortably within the card.
static const double height = 298.0;
final TravelDestination destination;
final ShapeBorder shape;
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
bottom: false,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
const SectionTitle(title: 'Tappable'),
SizedBox(
height: height,
child: Card(
// This ensures that the Card's children (including the ink splash) are clipped correctly.
clipBehavior: Clip.antiAlias,
shape: shape,
child: InkWell(
onTap: () {
print('Card was tapped');
},
// Generally, material cards use onSurface with 12% opacity for the pressed state.
splashColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.12),
// Generally, material cards do not have a highlight overlay.
highlightColor: Colors.transparent,
child: TravelDestinationContent(destination: destination),
),
),
),
],
),
),
);
}
}
class SelectableTravelDestinationItem extends StatefulWidget {
const SelectableTravelDestinationItem({ Key key, @required this.destination, this.shape })
: assert(destination != null),
super(key: key);
final TravelDestination destination;
final ShapeBorder shape;
@override
_SelectableTravelDestinationItemState createState() => _SelectableTravelDestinationItemState();
}
class _SelectableTravelDestinationItemState extends State<SelectableTravelDestinationItem> {
// This height will allow for all the Card's content to fit comfortably within the card.
static const double height = 298.0;
bool _isSelected = false;
@override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
return SafeArea(
top: false,
bottom: false,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
const SectionTitle(title: 'Selectable (long press)'),
SizedBox(
height: height,
child: Card(
// This ensures that the Card's children (including the ink splash) are clipped correctly.
clipBehavior: Clip.antiAlias,
shape: widget.shape,
child: InkWell(
onLongPress: () {
print('Selectable card state changed');
setState(() {
_isSelected = !_isSelected;
});
},
// Generally, material cards use onSurface with 12% opacity for the pressed state.
splashColor: colorScheme.onSurface.withOpacity(0.12),
// Generally, material cards do not have a highlight overlay.
highlightColor: Colors.transparent,
child: Stack(
children: <Widget>[
Container(
color: _isSelected
// Generally, material cards use primary with 8% opacity for the selected state.
// See: https://material.io/design/interaction/states.html#anatomy
? colorScheme.primary.withOpacity(0.08)
: Colors.transparent,
),
TravelDestinationContent(destination: widget.destination),
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
Icons.check_circle,
color: _isSelected ? colorScheme.primary : Colors.transparent,
),
),
),
],
),
),
),
),
],
),
),
);
}
}
class SectionTitle extends StatelessWidget {
const SectionTitle({
Key key,
this.title,
}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(4.0, 4.0, 4.0, 12.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(title, style: Theme.of(context).textTheme.subhead),
),
);
}
}
class TravelDestinationContent extends StatelessWidget {
const TravelDestinationContent({ Key key, @required this.destination })
: assert(destination != null),
super(key: key);
final TravelDestination destination;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextStyle titleStyle = theme.textTheme.headline.copyWith(color: Colors.white);
final TextStyle descriptionStyle = theme.textTheme.subhead;
final List<Widget> children = <Widget>[
// Photo and title.
SizedBox(
height: 184.0,
child: Stack(
children: <Widget>[
Positioned.fill(
// In order to have the ink splash appear above the image, you
// must use Ink.image. This allows the image to be painted as part
// of the Material and display ink effects above it. Using a
// standard Image will obscure the ink splash.
child: Ink.image(
image: AssetImage(destination.assetName, package: destination.assetPackage),
fit: BoxFit.cover,
child: Container(),
),
),
Positioned(
bottom: 16.0,
left: 16.0,
right: 16.0,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
destination.title,
style: titleStyle,
),
),
),
],
),
),
// Description and share/explore buttons.
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
child: DefaultTextStyle(
softWrap: false,
overflow: TextOverflow.ellipsis,
style: descriptionStyle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// three line description
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
destination.description,
style: descriptionStyle.copyWith(color: Colors.black54),
),
),
Text(destination.city),
Text(destination.location),
],
),
),
),
];
if (destination.type == CardDemoType.standard) {
children.add(
// share, explore buttons
ButtonBar(
alignment: MainAxisAlignment.start,
children: <Widget>[
FlatButton(
child: Text('SHARE', semanticsLabel: 'Share ${destination.title}'),
textColor: Colors.amber.shade500,
onPressed: () { print('pressed'); },
),
FlatButton(
child: Text('EXPLORE', semanticsLabel: 'Explore ${destination.title}'),
textColor: Colors.amber.shade500,
onPressed: () { print('pressed'); },
),
],
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
);
}
}
class CardsDemo extends StatefulWidget {
static const String routeName = '/material/cards';
@@ -156,26 +359,57 @@ class CardsDemo extends StatefulWidget {
class _CardsDemoState extends State<CardsDemo> {
ShapeBorder _shape;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
return wrapScaffold('Cards Demo', context, _scaffoldKey,
_buildContents(context), CardsDemo.routeName);
}
Widget _buildContents(BuildContext context) {
return ListView(
itemExtent: TravelDestinationItem.height,
padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
children: destinations.map((TravelDestination destination) {
return Container(
margin: const EdgeInsets.only(bottom: 8.0),
child: TravelDestinationItem(
destination: destination,
shape: _shape,
return Scaffold(
appBar: AppBar(
title: const Text('Cards'),
actions: <Widget>[
MaterialDemoDocumentationButton(CardsDemo.routeName),
IconButton(
icon: const Icon(
Icons.sentiment_very_satisfied,
semanticLabel: 'update shape',
),
);
}).toList());
onPressed: () {
setState(() {
_shape = _shape != null ? null : const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0),
bottomLeft: Radius.circular(2.0),
bottomRight: Radius.circular(2.0),
),
);
});
},
),
],
),
body: Scrollbar(
child: ListView(
padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
children: destinations.map<Widget>((TravelDestination destination) {
Widget child;
switch (destination.type) {
case CardDemoType.standard:
child = TravelDestinationItem(destination: destination, shape: _shape);
break;
case CardDemoType.tappable:
child = TappableTravelDestinationItem(destination: destination, shape: _shape);
break;
case CardDemoType.selectable:
child = SelectableTravelDestinationItem(destination: destination, shape: _shape);
break;
}
return Container(
margin: const EdgeInsets.only(bottom: 8.0),
child: child,
);
}).toList(),
),
),
);
}
}

View File

@@ -1,79 +1,335 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import '../../gallery/demo.dart';
class ChipDemo extends StatefulWidget {
static const routeName = '/material/chip';
const List<String> _defaultMaterials = <String>[
'poker',
'tortilla',
'fish and',
'micro',
'wood',
];
const List<String> _defaultActions = <String>[
'flake',
'cut',
'fragment',
'splinter',
'nick',
'fry',
'solder',
'cash in',
'eat',
];
const Map<String, String> _results = <String, String>{
'flake': 'flaking',
'cut': 'cutting',
'fragment': 'fragmenting',
'splinter': 'splintering',
'nick': 'nicking',
'fry': 'frying',
'solder': 'soldering',
'cash in': 'cashing in',
'eat': 'eating',
};
const List<String> _defaultTools = <String>[
'hammer',
'chisel',
'fryer',
'fabricator',
'customer',
];
const Map<String, String> _avatars = <String, String>{
'hammer': 'people/square/ali.png',
'chisel': 'people/square/sandra.png',
'fryer': 'people/square/trevor.png',
'fabricator': 'people/square/stella.png',
'customer': 'people/square/peter.png',
};
const Map<String, Set<String>> _toolActions = <String, Set<String>>{
'hammer': <String>{'flake', 'fragment', 'splinter'},
'chisel': <String>{'flake', 'nick', 'splinter'},
'fryer': <String>{'fry'},
'fabricator': <String>{'solder'},
'customer': <String>{'cash in', 'eat'},
};
const Map<String, Set<String>> _materialActions = <String, Set<String>>{
'poker': <String>{'cash in'},
'tortilla': <String>{'fry', 'eat'},
'fish and': <String>{'fry', 'eat'},
'micro': <String>{'solder', 'fragment'},
'wood': <String>{'flake', 'cut', 'splinter', 'nick'},
};
class _ChipsTile extends StatelessWidget {
const _ChipsTile({
Key key,
this.label,
this.children,
}) : super(key: key);
final String label;
final List<Widget> children;
// Wraps a list of chips into a ListTile for display as a section in the demo.
@override
State<StatefulWidget> createState() => _ChipDemoState();
Widget build(BuildContext context) {
final List<Widget> cardChildren = <Widget>[
Container(
padding: const EdgeInsets.only(top: 16.0, bottom: 4.0),
alignment: Alignment.center,
child: Text(label, textAlign: TextAlign.start),
),
];
if (children.isNotEmpty) {
cardChildren.add(Wrap(
children: children.map<Widget>((Widget chip) {
return Padding(
padding: const EdgeInsets.all(2.0),
child: chip,
);
}).toList()));
} else {
final TextStyle textStyle = Theme.of(context).textTheme.caption.copyWith(fontStyle: FontStyle.italic);
cardChildren.add(
Semantics(
container: true,
child: Container(
alignment: Alignment.center,
constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0),
padding: const EdgeInsets.all(8.0),
child: Text('None', style: textStyle),
),
));
}
return Card(
semanticContainer: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: cardChildren,
),
);
}
}
class ChipDemo extends StatefulWidget {
static const String routeName = '/material/chip';
@override
_ChipDemoState createState() => _ChipDemoState();
}
class _ChipDemoState extends State<ChipDemo> {
bool _filterChipSelected = false;
bool _hasAvatar = true;
_ChipDemoState() {
_reset();
}
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final Set<String> _materials = <String>{};
String _selectedMaterial = '';
String _selectedAction = '';
final Set<String> _tools = <String>{};
final Set<String> _selectedTools = <String>{};
final Set<String> _actions = <String>{};
bool _showShapeBorder = false;
// Initialize members with the default data.
void _reset() {
_materials.clear();
_materials.addAll(_defaultMaterials);
_actions.clear();
_actions.addAll(_defaultActions);
_tools.clear();
_tools.addAll(_defaultTools);
_selectedMaterial = '';
_selectedAction = '';
_selectedTools.clear();
}
void _removeMaterial(String name) {
_materials.remove(name);
if (_selectedMaterial == name) {
_selectedMaterial = '';
}
}
void _removeTool(String name) {
_tools.remove(name);
_selectedTools.remove(name);
}
String _capitalize(String name) {
assert(name != null && name.isNotEmpty);
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
// This converts a String to a unique color, based on the hash value of the
// String object. It takes the bottom 16 bits of the hash, and uses that to
// pick a hue for an HSV color, and then creates the color (with a preset
// saturation and value). This means that any unique strings will also have
// unique colors, but they'll all be readable, since they have the same
// saturation and value.
Color _nameToColor(String name) {
assert(name.length > 1);
final int hash = name.hashCode & 0xffff;
final double hue = (360.0 * hash / (1 << 15)) % 360.0;
return HSVColor.fromAHSV(1.0, hue, 0.4, 0.90).toColor();
}
AssetImage _nameToAvatar(String name) {
assert(_avatars.containsKey(name));
return AssetImage(
_avatars[name],
package: 'flutter_gallery_assets',
);
}
String _createResult() {
if (_selectedAction.isEmpty) {
return '';
}
return _capitalize(_results[_selectedAction]) + '!';
}
@override
Widget build(BuildContext context) {
return wrapScaffold('Chip Demo', context, _scaffoldKey, _buildContents(),
ChipDemo.routeName);
}
final List<Widget> chips = _materials.map<Widget>((String name) {
return Chip(
key: ValueKey<String>(name),
backgroundColor: _nameToColor(name),
label: Text(_capitalize(name)),
onDeleted: () {
setState(() {
_removeMaterial(name);
});
},
);
}).toList();
Widget _buildContents() {
return Material(
child: Column(
children: [
addPadding(Chip(
label: Text('Chip'),
)),
addPadding(InputChip(
label: Text('InputChip'),
)),
addPadding(ChoiceChip(
label: Text('Selected ChoiceChip'),
selected: true,
)),
addPadding(ChoiceChip(
label: Text('Deselected ChoiceChip'),
selected: false,
)),
addPadding(FilterChip(
label: Text('FilterChip'),
selected: _filterChipSelected,
onSelected: (bool newValue) {
final List<Widget> inputChips = _tools.map<Widget>((String name) {
return InputChip(
key: ValueKey<String>(name),
avatar: CircleAvatar(
backgroundImage: _nameToAvatar(name),
),
label: Text(_capitalize(name)),
onDeleted: () {
setState(() {
_filterChipSelected = newValue;
_removeTool(name);
});
},
)),
addPadding(ActionChip(
label: Text('ActionChip'),
onPressed: () {},
)),
addPadding(ActionChip(
label: Text('Chip with avatar'),
avatar: _hasAvatar
? CircleAvatar(
backgroundColor: Colors.amber,
child: Text('Z'),
)
: null,
onPressed: () {
setState(() {
_hasAvatar = !_hasAvatar;
});
},
)),
],
));
});
}).toList();
final List<Widget> choiceChips = _materials.map<Widget>((String name) {
return ChoiceChip(
key: ValueKey<String>(name),
backgroundColor: _nameToColor(name),
label: Text(_capitalize(name)),
selected: _selectedMaterial == name,
onSelected: (bool value) {
setState(() {
_selectedMaterial = value ? name : '';
});
},
);
}).toList();
final List<Widget> filterChips = _defaultTools.map<Widget>((String name) {
return FilterChip(
key: ValueKey<String>(name),
label: Text(_capitalize(name)),
selected: _tools.contains(name) && _selectedTools.contains(name),
onSelected: !_tools.contains(name)
? null
: (bool value) {
setState(() {
if (!value) {
_selectedTools.remove(name);
} else {
_selectedTools.add(name);
}
});
},
);
}).toList();
Set<String> allowedActions = <String>{};
if (_selectedMaterial != null && _selectedMaterial.isNotEmpty) {
for (String tool in _selectedTools) {
allowedActions.addAll(_toolActions[tool]);
}
allowedActions = allowedActions.intersection(_materialActions[_selectedMaterial]);
}
final List<Widget> actionChips = allowedActions.map<Widget>((String name) {
return ActionChip(
label: Text(_capitalize(name)),
onPressed: () {
setState(() {
_selectedAction = name;
});
},
);
}).toList();
final ThemeData theme = Theme.of(context);
final List<Widget> tiles = <Widget>[
const SizedBox(height: 8.0, width: 0.0),
_ChipsTile(label: 'Available Materials (Chip)', children: chips),
_ChipsTile(label: 'Available Tools (InputChip)', children: inputChips),
_ChipsTile(label: 'Choose a Material (ChoiceChip)', children: choiceChips),
_ChipsTile(label: 'Choose Tools (FilterChip)', children: filterChips),
_ChipsTile(label: 'Perform Allowed Action (ActionChip)', children: actionChips),
const Divider(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
_createResult(),
style: theme.textTheme.title,
),
),
),
];
return Scaffold(
appBar: AppBar(
title: const Text('Chips'),
actions: <Widget>[
MaterialDemoDocumentationButton(ChipDemo.routeName),
IconButton(
onPressed: () {
setState(() {
_showShapeBorder = !_showShapeBorder;
});
},
icon: const Icon(Icons.vignette, semanticLabel: 'Update border shape'),
),
],
),
body: ChipTheme(
data: _showShapeBorder
? theme.chipTheme.copyWith(
shape: BeveledRectangleBorder(
side: const BorderSide(width: 0.66, style: BorderStyle.solid, color: Colors.grey),
borderRadius: BorderRadius.circular(10.0),
))
: theme.chipTheme,
child: Scrollbar(child: ListView(children: tiles)),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(_reset),
child: const Icon(Icons.refresh, semanticLabel: 'Reset chips'),
),
);
}
}
Padding addPadding(Widget widget) => Padding(
padding: EdgeInsets.all(10.0),
child: widget,
);

View File

@@ -1,15 +1,14 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter_web/rendering.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import '../../gallery/demo.dart';
class Dessert {
Dessert(this.name, this.calories, this.fat, this.carbs, this.protein,
this.sodium, this.calcium, this.iron);
Dessert(this.name, this.calories, this.fat, this.carbs, this.protein, this.sodium, this.calcium, this.iron);
final String name;
final int calories;
final double fat;
@@ -24,57 +23,60 @@ class Dessert {
class DessertDataSource extends DataTableSource {
final List<Dessert> _desserts = <Dessert>[
Dessert('Frozen yogurt', 159, 6.0, 24, 4.0, 87, 14, 1),
Dessert('Ice cream sandwich', 237, 9.0, 37, 4.3, 129, 8, 1),
Dessert('Eclair', 262, 16.0, 24, 6.0, 337, 6, 7),
Dessert('Cupcake', 305, 3.7, 67, 4.3, 413, 3, 8),
Dessert('Gingerbread', 356, 16.0, 49, 3.9, 327, 7, 16),
Dessert('Jelly bean', 375, 0.0, 94, 0.0, 50, 0, 0),
Dessert('Lollipop', 392, 0.2, 98, 0.0, 38, 0, 2),
Dessert('Honeycomb', 408, 3.2, 87, 6.5, 562, 0, 45),
Dessert('Donut', 452, 25.0, 51, 4.9, 326, 2, 22),
Dessert('KitKat', 518, 26.0, 65, 7.0, 54, 12, 6),
Dessert('Frozen yogurt with sugar', 168, 6.0, 26, 4.0, 87, 14, 1),
Dessert('Ice cream sandwich with sugar', 246, 9.0, 39, 4.3, 129, 8, 1),
Dessert('Eclair with sugar', 271, 16.0, 26, 6.0, 337, 6, 7),
Dessert('Cupcake with sugar', 314, 3.7, 69, 4.3, 413, 3, 8),
Dessert('Gingerbread with sugar', 345, 16.0, 51, 3.9, 327, 7, 16),
Dessert('Jelly bean with sugar', 364, 0.0, 96, 0.0, 50, 0, 0),
Dessert('Lollipop with sugar', 401, 0.2, 100, 0.0, 38, 0, 2),
Dessert('Honeycomb with sugar', 417, 3.2, 89, 6.5, 562, 0, 45),
Dessert('Donut with sugar', 461, 25.0, 53, 4.9, 326, 2, 22),
Dessert('KitKat with sugar', 527, 26.0, 67, 7.0, 54, 12, 6),
Dessert('Frozen yogurt with honey', 223, 6.0, 36, 4.0, 87, 14, 1),
Dessert('Ice cream sandwich with honey', 301, 9.0, 49, 4.3, 129, 8, 1),
Dessert('Eclair with honey', 326, 16.0, 36, 6.0, 337, 6, 7),
Dessert('Cupcake with honey', 369, 3.7, 79, 4.3, 413, 3, 8),
Dessert('Gingerbread with honey', 420, 16.0, 61, 3.9, 327, 7, 16),
Dessert('Jelly bean with honey', 439, 0.0, 106, 0.0, 50, 0, 0),
Dessert('Lollipop with honey', 456, 0.2, 110, 0.0, 38, 0, 2),
Dessert('Honeycomb with honey', 472, 3.2, 99, 6.5, 562, 0, 45),
Dessert('Donut with honey', 516, 25.0, 63, 4.9, 326, 2, 22),
Dessert('KitKat with honey', 582, 26.0, 77, 7.0, 54, 12, 6),
Dessert('Frozen yogurt with milk', 262, 8.4, 36, 12.0, 194, 44, 1),
Dessert('Ice cream sandwich with milk', 339, 11.4, 49, 12.3, 236, 38, 1),
Dessert('Eclair with milk', 365, 18.4, 36, 14.0, 444, 36, 7),
Dessert('Cupcake with milk', 408, 6.1, 79, 12.3, 520, 33, 8),
Dessert('Gingerbread with milk', 459, 18.4, 61, 11.9, 434, 37, 16),
Dessert('Jelly bean with milk', 478, 2.4, 106, 8.0, 157, 30, 0),
Dessert('Lollipop with milk', 495, 2.6, 110, 8.0, 145, 30, 2),
Dessert('Honeycomb with milk', 511, 5.6, 99, 14.5, 669, 30, 45),
Dessert('Donut with milk', 555, 27.4, 63, 12.9, 433, 32, 22),
Dessert('KitKat with milk', 621, 28.4, 77, 15.0, 161, 42, 6),
Dessert('Coconut slice and frozen yogurt', 318, 21.0, 31, 5.5, 96, 14, 7),
Dessert(
'Coconut slice and ice cream sandwich', 396, 24.0, 44, 5.8, 138, 8, 7),
Dessert('Coconut slice and eclair', 421, 31.0, 31, 7.5, 346, 6, 13),
Dessert('Coconut slice and cupcake', 464, 18.7, 74, 5.8, 422, 3, 14),
Dessert('Coconut slice and gingerbread', 515, 31.0, 56, 5.4, 316, 7, 22),
Dessert('Coconut slice and jelly bean', 534, 15.0, 101, 1.5, 59, 0, 6),
Dessert('Coconut slice and lollipop', 551, 15.2, 105, 1.5, 47, 0, 8),
Dessert('Coconut slice and honeycomb', 567, 18.2, 94, 8.0, 571, 0, 51),
Dessert('Coconut slice and donut', 611, 40.0, 58, 6.4, 335, 2, 28),
Dessert('Coconut slice and KitKat', 677, 41.0, 72, 8.5, 63, 12, 12),
Dessert('Frozen yogurt', 159, 6.0, 24, 4.0, 87, 14, 1),
Dessert('Ice cream sandwich', 237, 9.0, 37, 4.3, 129, 8, 1),
Dessert('Eclair', 262, 16.0, 24, 6.0, 337, 6, 7),
Dessert('Cupcake', 305, 3.7, 67, 4.3, 413, 3, 8),
Dessert('Gingerbread', 356, 16.0, 49, 3.9, 327, 7, 16),
Dessert('Jelly bean', 375, 0.0, 94, 0.0, 50, 0, 0),
Dessert('Lollipop', 392, 0.2, 98, 0.0, 38, 0, 2),
Dessert('Honeycomb', 408, 3.2, 87, 6.5, 562, 0, 45),
Dessert('Donut', 452, 25.0, 51, 4.9, 326, 2, 22),
Dessert('KitKat', 518, 26.0, 65, 7.0, 54, 12, 6),
Dessert('Frozen yogurt with sugar', 168, 6.0, 26, 4.0, 87, 14, 1),
Dessert('Ice cream sandwich with sugar', 246, 9.0, 39, 4.3, 129, 8, 1),
Dessert('Eclair with sugar', 271, 16.0, 26, 6.0, 337, 6, 7),
Dessert('Cupcake with sugar', 314, 3.7, 69, 4.3, 413, 3, 8),
Dessert('Gingerbread with sugar', 345, 16.0, 51, 3.9, 327, 7, 16),
Dessert('Jelly bean with sugar', 364, 0.0, 96, 0.0, 50, 0, 0),
Dessert('Lollipop with sugar', 401, 0.2, 100, 0.0, 38, 0, 2),
Dessert('Honeycomb with sugar', 417, 3.2, 89, 6.5, 562, 0, 45),
Dessert('Donut with sugar', 461, 25.0, 53, 4.9, 326, 2, 22),
Dessert('KitKat with sugar', 527, 26.0, 67, 7.0, 54, 12, 6),
Dessert('Frozen yogurt with honey', 223, 6.0, 36, 4.0, 87, 14, 1),
Dessert('Ice cream sandwich with honey', 301, 9.0, 49, 4.3, 129, 8, 1),
Dessert('Eclair with honey', 326, 16.0, 36, 6.0, 337, 6, 7),
Dessert('Cupcake with honey', 369, 3.7, 79, 4.3, 413, 3, 8),
Dessert('Gingerbread with honey', 420, 16.0, 61, 3.9, 327, 7, 16),
Dessert('Jelly bean with honey', 439, 0.0, 106, 0.0, 50, 0, 0),
Dessert('Lollipop with honey', 456, 0.2, 110, 0.0, 38, 0, 2),
Dessert('Honeycomb with honey', 472, 3.2, 99, 6.5, 562, 0, 45),
Dessert('Donut with honey', 516, 25.0, 63, 4.9, 326, 2, 22),
Dessert('KitKat with honey', 582, 26.0, 77, 7.0, 54, 12, 6),
Dessert('Frozen yogurt with milk', 262, 8.4, 36, 12.0, 194, 44, 1),
Dessert('Ice cream sandwich with milk', 339, 11.4, 49, 12.3, 236, 38, 1),
Dessert('Eclair with milk', 365, 18.4, 36, 14.0, 444, 36, 7),
Dessert('Cupcake with milk', 408, 6.1, 79, 12.3, 520, 33, 8),
Dessert('Gingerbread with milk', 459, 18.4, 61, 11.9, 434, 37, 16),
Dessert('Jelly bean with milk', 478, 2.4, 106, 8.0, 157, 30, 0),
Dessert('Lollipop with milk', 495, 2.6, 110, 8.0, 145, 30, 2),
Dessert('Honeycomb with milk', 511, 5.6, 99, 14.5, 669, 30, 45),
Dessert('Donut with milk', 555, 27.4, 63, 12.9, 433, 32, 22),
Dessert('KitKat with milk', 621, 28.4, 77, 15.0, 161, 42, 6),
Dessert('Coconut slice and frozen yogurt', 318, 21.0, 31, 5.5, 96, 14, 7),
Dessert('Coconut slice and ice cream sandwich', 396, 24.0, 44, 5.8, 138, 8, 7),
Dessert('Coconut slice and eclair', 421, 31.0, 31, 7.5, 346, 6, 13),
Dessert('Coconut slice and cupcake', 464, 18.7, 74, 5.8, 422, 3, 14),
Dessert('Coconut slice and gingerbread', 515, 31.0, 56, 5.4, 316, 7, 22),
Dessert('Coconut slice and jelly bean', 534, 15.0, 101, 1.5, 59, 0, 6),
Dessert('Coconut slice and lollipop', 551, 15.2, 105, 1.5, 47, 0, 8),
Dessert('Coconut slice and honeycomb', 567, 18.2, 94, 8.0, 571, 0, 51),
Dessert('Coconut slice and donut', 611, 40.0, 58, 6.4, 335, 2, 28),
Dessert('Coconut slice and KitKat', 677, 41.0, 72, 8.5, 63, 12, 12),
];
void _sort<T>(Comparable<T> getField(Dessert d), bool ascending) {
@@ -96,29 +98,31 @@ class DessertDataSource extends DataTableSource {
@override
DataRow getRow(int index) {
assert(index >= 0);
if (index >= _desserts.length) return null;
if (index >= _desserts.length)
return null;
final Dessert dessert = _desserts[index];
return DataRow.byIndex(
index: index,
selected: dessert.selected,
onSelectChanged: (bool value) {
if (dessert.selected != value) {
_selectedCount += value ? 1 : -1;
assert(_selectedCount >= 0);
dessert.selected = value;
notifyListeners();
}
},
cells: <DataCell>[
DataCell(Text('${dessert.name}')),
DataCell(Text('${dessert.calories}')),
DataCell(Text('${dessert.fat.toStringAsFixed(1)}')),
DataCell(Text('${dessert.carbs}')),
DataCell(Text('${dessert.protein.toStringAsFixed(1)}')),
DataCell(Text('${dessert.sodium}')),
DataCell(Text('${dessert.calcium}%')),
DataCell(Text('${dessert.iron}%')),
]);
index: index,
selected: dessert.selected,
onSelectChanged: (bool value) {
if (dessert.selected != value) {
_selectedCount += value ? 1 : -1;
assert(_selectedCount >= 0);
dessert.selected = value;
notifyListeners();
}
},
cells: <DataCell>[
DataCell(Text('${dessert.name}')),
DataCell(Text('${dessert.calories}')),
DataCell(Text('${dessert.fat.toStringAsFixed(1)}')),
DataCell(Text('${dessert.carbs}')),
DataCell(Text('${dessert.protein.toStringAsFixed(1)}')),
DataCell(Text('${dessert.sodium}')),
DataCell(Text('${dessert.calcium}%')),
DataCell(Text('${dessert.iron}%')),
],
);
}
@override
@@ -131,7 +135,8 @@ class DessertDataSource extends DataTableSource {
int get selectedRowCount => _selectedCount;
void _selectAll(bool checked) {
for (Dessert dessert in _desserts) dessert.selected = checked;
for (Dessert dessert in _desserts)
dessert.selected = checked;
_selectedCount = checked ? _desserts.length : 0;
notifyListeners();
}
@@ -150,8 +155,7 @@ class _DataTableDemoState extends State<DataTableDemo> {
bool _sortAscending = true;
final DessertDataSource _dessertsDataSource = DessertDataSource();
void _sort<T>(
Comparable<T> getField(Dessert d), int columnIndex, bool ascending) {
void _sort<T>(Comparable<T> getField(Dessert d), int columnIndex, bool ascending) {
_dessertsDataSource._sort<T>(getField, ascending);
setState(() {
_sortColumnIndex = columnIndex;
@@ -162,70 +166,71 @@ class _DataTableDemoState extends State<DataTableDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Data tables'),
actions: <Widget>[
MaterialDemoDocumentationButton(DataTableDemo.routeName),
],
),
body: ListView(padding: const EdgeInsets.all(20.0), children: <Widget>[
PaginatedDataTable(
appBar: AppBar(
title: const Text('Data tables'),
actions: <Widget>[
MaterialDemoDocumentationButton(DataTableDemo.routeName),
],
),
body: Scrollbar(
child: ListView(
padding: const EdgeInsets.all(20.0),
children: <Widget>[
PaginatedDataTable(
header: const Text('Nutrition'),
rowsPerPage: _rowsPerPage,
onRowsPerPageChanged: (int value) {
setState(() {
_rowsPerPage = value;
});
},
onRowsPerPageChanged: (int value) { setState(() { _rowsPerPage = value; }); },
sortColumnIndex: _sortColumnIndex,
sortAscending: _sortAscending,
onSelectAll: _dessertsDataSource._selectAll,
columns: <DataColumn>[
DataColumn(
label: const Text('Dessert (100g serving)'),
onSort: (int columnIndex, bool ascending) => _sort<String>(
(Dessert d) => d.name, columnIndex, ascending)),
label: const Text('Dessert (100g serving)'),
onSort: (int columnIndex, bool ascending) => _sort<String>((Dessert d) => d.name, columnIndex, ascending),
),
DataColumn(
label: const Text('Calories'),
tooltip:
'The total amount of food energy in the given serving size.',
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>(
(Dessert d) => d.calories, columnIndex, ascending)),
label: const Text('Calories'),
tooltip: 'The total amount of food energy in the given serving size.',
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>((Dessert d) => d.calories, columnIndex, ascending),
),
DataColumn(
label: const Text('Fat (g)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>(
(Dessert d) => d.fat, columnIndex, ascending)),
label: const Text('Fat (g)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>((Dessert d) => d.fat, columnIndex, ascending),
),
DataColumn(
label: const Text('Carbs (g)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>(
(Dessert d) => d.carbs, columnIndex, ascending)),
label: const Text('Carbs (g)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>((Dessert d) => d.carbs, columnIndex, ascending),
),
DataColumn(
label: const Text('Protein (g)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>(
(Dessert d) => d.protein, columnIndex, ascending)),
label: const Text('Protein (g)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>((Dessert d) => d.protein, columnIndex, ascending),
),
DataColumn(
label: const Text('Sodium (mg)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>(
(Dessert d) => d.sodium, columnIndex, ascending)),
label: const Text('Sodium (mg)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>((Dessert d) => d.sodium, columnIndex, ascending),
),
DataColumn(
label: const Text('Calcium (%)'),
tooltip:
'The amount of calcium as a percentage of the recommended daily amount.',
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>(
(Dessert d) => d.calcium, columnIndex, ascending)),
label: const Text('Calcium (%)'),
tooltip: 'The amount of calcium as a percentage of the recommended daily amount.',
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>((Dessert d) => d.calcium, columnIndex, ascending),
),
DataColumn(
label: const Text('Iron (%)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>(
(Dessert d) => d.iron, columnIndex, ascending)),
label: const Text('Iron (%)'),
numeric: true,
onSort: (int columnIndex, bool ascending) => _sort<num>((Dessert d) => d.iron, columnIndex, ascending),
),
],
source: _dessertsDataSource)
]));
source: _dessertsDataSource,
),
],
),
),
);
}
}

View File

@@ -1,23 +1,23 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../gallery/demo.dart';
class _InputDropdown extends StatelessWidget {
const _InputDropdown(
{Key key,
this.child,
this.labelText,
this.valueText,
this.valueStyle,
this.onPressed})
: super(key: key);
const _InputDropdown({
Key key,
this.child,
this.labelText,
this.valueText,
this.valueStyle,
this.onPressed,
}) : super(key: key);
final String labelText;
final String valueText;
@@ -40,9 +40,8 @@ class _InputDropdown extends StatelessWidget {
children: <Widget>[
Text(valueText, style: valueStyle),
Icon(Icons.arrow_drop_down,
color: Theme.of(context).brightness == Brightness.light
? Colors.grey.shade700
: Colors.white70),
color: Theme.of(context).brightness == Brightness.light ? Colors.grey.shade700 : Colors.white70,
),
],
),
),
@@ -51,14 +50,14 @@ class _InputDropdown extends StatelessWidget {
}
class _DateTimePicker extends StatelessWidget {
const _DateTimePicker(
{Key key,
this.labelText,
this.selectedDate,
this.selectedTime,
this.selectDate,
this.selectTime})
: super(key: key);
const _DateTimePicker({
Key key,
this.labelText,
this.selectedDate,
this.selectedTime,
this.selectDate,
this.selectTime,
}) : super(key: key);
final String labelText;
final DateTime selectedDate;
@@ -68,17 +67,22 @@ class _DateTimePicker extends StatelessWidget {
Future<void> _selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2015, 8),
lastDate: DateTime(2101));
if (picked != null && picked != selectedDate) selectDate(picked);
context: context,
initialDate: selectedDate,
firstDate: DateTime(2015, 8),
lastDate: DateTime(2101),
);
if (picked != null && picked != selectedDate)
selectDate(picked);
}
Future<void> _selectTime(BuildContext context) async {
final TimeOfDay picked =
await showTimePicker(context: context, initialTime: selectedTime);
if (picked != null && picked != selectedTime) selectTime(picked);
final TimeOfDay picked = await showTimePicker(
context: context,
initialTime: selectedTime,
);
if (picked != null && picked != selectedTime)
selectTime(picked);
}
@override
@@ -93,9 +97,7 @@ class _DateTimePicker extends StatelessWidget {
labelText: labelText,
valueText: DateFormat.yMMMd().format(selectedDate),
valueStyle: valueStyle,
onPressed: () {
_selectDate(context);
},
onPressed: () { _selectDate(context); },
),
),
const SizedBox(width: 12.0),
@@ -104,9 +106,7 @@ class _DateTimePicker extends StatelessWidget {
child: _InputDropdown(
valueText: selectedTime.format(context),
valueStyle: valueStyle,
onPressed: () {
_selectTime(context);
},
onPressed: () { _selectTime(context); },
),
),
],
@@ -126,12 +126,7 @@ class _DateAndTimePickerDemoState extends State<DateAndTimePickerDemo> {
TimeOfDay _fromTime = const TimeOfDay(hour: 7, minute: 28);
DateTime _toDate = DateTime.now();
TimeOfDay _toTime = const TimeOfDay(hour: 7, minute: 28);
final List<String> _allActivities = <String>[
'hiking',
'swimming',
'boating',
'fishing'
];
final List<String> _allActivities = <String>['hiking', 'swimming', 'boating', 'fishing'];
String _activity = 'fishing';
@override
@@ -139,9 +134,7 @@ class _DateAndTimePickerDemoState extends State<DateAndTimePickerDemo> {
return Scaffold(
appBar: AppBar(
title: const Text('Date and time pickers'),
actions: <Widget>[
MaterialDemoDocumentationButton(DateAndTimePickerDemo.routeName)
],
actions: <Widget>[MaterialDemoDocumentationButton(DateAndTimePickerDemo.routeName)],
),
body: DropdownButtonHideUnderline(
child: SafeArea(
@@ -162,10 +155,7 @@ class _DateAndTimePickerDemoState extends State<DateAndTimePickerDemo> {
decoration: const InputDecoration(
labelText: 'Location',
),
style: Theme.of(context)
.textTheme
.display1
.copyWith(fontSize: 20.0),
style: Theme.of(context).textTheme.display1.copyWith(fontSize: 20.0),
),
_DateTimePicker(
labelText: 'From',
@@ -212,8 +202,7 @@ class _DateAndTimePickerDemoState extends State<DateAndTimePickerDemo> {
_activity = newValue;
});
},
items: _allActivities
.map<DropdownMenuItem<String>>((String value) {
items: _allActivities.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),

View File

@@ -1,8 +1,8 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
import 'full_screen_dialog_demo.dart';
@@ -17,13 +17,11 @@ enum DialogDemoAction {
const String _alertWithoutTitleText = 'Discard draft?';
const String _alertWithTitleText =
'Let Google help apps determine location. This means sending anonymous location '
'data to Google, even when no apps are running.';
'Let Google help apps determine location. This means sending anonymous location '
'data to Google, even when no apps are running.';
class DialogDemoItem extends StatelessWidget {
const DialogDemoItem(
{Key key, this.icon, this.color, this.text, this.onPressed})
: super(key: key);
const DialogDemoItem({ Key key, this.icon, this.color, this.text, this.onPressed }) : super(key: key);
final IconData icon;
final Color color;
@@ -68,15 +66,16 @@ class DialogDemoState extends State<DialogDemo> {
_selectedTime = TimeOfDay(hour: now.hour, minute: now.minute);
}
void showDemoDialog<T>({BuildContext context, Widget child}) {
void showDemoDialog<T>({ BuildContext context, Widget child }) {
showDialog<T>(
context: context,
builder: (BuildContext context) => child,
).then<void>((T value) {
// The value passed to Navigator.pop() or null.
)
.then<void>((T value) { // The value passed to Navigator.pop() or null.
if (value != null) {
_scaffoldKey.currentState
.showSnackBar(SnackBar(content: Text('You selected: $value')));
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('You selected: $value'),
));
}
});
}
@@ -84,128 +83,132 @@ class DialogDemoState extends State<DialogDemo> {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextStyle dialogTextStyle =
theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
final TextStyle dialogTextStyle = theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Dialogs'),
actions: <Widget>[
MaterialDemoDocumentationButton(DialogDemo.routeName)
],
),
body: ListView(
padding:
const EdgeInsets.symmetric(vertical: 24.0, horizontal: 72.0),
children: <Widget>[
RaisedButton(
child: const Text('ALERT'),
onPressed: () {
showDemoDialog<DialogDemoAction>(
context: context,
child: AlertDialog(
content: Text(_alertWithoutTitleText,
style: dialogTextStyle),
actions: <Widget>[
FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.pop(
context, DialogDemoAction.cancel);
}),
FlatButton(
child: const Text('DISCARD'),
onPressed: () {
Navigator.pop(
context, DialogDemoAction.discard);
})
]));
}),
RaisedButton(
child: const Text('ALERT WITH TITLE'),
onPressed: () {
showDemoDialog<DialogDemoAction>(
context: context,
child: AlertDialog(
title:
const Text('Use Google\'s location service?'),
content: Text(_alertWithTitleText,
style: dialogTextStyle),
actions: <Widget>[
FlatButton(
child: const Text('DISAGREE'),
onPressed: () {
Navigator.pop(
context, DialogDemoAction.disagree);
}),
FlatButton(
child: const Text('AGREE'),
onPressed: () {
Navigator.pop(
context, DialogDemoAction.agree);
})
]));
}),
RaisedButton(
child: const Text('SIMPLE'),
onPressed: () {
showDemoDialog<String>(
context: context,
child: SimpleDialog(
title: const Text('Set backup account'),
children: <Widget>[
DialogDemoItem(
icon: Icons.account_circle,
color: theme.primaryColor,
text: 'username@gmail.com',
onPressed: () {
Navigator.pop(
context, 'username@gmail.com');
}),
DialogDemoItem(
icon: Icons.account_circle,
color: theme.primaryColor,
text: 'user02@gmail.com',
onPressed: () {
Navigator.pop(context, 'user02@gmail.com');
}),
DialogDemoItem(
icon: Icons.add_circle,
text: 'add account',
color: theme.disabledColor)
]));
}),
RaisedButton(
child: const Text('CONFIRMATION'),
onPressed: () {
showTimePicker(context: context, initialTime: _selectedTime)
.then<void>((TimeOfDay value) {
if (value != null && value != _selectedTime) {
_selectedTime = value;
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(
'You selected: ${value.format(context)}')));
}
});
}),
RaisedButton(
child: const Text('FULLSCREEN'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<DismissDialogAction>(
builder: (BuildContext context) =>
FullScreenDialogDemo(),
fullscreenDialog: true,
));
}),
]
// Add a little space between the buttons
.map<Widget>((Widget button) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: button);
}).toList()));
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Dialogs'),
actions: <Widget>[MaterialDemoDocumentationButton(DialogDemo.routeName)],
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 72.0),
children: <Widget>[
RaisedButton(
child: const Text('ALERT'),
onPressed: () {
showDemoDialog<DialogDemoAction>(
context: context,
child: AlertDialog(
content: Text(
_alertWithoutTitleText,
style: dialogTextStyle,
),
actions: <Widget>[
FlatButton(
child: const Text('CANCEL'),
onPressed: () { Navigator.pop(context, DialogDemoAction.cancel); },
),
FlatButton(
child: const Text('DISCARD'),
onPressed: () { Navigator.pop(context, DialogDemoAction.discard); },
),
],
),
);
},
),
RaisedButton(
child: const Text('ALERT WITH TITLE'),
onPressed: () {
showDemoDialog<DialogDemoAction>(
context: context,
child: AlertDialog(
title: const Text('Use Google\'s location service?'),
content: Text(
_alertWithTitleText,
style: dialogTextStyle,
),
actions: <Widget>[
FlatButton(
child: const Text('DISAGREE'),
onPressed: () { Navigator.pop(context, DialogDemoAction.disagree); },
),
FlatButton(
child: const Text('AGREE'),
onPressed: () { Navigator.pop(context, DialogDemoAction.agree); },
),
],
),
);
},
),
RaisedButton(
child: const Text('SIMPLE'),
onPressed: () {
showDemoDialog<String>(
context: context,
child: SimpleDialog(
title: const Text('Set backup account'),
children: <Widget>[
DialogDemoItem(
icon: Icons.account_circle,
color: theme.primaryColor,
text: 'username@gmail.com',
onPressed: () { Navigator.pop(context, 'username@gmail.com'); },
),
DialogDemoItem(
icon: Icons.account_circle,
color: theme.primaryColor,
text: 'user02@gmail.com',
onPressed: () { Navigator.pop(context, 'user02@gmail.com'); },
),
DialogDemoItem(
icon: Icons.add_circle,
text: 'add account',
color: theme.disabledColor,
),
],
),
);
},
),
RaisedButton(
child: const Text('CONFIRMATION'),
onPressed: () {
showTimePicker(
context: context,
initialTime: _selectedTime,
)
.then<void>((TimeOfDay value) {
if (value != null && value != _selectedTime) {
_selectedTime = value;
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('You selected: ${value.format(context)}'),
));
}
});
},
),
RaisedButton(
child: const Text('FULLSCREEN'),
onPressed: () {
Navigator.push(context, MaterialPageRoute<DismissDialogAction>(
builder: (BuildContext context) => FullScreenDialogDemo(),
fullscreenDialog: true,
));
},
),
]
// Add a little space between the buttons
.map<Widget>((Widget button) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: button,
);
})
.toList(),
),
);
}
}

View File

@@ -1,11 +1,17 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import '../../gallery/demo.dart';
const String _kAsset0 = 'people/square/trevor.png';
const String _kAsset1 = 'people/square/stella.png';
const String _kAsset2 = 'people/square/sandra.png';
const String _kGalleryAssetsPackage = 'flutter_gallery_assets';
class DrawerDemo extends StatefulWidget {
static const String routeName = '/material/drawer';
@@ -17,11 +23,7 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
static const List<String> _drawerContents = <String>[
'A',
'B',
'C',
'D',
'E',
'A', 'B', 'C', 'D', 'E',
];
static final Animatable<Offset> _drawerDetailsTween = Tween<Offset>(
@@ -70,13 +72,15 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
void _showNotImplementedMessage() {
Navigator.pop(context); // Dismiss the drawer.
_scaffoldKey.currentState.showSnackBar(
const SnackBar(content: Text("The drawer's items don't do anything")));
_scaffoldKey.currentState.showSnackBar(const SnackBar(
content: Text("The drawer's items don't do anything"),
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
drawerDragStartBehavior: DragStartBehavior.down,
key: _scaffoldKey,
appBar: AppBar(
leading: IconButton(
@@ -88,9 +92,7 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
},
),
title: const Text('Navigation drawer'),
actions: <Widget>[
MaterialDemoDocumentationButton(DrawerDemo.routeName)
],
actions: <Widget>[MaterialDemoDocumentationButton(DrawerDemo.routeName)],
),
drawer: Drawer(
child: Column(
@@ -98,6 +100,44 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
UserAccountsDrawerHeader(
accountName: const Text('Trevor Widget'),
accountEmail: const Text('trevor.widget@example.com'),
currentAccountPicture: const CircleAvatar(
backgroundImage: AssetImage(
_kAsset0,
package: _kGalleryAssetsPackage,
),
),
otherAccountsPictures: <Widget>[
GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
_onOtherAccountsTap(context);
},
child: Semantics(
label: 'Switch to Account B',
child: const CircleAvatar(
backgroundImage: AssetImage(
_kAsset1,
package: _kGalleryAssetsPackage,
),
),
),
),
GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
_onOtherAccountsTap(context);
},
child: Semantics(
label: 'Switch to Account C',
child: const CircleAvatar(
backgroundImage: AssetImage(
_kAsset2,
package: _kGalleryAssetsPackage,
),
),
),
),
],
margin: EdgeInsets.zero,
onDetailsPressed: () {
_showDrawerContents = !_showDrawerContents;
@@ -113,6 +153,7 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
removeTop: true,
child: Expanded(
child: ListView(
dragStartBehavior: DragStartBehavior.down,
padding: const EdgeInsets.only(top: 8.0),
children: <Widget>[
Stack(
@@ -179,11 +220,19 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
Container(
width: 100.0,
height: 100.0,
decoration: const BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: AssetImage(
_kAsset0,
package: _kGalleryAssetsPackage,
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'Tap here to open the drawer',
child: Text('Tap here to open the drawer',
style: Theme.of(context).textTheme.subhead,
),
),
@@ -194,4 +243,23 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
),
);
}
void _onOtherAccountsTap(BuildContext context) {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Account switching not implemented.'),
actions: <Widget>[
FlatButton(
child: const Text('OK'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
},
);
}
}

View File

@@ -1,92 +0,0 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
class EditableTextDemo extends StatefulWidget {
static String routeName = '/material/editable_text';
@override
State<StatefulWidget> createState() => EditableTextDemoState();
}
class EditableTextDemoState extends State<EditableTextDemo> {
final cyanController = TextEditingController(text: 'Cyan');
final orangeController = TextEditingController(text: 'Orange');
final thickController = TextEditingController(text: 'Thick Rounded Cursor');
final multiController =
TextEditingController(text: 'First line\nSecond line');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text Editing'),
centerTitle: true,
),
body: ListView(
children: [
field(
cyanController,
color: Colors.cyan.shade50,
selection: Colors.cyan.shade200,
cursor: Colors.cyan.shade900,
),
field(
orangeController,
color: Colors.orange.shade50,
selection: Colors.orange.shade200,
cursor: Colors.orange.shade900,
center: true,
),
field(
thickController,
color: Colors.white,
selection: Colors.grey.shade200,
cursor: Colors.red.shade900,
radius: const Radius.circular(2),
cursorWidth: 8,
),
Banner(
child: TextField(
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
controller: multiController,
maxLines: 3,
),
message: 'W.I.P',
textDirection: TextDirection.ltr,
location: BannerLocation.bottomEnd,
),
],
),
);
}
}
Widget field(
TextEditingController controller, {
Color color,
Color selection,
Color cursor,
Radius radius = null,
double cursorWidth = 2,
bool center = false,
}) {
return Theme(
data: ThemeData(textSelectionColor: selection),
child: Container(
color: color,
child: TextField(
textAlign: center ? TextAlign.center : TextAlign.start,
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(8, 16, 8, 16),
),
controller: controller,
cursorColor: cursor,
cursorRadius: radius,
cursorWidth: cursorWidth,
),
),
);
}

View File

@@ -1,8 +1,4 @@
// 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/material.dart';
import '../../gallery/demo.dart';
@@ -58,12 +54,10 @@ class _ElevationDemoState extends State<ElevationDemo> {
onPressed: () {
setState(() => _showElevation = !_showElevation);
},
)
),
],
),
body: ListView(
children: buildCards(),
),
body: Scrollbar(child: ListView(children: buildCards())),
);
}
}

View File

@@ -1,19 +1,28 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
@visibleForTesting
enum Location { Barbados, Bahamas, Bermuda }
enum Location {
Barbados,
Bahamas,
Bermuda
}
typedef DemoItemBodyBuilder<T> = Widget Function(DemoItem<T> item);
typedef ValueToString<T> = String Function(T value);
class DualHeaderWithHint extends StatelessWidget {
const DualHeaderWithHint({this.name, this.value, this.hint, this.showHint});
const DualHeaderWithHint({
this.name,
this.value,
this.hint,
this.showHint,
});
final String name;
final String value;
@@ -27,8 +36,7 @@ class DualHeaderWithHint extends StatelessWidget {
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState:
isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
duration: const Duration(milliseconds: 200),
);
}
@@ -38,37 +46,45 @@ class DualHeaderWithHint extends StatelessWidget {
final ThemeData theme = Theme.of(context);
final TextTheme textTheme = theme.textTheme;
return Row(children: <Widget>[
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(left: 24.0),
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
name,
style: textTheme.body1.copyWith(fontSize: 15.0),
return Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(left: 24.0),
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
name,
style: textTheme.body1.copyWith(fontSize: 15.0),
),
),
),
),
),
Expanded(
Expanded(
flex: 3,
child: Container(
margin: const EdgeInsets.only(left: 24.0),
child: _crossFade(
Text(value,
style: textTheme.caption.copyWith(fontSize: 15.0)),
Text(hint, style: textTheme.caption.copyWith(fontSize: 15.0)),
showHint)))
]);
margin: const EdgeInsets.only(left: 24.0),
child: _crossFade(
Text(value, style: textTheme.caption.copyWith(fontSize: 15.0)),
Text(hint, style: textTheme.caption.copyWith(fontSize: 15.0)),
showHint,
),
),
),
],
);
}
}
class CollapsibleBody extends StatelessWidget {
const CollapsibleBody(
{this.margin = EdgeInsets.zero, this.child, this.onSave, this.onCancel});
const CollapsibleBody({
this.margin = EdgeInsets.zero,
this.child,
this.onSave,
this.onCancel,
});
final EdgeInsets margin;
final Widget child;
@@ -80,42 +96,62 @@ class CollapsibleBody extends StatelessWidget {
final ThemeData theme = Theme.of(context);
final TextTheme textTheme = theme.textTheme;
return Column(children: <Widget>[
Container(
margin: const EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0) -
margin,
return Column(
children: <Widget>[
Container(
margin: const EdgeInsets.only(
left: 24.0,
right: 24.0,
bottom: 24.0,
) - margin,
child: Center(
child: DefaultTextStyle(
style: textTheme.caption.copyWith(fontSize: 15.0),
child: child))),
const Divider(height: 1.0),
Container(
child: DefaultTextStyle(
style: textTheme.caption.copyWith(fontSize: 15.0),
child: child,
),
),
),
const Divider(height: 1.0),
Container(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child:
Row(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Container(
margin: const EdgeInsets.only(right: 8.0),
child: FlatButton(
onPressed: onCancel,
child: const Text('CANCEL',
style: TextStyle(
color: Colors.black54,
fontSize: 15.0,
fontWeight: FontWeight.w500)))),
Container(
onPressed: onCancel,
child: const Text('CANCEL', style: TextStyle(
color: Colors.black54,
fontSize: 15.0,
fontWeight: FontWeight.w500,
)),
),
),
Container(
margin: const EdgeInsets.only(right: 8.0),
child: FlatButton(
onPressed: onSave,
textTheme: ButtonTextTheme.accent,
child: const Text('SAVE')))
]))
]);
onPressed: onSave,
textTheme: ButtonTextTheme.accent,
child: const Text('SAVE'),
),
),
],
),
),
],
);
}
}
class DemoItem<T> {
DemoItem({this.name, this.value, this.hint, this.builder, this.valueToString})
: textController = TextEditingController(text: valueToString(value));
DemoItem({
this.name,
this.value,
this.hint,
this.builder,
this.valueToString,
}) : textController = TextEditingController(text: valueToString(value));
final String name;
final String hint;
@@ -128,10 +164,11 @@ class DemoItem<T> {
ExpansionPanelHeaderBuilder get headerBuilder {
return (BuildContext context, bool isExpanded) {
return DualHeaderWithHint(
name: name,
value: valueToString(value),
hint: hint,
showHint: isExpanded);
name: name,
value: valueToString(value),
hint: hint,
showHint: isExpanded,
);
};
}
@@ -170,14 +207,8 @@ class _ExpansionPanelsDemoState extends State<ExpansionPanelsDemo> {
builder: (BuildContext context) {
return CollapsibleBody(
margin: const EdgeInsets.symmetric(horizontal: 16.0),
onSave: () {
Form.of(context).save();
close();
},
onCancel: () {
Form.of(context).reset();
close();
},
onSave: () { Form.of(context).save(); close(); },
onCancel: () { Form.of(context).reset(); close(); },
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: TextFormField(
@@ -186,9 +217,7 @@ class _ExpansionPanelsDemoState extends State<ExpansionPanelsDemo> {
hintText: item.hint,
labelText: item.name,
),
onSaved: (String value) {
item.value = value;
},
onSaved: (String value) { item.value = value; },
),
),
);
@@ -198,104 +227,101 @@ class _ExpansionPanelsDemoState extends State<ExpansionPanelsDemo> {
},
),
DemoItem<Location>(
name: 'Location',
value: Location.Bahamas,
hint: 'Select location',
valueToString: (Location location) =>
location.toString().split('.')[1],
builder: (DemoItem<Location> item) {
void close() {
setState(() {
item.isExpanded = false;
});
}
return Form(child: Builder(builder: (BuildContext context) {
return CollapsibleBody(
onSave: () {
Form.of(context).save();
close();
},
onCancel: () {
Form.of(context).reset();
close();
},
child: FormField<Location>(
name: 'Location',
value: Location.Bahamas,
hint: 'Select location',
valueToString: (Location location) => location.toString().split('.')[1],
builder: (DemoItem<Location> item) {
void close() {
setState(() {
item.isExpanded = false;
});
}
return Form(
child: Builder(
builder: (BuildContext context) {
return CollapsibleBody(
onSave: () { Form.of(context).save(); close(); },
onCancel: () { Form.of(context).reset(); close(); },
child: FormField<Location>(
initialValue: item.value,
onSaved: (Location result) {
item.value = result;
},
onSaved: (Location result) { item.value = result; },
builder: (FormFieldState<Location> field) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
RadioListTile<Location>(
value: Location.Bahamas,
title: const Text('Bahamas'),
groupValue: field.value,
onChanged: field.didChange,
),
RadioListTile<Location>(
value: Location.Barbados,
title: const Text('Barbados'),
groupValue: field.value,
onChanged: field.didChange,
),
RadioListTile<Location>(
value: Location.Bermuda,
title: const Text('Bermuda'),
groupValue: field.value,
onChanged: field.didChange,
),
]);
}),
);
}));
}),
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
RadioListTile<Location>(
value: Location.Bahamas,
title: const Text('Bahamas'),
groupValue: field.value,
onChanged: field.didChange,
),
RadioListTile<Location>(
value: Location.Barbados,
title: const Text('Barbados'),
groupValue: field.value,
onChanged: field.didChange,
),
RadioListTile<Location>(
value: Location.Bermuda,
title: const Text('Bermuda'),
groupValue: field.value,
onChanged: field.didChange,
),
],
);
},
),
);
}
),
);
},
),
DemoItem<double>(
name: 'Sun',
value: 80.0,
hint: 'Select sun level',
valueToString: (double amount) => '${amount.round()}',
builder: (DemoItem<double> item) {
void close() {
setState(() {
item.isExpanded = false;
});
}
name: 'Sun',
value: 80.0,
hint: 'Select sun level',
valueToString: (double amount) => '${amount.round()}',
builder: (DemoItem<double> item) {
void close() {
setState(() {
item.isExpanded = false;
});
}
return Form(child: Builder(builder: (BuildContext context) {
return CollapsibleBody(
onSave: () {
Form.of(context).save();
close();
},
onCancel: () {
Form.of(context).reset();
close();
},
child: FormField<double>(
initialValue: item.value,
onSaved: (double value) {
item.value = value;
},
builder: (FormFieldState<double> field) {
return Slider(
min: 0.0,
max: 100.0,
divisions: 5,
activeColor:
Colors.orange[100 + (field.value * 5.0).round()],
label: '${field.value.round()}',
value: field.value,
onChanged: field.didChange,
);
},
),
);
}));
})
return Form(
child: Builder(
builder: (BuildContext context) {
return CollapsibleBody(
onSave: () { Form.of(context).save(); close(); },
onCancel: () { Form.of(context).reset(); close(); },
child: FormField<double>(
initialValue: item.value,
onSaved: (double value) { item.value = value; },
builder: (FormFieldState<double> field) {
return Container(
// Allow room for the value indicator.
padding: const EdgeInsets.only(top: 44.0),
child: Slider(
min: 0.0,
max: 100.0,
divisions: 5,
activeColor: Colors.orange[100 + (field.value * 5.0).round()],
label: '${field.value.round()}',
value: field.value,
onChanged: field.didChange,
),
);
},
),
);
}
),
);
},
),
];
}
@@ -315,18 +341,19 @@ class _ExpansionPanelsDemoState extends State<ExpansionPanelsDemo> {
child: Container(
margin: const EdgeInsets.all(24.0),
child: ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_demoItems[index].isExpanded = !isExpanded;
});
},
children:
_demoItems.map<ExpansionPanel>((DemoItem<dynamic> item) {
return ExpansionPanel(
isExpanded: item.isExpanded,
headerBuilder: item.headerBuilder,
body: item.build());
}).toList()),
expansionCallback: (int index, bool isExpanded) {
setState(() {
_demoItems[index].isExpanded = !isExpanded;
});
},
children: _demoItems.map<ExpansionPanel>((DemoItem<dynamic> item) {
return ExpansionPanel(
isExpanded: item.isExpanded,
headerBuilder: item.headerBuilder,
body: item.build(),
);
}).toList(),
),
),
),
),

View File

@@ -0,0 +1,40 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
class ExpansionTileListDemo extends StatelessWidget {
static const String routeName = '/material/expansion-tile-list';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Expand/collapse list control'),
actions: <Widget>[MaterialDemoDocumentationButton(routeName)],
),
body: Scrollbar(
child: ListView(
children: <Widget>[
const ListTile(title: Text('Top')),
ExpansionTile(
title: const Text('Sublist'),
backgroundColor: Theme.of(context).accentColor.withOpacity(0.025),
children: const <Widget>[
ListTile(title: Text('One')),
ListTile(title: Text('Two')),
// https://en.wikipedia.org/wiki/Free_Four
ListTile(title: Text('Free')),
ListTile(title: Text('Four')),
],
),
const ListTile(title: Text('Bottom')),
],
),
),
);
}
}

View File

@@ -1,14 +1,14 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
// This demo is based on
// https://material.google.com/components/dialogs.html#dialogs-full-screen-dialogs
// https://material.io/design/components/dialogs.html#full-screen-dialog
enum DismissDialogAction {
cancel,
@@ -17,11 +17,11 @@ enum DismissDialogAction {
}
class DateTimeItem extends StatelessWidget {
DateTimeItem({Key key, DateTime dateTime, @required this.onChanged})
: assert(onChanged != null),
date = DateTime(dateTime.year, dateTime.month, dateTime.day),
time = TimeOfDay(hour: dateTime.hour, minute: dateTime.minute),
super(key: key);
DateTimeItem({ Key key, DateTime dateTime, @required this.onChanged })
: assert(onChanged != null),
date = DateTime(dateTime.year, dateTime.month, dateTime.day),
time = TimeOfDay(hour: dateTime.hour, minute: dateTime.minute),
super(key: key);
final DateTime date;
final TimeOfDay time;
@@ -32,55 +32,66 @@ class DateTimeItem extends StatelessWidget {
final ThemeData theme = Theme.of(context);
return DefaultTextStyle(
style: theme.textTheme.subhead,
child: Row(children: <Widget>[
style: theme.textTheme.subhead,
child: Row(
children: <Widget>[
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: theme.dividerColor))),
child: InkWell(
onTap: () {
showDatePicker(
context: context,
initialDate: date,
firstDate:
date.subtract(const Duration(days: 30)),
lastDate: date.add(const Duration(days: 30)))
.then<void>((DateTime value) {
if (value != null)
onChanged(DateTime(value.year, value.month,
value.day, time.hour, time.minute));
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(DateFormat('EEE, MMM d yyyy').format(date)),
const Icon(Icons.arrow_drop_down,
color: Colors.black54),
])))),
Container(
margin: const EdgeInsets.only(left: 8.0),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
decoration: BoxDecoration(
border:
Border(bottom: BorderSide(color: theme.dividerColor))),
border: Border(bottom: BorderSide(color: theme.dividerColor))
),
child: InkWell(
onTap: () {
showTimePicker(context: context, initialTime: time)
.then<void>((TimeOfDay value) {
if (value != null)
onChanged(DateTime(date.year, date.month, date.day,
value.hour, value.minute));
});
},
child: Row(children: <Widget>[
Text('${time.format(context)}'),
onTap: () {
showDatePicker(
context: context,
initialDate: date,
firstDate: date.subtract(const Duration(days: 30)),
lastDate: date.add(const Duration(days: 30)),
)
.then<void>((DateTime value) {
if (value != null)
onChanged(DateTime(value.year, value.month, value.day, time.hour, time.minute));
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(DateFormat('EEE, MMM d yyyy').format(date)),
const Icon(Icons.arrow_drop_down, color: Colors.black54),
])))
]));
],
),
),
),
),
Container(
margin: const EdgeInsets.only(left: 8.0),
padding: const EdgeInsets.symmetric(vertical: 8.0),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: theme.dividerColor))
),
child: InkWell(
onTap: () {
showTimePicker(
context: context,
initialTime: time,
)
.then<void>((TimeOfDay value) {
if (value != null)
onChanged(DateTime(date.year, date.month, date.day, value.hour, value.minute));
});
},
child: Row(
children: <Widget>[
Text('${time.format(context)}'),
const Icon(Icons.arrow_drop_down, color: Colors.black54),
],
),
),
),
],
),
);
}
}
@@ -100,35 +111,37 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
Future<bool> _onWillPop() async {
_saveNeeded = _hasLocation || _hasName || _saveNeeded;
if (!_saveNeeded) return true;
if (!_saveNeeded)
return true;
final ThemeData theme = Theme.of(context);
final TextStyle dialogTextStyle =
theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
final TextStyle dialogTextStyle = theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
return await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Text('Discard new event?', style: dialogTextStyle),
actions: <Widget>[
FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop(
false); // Pops the confirmation dialog but not the page.
}),
FlatButton(
child: const Text('DISCARD'),
onPressed: () {
Navigator.of(context).pop(
true); // Returning true to _onWillPop will pop again.
})
],
);
},
) ??
false;
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Text(
'Discard new event?',
style: dialogTextStyle,
),
actions: <Widget>[
FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop(false); // Pops the confirmation dialog but not the page.
},
),
FlatButton(
child: const Text('DISCARD'),
onPressed: () {
Navigator.of(context).pop(true); // Returning true to _onWillPop will pop again.
},
),
],
);
},
) ?? false;
}
@override
@@ -137,96 +150,119 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
return Scaffold(
appBar: AppBar(
title: Text(_hasName ? _eventName : 'Event Name TBD'),
actions: <Widget>[
FlatButton(
child: Text('SAVE',
style: theme.textTheme.body1.copyWith(color: Colors.white)),
onPressed: () {
Navigator.pop(context, DismissDialogAction.save);
})
]),
title: Text(_hasName ? _eventName : 'Event Name TBD'),
actions: <Widget> [
FlatButton(
child: Text('SAVE', style: theme.textTheme.body1.copyWith(color: Colors.white)),
onPressed: () {
Navigator.pop(context, DismissDialogAction.save);
},
),
],
),
body: Form(
onWillPop: _onWillPop,
onWillPop: _onWillPop,
child: Scrollbar(
child: ListView(
padding: const EdgeInsets.all(16.0),
children: <Widget>[
Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
alignment: Alignment.bottomLeft,
child: TextField(
decoration: const InputDecoration(
labelText: 'Event name', filled: true),
style: theme.textTheme.headline,
onChanged: (String value) {
setState(() {
_hasName = value.isNotEmpty;
if (_hasName) {
_eventName = value;
}
});
})),
Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
alignment: Alignment.bottomLeft,
child: TextField(
decoration: const InputDecoration(
labelText: 'Location',
hintText: 'Where is the event?',
filled: true),
onChanged: (String value) {
setState(() {
_hasLocation = value.isNotEmpty;
});
})),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('From', style: theme.textTheme.caption),
DateTimeItem(
dateTime: _fromDateTime,
onChanged: (DateTime value) {
setState(() {
_fromDateTime = value;
_saveNeeded = true;
});
})
]),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('To', style: theme.textTheme.caption),
DateTimeItem(
dateTime: _toDateTime,
onChanged: (DateTime value) {
setState(() {
_toDateTime = value;
_saveNeeded = true;
});
}),
const Text('All-day'),
]),
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: theme.dividerColor))),
child: Row(children: <Widget>[
Checkbox(
value: _allDayValue,
onChanged: (bool value) {
setState(() {
_allDayValue = value;
_saveNeeded = true;
});
}),
const Text('All-day'),
]))
].map<Widget>((Widget child) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
height: 96.0,
child: child);
}).toList())),
padding: const EdgeInsets.all(16.0),
children: <Widget>[
Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
alignment: Alignment.bottomLeft,
child: TextField(
decoration: const InputDecoration(
labelText: 'Event name',
filled: true,
),
style: theme.textTheme.headline,
onChanged: (String value) {
setState(() {
_hasName = value.isNotEmpty;
if (_hasName) {
_eventName = value;
}
});
},
),
),
Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
alignment: Alignment.bottomLeft,
child: TextField(
decoration: const InputDecoration(
labelText: 'Location',
hintText: 'Where is the event?',
filled: true,
),
onChanged: (String value) {
setState(() {
_hasLocation = value.isNotEmpty;
});
},
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('From', style: theme.textTheme.caption),
DateTimeItem(
dateTime: _fromDateTime,
onChanged: (DateTime value) {
setState(() {
_fromDateTime = value;
_saveNeeded = true;
});
},
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('To', style: theme.textTheme.caption),
DateTimeItem(
dateTime: _toDateTime,
onChanged: (DateTime value) {
setState(() {
_toDateTime = value;
_saveNeeded = true;
});
},
),
const Text('All-day'),
],
),
Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: theme.dividerColor))
),
child: Row(
children: <Widget> [
Checkbox(
value: _allDayValue,
onChanged: (bool value) {
setState(() {
_allDayValue = value;
_saveNeeded = true;
});
},
),
const Text('All-day'),
],
),
),
]
.map<Widget>((Widget child) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
height: 96.0,
child: child,
);
})
.toList(),
),
),
),
);
}
}

View File

@@ -1,12 +1,16 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
enum GridDemoTileStyle { imageOnly, oneLine, twoLine }
enum GridDemoTileStyle {
imageOnly,
oneLine,
twoLine
}
typedef BannerTapCallback = void Function(Photo photo);
@@ -30,15 +34,11 @@ class Photo {
bool isFavorite;
String get tag => assetName; // Assuming that all asset names are unique.
bool get isValid =>
assetName != null &&
title != null &&
caption != null &&
isFavorite != null;
bool get isValid => assetName != null && title != null && caption != null && isFavorite != null;
}
class GridPhotoViewer extends StatefulWidget {
const GridPhotoViewer({Key key, this.photo}) : super(key: key);
const GridPhotoViewer({ Key key, this.photo }) : super(key: key);
final Photo photo;
@@ -61,8 +61,7 @@ class _GridTitleText extends StatelessWidget {
}
}
class _GridPhotoViewerState extends State<GridPhotoViewer>
with SingleTickerProviderStateMixin {
class _GridPhotoViewerState extends State<GridPhotoViewer> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _flingAnimation;
Offset _offset = Offset.zero;
@@ -88,8 +87,7 @@ class _GridPhotoViewerState extends State<GridPhotoViewer>
Offset _clampOffset(Offset offset) {
final Size size = context.size;
final Offset minOffset = Offset(size.width, size.height) * (1.0 - _scale);
return Offset(
offset.dx.clamp(minOffset.dx, 0.0), offset.dy.clamp(minOffset.dy, 0.0));
return Offset(offset.dx.clamp(minOffset.dx, 0.0), offset.dy.clamp(minOffset.dy, 0.0));
}
void _handleFlingAnimation() {
@@ -117,11 +115,14 @@ class _GridPhotoViewerState extends State<GridPhotoViewer>
void _handleOnScaleEnd(ScaleEndDetails details) {
final double magnitude = details.velocity.pixelsPerSecond.distance;
if (magnitude < _kMinFlingVelocity) return;
if (magnitude < _kMinFlingVelocity)
return;
final Offset direction = details.velocity.pixelsPerSecond / magnitude;
final double distance = (Offset.zero & context.size).shortestSide;
_flingAnimation = _controller.drive(Tween<Offset>(
begin: _offset, end: _clampOffset(_offset + direction * distance)));
begin: _offset,
end: _clampOffset(_offset + direction * distance),
));
_controller
..value = 0.0
..fling(velocity: magnitude / 1000.0);
@@ -139,8 +140,8 @@ class _GridPhotoViewerState extends State<GridPhotoViewer>
..translate(_offset.dx, _offset.dy)
..scale(_scale),
child: Image.asset(
'${widget.photo.assetName}',
// TODO(flutter_web): package: widget.photo.assetPackage,
widget.photo.assetName,
package: widget.photo.assetPackage,
fit: BoxFit.cover,
),
),
@@ -150,50 +151,52 @@ class _GridPhotoViewerState extends State<GridPhotoViewer>
}
class GridDemoPhotoItem extends StatelessWidget {
GridDemoPhotoItem(
{Key key,
@required this.photo,
@required this.tileStyle,
@required this.onBannerTap})
: assert(photo != null && photo.isValid),
assert(tileStyle != null),
assert(onBannerTap != null),
super(key: key);
GridDemoPhotoItem({
Key key,
@required this.photo,
@required this.tileStyle,
@required this.onBannerTap,
}) : assert(photo != null && photo.isValid),
assert(tileStyle != null),
assert(onBannerTap != null),
super(key: key);
final Photo photo;
final GridDemoTileStyle tileStyle;
final BannerTapCallback
onBannerTap; // User taps on the photo's header or footer.
final BannerTapCallback onBannerTap; // User taps on the photo's header or footer.
void showPhoto(BuildContext context) {
Navigator.push(context,
MaterialPageRoute<void>(builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(photo.title)),
body: SizedBox.expand(
child: Hero(
tag: photo.tag,
child: GridPhotoViewer(photo: photo),
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(photo.title),
),
),
);
}));
body: SizedBox.expand(
child: Hero(
tag: photo.tag,
child: GridPhotoViewer(photo: photo),
),
),
);
}
));
}
@override
Widget build(BuildContext context) {
final Widget image = GestureDetector(
onTap: () {
showPhoto(context);
},
child: Hero(
key: Key(photo.assetName),
tag: photo.tag,
child: Image.asset(
'${photo.assetName}',
// TODO(flutter_web): package: photo.assetPackage,
fit: BoxFit.cover,
)));
onTap: () { showPhoto(context); },
child: Hero(
key: Key(photo.assetName),
tag: photo.tag,
child: Image.asset(
photo.assetName,
package: photo.assetPackage,
fit: BoxFit.cover,
),
),
);
final IconData icon = photo.isFavorite ? Icons.star : Icons.star_border;
@@ -204,9 +207,7 @@ class GridDemoPhotoItem extends StatelessWidget {
case GridDemoTileStyle.oneLine:
return GridTile(
header: GestureDetector(
onTap: () {
onBannerTap(photo);
},
onTap: () { onBannerTap(photo); },
child: GridTileBar(
title: _GridTitleText(photo.title),
backgroundColor: Colors.black45,
@@ -222,9 +223,7 @@ class GridDemoPhotoItem extends StatelessWidget {
case GridDemoTileStyle.twoLine:
return GridTile(
footer: GestureDetector(
onTap: () {
onBannerTap(photo);
},
onTap: () { onBannerTap(photo); },
child: GridTileBar(
backgroundColor: Colors.black45,
title: _GridTitleText(photo.title),
@@ -244,7 +243,7 @@ class GridDemoPhotoItem extends StatelessWidget {
}
class GridListDemo extends StatefulWidget {
const GridListDemo({Key key}) : super(key: key);
const GridListDemo({ Key key }) : super(key: key);
static const String routeName = '/material/grid-list';
@@ -346,8 +345,7 @@ class GridListDemoState extends State<GridListDemo> {
MaterialDemoDocumentationButton(GridListDemo.routeName),
PopupMenuButton<GridDemoTileStyle>(
onSelected: changeTileStyle,
itemBuilder: (BuildContext context) =>
<PopupMenuItem<GridDemoTileStyle>>[
itemBuilder: (BuildContext context) => <PopupMenuItem<GridDemoTileStyle>>[
const PopupMenuItem<GridDemoTileStyle>(
value: GridDemoTileStyle.imageOnly,
child: Text('Image only'),
@@ -375,17 +373,17 @@ class GridListDemoState extends State<GridListDemo> {
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
padding: const EdgeInsets.all(4.0),
childAspectRatio:
(orientation == Orientation.portrait) ? 1.0 : 1.3,
childAspectRatio: (orientation == Orientation.portrait) ? 1.0 : 1.3,
children: photos.map<Widget>((Photo photo) {
return GridDemoPhotoItem(
photo: photo,
tileStyle: _tileStyle,
onBannerTap: (Photo photo) {
setState(() {
photo.isFavorite = !photo.isFavorite;
});
photo: photo,
tileStyle: _tileStyle,
onBannerTap: (Photo photo) {
setState(() {
photo.isFavorite = !photo.isFavorite;
});
},
);
}).toList(),
),
),

View File

@@ -1,8 +1,8 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
@@ -58,15 +58,15 @@ class IconsDemoState extends State<IconsDemo> {
child: SafeArea(
top: false,
bottom: false,
child: ListView(
padding: const EdgeInsets.all(24.0),
children: <Widget>[
_IconsDemoCard(
handleIconButtonPress, Icons.face), // direction-agnostic icon
const SizedBox(height: 24.0),
_IconsDemoCard(handleIconButtonPress,
Icons.battery_unknown), // direction-aware icon
],
child: Scrollbar(
child: ListView(
padding: const EdgeInsets.all(24.0),
children: <Widget>[
_IconsDemoCard(handleIconButtonPress, Icons.face), // direction-agnostic icon
const SizedBox(height: 24.0),
_IconsDemoCard(handleIconButtonPress, Icons.battery_unknown), // direction-aware icon
],
),
),
),
),
@@ -82,21 +82,23 @@ class _IconsDemoCard extends StatelessWidget {
Widget _buildIconButton(double iconSize, IconData icon, bool enabled) {
return IconButton(
icon: Icon(icon),
iconSize: iconSize,
tooltip: "${enabled ? 'Enabled' : 'Disabled'} icon button",
onPressed: enabled ? handleIconButtonPress : null);
icon: Icon(icon),
iconSize: iconSize,
tooltip: "${enabled ? 'Enabled' : 'Disabled'} icon button",
onPressed: enabled ? handleIconButtonPress : null,
);
}
Widget _centeredText(String label) => Padding(
// Match the default padding of IconButton.
padding: const EdgeInsets.all(8.0),
child: Text(label, textAlign: TextAlign.center),
);
Widget _centeredText(String label) =>
Padding(
// Match the default padding of IconButton.
padding: const EdgeInsets.all(8.0),
child: Text(label, textAlign: TextAlign.center),
);
TableRow _buildIconRow(double size) {
return TableRow(
children: <Widget>[
children: <Widget> [
_centeredText(size.floor().toString()),
_buildIconButton(size, icon, true),
_buildIconButton(size, icon, false),
@@ -107,8 +109,7 @@ class _IconsDemoCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextStyle textStyle =
theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
final TextStyle textStyle = theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
return Card(
child: DefaultTextStyle(
style: textStyle,
@@ -116,12 +117,14 @@ class _IconsDemoCard extends StatelessWidget {
explicitChildNodes: true,
child: Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: <TableRow>[
TableRow(children: <Widget>[
_centeredText('Size'),
_centeredText('Enabled'),
_centeredText('Disabled'),
]),
children: <TableRow> [
TableRow(
children: <Widget> [
_centeredText('Size'),
_centeredText('Enabled'),
_centeredText('Disabled'),
]
),
_buildIconRow(18.0),
_buildIconRow(24.0),
_buildIconRow(36.0),

View File

@@ -1,24 +1,27 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:collection/collection.dart' show lowerBound;
import 'package:flutter_web/material.dart';
import 'package:flutter_web/semantics.dart';
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import '../../gallery/demo.dart';
enum LeaveBehindDemoAction { reset, horizontalSwipe, leftSwipe, rightSwipe }
enum LeaveBehindDemoAction {
reset,
horizontalSwipe,
leftSwipe,
rightSwipe,
confirmDismiss,
}
class LeaveBehindItem implements Comparable<LeaveBehindItem> {
LeaveBehindItem({this.index, this.name, this.subject, this.body});
LeaveBehindItem({ this.index, this.name, this.subject, this.body });
LeaveBehindItem.from(LeaveBehindItem item)
: index = item.index,
name = item.name,
subject = item.subject,
body = item.body;
: index = item.index, name = item.name, subject = item.subject, body = item.body;
final int index;
final String name;
@@ -30,7 +33,7 @@ class LeaveBehindItem implements Comparable<LeaveBehindItem> {
}
class LeaveBehindDemo extends StatefulWidget {
const LeaveBehindDemo({Key key}) : super(key: key);
const LeaveBehindDemo({ Key key }) : super(key: key);
static const String routeName = '/material/leave-behind';
@@ -39,18 +42,19 @@ class LeaveBehindDemo extends StatefulWidget {
}
class LeaveBehindDemoState extends State<LeaveBehindDemo> {
static final GlobalKey<ScaffoldState> _scaffoldKey =
GlobalKey<ScaffoldState>();
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
DismissDirection _dismissDirection = DismissDirection.horizontal;
bool _confirmDismiss = true;
List<LeaveBehindItem> leaveBehindItems;
void initListItems() {
leaveBehindItems = List<LeaveBehindItem>.generate(16, (int index) {
return LeaveBehindItem(
index: index,
name: 'Item $index Sender',
subject: 'Subject: $index',
body: "[$index] first line of the message's body...");
index: index,
name: 'Item $index Sender',
subject: 'Subject: $index',
body: "[$index] first line of the message's body...",
);
});
}
@@ -75,6 +79,9 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
case LeaveBehindDemoAction.rightSwipe:
_dismissDirection = DismissDirection.startToEnd;
break;
case LeaveBehindDemoAction.confirmDismiss:
_confirmDismiss = !_confirmDismiss;
break;
}
});
}
@@ -91,12 +98,12 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
leaveBehindItems.remove(item);
});
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('You archived item ${item.index}'),
action: SnackBarAction(
label: 'UNDO',
onPressed: () {
handleUndo(item);
})));
content: Text('You archived item ${item.index}'),
action: SnackBarAction(
label: 'UNDO',
onPressed: () { handleUndo(item); },
),
));
}
void _handleDelete(LeaveBehindItem item) {
@@ -104,12 +111,12 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
leaveBehindItems.remove(item);
});
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('You deleted item ${item.index}'),
action: SnackBarAction(
label: 'UNDO',
onPressed: () {
handleUndo(item);
})));
content: Text('You deleted item ${item.index}'),
action: SnackBarAction(
label: 'UNDO',
onPressed: () { handleUndo(item); },
),
));
}
@override
@@ -123,43 +130,59 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
),
);
} else {
body = ListView(
body = Scrollbar(
child: ListView(
children: leaveBehindItems.map<Widget>((LeaveBehindItem item) {
return _LeaveBehindListItem(
item: item,
onArchive: _handleArchive,
onDelete: _handleDelete,
dismissDirection: _dismissDirection,
);
}).toList());
return _LeaveBehindListItem(
confirmDismiss: _confirmDismiss,
item: item,
onArchive: _handleArchive,
onDelete: _handleDelete,
dismissDirection: _dismissDirection,
);
}).toList(),
),
);
}
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(title: const Text('Swipe to dismiss'), actions: <Widget>[
MaterialDemoDocumentationButton(LeaveBehindDemo.routeName),
PopupMenuButton<LeaveBehindDemoAction>(
appBar: AppBar(
title: const Text('Swipe to dismiss'),
actions: <Widget>[
MaterialDemoDocumentationButton(LeaveBehindDemo.routeName),
PopupMenuButton<LeaveBehindDemoAction>(
onSelected: handleDemoAction,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<LeaveBehindDemoAction>>[
const PopupMenuItem<LeaveBehindDemoAction>(
value: LeaveBehindDemoAction.reset,
child: Text('Reset the list')),
const PopupMenuDivider(),
CheckedPopupMenuItem<LeaveBehindDemoAction>(
value: LeaveBehindDemoAction.horizontalSwipe,
checked: _dismissDirection == DismissDirection.horizontal,
child: const Text('Horizontal swipe')),
CheckedPopupMenuItem<LeaveBehindDemoAction>(
value: LeaveBehindDemoAction.leftSwipe,
checked: _dismissDirection == DismissDirection.endToStart,
child: const Text('Only swipe left')),
CheckedPopupMenuItem<LeaveBehindDemoAction>(
value: LeaveBehindDemoAction.rightSwipe,
checked: _dismissDirection == DismissDirection.startToEnd,
child: const Text('Only swipe right'))
])
]),
itemBuilder: (BuildContext context) => <PopupMenuEntry<LeaveBehindDemoAction>>[
const PopupMenuItem<LeaveBehindDemoAction>(
value: LeaveBehindDemoAction.reset,
child: Text('Reset the list'),
),
const PopupMenuDivider(),
CheckedPopupMenuItem<LeaveBehindDemoAction>(
value: LeaveBehindDemoAction.horizontalSwipe,
checked: _dismissDirection == DismissDirection.horizontal,
child: const Text('Horizontal swipe'),
),
CheckedPopupMenuItem<LeaveBehindDemoAction>(
value: LeaveBehindDemoAction.leftSwipe,
checked: _dismissDirection == DismissDirection.endToStart,
child: const Text('Only swipe left'),
),
CheckedPopupMenuItem<LeaveBehindDemoAction>(
value: LeaveBehindDemoAction.rightSwipe,
checked: _dismissDirection == DismissDirection.startToEnd,
child: const Text('Only swipe right'),
),
CheckedPopupMenuItem<LeaveBehindDemoAction>(
value: LeaveBehindDemoAction.confirmDismiss,
checked: _confirmDismiss,
child: const Text('Confirm dismiss'),
),
],
),
],
),
body: body,
);
}
@@ -172,12 +195,14 @@ class _LeaveBehindListItem extends StatelessWidget {
@required this.onArchive,
@required this.onDelete,
@required this.dismissDirection,
@required this.confirmDismiss,
}) : super(key: key);
final LeaveBehindItem item;
final DismissDirection dismissDirection;
final void Function(LeaveBehindItem) onArchive;
final void Function(LeaveBehindItem) onDelete;
final bool confirmDismiss;
void _handleArchive() {
onArchive(item);
@@ -204,25 +229,70 @@ class _LeaveBehindListItem extends StatelessWidget {
else
_handleDelete();
},
confirmDismiss: !confirmDismiss ? null : (DismissDirection dismissDirection) async {
switch(dismissDirection) {
case DismissDirection.endToStart:
return await _showConfirmationDialog(context, 'archive') == true;
case DismissDirection.startToEnd:
return await _showConfirmationDialog(context, 'delete') == true;
case DismissDirection.horizontal:
case DismissDirection.vertical:
case DismissDirection.up:
case DismissDirection.down:
assert(false);
}
return false;
},
background: Container(
color: theme.primaryColor,
child: const ListTile(
leading: Icon(Icons.delete, color: Colors.white, size: 36.0))),
color: theme.primaryColor,
child: const ListTile(
leading: Icon(Icons.delete, color: Colors.white, size: 36.0),
),
),
secondaryBackground: Container(
color: theme.primaryColor,
child: const ListTile(
trailing:
Icon(Icons.archive, color: Colors.white, size: 36.0))),
color: theme.primaryColor,
child: const ListTile(
trailing: Icon(Icons.archive, color: Colors.white, size: 36.0),
),
),
child: Container(
decoration: BoxDecoration(
color: theme.canvasColor,
border: Border(bottom: BorderSide(color: theme.dividerColor))),
color: theme.canvasColor,
border: Border(bottom: BorderSide(color: theme.dividerColor)),
),
child: ListTile(
title: Text(item.name),
subtitle: Text('${item.subject}\n${item.body}'),
isThreeLine: true),
title: Text(item.name),
subtitle: Text('${item.subject}\n${item.body}'),
isThreeLine: true,
),
),
),
);
}
Future<bool> _showConfirmationDialog(BuildContext context, String action) {
return showDialog<bool>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Do you want to $action this item?'),
actions: <Widget>[
FlatButton(
child: const Text('Yes'),
onPressed: () {
Navigator.pop(context, true); // showDialog() returns true
},
),
FlatButton(
child: const Text('No'),
onPressed: () {
Navigator.pop(context, false); // showDialog() returns false
},
),
],
);
},
);
}
}

View File

@@ -1,8 +1,8 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
@@ -21,7 +21,7 @@ enum _MaterialListType {
}
class ListDemo extends StatefulWidget {
const ListDemo({Key key}) : super(key: key);
const ListDemo({ Key key }) : super(key: key);
static const String routeName = '/material/list';
@@ -30,8 +30,7 @@ class ListDemo extends StatefulWidget {
}
class _ListDemoState extends State<ListDemo> {
static final GlobalKey<ScaffoldState> scaffoldKey =
GlobalKey<ScaffoldState>();
static final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
PersistentBottomSheetController<void> _bottomSheet;
_MaterialListType _itemType = _MaterialListType.threeLine;
@@ -41,33 +40,18 @@ class _ListDemoState extends State<ListDemo> {
bool _showDividers = false;
bool _reverseSort = false;
List<String> items = <String>[
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
];
void changeItemType(_MaterialListType type) {
setState(() {
_itemType = type;
});
_bottomSheet?.setState(() {});
_bottomSheet?.setState(() { });
}
void _showConfigurationSheet() {
final PersistentBottomSheetController<void> bottomSheet = scaffoldKey
.currentState
.showBottomSheet<void>((BuildContext bottomSheetContext) {
final PersistentBottomSheetController<void> bottomSheet = scaffoldKey.currentState.showBottomSheet<void>((BuildContext bottomSheetContext) {
return Container(
decoration: const BoxDecoration(
border: Border(top: BorderSide(color: Colors.black26)),
@@ -78,25 +62,25 @@ class _ListDemoState extends State<ListDemo> {
children: <Widget>[
MergeSemantics(
child: ListTile(
dense: true,
title: const Text('One-line'),
trailing: Radio<_MaterialListType>(
value: _showAvatars
? _MaterialListType.oneLineWithAvatar
: _MaterialListType.oneLine,
groupValue: _itemType,
onChanged: changeItemType,
)),
dense: true,
title: const Text('One-line'),
trailing: Radio<_MaterialListType>(
value: _showAvatars ? _MaterialListType.oneLineWithAvatar : _MaterialListType.oneLine,
groupValue: _itemType,
onChanged: changeItemType,
),
),
),
MergeSemantics(
child: ListTile(
dense: true,
title: const Text('Two-line'),
trailing: Radio<_MaterialListType>(
value: _MaterialListType.twoLine,
groupValue: _itemType,
onChanged: changeItemType,
)),
dense: true,
title: const Text('Two-line'),
trailing: Radio<_MaterialListType>(
value: _MaterialListType.twoLine,
groupValue: _itemType,
onChanged: changeItemType,
),
),
),
MergeSemantics(
child: ListTile(
@@ -119,7 +103,7 @@ class _ListDemoState extends State<ListDemo> {
setState(() {
_showAvatars = value;
});
_bottomSheet?.setState(() {});
_bottomSheet?.setState(() { });
},
),
),
@@ -134,7 +118,7 @@ class _ListDemoState extends State<ListDemo> {
setState(() {
_showIcons = value;
});
_bottomSheet?.setState(() {});
_bottomSheet?.setState(() { });
},
),
),
@@ -149,7 +133,7 @@ class _ListDemoState extends State<ListDemo> {
setState(() {
_showDividers = value;
});
_bottomSheet?.setState(() {});
_bottomSheet?.setState(() { });
},
),
),
@@ -164,7 +148,7 @@ class _ListDemoState extends State<ListDemo> {
setState(() {
_dense = value;
});
_bottomSheet?.setState(() {});
_bottomSheet?.setState(() { });
},
),
),
@@ -200,14 +184,10 @@ class _ListDemoState extends State<ListDemo> {
child: ListTile(
isThreeLine: _itemType == _MaterialListType.threeLine,
dense: _dense,
leading: _showAvatars
? ExcludeSemantics(child: CircleAvatar(child: Text(item)))
: null,
leading: _showAvatars ? ExcludeSemantics(child: CircleAvatar(child: Text(item))) : null,
title: Text('This item represents $item.'),
subtitle: secondary,
trailing: _showIcons
? Icon(Icons.info, color: Theme.of(context).disabledColor)
: null,
trailing: _showIcons ? Icon(Icons.info, color: Theme.of(context).disabledColor) : null,
),
);
}
@@ -229,8 +209,7 @@ class _ListDemoState extends State<ListDemo> {
break;
}
Iterable<Widget> listTiles =
items.map<Widget>((String item) => buildListTile(context, item));
Iterable<Widget> listTiles = items.map<Widget>((String item) => buildListTile(context, item));
if (_showDividers)
listTiles = ListTile.divideTiles(context: context, tiles: listTiles);
@@ -246,8 +225,7 @@ class _ListDemoState extends State<ListDemo> {
onPressed: () {
setState(() {
_reverseSort = !_reverseSort;
items.sort((String a, String b) =>
_reverseSort ? b.compareTo(a) : a.compareTo(b));
items.sort((String a, String b) => _reverseSort ? b.compareTo(a) : a.compareTo(b));
});
},
),

View File

@@ -1,20 +1,21 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2017 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.
export 'backdrop_demo.dart';
export 'banner_demo.dart';
export 'bottom_app_bar_demo.dart';
export 'bottom_navigation_demo.dart';
export 'material_button_demo.dart';
export 'buttons_demo.dart';
export 'cards_demo.dart';
export 'chip_demo.dart';
export 'data_table_demo.dart';
export 'date_and_time_picker_demo.dart';
export 'dialog_demo.dart';
export 'drawer_demo.dart';
export 'editable_text_demo.dart';
export 'elevation_demo.dart';
export 'expansion_panels_demo.dart';
export 'expansion_tile_list_demo.dart';
export 'grid_list_demo.dart';
export 'icons_demo.dart';
export 'leave_behind_demo.dart';
@@ -33,7 +34,5 @@ export 'slider_demo.dart';
export 'snack_bar_demo.dart';
export 'tabs_demo.dart';
export 'tabs_fab_demo.dart';
export 'text_demo.dart';
export 'text_form_field_demo.dart';
export 'tooltip_demo.dart';
export 'two_level_list_demo.dart';

View File

@@ -1,103 +0,0 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import '../../gallery/demo.dart';
class ButtonsDemo extends StatelessWidget {
static const String routeName = '/material/buttons';
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
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: _scaffoldKey,
appBar: AppBar(
leading: IconButton(
icon: Icon(_backIcon()),
alignment: Alignment.centerLeft,
tooltip: 'Back',
onPressed: () {
Navigator.pop(context);
},
),
title: const Text('Material buttons'),
actions: <Widget>[
MaterialDemoDocumentationButton(ButtonsDemo.routeName)
],
),
body: Center(
child: _buildButtons(),
),
);
}
Widget _buildButtons() {
return Column(
children: [
pad(MaterialButton(
onPressed: () {
print('MaterialButton pressed');
},
elevation: 3.0,
child: Text('MaterialButton'),
)),
pad(FlatButton(
onPressed: () {
print('FlatButton pressed');
},
child: Text('FlatButton'),
)),
pad(RaisedButton(
onPressed: () {},
elevation: 0.0,
child: Text('RaisedButton 0.0'),
)),
pad(RaisedButton(
onPressed: () {},
elevation: 1.0,
child: Text('RaisedButton 1.0'),
)),
pad(RaisedButton(
onPressed: () {},
elevation: 2.0,
child: Text('RaisedButton 2.0'),
)),
pad(RaisedButton(
onPressed: () {},
elevation: 3.0,
child: Text('RaisedButton 3.0'),
)),
pad(RaisedButton(
onPressed: () {},
elevation: 4.0,
child: Text('RaisedButton 4.0'),
)),
pad(RaisedButton(
onPressed: () {},
elevation: 8.0,
child: Text('RaisedButton 8.0'),
)),
],
);
}
}
Padding pad(Widget widget) => Padding(
padding: EdgeInsets.all(10.0),
child: widget,
);

View File

@@ -1,13 +1,13 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
class MenuDemo extends StatefulWidget {
const MenuDemo({Key key}) : super(key: key);
const MenuDemo({ Key key }) : super(key: key);
static const String routeName = '/material/menu';
@@ -37,7 +37,9 @@ class MenuDemoState extends State<MenuDemo> {
}
void showInSnackBar(String value) {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(value)));
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(value),
));
}
void showMenuSelection(String value) {
@@ -60,122 +62,158 @@ class MenuDemoState extends State<MenuDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Menus'),
actions: <Widget>[
MaterialDemoDocumentationButton(MenuDemo.routeName),
PopupMenuButton<String>(
onSelected: showMenuSelection,
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
const PopupMenuItem<String>(
value: 'Toolbar menu', child: Text('Toolbar menu')),
const PopupMenuItem<String>(
value: 'Right here', child: Text('Right here')),
const PopupMenuItem<String>(
value: 'Hooray!', child: Text('Hooray!')),
],
),
],
),
body: ListView(padding: kMaterialListPadding, children: <Widget>[
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Menus'),
actions: <Widget>[
MaterialDemoDocumentationButton(MenuDemo.routeName),
PopupMenuButton<String>(
onSelected: showMenuSelection,
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
const PopupMenuItem<String>(
value: 'Toolbar menu',
child: Text('Toolbar menu'),
),
const PopupMenuItem<String>(
value: 'Right here',
child: Text('Right here'),
),
const PopupMenuItem<String>(
value: 'Hooray!',
child: Text('Hooray!'),
),
],
),
],
),
body: ListView(
padding: kMaterialListPadding,
children: <Widget>[
// Pressing the PopupMenuButton on the right of this item shows
// a simple menu with one disabled item. Typically the contents
// of this "contextual menu" would reflect the app's state.
ListTile(
title: const Text('An item with a context menu button'),
trailing: PopupMenuButton<String>(
padding: EdgeInsets.zero,
onSelected: showMenuSelection,
itemBuilder: (BuildContext context) =>
<PopupMenuItem<String>>[
PopupMenuItem<String>(
value: _simpleValue1,
child: const Text('Context menu item one')),
const PopupMenuItem<String>(
enabled: false,
child: Text('A disabled menu item')),
PopupMenuItem<String>(
value: _simpleValue3,
child: const Text('Context menu item three')),
])),
title: const Text('An item with a context menu button'),
trailing: PopupMenuButton<String>(
padding: EdgeInsets.zero,
onSelected: showMenuSelection,
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
PopupMenuItem<String>(
value: _simpleValue1,
child: const Text('Context menu item one'),
),
const PopupMenuItem<String>(
enabled: false,
child: Text('A disabled menu item'),
),
PopupMenuItem<String>(
value: _simpleValue3,
child: const Text('Context menu item three'),
),
],
),
),
// Pressing the PopupMenuButton on the right of this item shows
// a menu whose items have text labels and icons and a divider
// That separates the first three items from the last one.
ListTile(
title: const Text('An item with a sectioned menu'),
trailing: PopupMenuButton<String>(
padding: EdgeInsets.zero,
onSelected: showMenuSelection,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
const PopupMenuItem<String>(
value: 'Preview',
child: ListTile(
leading: Icon(Icons.visibility),
title: Text('Preview'))),
const PopupMenuItem<String>(
value: 'Share',
child: ListTile(
leading: Icon(Icons.person_add),
title: Text('Share'))),
const PopupMenuItem<String>(
value: 'Get Link',
child: ListTile(
leading: Icon(Icons.link),
title: Text('Get link'))),
const PopupMenuDivider(),
const PopupMenuItem<String>(
value: 'Remove',
child: ListTile(
leading: Icon(Icons.delete),
title: Text('Remove')))
])),
title: const Text('An item with a sectioned menu'),
trailing: PopupMenuButton<String>(
padding: EdgeInsets.zero,
onSelected: showMenuSelection,
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
const PopupMenuItem<String>(
value: 'Preview',
child: ListTile(
leading: Icon(Icons.visibility),
title: Text('Preview'),
),
),
const PopupMenuItem<String>(
value: 'Share',
child: ListTile(
leading: Icon(Icons.person_add),
title: Text('Share'),
),
),
const PopupMenuItem<String>(
value: 'Get Link',
child: ListTile(
leading: Icon(Icons.link),
title: Text('Get link'),
),
),
const PopupMenuDivider(),
const PopupMenuItem<String>(
value: 'Remove',
child: ListTile(
leading: Icon(Icons.delete),
title: Text('Remove'),
),
),
],
),
),
// This entire list item is a PopupMenuButton. Tapping anywhere shows
// a menu whose current value is highlighted and aligned over the
// list item's center line.
PopupMenuButton<String>(
padding: EdgeInsets.zero,
initialValue: _simpleValue,
onSelected: showMenuSelection,
child: ListTile(
title: const Text('An item with a simple menu'),
subtitle: Text(_simpleValue)),
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
PopupMenuItem<String>(
value: _simpleValue1, child: Text(_simpleValue1)),
PopupMenuItem<String>(
value: _simpleValue2, child: Text(_simpleValue2)),
PopupMenuItem<String>(
value: _simpleValue3, child: Text(_simpleValue3))
]),
padding: EdgeInsets.zero,
initialValue: _simpleValue,
onSelected: showMenuSelection,
child: ListTile(
title: const Text('An item with a simple menu'),
subtitle: Text(_simpleValue),
),
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
PopupMenuItem<String>(
value: _simpleValue1,
child: Text(_simpleValue1),
),
PopupMenuItem<String>(
value: _simpleValue2,
child: Text(_simpleValue2),
),
PopupMenuItem<String>(
value: _simpleValue3,
child: Text(_simpleValue3),
),
],
),
// Pressing the PopupMenuButton on the right of this item shows a menu
// whose items have checked icons that reflect this app's state.
ListTile(
title: const Text('An item with a checklist menu'),
trailing: PopupMenuButton<String>(
padding: EdgeInsets.zero,
onSelected: showCheckedMenuSelections,
itemBuilder: (BuildContext context) =>
<PopupMenuItem<String>>[
CheckedPopupMenuItem<String>(
value: _checkedValue1,
checked: isChecked(_checkedValue1),
child: Text(_checkedValue1)),
CheckedPopupMenuItem<String>(
value: _checkedValue2,
enabled: false,
checked: isChecked(_checkedValue2),
child: Text(_checkedValue2)),
CheckedPopupMenuItem<String>(
value: _checkedValue3,
checked: isChecked(_checkedValue3),
child: Text(_checkedValue3)),
CheckedPopupMenuItem<String>(
value: _checkedValue4,
checked: isChecked(_checkedValue4),
child: Text(_checkedValue4))
]))
]));
title: const Text('An item with a checklist menu'),
trailing: PopupMenuButton<String>(
padding: EdgeInsets.zero,
onSelected: showCheckedMenuSelections,
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
CheckedPopupMenuItem<String>(
value: _checkedValue1,
checked: isChecked(_checkedValue1),
child: Text(_checkedValue1),
),
CheckedPopupMenuItem<String>(
value: _checkedValue2,
enabled: false,
checked: isChecked(_checkedValue2),
child: Text(_checkedValue2),
),
CheckedPopupMenuItem<String>(
value: _checkedValue3,
checked: isChecked(_checkedValue3),
child: Text(_checkedValue3),
),
CheckedPopupMenuItem<String>(
value: _checkedValue4,
checked: isChecked(_checkedValue4),
child: Text(_checkedValue4),
),
],
),
),
],
),
);
}
}

View File

@@ -1,8 +1,8 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import '../../gallery/demo.dart';
@@ -12,27 +12,31 @@ class ModalBottomSheetDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Modal bottom sheet'),
actions: <Widget>[MaterialDemoDocumentationButton(routeName)],
appBar: AppBar(
title: const Text('Modal bottom sheet'),
actions: <Widget>[MaterialDemoDocumentationButton(routeName)],
),
body: Center(
child: RaisedButton(
child: const Text('SHOW BOTTOM SHEET'),
onPressed: () {
showModalBottomSheet<void>(context: context, builder: (BuildContext context) {
return Container(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Text('This is the modal bottom sheet. Slide down to dismiss.',
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 24.0,
),
),
),
);
});
},
),
body: Center(
child: RaisedButton(
child: const Text('SHOW BOTTOM SHEET'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Text(
'This is the modal bottom sheet. Tap anywhere to dismiss.',
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 24.0))));
});
})));
),
);
}
}

View File

@@ -1,17 +1,17 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
enum IndicatorType { overscroll, refresh }
class OverscrollDemo extends StatefulWidget {
const OverscrollDemo({Key key}) : super(key: key);
const OverscrollDemo({ Key key }) : super(key: key);
static const String routeName = '/material/overscroll';
@@ -21,38 +21,24 @@ class OverscrollDemo extends StatefulWidget {
class OverscrollDemoState extends State<OverscrollDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>();
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
static final List<String> _items = <String>[
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N'
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
];
Future<void> _handleRefresh() {
final Completer<void> completer = Completer<void>();
Timer(const Duration(seconds: 3), () {
completer.complete();
});
Timer(const Duration(seconds: 3), () { completer.complete(); });
return completer.future.then<void>((_) {
_scaffoldKey.currentState?.showSnackBar(SnackBar(
content: const Text('Refresh complete'),
action: SnackBarAction(
label: 'RETRY',
onPressed: () {
_refreshIndicatorKey.currentState.show();
})));
content: const Text('Refresh complete'),
action: SnackBarAction(
label: 'RETRY',
onPressed: () {
_refreshIndicatorKey.currentState.show();
},
),
));
});
}
@@ -60,31 +46,36 @@ class OverscrollDemoState extends State<OverscrollDemo> {
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(title: const Text('Pull to refresh'), actions: <Widget>[
MaterialDemoDocumentationButton(OverscrollDemo.routeName),
IconButton(
appBar: AppBar(
title: const Text('Pull to refresh'),
actions: <Widget>[
MaterialDemoDocumentationButton(OverscrollDemo.routeName),
IconButton(
icon: const Icon(Icons.refresh),
tooltip: 'Refresh',
onPressed: () {
_refreshIndicatorKey.currentState.show();
}),
]),
},
),
],
),
body: RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _handleRefresh,
child: ListView.builder(
padding: kMaterialListPadding,
itemCount: _items.length,
itemBuilder: (BuildContext context, int index) {
final String item = _items[index];
return ListTile(
isThreeLine: true,
leading: CircleAvatar(child: Text(item)),
title: Text('This item represents $item.'),
subtitle: const Text(
'Even more additional list item information appears on line three.'),
);
},
child: Scrollbar(
child: ListView.builder(
padding: kMaterialListPadding,
itemCount: _items.length,
itemBuilder: (BuildContext context, int index) {
final String item = _items[index];
return ListTile(
isThreeLine: true,
leading: CircleAvatar(child: Text(item)),
title: Text('This item represents $item.'),
subtitle: const Text('Even more additional list item information appears on line three.'),
);
},
),
),
),
);

View File

@@ -1,21 +1,20 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import '../../gallery/demo.dart';
class _PageSelector extends StatelessWidget {
const _PageSelector({this.icons});
const _PageSelector({ this.icons });
final List<Icon> icons;
void _handleArrowButtonPress(BuildContext context, int delta) {
final TabController controller = DefaultTabController.of(context);
if (!controller.indexIsChanging)
controller
.animateTo((controller.index + delta).clamp(0, icons.length - 1));
controller.animateTo((controller.index + delta).clamp(0, icons.length - 1));
}
@override
@@ -28,24 +27,26 @@ class _PageSelector extends StatelessWidget {
child: Column(
children: <Widget>[
Container(
margin: const EdgeInsets.only(top: 16.0),
child: Row(children: <Widget>[
margin: const EdgeInsets.only(top: 16.0),
child: Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.chevron_left),
color: color,
onPressed: () {
_handleArrowButtonPress(context, -1);
},
tooltip: 'Page back'),
icon: const Icon(Icons.chevron_left),
color: color,
onPressed: () { _handleArrowButtonPress(context, -1); },
tooltip: 'Page back',
),
TabPageSelector(controller: controller),
IconButton(
icon: const Icon(Icons.chevron_right),
color: color,
onPressed: () {
_handleArrowButtonPress(context, 1);
},
tooltip: 'Page forward')
], mainAxisAlignment: MainAxisAlignment.spaceBetween)),
icon: const Icon(Icons.chevron_right),
color: color,
onPressed: () { _handleArrowButtonPress(context, 1); },
tooltip: 'Page forward',
),
],
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
),
Expanded(
child: IconTheme(
data: IconThemeData(
@@ -53,16 +54,17 @@ class _PageSelector extends StatelessWidget {
color: color,
),
child: TabBarView(
children: icons.map<Widget>((Icon icon) {
return Container(
padding: const EdgeInsets.all(12.0),
child: Card(
child: Center(
child: icon,
children: icons.map<Widget>((Icon icon) {
return Container(
padding: const EdgeInsets.all(12.0),
child: Card(
child: Center(
child: icon,
),
),
),
);
}).toList()),
);
}).toList(),
),
),
),
],

View File

@@ -1,8 +1,8 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import '../../gallery/demo.dart';
@@ -10,8 +10,7 @@ class PersistentBottomSheetDemo extends StatefulWidget {
static const String routeName = '/material/persistent-bottom-sheet';
@override
_PersistentBottomSheetDemoState createState() =>
_PersistentBottomSheetDemoState();
_PersistentBottomSheetDemoState createState() => _PersistentBottomSheetDemoState();
}
class _PersistentBottomSheetDemoState extends State<PersistentBottomSheetDemo> {
@@ -26,36 +25,34 @@ class _PersistentBottomSheetDemoState extends State<PersistentBottomSheetDemo> {
}
void _showBottomSheet() {
setState(() {
// disable the button
setState(() { // disable the button
_showBottomSheetCallback = null;
});
_scaffoldKey.currentState
.showBottomSheet<Null>((BuildContext context) {
final ThemeData themeData = Theme.of(context);
return Container(
decoration: BoxDecoration(
border:
Border(top: BorderSide(color: themeData.disabledColor))),
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Text(
'This is a Material persistent bottom sheet. Drag downwards to dismiss it.',
textAlign: TextAlign.center,
style: TextStyle(color: themeData.accentColor, fontSize: 24.0),
),
_scaffoldKey.currentState.showBottomSheet<void>((BuildContext context) {
final ThemeData themeData = Theme.of(context);
return Container(
decoration: BoxDecoration(
border: Border(top: BorderSide(color: themeData.disabledColor))
),
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Text('This is a Material persistent bottom sheet. Drag downwards to dismiss it.',
textAlign: TextAlign.center,
style: TextStyle(
color: themeData.accentColor,
fontSize: 24.0,
),
);
})
.closed
.whenComplete(() {
if (mounted) {
setState(() {
// re-enable the button
_showBottomSheetCallback = _showBottomSheet;
});
}
),
),
);
})
.closed.whenComplete(() {
if (mounted) {
setState(() { // re-enable the button
_showBottomSheetCallback = _showBottomSheet;
});
}
});
}
void _showMessage() {
@@ -66,10 +63,11 @@ class _PersistentBottomSheetDemoState extends State<PersistentBottomSheetDemo> {
content: const Text('You tapped the floating action button.'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'))
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'),
),
],
);
},
@@ -79,25 +77,27 @@ class _PersistentBottomSheetDemoState extends State<PersistentBottomSheetDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Persistent bottom sheet'),
actions: <Widget>[
MaterialDemoDocumentationButton(
PersistentBottomSheetDemo.routeName),
],
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Persistent bottom sheet'),
actions: <Widget>[
MaterialDemoDocumentationButton(PersistentBottomSheetDemo.routeName),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _showMessage,
backgroundColor: Colors.redAccent,
child: const Icon(
Icons.add,
semanticLabel: 'Add',
),
floatingActionButton: FloatingActionButton(
onPressed: _showMessage,
backgroundColor: Colors.redAccent,
child: const Icon(
Icons.add,
semanticLabel: 'Add',
),
),
body: Center(
child: RaisedButton(
onPressed: _showBottomSheetCallback,
child: const Text('SHOW BOTTOM SHEET'),
),
body: Center(
child: RaisedButton(
onPressed: _showBottomSheetCallback,
child: const Text('SHOW BOTTOM SHEET'))));
),
);
}
}

View File

@@ -1,8 +1,8 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import '../../gallery/demo.dart';
@@ -13,8 +13,7 @@ class ProgressIndicatorDemo extends StatefulWidget {
_ProgressIndicatorDemoState createState() => _ProgressIndicatorDemoState();
}
class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo>
with SingleTickerProviderStateMixin {
class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
@@ -28,14 +27,15 @@ class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo>
)..forward();
_animation = CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.9, curve: Curves.fastOutSlowIn),
reverseCurve: Curves.fastOutSlowIn)
..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed)
_controller.forward();
else if (status == AnimationStatus.completed) _controller.reverse();
});
parent: _controller,
curve: const Interval(0.0, 0.9, curve: Curves.fastOutSlowIn),
reverseCurve: Curves.fastOutSlowIn,
)..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed)
_controller.forward();
else if (status == AnimationStatus.completed)
_controller.reverse();
});
}
@override
@@ -66,7 +66,10 @@ class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo>
Widget _buildIndicators(BuildContext context, Widget child) {
final List<Widget> indicators = <Widget>[
const SizedBox(width: 200.0, child: LinearProgressIndicator()),
const SizedBox(
width: 200.0,
child: LinearProgressIndicator(),
),
const LinearProgressIndicator(),
const LinearProgressIndicator(),
LinearProgressIndicator(value: _animation.value),
@@ -77,23 +80,22 @@ class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo>
SizedBox(
width: 20.0,
height: 20.0,
child: CircularProgressIndicator(value: _animation.value)),
child: CircularProgressIndicator(value: _animation.value),
),
SizedBox(
width: 100.0,
height: 20.0,
child: Text('${(_animation.value * 100.0).toStringAsFixed(1)}%',
textAlign: TextAlign.right),
textAlign: TextAlign.right,
),
),
],
),
];
return Column(
children: indicators
.map<Widget>((Widget c) => Container(
child: c,
margin:
const EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0)))
.toList(),
.map<Widget>((Widget c) => Container(child: c, margin: const EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0)))
.toList(),
);
}
@@ -102,9 +104,7 @@ class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo>
return Scaffold(
appBar: AppBar(
title: const Text('Progress indicators'),
actions: <Widget>[
MaterialDemoDocumentationButton(ProgressIndicatorDemo.routeName)
],
actions: <Widget>[MaterialDemoDocumentationButton(ProgressIndicatorDemo.routeName)],
),
body: Center(
child: SingleChildScrollView(
@@ -117,10 +117,11 @@ class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo>
top: false,
bottom: false,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 12.0, horizontal: 8.0),
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 8.0),
child: AnimatedBuilder(
animation: _animation, builder: _buildIndicators),
animation: _animation,
builder: _buildIndicators,
),
),
),
),

View File

@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/foundation.dart';
import 'package:flutter_web/material.dart';
import 'package:flutter_web/rendering.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import '../../gallery/demo.dart';
@@ -20,7 +20,7 @@ enum _ReorderableListType {
}
class ReorderableListDemo extends StatefulWidget {
const ReorderableListDemo({Key key}) : super(key: key);
const ReorderableListDemo({ Key key }) : super(key: key);
static const String routeName = '/material/reorderable-list';
@@ -37,27 +37,14 @@ class _ListItem {
}
class _ListDemoState extends State<ReorderableListDemo> {
static final GlobalKey<ScaffoldState> scaffoldKey =
GlobalKey<ScaffoldState>();
static final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
PersistentBottomSheetController<void> _bottomSheet;
_ReorderableListType _itemType = _ReorderableListType.threeLine;
bool _reverse = false;
bool _reverseSort = false;
final List<_ListItem> _items = <String>[
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
].map<_ListItem>((String item) => _ListItem(item, false)).toList();
void changeItemType(_ReorderableListType type) {
@@ -65,15 +52,28 @@ class _ListDemoState extends State<ReorderableListDemo> {
_itemType = type;
});
// Rebuild the bottom sheet to reflect the selected list view.
_bottomSheet?.setState(() {});
_bottomSheet?.setState(() {
// Trigger a rebuild.
});
// Close the bottom sheet to give the user a clear view of the list.
_bottomSheet?.close();
}
void changeReverse(bool newValue) {
setState(() {
_reverse = newValue;
});
// Rebuild the bottom sheet to reflect the selected list view.
_bottomSheet?.setState(() {
// Trigger a rebuild.
});
// Close the bottom sheet to give the user a clear view of the list.
_bottomSheet?.close();
}
void _showConfigurationSheet() {
setState(() {
_bottomSheet = scaffoldKey.currentState
.showBottomSheet<void>((BuildContext bottomSheetContext) {
_bottomSheet = scaffoldKey.currentState.showBottomSheet<void>((BuildContext bottomSheetContext) {
return DecoratedBox(
decoration: const BoxDecoration(
border: Border(top: BorderSide(color: Colors.black26)),
@@ -82,6 +82,12 @@ class _ListDemoState extends State<ReorderableListDemo> {
shrinkWrap: true,
primary: false,
children: <Widget>[
CheckboxListTile(
dense: true,
title: const Text('Reverse'),
value: _reverse,
onChanged: changeReverse,
),
RadioListTile<_ReorderableListType>(
dense: true,
title: const Text('Horizontal Avatars'),
@@ -146,8 +152,7 @@ class _ListDemoState extends State<ReorderableListDemo> {
key: Key(item.value),
height: 100.0,
width: 100.0,
child: CircleAvatar(
child: Text(item.value),
child: CircleAvatar(child: Text(item.value),
backgroundColor: Colors.green,
),
);
@@ -167,6 +172,7 @@ class _ListDemoState extends State<ReorderableListDemo> {
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -181,9 +187,7 @@ class _ListDemoState extends State<ReorderableListDemo> {
onPressed: () {
setState(() {
_reverseSort = !_reverseSort;
_items.sort((_ListItem a, _ListItem b) => _reverseSort
? b.value.compareTo(a.value)
: a.value.compareTo(b.value));
_items.sort((_ListItem a, _ListItem b) => _reverseSort ? b.value.compareTo(a.value) : a.value.compareTo(b.value));
});
},
),
@@ -203,13 +207,11 @@ class _ListDemoState extends State<ReorderableListDemo> {
header: _itemType != _ReorderableListType.threeLine
? Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Header of the list',
style: Theme.of(context).textTheme.headline))
child: Text('Header of the list', style: Theme.of(context).textTheme.headline))
: null,
onReorder: _onReorder,
scrollDirection: _itemType == _ReorderableListType.horizontalAvatar
? Axis.horizontal
: Axis.vertical,
reverse: _reverse,
scrollDirection: _itemType == _ReorderableListType.horizontalAvatar ? Axis.horizontal : Axis.vertical,
padding: const EdgeInsets.symmetric(vertical: 8.0),
children: _items.map<Widget>(buildListTile).toList(),
),

View File

@@ -1,15 +1,19 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import '../../gallery/demo.dart';
enum TabsDemoStyle { iconsAndText, iconsOnly, textOnly }
enum TabsDemoStyle {
iconsAndText,
iconsOnly,
textOnly
}
class _Page {
const _Page({this.icon, this.text});
const _Page({ this.icon, this.text });
final IconData icon;
final String text;
}
@@ -38,8 +42,7 @@ class ScrollableTabsDemo extends StatefulWidget {
ScrollableTabsDemoState createState() => ScrollableTabsDemoState();
}
class ScrollableTabsDemoState extends State<ScrollableTabsDemo>
with SingleTickerProviderStateMixin {
class ScrollableTabsDemoState extends State<ScrollableTabsDemo> with SingleTickerProviderStateMixin {
TabController _controller;
TabsDemoStyle _demoStyle = TabsDemoStyle.iconsAndText;
bool _customIndicator = false;
@@ -63,57 +66,55 @@ class ScrollableTabsDemoState extends State<ScrollableTabsDemo>
}
Decoration getIndicator() {
if (!_customIndicator) return const UnderlineTabIndicator();
if (!_customIndicator)
return const UnderlineTabIndicator();
switch (_demoStyle) {
switch(_demoStyle) {
case TabsDemoStyle.iconsAndText:
return ShapeDecoration(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
side: BorderSide(
color: Colors.white24,
width: 2.0,
),
) +
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
side: BorderSide(
color: Colors.transparent,
width: 4.0,
),
),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
side: BorderSide(
color: Colors.white24,
width: 2.0,
),
) + const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
side: BorderSide(
color: Colors.transparent,
width: 4.0,
),
),
);
case TabsDemoStyle.iconsOnly:
return ShapeDecoration(
shape: const CircleBorder(
side: BorderSide(
color: Colors.white24,
width: 4.0,
),
) +
const CircleBorder(
side: BorderSide(
color: Colors.transparent,
width: 4.0,
),
),
side: BorderSide(
color: Colors.white24,
width: 4.0,
),
) + const CircleBorder(
side: BorderSide(
color: Colors.transparent,
width: 4.0,
),
),
);
case TabsDemoStyle.textOnly:
return ShapeDecoration(
shape: const StadiumBorder(
side: BorderSide(
color: Colors.white24,
width: 2.0,
),
) +
const StadiumBorder(
side: BorderSide(
color: Colors.transparent,
width: 4.0,
),
),
side: BorderSide(
color: Colors.white24,
width: 2.0,
),
) + const StadiumBorder(
side: BorderSide(
color: Colors.transparent,
width: 4.0,
),
),
);
}
return null;
@@ -137,15 +138,19 @@ class ScrollableTabsDemoState extends State<ScrollableTabsDemo>
),
PopupMenuButton<TabsDemoStyle>(
onSelected: changeDemoStyle,
itemBuilder: (BuildContext context) =>
<PopupMenuItem<TabsDemoStyle>>[
itemBuilder: (BuildContext context) => <PopupMenuItem<TabsDemoStyle>>[
const PopupMenuItem<TabsDemoStyle>(
value: TabsDemoStyle.iconsAndText,
child: Text('Icons and text')),
value: TabsDemoStyle.iconsAndText,
child: Text('Icons and text'),
),
const PopupMenuItem<TabsDemoStyle>(
value: TabsDemoStyle.iconsOnly, child: Text('Icons only')),
value: TabsDemoStyle.iconsOnly,
child: Text('Icons only'),
),
const PopupMenuItem<TabsDemoStyle>(
value: TabsDemoStyle.textOnly, child: Text('Text only')),
value: TabsDemoStyle.textOnly,
child: Text('Text only'),
),
],
),
],
@@ -168,27 +173,28 @@ class ScrollableTabsDemoState extends State<ScrollableTabsDemo>
),
),
body: TabBarView(
controller: _controller,
children: _allPages.map<Widget>((_Page page) {
return SafeArea(
top: false,
bottom: false,
child: Container(
key: ObjectKey(page.icon),
padding: const EdgeInsets.all(12.0),
child: Card(
child: Center(
child: Icon(
page.icon,
color: iconColor,
size: 128.0,
semanticLabel: 'Placeholder for ${page.text} tab',
),
controller: _controller,
children: _allPages.map<Widget>((_Page page) {
return SafeArea(
top: false,
bottom: false,
child: Container(
key: ObjectKey(page.icon),
padding: const EdgeInsets.all(12.0),
child: Card(
child: Center(
child: Icon(
page.icon,
color: iconColor,
size: 128.0,
semanticLabel: 'Placeholder for ${page.text} tab',
),
),
),
);
}).toList()),
),
);
}).toList(),
),
);
}
}

View File

@@ -2,7 +2,7 @@
// 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/material.dart';
import '../../gallery/demo.dart';
@@ -60,7 +60,7 @@ class _SearchDemoState extends State<SearchDemo> {
? Icons.more_horiz
: Icons.more_vert,
),
onPressed: () {},
onPressed: () { },
),
],
),
@@ -86,13 +86,12 @@ class _SearchDemoState extends State<SearchDemo> {
Text(' icon in the AppBar'),
],
),
const Text(
'and search for an integer between 0 and 100,000.'),
const Text('and search for an integer between 0 and 100,000.'),
],
),
),
const SizedBox(height: 64.0),
Text('Last selected integer: ${_lastIntegerSelected ?? 'NONE'}.')
Text('Last selected integer: ${_lastIntegerSelected ?? 'NONE' }.'),
],
),
),
@@ -113,6 +112,7 @@ class _SearchDemoState extends State<SearchDemo> {
currentAccountPicture: CircleAvatar(
backgroundImage: AssetImage(
'people/square/peter.png',
package: 'flutter_gallery_assets',
),
),
margin: EdgeInsets.zero,
@@ -134,8 +134,7 @@ class _SearchDemoState extends State<SearchDemo> {
}
class _SearchDemoSearchDelegate extends SearchDelegate<int> {
final List<int> _data =
List<int>.generate(100001, (int i) => i).reversed.toList();
final List<int> _data = List<int>.generate(100001, (int i) => i).reversed.toList();
final List<int> _history = <int>[42607, 85604, 66374, 44, 174];
@override
@@ -154,6 +153,7 @@ class _SearchDemoSearchDelegate extends SearchDelegate<int> {
@override
Widget buildSuggestions(BuildContext context) {
final Iterable<int> suggestions = query.isEmpty
? _history
: _data.where((int i) => '$i'.startsWith(query));
@@ -219,7 +219,7 @@ class _SearchDemoSearchDelegate extends SearchDelegate<int> {
query = '';
showSuggestions(context);
},
)
),
];
}
}
@@ -275,8 +275,7 @@ class _SuggestionList extends StatelessWidget {
title: RichText(
text: TextSpan(
text: suggestion.substring(0, query.length),
style:
theme.textTheme.subhead.copyWith(fontWeight: FontWeight.bold),
style: theme.textTheme.subhead.copyWith(fontWeight: FontWeight.bold),
children: <TextSpan>[
TextSpan(
text: suggestion.substring(query.length),

View File

@@ -1,24 +1,77 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import '../../gallery/demo.dart';
class SelectionControlsDemo extends StatefulWidget {
static const String routeName = '/material/selection';
const String _checkboxText =
'Checkboxes allow the user to select multiple options from a set. '
'A normal checkbox\'s value is true or false and a tristate checkbox\'s '
'value can also be null.';
const String _checkboxCode = 'selectioncontrols_checkbox';
const String _radioText =
'Radio buttons allow the user to select one option from a set. Use radio '
'buttons for exclusive selection if you think that the user needs to see '
'all available options side-by-side.';
const String _radioCode = 'selectioncontrols_radio';
const String _switchText =
'On/off switches toggle the state of a single settings option. The option '
'that the switch controls, as well as the state its in, should be made '
'clear from the corresponding inline label.';
const String _switchCode = 'selectioncontrols_switch';
class SelectionControlsDemo extends StatefulWidget {
static const String routeName = '/material/selection-controls';
@override
_SelectionControlsDemoState createState() => _SelectionControlsDemoState();
}
class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
final List<ComponentDemoTabData> demos = <ComponentDemoTabData>[
ComponentDemoTabData(
tabName: 'CHECKBOX',
description: _checkboxText,
demoWidget: buildCheckbox(),
exampleCodeTag: _checkboxCode,
documentationUrl: 'https://docs.flutter.io/flutter/material/Checkbox-class.html',
),
ComponentDemoTabData(
tabName: 'RADIO',
description: _radioText,
demoWidget: buildRadio(),
exampleCodeTag: _radioCode,
documentationUrl: 'https://docs.flutter.io/flutter/material/Radio-class.html',
),
ComponentDemoTabData(
tabName: 'SWITCH',
description: _switchText,
demoWidget: buildSwitch(),
exampleCodeTag: _switchCode,
documentationUrl: 'https://docs.flutter.io/flutter/material/Switch-class.html',
),
];
return TabbedComponentDemoScaffold(
title: 'Selection controls',
demos: demos,
);
}
bool checkboxValueA = true;
bool checkboxValueB = false;
bool checkboxValueC;
int radioValue = 0;
bool switchValue = false;
void handleRadioValueChanged(int value) {
setState(() {
@@ -26,23 +79,12 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
});
}
@override
Widget build(BuildContext context) {
return wrapScaffold('Selection Controls', context, _scaffoldKey,
_buildContents(), SelectionControlsDemo.routeName);
}
Widget _buildContents() {
return Material(
color: Colors.white,
child: new Column(
children: <Widget>[buildCheckbox(), Divider(), buildRadio()]));
}
Widget buildCheckbox() {
return Align(
alignment: const Alignment(0.0, -0.2),
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
alignment: const Alignment(0.0, -0.2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
@@ -73,39 +115,91 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
),
],
),
Row(mainAxisSize: MainAxisSize.min, children: const <Widget>[
// Disabled checkboxes
Checkbox(value: true, onChanged: null),
Checkbox(value: false, onChanged: null),
Checkbox(value: null, tristate: true, onChanged: null),
])
]));
Row(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
// Disabled checkboxes
Checkbox(value: true, onChanged: null),
Checkbox(value: false, onChanged: null),
Checkbox(value: null, tristate: true, onChanged: null),
],
),
],
),
);
}
Widget buildRadio() {
return Align(
alignment: const Alignment(0.0, -0.2),
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
Radio<int>(
alignment: const Alignment(0.0, -0.2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Radio<int>(
value: 0,
groupValue: radioValue,
onChanged: handleRadioValueChanged),
Radio<int>(
onChanged: handleRadioValueChanged,
),
Radio<int>(
value: 1,
groupValue: radioValue,
onChanged: handleRadioValueChanged),
Radio<int>(
onChanged: handleRadioValueChanged,
),
Radio<int>(
value: 2,
groupValue: radioValue,
onChanged: handleRadioValueChanged)
]),
onChanged: handleRadioValueChanged,
),
],
),
// Disabled radio buttons
Row(mainAxisSize: MainAxisSize.min, children: const <Widget>[
Radio<int>(value: 0, groupValue: 0, onChanged: null),
Radio<int>(value: 1, groupValue: 0, onChanged: null),
Radio<int>(value: 2, groupValue: 0, onChanged: null)
])
]));
Row(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Radio<int>(
value: 0,
groupValue: 0,
onChanged: null,
),
Radio<int>(
value: 1,
groupValue: 0,
onChanged: null,
),
Radio<int>(
value: 2,
groupValue: 0,
onChanged: null,
),
],
),
],
),
);
}
Widget buildSwitch() {
return Align(
alignment: const Alignment(0.0, -0.2),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Switch.adaptive(
value: switchValue,
onChanged: (bool value) {
setState(() {
switchValue = value;
});
},
),
// Disabled switches
const Switch.adaptive(value: true, onChanged: null),
const Switch.adaptive(value: false, onChanged: null),
],
),
);
}
}

View File

@@ -1,10 +1,10 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
@@ -15,30 +15,102 @@ class SliderDemo extends StatefulWidget {
_SliderDemoState createState() => _SliderDemoState();
}
Path _triangle(double size, Offset thumbCenter, {bool invert = false}) {
Path _downTriangle(double size, Offset thumbCenter, { bool invert = false }) {
final Path thumbPath = Path();
final double height = math.sqrt(3.0) / 2.0;
final double halfSide = size / 2.0;
final double centerHeight = size * height / 3.0;
final double halfSize = size / 2.0;
final double sign = invert ? -1.0 : 1.0;
thumbPath.moveTo(
thumbCenter.dx - halfSide, thumbCenter.dy + sign * centerHeight);
thumbPath.moveTo(thumbCenter.dx - halfSize, thumbCenter.dy + sign * centerHeight);
thumbPath.lineTo(thumbCenter.dx, thumbCenter.dy - 2.0 * sign * centerHeight);
thumbPath.lineTo(
thumbCenter.dx + halfSide, thumbCenter.dy + sign * centerHeight);
thumbPath.lineTo(thumbCenter.dx + halfSize, thumbCenter.dy + sign * centerHeight);
thumbPath.close();
return thumbPath;
}
Path _rightTriangle(double size, Offset thumbCenter, { bool invert = false }) {
final Path thumbPath = Path();
final double halfSize = size / 2.0;
final double sign = invert ? -1.0 : 1.0;
thumbPath.moveTo(thumbCenter.dx + halfSize * sign, thumbCenter.dy);
thumbPath.lineTo(thumbCenter.dx - halfSize * sign, thumbCenter.dy - size);
thumbPath.lineTo(thumbCenter.dx - halfSize * sign, thumbCenter.dy + size);
thumbPath.close();
return thumbPath;
}
Path _upTriangle(double size, Offset thumbCenter) => _downTriangle(size, thumbCenter, invert: true);
Path _leftTriangle(double size, Offset thumbCenter) => _rightTriangle(size, thumbCenter, invert: true);
class _CustomRangeThumbShape extends RangeSliderThumbShape {
static const double _thumbSize = 4.0;
static const double _disabledThumbSize = 3.0;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize);
}
static final Animatable<double> sizeTween = Tween<double>(
begin: _disabledThumbSize,
end: _thumbSize,
);
@override
void paint(
PaintingContext context,
Offset center, {
@required Animation<double> activationAnimation,
@required Animation<double> enableAnimation,
bool isDiscrete = false,
bool isEnabled = false,
bool isOnTop,
@required SliderThemeData sliderTheme,
TextDirection textDirection,
Thumb thumb,
}) {
final Canvas canvas = context.canvas;
final ColorTween colorTween = ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.thumbColor,
);
final double size = _thumbSize * sizeTween.evaluate(enableAnimation);
Path thumbPath;
switch (textDirection) {
case TextDirection.rtl:
switch (thumb) {
case Thumb.start:
thumbPath = _rightTriangle(size, center);
break;
case Thumb.end:
thumbPath = _leftTriangle(size, center);
break;
}
break;
case TextDirection.ltr:
switch (thumb) {
case Thumb.start:
thumbPath = _leftTriangle(size, center);
break;
case Thumb.end:
thumbPath = _rightTriangle(size, center);
break;
}
break;
}
canvas.drawPath(thumbPath, Paint()..color = colorTween.evaluate(enableAnimation));
}
}
class _CustomThumbShape extends SliderComponentShape {
static const double _thumbSize = 4.0;
static const double _disabledThumbSize = 3.0;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return isEnabled
? const Size.fromRadius(_thumbSize)
: const Size.fromRadius(_disabledThumbSize);
return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize);
}
static final Animatable<double> sizeTween = Tween<double>(
@@ -65,9 +137,8 @@ class _CustomThumbShape extends SliderComponentShape {
end: sliderTheme.thumbColor,
);
final double size = _thumbSize * sizeTween.evaluate(enableAnimation);
final Path thumbPath = _triangle(size, thumbCenter);
canvas.drawPath(
thumbPath, Paint()..color = colorTween.evaluate(enableAnimation));
final Path thumbPath = _downTriangle(size, thumbCenter);
canvas.drawPath(thumbPath, Paint()..color = colorTween.evaluate(enableAnimation));
}
}
@@ -109,16 +180,9 @@ class _CustomValueIndicatorShape extends SliderComponentShape {
end: _slideUpHeight,
);
final double size = _indicatorSize * sizeTween.evaluate(enableAnimation);
final Offset slideUpOffset =
Offset(0.0, -slideUpTween.evaluate(activationAnimation));
final Path thumbPath = _triangle(
size,
thumbCenter + slideUpOffset,
invert: true,
);
final Color paintColor = enableColor
.evaluate(enableAnimation)
.withAlpha((255.0 * activationAnimation.value).round());
final Offset slideUpOffset = Offset(0.0, -slideUpTween.evaluate(activationAnimation));
final Path thumbPath = _upTriangle(size, thumbCenter + slideUpOffset);
final Color paintColor = enableColor.evaluate(enableAnimation).withAlpha((255.0 * activationAnimation.value).round());
canvas.drawPath(
thumbPath,
Paint()..color = paintColor,
@@ -130,27 +194,49 @@ class _CustomValueIndicatorShape extends SliderComponentShape {
..color = paintColor
..style = PaintingStyle.stroke
..strokeWidth = 2.0);
labelPainter.paint(
canvas,
thumbCenter +
slideUpOffset +
Offset(-labelPainter.width / 2.0, -labelPainter.height - 4.0));
labelPainter.paint(canvas, thumbCenter + slideUpOffset + Offset(-labelPainter.width / 2.0, -labelPainter.height - 4.0));
}
}
class _SliderDemoState extends State<SliderDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
final List<ComponentDemoTabData> demos = <ComponentDemoTabData>[
ComponentDemoTabData(
tabName: 'SINGLE',
description: 'Sliders containing 1 thumb',
demoWidget: _Sliders(),
documentationUrl: 'https://docs.flutter.io/flutter/material/Slider-class.html',
),
ComponentDemoTabData(
tabName: 'RANGE',
description: 'Sliders containing 2 thumbs',
demoWidget: _RangeSliders(),
documentationUrl: 'https://docs.flutter.io/flutter/material/RangeSlider-class.html',
),
];
double _value = 25.0;
double _discreteValue = 40.0;
return TabbedComponentDemoScaffold(
title: 'Sliders',
demos: demos,
isScrollable: false,
showExampleCodeAction: false,
);
}
}
class _Sliders extends StatefulWidget {
@override
_SlidersState createState() => _SlidersState();
}
class _SlidersState extends State<_Sliders> {
double _continuousValue = 25.0;
double _discreteValue = 20.0;
double _discreteCustomValue = 25.0;
@override
Widget build(BuildContext context) {
return wrapScaffold('Slider Demo', context, _scaffoldKey,
_buildContents(context), SliderDemo.routeName);
}
Widget _buildContents(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
@@ -160,30 +246,52 @@ class _SliderDemoState extends State<SliderDemo> {
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Slider(
value: _value,
Semantics(
label: 'Editable numerical value',
child: SizedBox(
width: 64,
height: 48,
child: TextField(
textAlign: TextAlign.center,
onSubmitted: (String value) {
final double newValue = double.tryParse(value);
if (newValue != null && newValue != _continuousValue) {
setState(() {
_continuousValue = newValue.clamp(0, 100);
});
}
},
keyboardType: TextInputType.number,
controller: TextEditingController(
text: _continuousValue.toStringAsFixed(0),
),
),
),
),
Slider.adaptive(
value: _continuousValue,
min: 0.0,
max: 100.0,
onChanged: (double value) {
setState(() {
_value = value;
_continuousValue = value;
});
},
),
const Text('Continuous'),
const Text('Continuous with Editable Numerical Value'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Slider(value: 0.25, onChanged: (double val) {}),
children: const <Widget>[
Slider.adaptive(value: 0.25, onChanged: null),
Text('Disabled'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Slider(
Slider.adaptive(
value: _discreteValue,
min: 0.0,
max: 200.0,
@@ -204,28 +312,26 @@ class _SliderDemoState extends State<SliderDemo> {
SliderTheme(
data: theme.sliderTheme.copyWith(
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: Colors.black26,
activeTickMarkColor: Colors.white70,
inactiveTickMarkColor: Colors.black,
overlayColor: Colors.black12,
inactiveTrackColor: theme.colorScheme.onSurface.withOpacity(0.5),
activeTickMarkColor: theme.colorScheme.onSurface.withOpacity(0.7),
inactiveTickMarkColor: theme.colorScheme.surface.withOpacity(0.7),
overlayColor: theme.colorScheme.onSurface.withOpacity(0.12),
thumbColor: Colors.deepPurple,
valueIndicatorColor: Colors.deepPurpleAccent,
thumbShape: _CustomThumbShape(),
valueIndicatorShape: _CustomValueIndicatorShape(),
valueIndicatorTextStyle: theme.accentTextTheme.body2
.copyWith(color: Colors.black87),
valueIndicatorTextStyle: theme.accentTextTheme.body2.copyWith(color: theme.colorScheme.onSurface),
),
child: Slider(
value: _discreteValue,
value: _discreteCustomValue,
min: 0.0,
max: 200.0,
divisions: 5,
semanticFormatterCallback: (double value) =>
value.round().toString(),
label: '${_discreteValue.round()}',
semanticFormatterCallback: (double value) => value.round().toString(),
label: '${_discreteCustomValue.round()}',
onChanged: (double value) {
setState(() {
_discreteValue = value;
_discreteCustomValue = value;
});
},
),
@@ -238,3 +344,98 @@ class _SliderDemoState extends State<SliderDemo> {
);
}
}
class _RangeSliders extends StatefulWidget {
@override
_RangeSlidersState createState() => _RangeSlidersState();
}
class _RangeSlidersState extends State<_RangeSliders> {
RangeValues _continuousValues = const RangeValues(25.0, 75.0);
RangeValues _discreteValues = const RangeValues(40.0, 120.0);
RangeValues _discreteCustomValues = const RangeValues(40.0, 160.0);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RangeSlider(
values: _continuousValues,
min: 0.0,
max: 100.0,
onChanged: (RangeValues values) {
setState(() {
_continuousValues = values;
});
},
),
const Text('Continuous'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RangeSlider(values: const RangeValues(0.25, 0.75), onChanged: null),
const Text('Disabled'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RangeSlider(
values: _discreteValues,
min: 0.0,
max: 200.0,
divisions: 5,
labels: RangeLabels('${_discreteValues.start.round()}', '${_discreteValues.end.round()}'),
onChanged: (RangeValues values) {
setState(() {
_discreteValues = values;
});
},
),
const Text('Discrete'),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SliderTheme(
data: SliderThemeData(
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: Colors.black26,
activeTickMarkColor: Colors.white70,
inactiveTickMarkColor: Colors.black,
overlayColor: Colors.black12,
thumbColor: Colors.deepPurple,
rangeThumbShape: _CustomRangeThumbShape(),
showValueIndicator: ShowValueIndicator.never,
),
child: RangeSlider(
values: _discreteCustomValues,
min: 0.0,
max: 200.0,
divisions: 5,
labels: RangeLabels('${_discreteCustomValues.start.round()}', '${_discreteCustomValues.end.round()}'),
onChanged: (RangeValues values) {
setState(() {
_discreteCustomValues = values;
});
},
),
),
const Text('Discrete with Custom Theme'),
],
),
],
),
);
}
}

View File

@@ -1,24 +1,25 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
const String _text1 =
'Snackbars provide lightweight feedback about an operation by '
'showing a brief message at the bottom of the screen. Snackbars '
'can contain an action.';
'Snackbars provide lightweight feedback about an operation by '
'showing a brief message at the bottom of the screen. Snackbars '
'can contain an action.';
const String _text2 =
'Snackbars should contain a single line of text directly related '
'to the operation performed. They cannot contain icons.';
'Snackbars should contain a single line of text directly related '
'to the operation performed. They cannot contain icons.';
const String _text3 =
'By default snackbars automatically disappear after a few seconds ';
'By default snackbars automatically disappear after a few seconds ';
class SnackBarDemo extends StatefulWidget {
const SnackBarDemo({Key key}) : super(key: key);
const SnackBarDemo({ Key key }) : super(key: key);
static const String routeName = '/material/snack-bar';
@@ -34,50 +35,61 @@ class _SnackBarDemoState extends State<SnackBarDemo> {
top: false,
bottom: false,
child: ListView(
padding: const EdgeInsets.all(24.0),
children: <Widget>[
const Text(_text1),
const Text(_text2),
Center(
child: Row(children: <Widget>[
RaisedButton(
child: const Text('SHOW A SNACKBAR'),
padding: const EdgeInsets.all(24.0),
children: <Widget>[
const Text(_text1),
const Text(_text2),
Center(
child: RaisedButton(
child: const Text('SHOW A SNACKBAR'),
onPressed: () {
final int thisSnackBarIndex = _snackBarIndex++;
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('This is snackbar #$thisSnackBarIndex.'),
action: SnackBarAction(
label: 'ACTION',
onPressed: () {
final int thisSnackBarIndex = _snackBarIndex++;
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('This is snackbar #$thisSnackBarIndex.'),
action: SnackBarAction(
label: 'ACTION',
onPressed: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
'You pressed snackbar $thisSnackBarIndex\'s action.')));
}),
content: Text('You pressed snackbar $thisSnackBarIndex\'s action.'),
));
}),
]),
},
),
));
},
),
const Text(_text3),
].map<Widget>((Widget child) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 12.0),
child: child);
}).toList()),
),
const Text(_text3),
]
.map<Widget>((Widget child) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 12.0),
child: child,
);
})
.toList(),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Snackbar'),
actions: <Widget>[
MaterialDemoDocumentationButton(SnackBarDemo.routeName)
],
),
body: Builder(
// Create an inner BuildContext so that the snackBar onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: buildBody));
appBar: AppBar(
title: const Text('Snackbar'),
actions: <Widget>[MaterialDemoDocumentationButton(SnackBarDemo.routeName)],
),
body: Builder(
// Create an inner BuildContext so that the snackBar onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: buildBody
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
tooltip: 'Create',
onPressed: () {
print('Floating Action Button was pressed');
}
),
);
}
}

View File

@@ -1,23 +0,0 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
class StackDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: Colors.greenAccent,
width: 1.0,
),
),
child: Stack(children: [
Text('A'),
Text('B'),
]),
);
}
}

View File

@@ -1,42 +0,0 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import '../../gallery/demo.dart';
class SwitchDemo extends StatefulWidget {
static const routeName = '/material/switch';
@override
SwitchDemoState createState() => SwitchDemoState();
}
class SwitchDemoState extends State<SwitchDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
return wrapScaffold('Switch Demo', context, _scaffoldKey, _buildContents(),
SwitchDemo.routeName);
}
bool _value = true;
Widget _buildContents() {
return Material(
child: Column(
children: [
Switch(
value: _value,
onChanged: (bool newValue) {
setState(() {
_value = newValue;
});
}),
],
),
);
}
}

View File

@@ -1,18 +1,18 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
// Each TabBarView contains a _Page and for each _Page there is a list
// of _CardData objects. Each _CardData object is displayed by a _CardItem.
import 'package:flutter_web/material.dart';
import '../../gallery/demo.dart';
const String _kGalleryAssetsPackage = 'flutter_gallery_assets';
class _Page {
_Page({this.label});
_Page({ this.label });
final String label;
String get id => label[0];
@override
@@ -20,7 +20,7 @@ class _Page {
}
class _CardData {
const _CardData({this.title, this.imageAsset, this.imageAssetPackage});
const _CardData({ this.title, this.imageAsset, this.imageAssetPackage });
final String title;
final String imageAsset;
final String imageAssetPackage;
@@ -94,7 +94,7 @@ final Map<_Page, List<_CardData>> _allPages = <_Page, List<_CardData>>{
};
class _CardDataItem extends StatelessWidget {
const _CardDataItem({this.page, this.data});
const _CardDataItem({ this.page, this.data });
static const double height = 272.0;
final _Page page;
@@ -110,17 +110,20 @@ class _CardDataItem extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Align(
alignment:
page.id == 'H' ? Alignment.centerLeft : Alignment.centerRight,
alignment: page.id == 'H'
? Alignment.centerLeft
: Alignment.centerRight,
child: CircleAvatar(child: Text('${page.id}')),
),
SizedBox(width: 144.0, height: 144.0, child: new Text('image')
// Image.asset(
// data.imageAsset,
// package: data.imageAssetPackage,
// fit: BoxFit.contain,
// ),
),
SizedBox(
width: 144.0,
height: 144.0,
child: Image.asset(
data.imageAsset,
package: data.imageAssetPackage,
fit: BoxFit.contain,
),
),
Center(
child: Text(
data.title,
@@ -145,18 +148,19 @@ class TabsDemo extends StatelessWidget {
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: const Text('Tabs and scrolling'),
actions: <Widget>[MaterialDemoDocumentationButton(routeName)],
pinned: true,
expandedHeight: 150.0,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: _allPages.keys
.map<Widget>(
(_Page page) => Tab(text: page.label),
)
.toList(),
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: SliverAppBar(
title: const Text('Tabs and scrolling'),
actions: <Widget>[MaterialDemoDocumentationButton(routeName)],
pinned: true,
expandedHeight: 150.0,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: _allPages.keys.map<Widget>(
(_Page page) => Tab(text: page.label),
).toList(),
),
),
),
];
@@ -171,6 +175,9 @@ class TabsDemo extends StatelessWidget {
return CustomScrollView(
key: PageStorageKey<_Page>(page),
slivers: <Widget>[
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,

View File

@@ -1,26 +1,25 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2015 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/material.dart';
import '../../gallery/demo.dart';
const String _explanatoryText =
"When the Scaffold's floating action button changes, the new button fades and "
'turns into view. In this demo, changing tabs can cause the app to be rebuilt '
'with a FloatingActionButton that the Scaffold distinguishes from the others '
'by its key.';
"When the Scaffold's floating action button changes, the new button fades and "
'turns into view. In this demo, changing tabs can cause the app to be rebuilt '
'with a FloatingActionButton that the Scaffold distinguishes from the others '
'by its key.';
class _Page {
_Page({this.label, this.colors, this.icon});
_Page({ this.label, this.colors, this.icon });
final String label;
final MaterialColor colors;
final IconData icon;
Color get labelColor =>
colors != null ? colors.shade300 : Colors.grey.shade300;
Color get labelColor => colors != null ? colors.shade300 : Colors.grey.shade300;
bool get fabDefined => colors != null && icon != null;
Color get fabColor => colors.shade400;
Icon get fabIcon => Icon(icon);
@@ -42,8 +41,7 @@ class TabsFabDemo extends StatefulWidget {
_TabsFabDemoState createState() => _TabsFabDemoState();
}
class _TabsFabDemoState extends State<TabsFabDemo>
with SingleTickerProviderStateMixin {
class _TabsFabDemoState extends State<TabsFabDemo> with SingleTickerProviderStateMixin {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
TabController _controller;
@@ -71,50 +69,63 @@ class _TabsFabDemoState extends State<TabsFabDemo>
}
void _showExplanatoryText() {
_scaffoldKey.currentState.showBottomSheet<Null>((BuildContext context) {
_scaffoldKey.currentState.showBottomSheet<void>((BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Theme.of(context).dividerColor))),
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Text(_explanatoryText,
style: Theme.of(context).textTheme.subhead)));
decoration: BoxDecoration(
border: Border(top: BorderSide(color: Theme.of(context).dividerColor))
),
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Text(_explanatoryText, style: Theme.of(context).textTheme.subhead),
),
);
});
}
Widget buildTabView(_Page page) {
return Builder(builder: (BuildContext context) {
return Container(
return Builder(
builder: (BuildContext context) {
return Container(
key: ValueKey<String>(page.label),
padding: const EdgeInsets.fromLTRB(48.0, 48.0, 48.0, 96.0),
child: Card(
child: Center(
child: Text(page.label,
style: TextStyle(color: page.labelColor, fontSize: 32.0),
textAlign: TextAlign.center))));
});
child: Center(
child: Text(page.label,
style: TextStyle(
color: page.labelColor,
fontSize: 32.0,
),
textAlign: TextAlign.center,
),
),
),
);
}
);
}
Widget buildFloatingActionButton(_Page page) {
if (!page.fabDefined) return null;
if (!page.fabDefined)
return null;
if (_extendedButtons) {
return FloatingActionButton.extended(
key: ValueKey<Key>(page.fabKey),
tooltip: 'Show explanation',
backgroundColor: page.fabColor,
icon: page.fabIcon,
label: Text(page.label.toUpperCase()),
onPressed: _showExplanatoryText);
key: ValueKey<Key>(page.fabKey),
tooltip: 'Show explanation',
backgroundColor: page.fabColor,
icon: page.fabIcon,
label: Text(page.label.toUpperCase()),
onPressed: _showExplanatoryText,
);
}
return FloatingActionButton(
key: page.fabKey,
tooltip: 'Show explanation',
backgroundColor: page.fabColor,
child: page.fabIcon,
onPressed: _showExplanatoryText);
key: page.fabKey,
tooltip: 'Show explanation',
backgroundColor: page.fabColor,
child: page.fabIcon,
onPressed: _showExplanatoryText,
);
}
@override
@@ -125,14 +136,12 @@ class _TabsFabDemoState extends State<TabsFabDemo>
title: const Text('FAB per tab'),
bottom: TabBar(
controller: _controller,
tabs: _allPages
.map<Widget>((_Page page) => Tab(text: page.label.toUpperCase()))
.toList(),
tabs: _allPages.map<Widget>((_Page page) => Tab(text: page.label.toUpperCase())).toList(),
),
actions: <Widget>[
MaterialDemoDocumentationButton(TabsFabDemo.routeName),
IconButton(
icon: const Icon(Icons.sentiment_very_satisfied),
icon: const Icon(Icons.sentiment_very_satisfied, semanticLabel: 'Toggle extended buttons'),
onPressed: () {
setState(() {
_extendedButtons = !_extendedButtons;
@@ -143,8 +152,9 @@ class _TabsFabDemoState extends State<TabsFabDemo>
),
floatingActionButton: buildFloatingActionButton(_selectedPage),
body: TabBarView(
controller: _controller,
children: _allPages.map<Widget>(buildTabView).toList()),
controller: _controller,
children: _allPages.map<Widget>(buildTabView).toList(),
),
);
}
}

View File

@@ -1,52 +0,0 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
class TextDemo extends StatelessWidget {
static const routeName = '/material/text';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text'),
centerTitle: true,
),
body: ListView(
children: [
pad(Text('Single line of text')),
Divider(),
// Single line with many whitespaces in between.
pad(Text(' Text with a lot of whitespace ')),
Divider(),
// Forced multi-line because of the \n.
pad(Text('Text with a newline\ncharacter should render in 2 lines')),
Divider(),
// Multi-line with regular whitespace.
pad(Text(
'''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas auctor
vel ligula eget fermentum. Integer mattis nulla vitae ullamcorper
dignissim. Donec vel velit vel eros lobortis laoreet at sit amet turpis.
Ut in orci blandit, rhoncus metus quis, finibus augue. Nullam a elit
venenatis metus accumsan dapibus. Vestibulum imperdiet tristique viverra.''',
)),
Divider(),
// Multi-line with a lot of whitespace in between.
pad(Text(
'''
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Maecenas auctor vel ligula eget fermentum.
Integer mattis nulla vitae ullamcorper dignissim.
Donec vel velit vel eros lobortis laoreet at sit amet turpis.''',
)),
Divider(),
],
),
);
}
Padding pad(Widget child) =>
Padding(padding: EdgeInsets.all(12), child: child);
}

View File

@@ -1,16 +1,17 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_web/material.dart';
import 'package:flutter_web/services.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import '../../gallery/demo.dart';
class TextFormFieldDemo extends StatefulWidget {
const TextFormFieldDemo({Key key}) : super(key: key);
const TextFormFieldDemo({ Key key }) : super(key: key);
static const String routeName = '/material/text-form-field';
@@ -67,6 +68,7 @@ class _PasswordFieldState extends State<PasswordField> {
labelText: widget.labelText,
helperText: widget.helperText,
suffixIcon: GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
setState(() {
_obscureText = !_obscureText;
@@ -88,17 +90,17 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
PersonData person = PersonData();
void showInSnackBar(String value) {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(value)));
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(value),
));
}
bool _autovalidate = false;
bool _formWasEdited = false;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final GlobalKey<FormFieldState<String>> _passwordFieldKey =
GlobalKey<FormFieldState<String>>();
final _UsNumberTextInputFormatter _phoneNumberFormatter =
_UsNumberTextInputFormatter();
final GlobalKey<FormFieldState<String>> _passwordFieldKey = GlobalKey<FormFieldState<String>>();
final _UsNumberTextInputFormatter _phoneNumberFormatter = _UsNumberTextInputFormatter();
void _handleSubmitted() {
final FormState form = _formKey.currentState;
if (!form.validate()) {
@@ -112,7 +114,8 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
String _validateName(String value) {
_formWasEdited = true;
if (value.isEmpty) return 'Name is required.';
if (value.isEmpty)
return 'Name is required.';
final RegExp nameExp = RegExp(r'^[A-Za-z ]+$');
if (!nameExp.hasMatch(value))
return 'Please enter only alphabetical characters.';
@@ -132,49 +135,45 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
final FormFieldState<String> passwordField = _passwordFieldKey.currentState;
if (passwordField.value == null || passwordField.value.isEmpty)
return 'Please enter a password.';
if (passwordField.value != value) return 'The passwords don\'t match';
if (passwordField.value != value)
return 'The passwords don\'t match';
return null;
}
Future<bool> _warnUserAboutInvalidData() async {
final FormState form = _formKey.currentState;
if (form == null || !_formWasEdited || form.validate()) return true;
if (form == null || !_formWasEdited || form.validate())
return true;
return await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('This form has errors'),
content: const Text('Really leave this form?'),
actions: <Widget>[
FlatButton(
child: const Text('YES'),
onPressed: () {
Navigator.of(context).pop(true);
},
),
FlatButton(
child: const Text('NO'),
onPressed: () {
Navigator.of(context).pop(false);
},
),
],
);
},
) ??
false;
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('This form has errors'),
content: const Text('Really leave this form?'),
actions: <Widget> [
FlatButton(
child: const Text('YES'),
onPressed: () { Navigator.of(context).pop(true); },
),
FlatButton(
child: const Text('NO'),
onPressed: () { Navigator.of(context).pop(false); },
),
],
);
},
) ?? false;
}
@override
Widget build(BuildContext context) {
return Scaffold(
drawerDragStartBehavior: DragStartBehavior.down,
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Text fields'),
actions: <Widget>[
MaterialDemoDocumentationButton(TextFormFieldDemo.routeName)
],
actions: <Widget>[MaterialDemoDocumentationButton(TextFormFieldDemo.routeName)],
),
body: SafeArea(
top: false,
@@ -183,120 +182,118 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
key: _formKey,
autovalidate: _autovalidate,
onWillPop: _warnUserAboutInvalidData,
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const SizedBox(height: 24.0),
TextFormField(
textCapitalization: TextCapitalization.words,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
icon: Icon(Icons.person),
hintText: 'What do people call you?',
labelText: 'Name * ',
child: Scrollbar(
child: SingleChildScrollView(
dragStartBehavior: DragStartBehavior.down,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const SizedBox(height: 24.0),
TextFormField(
textCapitalization: TextCapitalization.words,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
icon: Icon(Icons.person),
hintText: 'What do people call you?',
labelText: 'Name *',
),
onSaved: (String value) { person.name = value; },
validator: _validateName,
),
onSaved: (String value) {
person.name = value;
},
validator: _validateName,
),
const SizedBox(height: 24.0),
TextFormField(
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
icon: Icon(Icons.phone),
hintText: 'Where can we reach you?',
labelText: 'Phone Number * ',
prefixText: '+1',
const SizedBox(height: 24.0),
TextFormField(
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
icon: Icon(Icons.phone),
hintText: 'Where can we reach you?',
labelText: 'Phone Number *',
prefixText: '+1',
),
keyboardType: TextInputType.phone,
onSaved: (String value) { person.phoneNumber = value; },
validator: _validatePhoneNumber,
// TextInputFormatters are applied in sequence.
inputFormatters: <TextInputFormatter> [
WhitelistingTextInputFormatter.digitsOnly,
// Fit the validating format.
_phoneNumberFormatter,
],
),
keyboardType: TextInputType.phone,
onSaved: (String value) {
person.phoneNumber = value;
},
validator: _validatePhoneNumber,
// TextInputFormatters are applied in sequence.
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly,
// Fit the validating format.
_phoneNumberFormatter,
],
),
const SizedBox(height: 24.0),
TextFormField(
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
icon: Icon(Icons.email),
hintText: 'Your email address',
labelText: 'E-mail',
const SizedBox(height: 24.0),
TextFormField(
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
icon: Icon(Icons.email),
hintText: 'Your email address',
labelText: 'E-mail',
),
keyboardType: TextInputType.emailAddress,
onSaved: (String value) { person.email = value; },
),
keyboardType: TextInputType.emailAddress,
onSaved: (String value) {
person.email = value;
},
),
const SizedBox(height: 24.0),
TextFormField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText:
'Tell us about yourself (e.g., write down what you do or what hobbies you have)',
helperText: 'Keep it short, this is just a demo.',
labelText: 'Life story',
const SizedBox(height: 24.0),
TextFormField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Tell us about yourself (e.g., write down what you do or what hobbies you have)',
helperText: 'Keep it short, this is just a demo.',
labelText: 'Life story',
),
maxLines: 3,
),
maxLines: 3,
),
const SizedBox(height: 24.0),
TextFormField(
keyboardType: TextInputType.number,
decoration: const InputDecoration(
const SizedBox(height: 24.0),
TextFormField(
keyboardType: TextInputType.number,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Salary',
prefixText: '\$',
suffixText: 'USD',
suffixStyle: TextStyle(color: Colors.green)),
maxLines: 1,
),
const SizedBox(height: 24.0),
PasswordField(
fieldKey: _passwordFieldKey,
helperText: 'No more than 8 characters.',
labelText: 'Password *',
onFieldSubmitted: (String value) {
setState(() {
person.password = value;
});
},
),
const SizedBox(height: 24.0),
TextFormField(
enabled:
person.password != null && person.password.isNotEmpty,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
labelText: 'Re-type password',
suffixStyle: TextStyle(color: Colors.green),
),
maxLines: 1,
),
maxLength: 8,
obscureText: true,
validator: _validatePassword,
),
const SizedBox(height: 24.0),
Center(
child: RaisedButton(
child: const Text('SUBMIT'),
onPressed: _handleSubmitted,
const SizedBox(height: 24.0),
PasswordField(
fieldKey: _passwordFieldKey,
helperText: 'No more than 8 characters.',
labelText: 'Password *',
onFieldSubmitted: (String value) {
setState(() {
person.password = value;
});
},
),
),
const SizedBox(height: 24.0),
Text('* indicates required field',
style: Theme.of(context).textTheme.caption),
const SizedBox(height: 24.0),
],
const SizedBox(height: 24.0),
TextFormField(
enabled: person.password != null && person.password.isNotEmpty,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
labelText: 'Re-type password',
),
maxLength: 8,
obscureText: true,
validator: _validatePassword,
),
const SizedBox(height: 24.0),
Center(
child: RaisedButton(
child: const Text('SUBMIT'),
onPressed: _handleSubmitted,
),
),
const SizedBox(height: 24.0),
Text(
'* indicates required field',
style: Theme.of(context).textTheme.caption,
),
const SizedBox(height: 24.0),
],
),
),
),
),
@@ -309,26 +306,32 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
class _UsNumberTextInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final int newTextLength = newValue.text.length;
int selectionIndex = newValue.selection.end;
int usedSubstringIndex = 0;
final StringBuffer newText = StringBuffer();
if (newTextLength >= 1) {
newText.write('(');
if (newValue.selection.end >= 1) selectionIndex++;
if (newValue.selection.end >= 1)
selectionIndex++;
}
if (newTextLength >= 4) {
newText.write(newValue.text.substring(0, usedSubstringIndex = 3) + ') ');
if (newValue.selection.end >= 3) selectionIndex += 2;
if (newValue.selection.end >= 3)
selectionIndex += 2;
}
if (newTextLength >= 7) {
newText.write(newValue.text.substring(3, usedSubstringIndex = 6) + '-');
if (newValue.selection.end >= 6) selectionIndex++;
if (newValue.selection.end >= 6)
selectionIndex++;
}
if (newTextLength >= 11) {
newText.write(newValue.text.substring(6, usedSubstringIndex = 10) + ' ');
if (newValue.selection.end >= 10) selectionIndex++;
if (newValue.selection.end >= 10)
selectionIndex++;
}
// Dump the rest.
if (newTextLength >= usedSubstringIndex)

View File

@@ -1,59 +1,75 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
const String _introText =
'Tooltips are short identifying messages that briefly appear in response to '
'a long press. Tooltip messages are also used by services that make Flutter '
'apps accessible, like screen readers.';
'Tooltips are short identifying messages that briefly appear in response to '
'a long press. Tooltip messages are also used by services that make Flutter '
'apps accessible, like screen readers.';
class TooltipDemo extends StatelessWidget {
static const String routeName = '/material/tooltips';
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Tooltips'),
actions: <Widget>[MaterialDemoDocumentationButton(routeName)],
),
body: Builder(builder: (BuildContext context) {
appBar: AppBar(
title: const Text('Tooltips'),
actions: <Widget>[MaterialDemoDocumentationButton(routeName)],
),
body: Builder(
builder: (BuildContext context) {
return SafeArea(
top: false,
bottom: false,
child: ListView(
children: <Widget>[
Text(_introText, style: theme.textTheme.subhead),
Row(children: <Widget>[
Text('Long press the ', style: theme.textTheme.subhead),
Tooltip(
message: 'call icon',
child: Icon(Icons.call,
size: 18.0, color: theme.iconTheme.color)),
Text(' icon.', style: theme.textTheme.subhead)
]),
Center(
children: <Widget>[
Text(_introText, style: theme.textTheme.subhead),
Row(
children: <Widget>[
Text('Long press the ', style: theme.textTheme.subhead),
Tooltip(
message: 'call icon',
child: Icon(
Icons.call,
size: 18.0,
color: theme.iconTheme.color,
),
),
Text(' icon.', style: theme.textTheme.subhead),
],
),
Center(
child: IconButton(
iconSize: 48.0,
icon: const Icon(Icons.call),
color: theme.iconTheme.color,
tooltip: 'Place a phone call',
onPressed: () {
Scaffold.of(context).showSnackBar(const SnackBar(
content: Text('That was an ordinary tap.')));
}))
].map<Widget>((Widget widget) {
return Padding(
padding:
const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0),
child: widget);
}).toList()),
iconSize: 48.0,
icon: const Icon(Icons.call),
color: theme.iconTheme.color,
tooltip: 'Place a phone call',
onPressed: () {
Scaffold.of(context).showSnackBar(const SnackBar(
content: Text('That was an ordinary tap.'),
));
},
),
),
]
.map<Widget>((Widget widget) {
return Padding(
padding: const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0),
child: widget,
);
})
.toList(),
),
);
}));
}
),
);
}
}

View File

@@ -1,34 +0,0 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import '../../gallery/demo.dart';
class TwoLevelListDemo extends StatelessWidget {
static const String routeName = '/material/two-level-list';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Expand/collapse list control'),
actions: <Widget>[MaterialDemoDocumentationButton(routeName)],
),
body: ListView(children: <Widget>[
const ListTile(title: Text('Top')),
ExpansionTile(
title: const Text('Sublist'),
backgroundColor: Theme.of(context).accentColor.withOpacity(0.025),
children: const <Widget>[
ListTile(title: Text('One')),
ListTile(title: Text('Two')),
// https://en.wikipedia.org/wiki/Free_Four
ListTile(title: Text('Free')),
ListTile(title: Text('Four'))
]),
const ListTile(title: Text('Bottom'))
]));
}
}