import 'dart:io'; import 'package:flutter/material.dart'; class AppNavigator { AppNavigator._(); static final navigatorKey = GlobalKey(); static final Set _pushingRoutes = {}; static BuildContext? get context => navigatorKey.currentContext; static Future push( Widget page, { BuildContext? context, Object? arguments, String? name, bool preventDuplicate = true, }) async { final routeName = name ?? page.runtimeType.toString(); if (preventDuplicate && RouteTracker.contains(routeName)) { return null; } if (_pushingRoutes.contains(routeName)) { return null; } _pushingRoutes.add(routeName); final nav = Navigator.of( context ?? AppNavigator.context!, rootNavigator: true, ); try { return await nav.push( SlidePageRoute( builder: (_) => page, settings: RouteSettings(name: routeName, arguments: arguments), ), ); } finally { WidgetsBinding.instance.addPostFrameCallback((_) { _pushingRoutes.remove(routeName); }); } } static Future pushReplacement( Widget page, { BuildContext? context, Object? arguments, String? name, }) { return Navigator.of( context ?? AppNavigator.context!, rootNavigator: true, ).pushReplacement( SlidePageRoute( builder: (_) => page, settings: RouteSettings(name: name, arguments: arguments), ), ); } static Future pushAndRemoveUntil( Widget page, { BuildContext? context, RoutePredicate? predicate, Object? arguments, String? name, }) { return Navigator.of( context ?? AppNavigator.context!, rootNavigator: true, ).pushAndRemoveUntil( SlidePageRoute( builder: (_) => page, settings: RouteSettings(name: name, arguments: arguments), ), predicate ?? (_) => false, ); } static Future pushTransparent( Widget page, { BuildContext? context, Color barrierColor = Colors.black54, Duration duration = const Duration(milliseconds: 200), bool dismissible = true, }) { return Navigator.of(context ?? AppNavigator.context!).push( PageRouteBuilder( opaque: false, barrierColor: barrierColor, barrierDismissible: dismissible, transitionDuration: duration, reverseTransitionDuration: duration, pageBuilder: (_, __, ___) => page, transitionsBuilder: (_, animation, __, child) { return FadeTransition(opacity: animation, child: child); }, ), ); } static void pop({BuildContext? context, T? result}) { Navigator.of( context ?? AppNavigator.context!, rootNavigator: true, ).pop(result); } static void popTimes({BuildContext? context, int count = 1}) { var popped = 0; Navigator.of(context ?? AppNavigator.context!).popUntil((route) { if (popped < count) { popped++; return false; } return true; }); } } class SlidePageRoute extends MaterialPageRoute { SlidePageRoute({required super.builder, super.settings}); @override Widget buildTransitions( BuildContext context, Animation animation, Animation secondaryAnimation, Widget child, ) { if (Platform.isAndroid) { final tween = Tween( begin: const Offset(1, 0), end: Offset.zero, ).chain(CurveTween(curve: Curves.easeOutCubic)); return SlideTransition(position: animation.drive(tween), child: child); } return Theme.of(context).pageTransitionsTheme.buildTransitions( this, context, animation, secondaryAnimation, child, ); } } class RouteTracker extends NavigatorObserver { static final List pageStack = []; bool _isPage(Route route) => route is PageRoute; @override void didPush(Route route, Route? previousRoute) { if (_isPage(route)) { pageStack.add(route.settings.name); } } @override void didPop(Route route, Route? previousRoute) { if (_isPage(route) && pageStack.isNotEmpty) { pageStack.removeLast(); } } @override void didRemove(Route route, Route? previousRoute) { if (_isPage(route)) { pageStack.remove(route.settings.name); } } @override void didReplace({Route? newRoute, Route? oldRoute}) { if (oldRoute != null && _isPage(oldRoute) && pageStack.isNotEmpty) { pageStack.removeLast(); } if (newRoute != null && _isPage(newRoute)) { pageStack.add(newRoute.settings.name); } } static bool contains(String name) => pageStack.contains(name); }