Created
August 26, 2023 02:10
-
-
Save clragon/18038b16e9bbdc5e14d4a6e5840517d1 to your computer and use it in GitHub Desktop.
navgiator & overlay proxy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'proxy.dart'; | |
| /// [BottomNavigationBar] has a weird assert that checks for [Overlay]. | |
| /// Proxies do not work with that. This test can only run properly in release mode. | |
| void main() => runApp(const App()); | |
| class App extends StatelessWidget { | |
| const App({super.key}); | |
| @override | |
| Widget build(BuildContext context) => MaterialApp( | |
| theme: ThemeData( | |
| colorScheme: ColorScheme.fromSeed( | |
| brightness: Brightness.dark, | |
| seedColor: Colors.deepPurple, | |
| ), | |
| useMaterial3: true, | |
| ), | |
| initialRoute: '/1', | |
| routes: { | |
| '/1': (context) => const Page(index: 1), | |
| '/2': (context) => const Page(index: 2), | |
| '/3': (context) => const Page(index: 3), | |
| '/4': (context) => const Page(index: 4), | |
| }, | |
| builder: (context, child) => Proxy( | |
| child: child!, | |
| builder: (context, child) => Scaffold( | |
| body: child!, | |
| bottomNavigationBar: const NavBottomBar(), | |
| ), | |
| ), | |
| ); | |
| } | |
| class Page extends StatefulWidget { | |
| const Page({ | |
| super.key, | |
| required this.index, | |
| }); | |
| final int index; | |
| @override | |
| State<Page> createState() => _PageState(); | |
| } | |
| class _PageState extends State<Page> { | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| body: Center( | |
| child: Text('Page ${widget.index}'), | |
| ), | |
| ); | |
| } | |
| } | |
| class NavBottomBar extends StatefulWidget { | |
| const NavBottomBar({super.key}); | |
| @override | |
| State<NavBottomBar> createState() => _NavBottomBarState(); | |
| } | |
| class _NavBottomBarState extends State<NavBottomBar> { | |
| int _index = 0; | |
| @override | |
| Widget build(BuildContext context) { | |
| return BottomNavigationBar( | |
| useLegacyColorScheme: false, | |
| currentIndex: _index, | |
| onTap: (index) { | |
| switch (index) { | |
| case 0: | |
| Navigator.of(context).pushNamed('/1'); | |
| setState(() => _index = 0); | |
| break; | |
| case 1: | |
| Navigator.of(context).pushNamed('/2'); | |
| setState(() => _index = 1); | |
| break; | |
| case 2: | |
| Navigator.of(context).pushNamed('/3'); | |
| setState(() => _index = 2); | |
| break; | |
| case 3: | |
| Navigator.of(context).pushNamed('/4'); | |
| setState(() => _index = 3); | |
| break; | |
| } | |
| }, | |
| items: const [ | |
| BottomNavigationBarItem( | |
| icon: Text('1'), | |
| label: 'Page', | |
| ), | |
| BottomNavigationBarItem( | |
| icon: Text('2'), | |
| label: 'Page', | |
| ), | |
| BottomNavigationBarItem( | |
| icon: Text('3'), | |
| label: 'Page', | |
| ), | |
| BottomNavigationBarItem( | |
| icon: Text('4'), | |
| label: 'Page', | |
| ), | |
| ], | |
| ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'proxy_grabber.dart'; | |
| import 'proxy_navigator.dart'; | |
| import 'proxy_overlay.dart'; | |
| /// A horrible abomination from the depths of hell. | |
| /// | |
| /// Lifts the State of a [Navigator] or [Overlay] up to this widget, | |
| /// so that children can access them. | |
| /// | |
| /// This allows creating children which have access to the [Navigator] or [Overlay] | |
| /// from their [BuildContext] without being inside of Routes and without needing a [GlobalKey]. | |
| class Proxy extends StatelessWidget { | |
| const Proxy({ | |
| super.key, | |
| required this.child, | |
| this.builder, | |
| }); | |
| final Widget child; | |
| final TransitionBuilder? builder; | |
| @override | |
| Widget build(BuildContext context) { | |
| return ProxyParent( | |
| builder: (context, navigatorKey, overlayKey) => ProxyChild( | |
| navigatorKey: navigatorKey, | |
| overlayKey: overlayKey, | |
| builder: builder, | |
| child: child, | |
| ), | |
| ); | |
| } | |
| } | |
| class ProxyParent extends StatelessWidget { | |
| const ProxyParent({ | |
| super.key, | |
| required this.builder, | |
| }); | |
| final Widget Function( | |
| BuildContext, | |
| GlobalKey<NavigatorState>? navigatorKey, | |
| GlobalKey<OverlayState>? overlayKey, | |
| ) builder; | |
| @override | |
| Widget build(BuildContext context) => NavigatorGrabber( | |
| builder: (context, navigatorKey) => OverlayGrabber( | |
| builder: (context, overlayKey) => | |
| builder(context, navigatorKey, overlayKey), | |
| ), | |
| ); | |
| } | |
| class ProxyChild extends StatefulWidget { | |
| const ProxyChild({ | |
| super.key, | |
| required this.child, | |
| required this.builder, | |
| required this.navigatorKey, | |
| required this.overlayKey, | |
| }); | |
| final Widget child; | |
| final TransitionBuilder? builder; | |
| final GlobalKey<NavigatorState>? navigatorKey; | |
| final GlobalKey<OverlayState>? overlayKey; | |
| @override | |
| State<ProxyChild> createState() => _ProxyChildState(); | |
| } | |
| class _ProxyChildState extends State<ProxyChild> { | |
| final GlobalKey<_ReparentState> _reparentKey = GlobalKey<_ReparentState>(); | |
| @override | |
| Widget build(BuildContext context) { | |
| bool hasProxies = widget.navigatorKey != null || widget.overlayKey != null; | |
| Widget child = _Reparent( | |
| key: _reparentKey, | |
| child: widget.child, | |
| ); | |
| if (hasProxies) { | |
| child = widget.builder?.call(context, child) ?? child; | |
| } | |
| if (widget.navigatorKey != null) { | |
| child = ProxyNavigator( | |
| navigatorKey: widget.navigatorKey, | |
| child: child, | |
| ); | |
| } | |
| if (widget.overlayKey != null) { | |
| child = ProxyOverlay( | |
| overlayKey: widget.overlayKey, | |
| child: child, | |
| ); | |
| } | |
| return child; | |
| } | |
| } | |
| class _Reparent extends StatefulWidget { | |
| const _Reparent({ | |
| required GlobalKey super.key, | |
| required this.child, | |
| }); | |
| final Widget child; | |
| @override | |
| State<_Reparent> createState() => _ReparentState(); | |
| } | |
| class _ReparentState extends State<_Reparent> { | |
| @override | |
| Widget build(BuildContext context) => widget.child; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| /// A horrible abomination from the depths of hell. | |
| /// | |
| /// Looks for the nearest [Navigator] Widget and returns its [GlobalKey]. | |
| class NavigatorGrabber extends GlobalKeyGrabber<Navigator, NavigatorState> { | |
| const NavigatorGrabber({ | |
| Key? key, | |
| required Widget Function( | |
| BuildContext context, | |
| GlobalKey<NavigatorState>? key, | |
| ) builder, | |
| int searchDepth = 100, | |
| }) : super( | |
| key: key, | |
| builder: builder, | |
| searchDepth: searchDepth, | |
| ); | |
| } | |
| /// A horrible abomination from the depths of hell. | |
| /// | |
| /// Looks for the nearest [Overlay] Widget and returns its [GlobalKey]. | |
| class OverlayGrabber extends GlobalKeyGrabber<Overlay, OverlayState> { | |
| const OverlayGrabber({ | |
| Key? key, | |
| required Widget Function( | |
| BuildContext context, | |
| GlobalKey<OverlayState>? key, | |
| ) builder, | |
| int searchDepth = 100, | |
| }) : super( | |
| key: key, | |
| builder: builder, | |
| searchDepth: searchDepth, | |
| ); | |
| } | |
| /// A horrible abomination from the depths of hell. | |
| /// | |
| /// A widget that looks for the nearest [T] Widget and returns its [GlobalKey] of type [R]. | |
| class GlobalKeyGrabber<T extends Widget, R extends State<StatefulWidget>> | |
| extends StatefulWidget { | |
| const GlobalKeyGrabber({ | |
| super.key, | |
| required this.builder, | |
| this.searchDepth = 100, | |
| }); | |
| final Widget Function(BuildContext context, GlobalKey<R>? key) builder; | |
| final int searchDepth; | |
| @override | |
| State<GlobalKeyGrabber<T, R>> createState() => _GlobalKeyGrabberState<T, R>(); | |
| } | |
| class _GlobalKeyGrabberState<T extends Widget, R extends State<StatefulWidget>> | |
| extends State<GlobalKeyGrabber<T, R>> { | |
| final GlobalKey<_GrabberTargetState> _searchKey = | |
| GlobalKey<_GrabberTargetState>(); | |
| GlobalKey<R>? _key; | |
| void _findKey(Element element, int depth) { | |
| if (element.widget is T) { | |
| setState(() { | |
| _key = element.widget.key as GlobalKey<R>?; | |
| }); | |
| } else { | |
| if (depth > widget.searchDepth) { | |
| throw FlutterError.fromParts( | |
| <DiagnosticsNode>[ | |
| ErrorSummary( | |
| 'Could not find GlobalKey $R of Widget $T after searching $depth elements', | |
| ), | |
| ErrorDescription( | |
| 'Could not find GlobalKey $R of Widget $T after searching $depth elements. ' | |
| 'This usually means that you are trying to find a GlobalKey ' | |
| 'of a widget that is not in the widget tree. ', | |
| ), | |
| ], | |
| ); | |
| } | |
| element.visitChildElements((element) => _findKey(element, depth + 1)); | |
| } | |
| } | |
| @override | |
| void initState() { | |
| super.initState(); | |
| WidgetsBinding.instance.addPostFrameCallback((_) { | |
| _findKey(_searchKey.currentContext as Element, 0); | |
| }); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return _GrabberTarget( | |
| key: _searchKey, | |
| child: widget.builder(context, _key), | |
| ); | |
| } | |
| } | |
| class _GrabberTarget extends StatefulWidget { | |
| const _GrabberTarget({ | |
| required super.key, | |
| required this.child, | |
| }); | |
| final Widget child; | |
| @override | |
| State<_GrabberTarget> createState() => _GrabberTargetState(); | |
| } | |
| class _GrabberTargetState extends State<_GrabberTarget> { | |
| @override | |
| Widget build(BuildContext context) { | |
| return widget.child; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'package:flutter/scheduler.dart'; | |
| /// A horrible abomination from the depths of hell. | |
| /// | |
| /// Routes all calls to an [Overlay] to an [Overlay] with a different key. | |
| /// This is needed when we want to lift an [OverlayState] up in the [BuildContext] | |
| /// so that we can wrap our Routes with Widgets that need access to the [OverlayState], | |
| /// without needing a [GlobalKey] to the [Overlay]. | |
| class ProxyOverlay extends Overlay { | |
| const ProxyOverlay({ | |
| super.key, | |
| required this.overlayKey, | |
| required this.child, | |
| }); | |
| final GlobalKey<OverlayState>? overlayKey; | |
| final Widget child; | |
| @override | |
| OverlayState createState() => ProxyOverlayState(); | |
| } | |
| class ProxyOverlayState extends OverlayState { | |
| @override | |
| ProxyOverlay get widget => super.widget as ProxyOverlay; | |
| OverlayState get target { | |
| if (widget.overlayKey == null) { | |
| throw FlutterError.fromParts( | |
| <DiagnosticsNode>[ | |
| ErrorSummary('ProxyOverlayState has no overlayKey'), | |
| ErrorDescription( | |
| 'ProxyOverlayState was called but has no overlayKey. ' | |
| 'This usually means that you are trying to use a ProxyOverlay ' | |
| 'before a valid overlayKey has been assigned.', | |
| ), | |
| ], | |
| ); | |
| } | |
| return widget.overlayKey!.currentState!; | |
| } | |
| @override | |
| Ticker createTicker(TickerCallback onTick) => target.createTicker(onTick); | |
| @override | |
| bool debugIsVisible(OverlayEntry entry) => target.debugIsVisible(entry); | |
| @override | |
| void insert( | |
| OverlayEntry entry, { | |
| OverlayEntry? below, | |
| OverlayEntry? above, | |
| }) => | |
| target.insert( | |
| entry, | |
| below: below, | |
| above: above, | |
| ); | |
| @override | |
| void insertAll( | |
| Iterable<OverlayEntry> entries, { | |
| OverlayEntry? below, | |
| OverlayEntry? above, | |
| }) => | |
| target.insertAll( | |
| entries, | |
| below: below, | |
| above: above, | |
| ); | |
| @override | |
| void rearrange( | |
| Iterable<OverlayEntry> newEntries, { | |
| OverlayEntry? below, | |
| OverlayEntry? above, | |
| }) => | |
| target.rearrange( | |
| newEntries, | |
| below: below, | |
| above: above, | |
| ); | |
| @override | |
| Widget build(BuildContext context) => widget.child; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment