Unverified Commit c418b2f3 authored by xster's avatar xster Committed by GitHub

Auto populate nav bar title and previous from page route (#19637)

parent ce8ba6e8
......@@ -47,51 +47,58 @@ class CupertinoNavigationDemo extends StatelessWidget {
return new WillPopScope(
// Prevent swipe popping of this page. Use explicit exit buttons only.
onWillPop: () => new Future<bool>.value(true),
child: new CupertinoTabScaffold(
tabBar: new CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.conversation_bubble),
title: Text('Support'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.profile_circled),
title: Text('Profile'),
),
],
child: new DefaultTextStyle(
style: const TextStyle(
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
tabBuilder: (BuildContext context, int index) {
return new DefaultTextStyle(
style: const TextStyle(
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
child: new CupertinoTabView(
builder: (BuildContext context) {
switch (index) {
case 0:
child: new CupertinoTabScaffold(
tabBar: new CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.conversation_bubble),
title: Text('Support'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.profile_circled),
title: Text('Profile'),
),
],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return new CupertinoTabView(
builder: (BuildContext context) {
return new CupertinoDemoTab1(
colorItems: colorItems,
colorNameItems: colorNameItems
);
break;
case 1:
return new CupertinoDemoTab2();
break;
case 2:
return new CupertinoDemoTab3();
break;
default:
}
},
),
);
},
},
defaultTitle: 'Colors',
);
break;
case 1:
return new CupertinoTabView(
builder: (BuildContext context) => CupertinoDemoTab2(),
defaultTitle: 'Support Chat',
);
break;
case 2:
return new CupertinoTabView(
builder: (BuildContext context) => CupertinoDemoTab3(),
defaultTitle: 'Account',
);
break;
default:
}
},
),
),
);
}
......@@ -129,7 +136,6 @@ class CupertinoDemoTab1 extends StatelessWidget {
child: new CustomScrollView(
slivers: <Widget>[
const CupertinoSliverNavigationBar(
largeTitle: Text('Colors'),
trailing: ExitButton(),
),
new SliverPadding(
......@@ -174,6 +180,7 @@ class Tab1RowItem extends StatelessWidget {
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(context).push(new CupertinoPageRoute<void>(
title: colorName,
builder: (BuildContext context) => new Tab1ItemPage(
color: color,
colorName: colorName,
......@@ -285,9 +292,8 @@ class Tab1ItemPageState extends State<Tab1ItemPage> {
@override
Widget build(BuildContext context) {
return new CupertinoPageScaffold(
navigationBar: new CupertinoNavigationBar(
middle: new Text(widget.colorName),
trailing: const ExitButton(),
navigationBar: const CupertinoNavigationBar(
trailing: ExitButton(),
),
child: new SafeArea(
top: false,
......@@ -415,7 +421,6 @@ class CupertinoDemoTab2 extends StatelessWidget {
Widget build(BuildContext context) {
return new CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text('Support Chat'),
trailing: ExitButton(),
),
child: new ListView(
......@@ -699,7 +704,6 @@ class CupertinoDemoTab3 extends StatelessWidget {
Widget build(BuildContext context) {
return new CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text('Account'),
trailing: ExitButton(),
),
child: new DecoratedBox(
......
......@@ -50,6 +50,7 @@ class _CupertinoRefreshControlDemoState extends State<CupertinoRefreshControlDem
slivers: <Widget>[
const CupertinoSliverNavigationBar(
largeTitle: Text('Cupertino Refresh'),
previousPageTitle: 'Cupertino',
),
new CupertinoSliverRefreshControl(
onRefresh: () {
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
......@@ -87,6 +88,7 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
/// be null.
CupertinoPageRoute({
@required this.builder,
this.title,
RouteSettings settings,
this.maintainState = true,
bool fullscreenDialog = false,
......@@ -102,6 +104,50 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
/// Builds the primary contents of the route.
final WidgetBuilder builder;
/// A title string for this route.
///
/// Used to autopopulate [CupertinoNavigationBar] and
/// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when
/// one is not manually supplied.
final String title;
ValueNotifier<String> _previousTitle;
/// The title string of the previous [CupertinoPageRoute].
///
/// The [ValueListenable]'s value is readable after the route is installed
/// onto a [Navigator]. The [ValueListenable] will also notify its listeners
/// if the value changes (such as by replacing the previous route).
///
/// The [ValueListenable] itself will be null before the route is installed.
/// Its content value will be null if the previous route has no title or
/// is not a [CupertinoPageRoute].
///
/// See also:
///
/// * [ValueListenableBuilder], which can be used to listen and rebuild
/// widgets based on a ValueListenable.
ValueListenable<String> get previousTitle {
assert(
_previousTitle != null,
'Cannot read the previousTitle for a route that has not yet been installed',
);
return _previousTitle;
}
@override
void didChangePrevious(Route<dynamic> previousRoute) {
final String previousTitleString = previousRoute is CupertinoPageRoute
? previousRoute.title
: null;
if (_previousTitle == null) {
_previousTitle = new ValueNotifier<String>(previousTitleString);
} else {
_previousTitle.value = previousTitleString;
}
super.didChangePrevious(previousRoute);
}
@override
final bool maintainState;
......@@ -511,7 +557,6 @@ class _CupertinoBackGestureDetectorState<T> extends State<_CupertinoBackGestureD
}
}
/// A controller for an iOS-style back gesture.
///
/// This is created by a [CupertinoPageRoute] in response from a gesture caught
......
......@@ -42,6 +42,7 @@ class CupertinoTabView extends StatelessWidget {
const CupertinoTabView({
Key key,
this.builder,
this.defaultTitle,
this.routes,
this.onGenerateRoute,
this.onUnknownRoute,
......@@ -56,6 +57,9 @@ class CupertinoTabView extends StatelessWidget {
/// as [builder] takes its place.
final WidgetBuilder builder;
/// The title of the default route.
final String defaultTitle;
/// This tab view's routing table.
///
/// When a named route is pushed with [Navigator.pushNamed] inside this tab view,
......@@ -109,13 +113,17 @@ class CupertinoTabView extends StatelessWidget {
Route<dynamic> _onGenerateRoute(RouteSettings settings) {
final String name = settings.name;
WidgetBuilder routeBuilder;
if (name == Navigator.defaultRouteName && builder != null)
String title;
if (name == Navigator.defaultRouteName && builder != null) {
routeBuilder = builder;
title = defaultTitle;
}
else if (routes != null)
routeBuilder = routes[name];
if (routeBuilder != null) {
return new CupertinoPageRoute<dynamic>(
builder: routeBuilder,
title: title,
settings: settings,
);
}
......
......@@ -111,15 +111,15 @@ abstract class Route<T> {
///
/// The returned value resolves when the push transition is complete.
///
/// The [didChangeNext] method is typically called immediately after this
/// method is called.
/// The [didChangeNext] and [didChangePrevious] methods are typically called
/// immediately after this method is called.
@protected
TickerFuture didPush() => new TickerFuture.complete();
/// Called after [install] when the route replaced another in the navigator.
///
/// The [didChangeNext] method is typically called immediately after this
/// method is called.
/// The [didChangeNext] and [didChangePrevious] methods are typically called
/// immediately after this method is called.
@protected
@mustCallSuper
void didReplace(Route<dynamic> oldRoute) { }
......@@ -201,9 +201,8 @@ abstract class Route<T> {
/// This route's previous route has changed to the given new route. This is
/// called on a route whenever the previous route changes for any reason, so
/// long as it is in the history, except for immediately after the route has
/// been pushed (in which case [didPush] or [didReplace] will be called
/// instead). `previousRoute` will be null if there's no previous route.
/// long as it is in the history. `previousRoute` will be null if there's no
/// previous route.
@protected
@mustCallSuper
void didChangePrevious(Route<dynamic> previousRoute) { }
......@@ -1539,8 +1538,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
_history.add(route);
route.didPush();
route.didChangeNext(null);
if (oldRoute != null)
if (oldRoute != null) {
oldRoute.didChangeNext(route);
route.didChangePrevious(oldRoute);
}
for (NavigatorObserver observer in widget.observers)
observer.didPush(route, oldRoute);
assert(() { _debugLocked = false; return true; }());
......@@ -1589,8 +1590,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
}
});
newRoute.didChangeNext(null);
if (index > 0)
if (index > 0) {
_history[index - 1].didChangeNext(newRoute);
newRoute.didChangePrevious(_history[index - 1]);
}
for (NavigatorObserver observer in widget.observers)
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
assert(() { _debugLocked = false; return true; }());
......@@ -1684,8 +1687,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
} else {
newRoute.didChangeNext(null);
}
if (index > 0)
if (index > 0) {
_history[index - 1].didChangeNext(newRoute);
newRoute.didChangePrevious(_history[index - 1]);
}
for (NavigatorObserver observer in widget.observers)
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
oldRoute.dispose();
......
......@@ -393,7 +393,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 200));
expect(find.byType(CupertinoButton), findsOneWidget);
expect(find.byType(Icon), findsOneWidget);
expect(find.text(new String.fromCharCode(CupertinoIcons.back.codePoint)), findsOneWidget);
tester.state<NavigatorState>(find.byType(Navigator)).push(new CupertinoPageRoute<void>(
fullscreenDialog: true,
......@@ -418,7 +418,7 @@ void main() {
expect(find.text('Page 2'), findsOneWidget);
await tester.tap(find.byType(Icon));
await tester.tap(find.text(new String.fromCharCode(CupertinoIcons.back.codePoint)));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
......@@ -426,6 +426,49 @@ void main() {
expect(find.text('Home page'), findsOneWidget);
});
testWidgets('Long back label turns into "back"', (WidgetTester tester) async {
await tester.pumpWidget(
new CupertinoApp(
home: const Placeholder(),
),
);
tester.state<NavigatorState>(find.byType(Navigator)).push(
new CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
previousPageTitle: '0123456789',
),
child: Placeholder(),
);
}
)
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(find.widgetWithText(CupertinoButton, '0123456789'), findsOneWidget);
tester.state<NavigatorState>(find.byType(Navigator)).push(
new CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
previousPageTitle: '01234567890',
),
child: Placeholder(),
);
}
)
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(find.widgetWithText(CupertinoButton, 'Back'), findsOneWidget);
});
testWidgets('Border should be displayed by default', (WidgetTester tester) async {
await tester.pumpWidget(
new CupertinoApp(
......
// 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/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Middle auto-populates with title', (WidgetTester tester) async {
await tester.pumpWidget(
new CupertinoApp(
home: const Placeholder(),
),
);
tester.state<NavigatorState>(find.byType(Navigator)).push(
new CupertinoPageRoute<void>(
title: 'An iPod',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
)
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
// There should be a Text widget with the title in the nav bar even though
// we didn't specify anything in the nav bar constructor.
expect(find.widgetWithText(CupertinoNavigationBar, 'An iPod'), findsOneWidget);
// As a title, it should also be centered.
expect(tester.getCenter(find.text('An iPod')).dx, 400.0);
});
testWidgets('Leading auto-populates with back button with previous title', (WidgetTester tester) async {
await tester.pumpWidget(
new CupertinoApp(
home: const Placeholder(),
),
);
tester.state<NavigatorState>(find.byType(Navigator)).push(
new CupertinoPageRoute<void>(
title: 'An iPod',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
)
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
tester.state<NavigatorState>(find.byType(Navigator)).push(
new CupertinoPageRoute<void>(
title: 'A Phone',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
)
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(find.widgetWithText(CupertinoNavigationBar, 'A Phone'), findsOneWidget);
expect(tester.getCenter(find.text('A Phone')).dx, 400.0);
// Also shows the previous page's title next to the back button.
expect(find.widgetWithText(CupertinoButton, 'An iPod'), findsOneWidget);
// 2 paddings + 1 ahem character at font size 34.0.
expect(tester.getTopLeft(find.text('An iPod')).dx, 8.0 + 34.0 + 6.0);
});
testWidgets('Previous title is correct on first transition frame', (WidgetTester tester) async {
await tester.pumpWidget(
new CupertinoApp(
home: const Placeholder(),
),
);
tester.state<NavigatorState>(find.byType(Navigator)).push(
new CupertinoPageRoute<void>(
title: 'An iPod',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
)
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
tester.state<NavigatorState>(find.byType(Navigator)).push(
new CupertinoPageRoute<void>(
title: 'A Phone',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
)
);
// Trigger the route push
await tester.pump();
// Draw the first frame.
await tester.pump();
// Also shows the previous page's title next to the back button.
expect(find.widgetWithText(CupertinoButton, 'An iPod'), findsOneWidget);
});
testWidgets('Previous title stays up to date with changing routes', (WidgetTester tester) async {
await tester.pumpWidget(
new CupertinoApp(
home: const Placeholder(),
),
);
final CupertinoPageRoute<void> route2 = new CupertinoPageRoute<void>(
title: 'An iPod',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
);
final CupertinoPageRoute<void> route3 = new CupertinoPageRoute<void>(
title: 'A Phone',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
);
tester.state<NavigatorState>(find.byType(Navigator)).push(route2);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
tester.state<NavigatorState>(find.byType(Navigator)).push(route3);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
tester.state<NavigatorState>(find.byType(Navigator)).replace(
oldRoute: route2,
newRoute: new CupertinoPageRoute<void>(
title: 'An Internet communicator',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
)
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(find.widgetWithText(CupertinoNavigationBar, 'A Phone'), findsOneWidget);
expect(tester.getCenter(find.text('A Phone')).dx, 400.0);
// After swapping the route behind the top one, the previous label changes
// from An iPod to Back (since An Internet communicator is too long to
// fit in the back button).
expect(find.widgetWithText(CupertinoButton, 'Back'), findsOneWidget);
expect(tester.getTopLeft(find.text('Back')).dx, 8.0 + 34.0 + 6.0);
});
}
......@@ -608,7 +608,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
return TestAsyncUtils.guard(() async {
Finder backButton = find.byTooltip('Back');
if (backButton.evaluate().isEmpty) {
backButton = find.widgetWithIcon(CupertinoButton, CupertinoIcons.back);
backButton = find.byType(CupertinoNavigationBarBackButton);
}
expectSync(backButton, findsOneWidget, reason: 'One back button expected on screen');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment