Allow heroes to fly across navigators and restrict Cupertino nav bars to per navigator by default (#23322)

diff --git a/examples/flutter_gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/flutter_gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..949b678
--- /dev/null
+++ b/examples/flutter_gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?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>
diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart
index 804fa14..de9bda4 100644
--- a/packages/flutter/lib/src/cupertino/nav_bar.dart
+++ b/packages/flutter/lib/src/cupertino/nav_bar.dart
@@ -65,13 +65,33 @@
 
 // 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 @@
   /// 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 @@
     }
 
     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 @@
     }
 
     return Hero(
-      tag: heroTag,
+      tag: heroTag == _defaultHeroTag
+          ? _HeroTag(Navigator.of(context))
+          : heroTag,
       createRectTween: _linearTranslateWithLargestRectSizeTween,
       flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
       placeholderBuilder: _navBarHeroLaunchPadBuilder,
diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart
index c9d03df..7887114 100644
--- a/packages/flutter/lib/src/widgets/heroes.dart
+++ b/packages/flutter/lib/src/widgets/heroes.dart
@@ -222,10 +222,6 @@
           result[tag] = heroState;
         }
       }
-      // Don't perform transitions across different Navigators.
-      if (element.widget is Navigator) {
-        return;
-      }
       element.visitChildren(visitor);
     }
     context.visitChildElements(visitor);
diff --git a/packages/flutter/test/cupertino/nav_bar_transition_test.dart b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
index ea3d146..72de5c9 100644
--- a/packages/flutter/test/cupertino/nav_bar_transition_test.dart
+++ b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
@@ -385,6 +385,82 @@
     );
   });
 
+  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(