From fcf674085114a90c7c8ac80b42dfaf1baaeaf483 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Wed, 1 Mar 2017 10:45:44 -0800 Subject: [PATCH] Factor BackButton out of AppBar (#8491) This widget is useful on its own. This patch factors it out of AppBar so folks can use it separately. Fixes #8489 --- packages/flutter/lib/material.dart | 1 + .../flutter/lib/src/material/app_bar.dart | 27 ++------- .../flutter/lib/src/material/back_button.dart | 60 +++++++++++++++++++ .../test/material/back_button_test.dart | 37 ++++++++++++ 4 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 packages/flutter/lib/src/material/back_button.dart create mode 100644 packages/flutter/test/material/back_button_test.dart diff --git a/packages/flutter/lib/material.dart b/packages/flutter/lib/material.dart index aa7fb27757..aa0a0b10a2 100644 --- a/packages/flutter/lib/material.dart +++ b/packages/flutter/lib/material.dart @@ -15,6 +15,7 @@ export 'src/material/about.dart'; export 'src/material/app.dart'; export 'src/material/app_bar.dart'; export 'src/material/arc.dart'; +export 'src/material/back_button.dart'; export 'src/material/bottom_navigation_bar.dart'; export 'src/material/bottom_sheet.dart'; export 'src/material/button.dart'; diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index c5cc458ec0..6264ed73e3 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'back_button.dart'; import 'constants.dart'; import 'flexible_space_bar.dart'; import 'icon.dart'; @@ -188,7 +189,7 @@ class AppBar extends StatefulWidget { /// example, if the [AppBar] is in a [Scaffold] that also has a [Drawer], the /// [Scaffold] will fill this widget with an [IconButton] that opens the /// drawer. If there's no [Drawer] and the parent [Navigator] can go back, the - /// [AppBar] will use an [IconButton] that calls [Navigator.pop]. + /// [AppBar] will use a [BackButton] that calls [Navigator.maybePop]. final Widget leading; /// The primary widget displayed in the appbar. @@ -345,10 +346,6 @@ class _AppBarState extends State { Scaffold.of(context).openDrawer(); } - void _handleBackButton() { - Navigator.of(context).maybePop(); - } - @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); @@ -383,24 +380,8 @@ class _AppBarState extends State { tooltip: 'Open navigation menu' // TODO(ianh): Figure out how to localize this string ); } else { - if (_canPop) { - IconData backIcon; - switch (Theme.of(context).platform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - backIcon = Icons.arrow_back; - break; - case TargetPlatform.iOS: - backIcon = Icons.arrow_back_ios; - break; - } - assert(backIcon != null); - leading = new IconButton( - icon: new Icon(backIcon), - onPressed: _handleBackButton, - tooltip: 'Back' // TODO(ianh): Figure out how to localize this string - ); - } + if (_canPop) + leading = const BackButton(); } } if (leading != null) { diff --git a/packages/flutter/lib/src/material/back_button.dart b/packages/flutter/lib/src/material/back_button.dart new file mode 100644 index 0000000000..3ea25ef563 --- /dev/null +++ b/packages/flutter/lib/src/material/back_button.dart @@ -0,0 +1,60 @@ +// 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. + +import 'package:flutter/widgets.dart'; + +import 'theme.dart'; +import 'icon_button.dart'; +import 'icon.dart'; +import 'icons.dart'; + +/// A material design back button. +/// +/// A [BackButton] is an [IconButton] with a "back" icon appropriate for the +/// current [TargetPlatform]. When pressed, the back button calls +/// [Navigator.maybePop] to return to the previous route. +/// +/// When deciding to display a [BackButton], consider using +/// `ModalRoute.of(context)?.canPop` to check whether the current route can be +/// popped. If that value is false (e.g., because the current route is the +/// initial route), the [BackButton] will not have any effect when pressed, +/// which could frustrate the user. +/// +/// Requires one of its ancestors to be a [Material] widget. +/// +/// See also: +/// +/// * [AppBar], which automatically uses a [BackButton] in its +/// [AppBar.leading] slot when appropriate. +/// * [IconButton], which is a more general widget for creating buttons with +/// icons. +class BackButton extends StatelessWidget { + /// Creates an [IconButton] with the appropriate "back" icon for the current + /// target platform. + const BackButton({ Key key }) : super(key: key); + + /// Returns tha appropriate "back" icon for the given `platform`. + static IconData getIconData(TargetPlatform platform) { + switch (platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + return Icons.arrow_back; + case TargetPlatform.iOS: + return Icons.arrow_back_ios; + } + assert(false); + return null; + } + + @override + Widget build(BuildContext context) { + return new IconButton( + icon: new Icon(getIconData(Theme.of(context).platform)), + tooltip: 'Back', // TODO(ianh): Figure out how to localize this string + onPressed: () { + Navigator.of(context).maybePop(); + }, + ); + } +} diff --git a/packages/flutter/test/material/back_button_test.dart b/packages/flutter/test/material/back_button_test.dart new file mode 100644 index 0000000000..29dc4fd226 --- /dev/null +++ b/packages/flutter/test/material/back_button_test.dart @@ -0,0 +1,37 @@ +// 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 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('BackButton control test', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + home: new Material(child: new Text('Home')), + routes: { + '/next': (BuildContext context) { + return new Material( + child: new Center( + child: const BackButton(), + ) + ); + }, + } + ) + ); + + tester.state(find.byType(Navigator)).pushNamed('/next'); + + await tester.pump(); + await tester.pumpUntilNoTransientCallbacks(); + + await tester.tap(find.byType(BackButton)); + + await tester.pump(); + await tester.pumpUntilNoTransientCallbacks(); + + expect(find.text('Home'), findsOneWidget); + }); +}