mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
AppBar automatically show a close button for dialogs (#9268)
* Wiring fullscreen dialog * Make fullscreen dialog use the new API. Add tests * Review notes * Move field back up * final
This commit is contained in:
parent
70e2acfb6c
commit
7ce1cfd48d
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@ -104,16 +106,14 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
|
|||||||
bool _allDayValue = false;
|
bool _allDayValue = false;
|
||||||
bool _saveNeeded = false;
|
bool _saveNeeded = false;
|
||||||
|
|
||||||
void handleDismissButton(BuildContext context) {
|
Future<bool> _onWillPop() async {
|
||||||
if (!_saveNeeded) {
|
if (!_saveNeeded)
|
||||||
Navigator.pop(context, null);
|
return true;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ThemeData theme = Theme.of(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);
|
||||||
|
|
||||||
showDialog<DismissDialogAction>(
|
return await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
child: new AlertDialog(
|
child: new AlertDialog(
|
||||||
content: new Text(
|
content: new Text(
|
||||||
@ -123,19 +123,19 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
|
|||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
new FlatButton(
|
new FlatButton(
|
||||||
child: const Text('CANCEL'),
|
child: const Text('CANCEL'),
|
||||||
onPressed: () { Navigator.pop(context, DismissDialogAction.cancel); }
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(false); // Pops the confirmation dialog but not the page.
|
||||||
|
}
|
||||||
),
|
),
|
||||||
new FlatButton(
|
new FlatButton(
|
||||||
child: const Text('DISCARD'),
|
child: const Text('DISCARD'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context)
|
Navigator.of(context).pop(true); // Returning true to _onWillPop will pop again.
|
||||||
..pop(DismissDialogAction.discard) // pop the cancel/discard dialog
|
|
||||||
..pop(); // pop this route
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -144,10 +144,6 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
|
|||||||
|
|
||||||
return new Scaffold(
|
return new Scaffold(
|
||||||
appBar: new AppBar(
|
appBar: new AppBar(
|
||||||
leading: new IconButton(
|
|
||||||
icon: const Icon(Icons.clear),
|
|
||||||
onPressed: () { handleDismissButton(context); }
|
|
||||||
),
|
|
||||||
title: const Text('New event'),
|
title: const Text('New event'),
|
||||||
actions: <Widget> [
|
actions: <Widget> [
|
||||||
new FlatButton(
|
new FlatButton(
|
||||||
@ -158,84 +154,88 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
body: new ListView(
|
body: new Form(
|
||||||
padding: const EdgeInsets.all(16.0),
|
onWillPop: _onWillPop,
|
||||||
children: <Widget>[
|
child: new ListView(
|
||||||
new Container(
|
padding: const EdgeInsets.all(16.0),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
children: <Widget>[
|
||||||
decoration: new BoxDecoration(
|
new Container(
|
||||||
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
|
||||||
|
),
|
||||||
|
alignment: FractionalOffset.bottomLeft,
|
||||||
|
child: new Text('Event name', style: theme.textTheme.display2)
|
||||||
),
|
),
|
||||||
alignment: FractionalOffset.bottomLeft,
|
new Container(
|
||||||
child: new Text('Event name', style: theme.textTheme.display2)
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
),
|
decoration: new BoxDecoration(
|
||||||
new Container(
|
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
),
|
||||||
decoration: new BoxDecoration(
|
alignment: FractionalOffset.bottomLeft,
|
||||||
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
|
child: new Text('Location', style: theme.textTheme.title.copyWith(color: Colors.black54))
|
||||||
),
|
),
|
||||||
alignment: FractionalOffset.bottomLeft,
|
new Column(
|
||||||
child: new Text('Location', style: theme.textTheme.title.copyWith(color: Colors.black54))
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: <Widget>[
|
||||||
new Column(
|
new Text('From', style: theme.textTheme.caption),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
new DateTimeItem(
|
||||||
children: <Widget>[
|
dateTime: _fromDateTime,
|
||||||
new Text('From', style: theme.textTheme.caption),
|
onChanged: (DateTime value) {
|
||||||
new DateTimeItem(
|
|
||||||
dateTime: _fromDateTime,
|
|
||||||
onChanged: (DateTime value) {
|
|
||||||
setState(() {
|
|
||||||
_fromDateTime = value;
|
|
||||||
_saveNeeded = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
new Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
new Text('To', style: theme.textTheme.caption),
|
|
||||||
new DateTimeItem(
|
|
||||||
dateTime: _toDateTime,
|
|
||||||
onChanged: (DateTime value) {
|
|
||||||
setState(() {
|
|
||||||
_toDateTime = value;
|
|
||||||
_saveNeeded = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
new Container(
|
|
||||||
decoration: new BoxDecoration(
|
|
||||||
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
|
|
||||||
),
|
|
||||||
child: new Row(
|
|
||||||
children: <Widget> [
|
|
||||||
new Checkbox(
|
|
||||||
value: _allDayValue,
|
|
||||||
onChanged: (bool value) {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_allDayValue = value;
|
_fromDateTime = value;
|
||||||
|
_saveNeeded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
new Text('To', style: theme.textTheme.caption),
|
||||||
|
new DateTimeItem(
|
||||||
|
dateTime: _toDateTime,
|
||||||
|
onChanged: (DateTime value) {
|
||||||
|
setState(() {
|
||||||
|
_toDateTime = value;
|
||||||
_saveNeeded = true;
|
_saveNeeded = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
const Text('All-day')
|
const Text('All-day')
|
||||||
]
|
]
|
||||||
|
),
|
||||||
|
new Container(
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
|
||||||
|
),
|
||||||
|
child: new Row(
|
||||||
|
children: <Widget> [
|
||||||
|
new Checkbox(
|
||||||
|
value: _allDayValue,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
_allDayValue = value;
|
||||||
|
_saveNeeded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
new Text('All-day')
|
||||||
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
]
|
||||||
]
|
.map((Widget child) {
|
||||||
.map((Widget child) {
|
return new Container(
|
||||||
return new Container(
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
height: 96.0,
|
||||||
height: 96.0,
|
child: child
|
||||||
child: child
|
);
|
||||||
);
|
})
|
||||||
})
|
.toList()
|
||||||
.toList()
|
)
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import 'icon_theme.dart';
|
|||||||
import 'icon_theme_data.dart';
|
import 'icon_theme_data.dart';
|
||||||
import 'icons.dart';
|
import 'icons.dart';
|
||||||
import 'material.dart';
|
import 'material.dart';
|
||||||
|
import 'page.dart';
|
||||||
import 'scaffold.dart';
|
import 'scaffold.dart';
|
||||||
import 'tabs.dart';
|
import 'tabs.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
@ -332,13 +333,16 @@ class AppBar extends StatefulWidget {
|
|||||||
class _AppBarState extends State<AppBar> {
|
class _AppBarState extends State<AppBar> {
|
||||||
bool _hasDrawer = false;
|
bool _hasDrawer = false;
|
||||||
bool _canPop = false;
|
bool _canPop = false;
|
||||||
|
bool _useCloseButton = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
final ScaffoldState scaffold = Scaffold.of(context, nullOk: true);
|
final ScaffoldState scaffold = Scaffold.of(context, nullOk: true);
|
||||||
_hasDrawer = scaffold?.hasDrawer ?? false;
|
_hasDrawer = scaffold?.hasDrawer ?? false;
|
||||||
_canPop = ModalRoute.of(context)?.canPop ?? false;
|
final ModalRoute<dynamic> parentRoute = ModalRoute.of(context);
|
||||||
|
_canPop = parentRoute?.canPop ?? false;
|
||||||
|
_useCloseButton = parentRoute is MaterialPageRoute<dynamic> && parentRoute.fullscreenDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDrawerButton() {
|
void _handleDrawerButton() {
|
||||||
@ -380,7 +384,7 @@ class _AppBarState extends State<AppBar> {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (_canPop)
|
if (_canPop)
|
||||||
leading = const BackButton();
|
leading = _useCloseButton ? const CloseButton() : const BackButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (leading != null) {
|
if (leading != null) {
|
||||||
|
@ -29,6 +29,8 @@ import 'theme.dart';
|
|||||||
/// [AppBar.leading] slot when appropriate.
|
/// [AppBar.leading] slot when appropriate.
|
||||||
/// * [IconButton], which is a more general widget for creating buttons with
|
/// * [IconButton], which is a more general widget for creating buttons with
|
||||||
/// icons.
|
/// icons.
|
||||||
|
/// * [CloseButton], an alternative which may be more appropriate for leaf
|
||||||
|
/// node pages in the navigation tree.
|
||||||
class BackButton extends StatelessWidget {
|
class BackButton extends StatelessWidget {
|
||||||
/// Creates an [IconButton] with the appropriate "back" icon for the current
|
/// Creates an [IconButton] with the appropriate "back" icon for the current
|
||||||
/// target platform.
|
/// target platform.
|
||||||
@ -58,3 +60,33 @@ class BackButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A material design close button.
|
||||||
|
///
|
||||||
|
/// A [CloseButton] is an [IconButton] with a "close" icon. When pressed, the
|
||||||
|
/// close button calls [Navigator.maybePop] to return to the previous route.
|
||||||
|
///
|
||||||
|
/// Use a [CloseButton] instead of a [BackButton] on fullscreen dialogs or
|
||||||
|
/// pages that may solicit additional actions to close.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [AppBar], which automatically uses a [CloseButton] in its
|
||||||
|
/// [AppBar.leading] slot when appropriate.
|
||||||
|
/// * [BackButton], which is more appropriate for middle nodes in the
|
||||||
|
/// navigation tree or where pages can be popped instantaneously with
|
||||||
|
/// no user data consequence.
|
||||||
|
class CloseButton extends StatelessWidget {
|
||||||
|
const CloseButton({ Key key }) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
tooltip: 'Close',
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).maybePop();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -329,6 +329,42 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('close button', () {
|
||||||
|
Future<Null> expectCloseIcon(WidgetTester tester, TargetPlatform platform, IconData expectedIcon) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
theme: new ThemeData(platform: platform),
|
||||||
|
home: new Scaffold(appBar: new AppBar(), body: new Text('Page 1')),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
tester.state<NavigatorState>(find.byType(Navigator)).push(new MaterialPageRoute<Null>(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new Scaffold(appBar: new AppBar(), body: new Text('Page 2'));
|
||||||
|
},
|
||||||
|
fullscreenDialog: true,
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
final Icon icon = tester.widget(find.byType(Icon));
|
||||||
|
expect(icon.icon, expectedIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('Close button shows correctly on Android', (WidgetTester tester) async {
|
||||||
|
await expectCloseIcon(tester, TargetPlatform.android, Icons.close);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Close button shows correctly on Fuchsia', (WidgetTester tester) async {
|
||||||
|
await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Close button shows correctly on iOS', (WidgetTester tester) async {
|
||||||
|
await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
group('body size', () {
|
group('body size', () {
|
||||||
testWidgets('body size with container', (WidgetTester tester) async {
|
testWidgets('body size with container', (WidgetTester tester) async {
|
||||||
final Key testKey = new UniqueKey();
|
final Key testKey = new UniqueKey();
|
||||||
|
Loading…
Reference in New Issue
Block a user