Unverified Commit 17d068d7 authored by xster's avatar xster Committed by GitHub

Allow heroes to fly across navigators and restrict Cupertino nav bars to per...

Allow heroes to fly across navigators and restrict Cupertino nav bars to per navigator by default (#23322)
parent 87ca3d52
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Original</string>
</dict>
</plist>
......@@ -65,13 +65,33 @@ const TextStyle _kLargeTitleTextStyle = TextStyle(
// There's a single tag for all instances of navigation bars because they can
// all transition between each other (per Navigator) via Hero transitions.
const _HeroTag _defaultHeroTag = _HeroTag();
const _HeroTag _defaultHeroTag = _HeroTag(null);
class _HeroTag {
const _HeroTag();
const _HeroTag(this.navigator);
final NavigatorState navigator;
// Let the Hero tag be described in tree dumps.
@override
String toString() => 'Default Hero tag for Cupertino navigation bars';
String toString() => 'Default Hero tag for Cupertino navigation bars with navigator $navigator';
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
final _HeroTag otherTag = other;
return navigator == otherTag.navigator;
}
@override
int get hashCode {
return identityHashCode(navigator);
}
}
TextStyle _navBarItemStyle(Color color) {
......@@ -331,9 +351,13 @@ class CupertinoNavigationBar extends StatefulWidget implements ObstructingPrefer
/// Tag for the navigation bar's Hero widget if [transitionBetweenRoutes] is true.
///
/// Defaults to a common tag between all [CupertinoNavigationBar] and
/// [CupertinoSliverNavigationBar] instances so they can all transition
/// between each other as long as there's only one per route. Use this tag
/// override with different tags to have multiple navigation bars per route.
/// [CupertinoSliverNavigationBar] instances of the same [Navigator]. With the
/// default tag, all navigation bars of the same navigator can transition
/// between each other as long as there's only one navigation bar per route.
///
/// This [heroTag] can be overridden to manually handle having multiple
/// navigation bars per route or to transition between multiple
/// [Navigator]s.
///
/// Cannot be null. To disable Hero transitions for this navigation bar,
/// set [transitionBetweenRoutes] to false.
......@@ -398,7 +422,9 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
}
return Hero(
tag: widget.heroTag,
tag: widget.heroTag == _defaultHeroTag
? _HeroTag(Navigator.of(context))
: widget.heroTag,
createRectTween: _linearTranslateWithLargestRectSizeTween,
placeholderBuilder: _navBarHeroLaunchPadBuilder,
flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
......@@ -733,7 +759,9 @@ class _LargeTitleNavigationBarSliverDelegate
}
return Hero(
tag: heroTag,
tag: heroTag == _defaultHeroTag
? _HeroTag(Navigator.of(context))
: heroTag,
createRectTween: _linearTranslateWithLargestRectSizeTween,
flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
placeholderBuilder: _navBarHeroLaunchPadBuilder,
......
......@@ -222,10 +222,6 @@ class Hero extends StatefulWidget {
result[tag] = heroState;
}
}
// Don't perform transitions across different Navigators.
if (element.widget is Navigator) {
return;
}
element.visitChildren(visitor);
}
context.visitChildElements(visitor);
......
......@@ -385,6 +385,82 @@ void main() {
);
});
testWidgets('Multiple nav bars tags do not conflict if in different navigators',
(WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.search),
title: Text('Tab 1'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
title: Text('Tab 2'),
),
],
),
tabBuilder: (BuildContext context, int tab) {
return CupertinoTabView(
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Tab ${tab + 1} Page 1'),
),
child: Center(
child: CupertinoButton(
child: const Text('Next'),
onPressed: () {
Navigator.push<void>(context, CupertinoPageRoute<void>(
title: 'Tab ${tab + 1} Page 2',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
));
},
),
),
);
},
);
},
),
),
);
await tester.tap(find.text('Tab 2'));
await tester.pump();
expect(find.text('Tab 1 Page 1', skipOffstage: false), findsOneWidget);
expect(find.text('Tab 2 Page 1'), findsOneWidget);
// At this point, there are 2 nav bars seeded with the same _defaultHeroTag.
// But they're inside different navigators.
await tester.tap(find.text('Next'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
// One is inside the flight shuttle and another is invisible in the
// incoming route in case a new flight needs to be created midflight.
expect(find.text('Tab 2 Page 2'), findsNWidgets(2));
await tester.pump(const Duration(milliseconds: 500));
expect(find.text('Tab 2 Page 2'), findsOneWidget);
// Offstaged by tab 2's navigator.
expect(find.text('Tab 2 Page 1', skipOffstage: false), findsOneWidget);
// Offstaged by the CupertinoTabScaffold.
expect(find.text('Tab 1 Page 1', skipOffstage: false), findsOneWidget);
// Never navigated to tab 1 page 2.
expect(find.text('Tab 1 Page 2', skipOffstage: false), findsNothing);
});
testWidgets('Transition box grows to large title size',
(WidgetTester tester) async {
await startTransitionBetween(
......
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