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:
@@ -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,
|
||||
|
||||
109
web/gallery/lib/demo/material/banner_demo.dart
Normal file
109
web/gallery/lib/demo/material/banner_demo.dart
Normal 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}'),);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
386
web/gallery/lib/demo/material/buttons_demo.dart
Normal file
386
web/gallery/lib/demo/material/buttons_demo.dart
Normal 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',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
40
web/gallery/lib/demo/material/expansion_tile_list_demo.dart
Normal file
40
web/gallery/lib/demo/material/expansion_tile_list_demo.dart
Normal 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')),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))));
|
||||
});
|
||||
})));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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'))));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 it’s 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}));
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'))
|
||||
]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user