first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../../../get.dart';
class GetInformationParser extends RouteInformationParser<GetNavConfig> {
final String initialRoute;
GetInformationParser({
this.initialRoute = '/',
}) {
Get.log('GetInformationParser is created !');
}
@override
SynchronousFuture<GetNavConfig> parseRouteInformation(
RouteInformation routeInformation,
) {
var location = routeInformation.location;
if (location == '/') {
//check if there is a corresponding page
//if not, relocate to initialRoute
if (!Get.routeTree.routes.any((element) => element.name == '/')) {
location = initialRoute;
}
}
Get.log('GetInformationParser: route location: $location');
final matchResult = Get.routeTree.matchRoute(location ?? initialRoute);
return SynchronousFuture(
GetNavConfig(
currentTreeBranch: matchResult.treeBranch,
location: location,
state: routeInformation.state,
),
);
}
@override
RouteInformation restoreRouteInformation(GetNavConfig configuration) {
return RouteInformation(
location: configuration.location,
state: configuration.state,
);
}
}

View File

@@ -0,0 +1,60 @@
import 'package:flutter/widgets.dart';
import '../../../get.dart';
// class GetRouterState extends GetxController {
// GetRouterState({required this.currentTreeBranch});
// final List<GetPage> currentTreeBranch;
// GetPage? get currentPage => currentTreeBranch.last;
// static GetNavConfig? fromRoute(String route) {
// final res = Get.routeTree.matchRoute(route);
// if (res.treeBranch.isEmpty) return null;
// return GetNavConfig(
// currentTreeBranch: res.treeBranch,
// location: route,
// state: null,
// );
// }
// }
/// This config enables us to navigate directly to a sub-url
class GetNavConfig extends RouteInformation {
final List<GetPage> currentTreeBranch;
GetPage? get currentPage => currentTreeBranch.last;
GetNavConfig({
required this.currentTreeBranch,
required String? location,
required Object? state,
}) : super(
location: location,
state: state,
);
GetNavConfig copyWith({
List<GetPage>? currentTreeBranch,
required String? location,
required Object? state,
}) {
return GetNavConfig(
currentTreeBranch: currentTreeBranch ?? this.currentTreeBranch,
location: location ?? this.location,
state: state ?? this.state,
);
}
static GetNavConfig? fromRoute(String route) {
final res = Get.routeTree.matchRoute(route);
if (res.treeBranch.isEmpty) return null;
return GetNavConfig(
currentTreeBranch: res.treeBranch,
location: route,
state: null,
);
}
@override
String toString() => '''
======GetNavConfig=====\ncurrentTreeBranch: $currentTreeBranch\ncurrentPage: $currentPage\n======GetNavConfig=====''';
}

View File

@@ -0,0 +1,486 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../../../get_state_manager/src/simple/list_notifier.dart';
class GetDelegate extends RouterDelegate<GetNavConfig>
with ListenableMixin, ListNotifierMixin {
final List<GetNavConfig> history = <GetNavConfig>[];
final PopMode backButtonPopMode;
final PreventDuplicateHandlingMode preventDuplicateHandlingMode;
final GetPage notFoundRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionDelegate<dynamic>? transitionDelegate;
final _allCompleters = <GetPage, Completer>{};
GetDelegate({
GetPage? notFoundRoute,
this.navigatorObservers,
this.transitionDelegate,
this.backButtonPopMode = PopMode.History,
this.preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
}) : notFoundRoute = notFoundRoute ??
GetPage(
name: '/404',
page: () => const Scaffold(
body: Text('Route not found'),
),
) {
Get.log('GetDelegate is created !');
}
@override
GetNavConfig? get currentConfiguration {
if (history.isEmpty) return null;
final route = history.last;
return route;
}
GlobalKey<NavigatorState> get navigatorKey => Get.key;
Map<String, String> get parameters {
return currentConfiguration?.currentPage?.parameters ?? {};
}
T arguments<T>() {
return currentConfiguration?.currentPage?.arguments as T;
}
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
Future<void> backUntil(
String fullRoute, {
PopMode popMode = PopMode.Page,
}) async {
// remove history or page entries until you meet route
var iterator = currentConfiguration;
while (_canPop(popMode) &&
iterator != null &&
iterator.location != fullRoute) {
await _pop(popMode);
// replace iterator
iterator = currentConfiguration;
}
refresh();
}
@override
Widget build(BuildContext context) {
final pages = getVisualPages();
if (pages.isEmpty) return const SizedBox.shrink();
final extraObservers = navigatorObservers;
return GetNavigator(
key: navigatorKey,
onPopPage: _onPopVisualRoute,
pages: pages,
observers: [
GetObserver(),
if (extraObservers != null) ...extraObservers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
// void _unsafeHistoryClear() {
// history.clear();
// }
Future<bool> canPopHistory() {
return SynchronousFuture(_canPopHistory());
}
Future<bool> canPopPage() {
return SynchronousFuture(_canPopPage());
}
/// gets the visual pages from the current history entry
///
/// visual pages must have [participatesInRootNavigator] set to true
List<GetPage> getVisualPages() {
final currentHistory = currentConfiguration;
if (currentHistory == null) return <GetPage>[];
final res = currentHistory.currentTreeBranch
.where((r) => r.participatesInRootNavigator != null);
if (res.isEmpty) {
//default behavoir, all routes participate in root navigator
return history.map((e) => e.currentPage!).toList();
} else {
//user specified at least one participatesInRootNavigator
return res
.where((element) => element.participatesInRootNavigator == true)
.toList();
}
}
// GetPageRoute getPageRoute(RouteSettings? settings) {
// return PageRedirect(settings ?? RouteSettings(name: '/404'), _notFound())
// .page();
// }
Future<bool> handlePopupRoutes({
Object? result,
}) async {
Route? currentRoute;
navigatorKey.currentState!.popUntil((route) {
currentRoute = route;
return true;
});
if (currentRoute is PopupRoute) {
return await navigatorKey.currentState!.maybePop(result);
}
return false;
}
Future<T?>? offAndToNamed<T>(
String page, {
dynamic arguments,
int? id,
dynamic result,
Map<String, String>? parameters,
PopMode popMode = PopMode.History,
}) async {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
await popRoute(result: result);
return toNamed(page, arguments: arguments, parameters: parameters);
}
Future<T> offNamed<T>(
String page, {
dynamic arguments,
Map<String, String>? parameters,
}) async {
history.removeLast();
return toNamed<T>(page, arguments: arguments, parameters: parameters);
}
Future<GetNavConfig?> popHistory() async {
return await _popHistory();
}
// returns the popped page
@override
Future<bool> popRoute({
Object? result,
PopMode popMode = PopMode.Page,
}) async {
//Returning false will cause the entire app to be popped.
final wasPopup = await handlePopupRoutes(result: result);
if (wasPopup) return true;
final popped = await _pop(popMode);
refresh();
if (popped != null) {
//emulate the old pop with result
return true;
}
return false;
}
/// Adds a new history entry and waits for the result
Future<void> pushHistory(
GetNavConfig config, {
bool rebuildStack = true,
}) async {
//this changes the currentConfiguration
await _pushHistory(config);
if (rebuildStack) {
refresh();
}
}
Future<GetNavConfig?> runMiddleware(GetNavConfig config) async {
final middlewares = config.currentTreeBranch.last.middlewares;
if (middlewares == null) {
return config;
}
var iterator = config;
for (var item in middlewares) {
var redirectRes = await item.redirectDelegate(iterator);
if (redirectRes == null) return null;
iterator = redirectRes;
}
return iterator;
}
@override
Future<void> setNewRoutePath(GetNavConfig configuration) async {
await pushHistory(configuration);
}
Future<T> toNamed<T>(
String page, {
dynamic arguments,
Map<String, String>? parameters,
}) async {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
final decoder = Get.routeTree.matchRoute(page, arguments: arguments);
decoder.replaceArguments(arguments);
final completer = Completer<T>();
if (decoder.route != null) {
_allCompleters[decoder.route!] = completer;
await pushHistory(
GetNavConfig(
currentTreeBranch: decoder.treeBranch,
location: page,
state: null, //TODO: persist state?
),
);
return completer.future;
} else {
///TODO: IMPLEMENT ROUTE NOT FOUND
return Future.value();
}
}
bool _canPop(PopMode mode) {
switch (mode) {
case PopMode.History:
return _canPopHistory();
case PopMode.Page:
default:
return _canPopPage();
}
}
bool _canPopHistory() {
return history.length > 1;
}
bool _canPopPage() {
final currentTreeBranch = currentConfiguration?.currentTreeBranch;
if (currentTreeBranch == null) return false;
return currentTreeBranch.length > 1 ? true : _canPopHistory();
}
Future<GetNavConfig?> _doPopHistory() async {
return await _unsafeHistoryRemoveAt(history.length - 1);
}
// @override
// Future<void> setInitialRoutePath(GetNavConfig configuration) async {
// //no need to clear history with Reorder route strategy
// // _unsafeHistoryClear();
// // _resultCompleter.clear();
// await pushHistory(configuration);
// }
Future<GetNavConfig?> _doPopPage() async {
final currentBranch = currentConfiguration?.currentTreeBranch;
if (currentBranch != null && currentBranch.length > 1) {
//remove last part only
final remaining = currentBranch.take(currentBranch.length - 1);
final prevHistoryEntry =
history.length > 1 ? history[history.length - 2] : null;
//check if current route is the same as the previous route
if (prevHistoryEntry != null) {
//if so, pop the entire history entry
final newLocation = remaining.last.name;
final prevLocation = prevHistoryEntry.location;
if (newLocation == prevLocation) {
//pop the entire history entry
return await _popHistory();
}
}
//create a new route with the remaining tree branch
final res = await _popHistory();
await _pushHistory(
GetNavConfig(
currentTreeBranch: remaining.toList(),
location: remaining.last.name,
state: null, //TOOD: persist state??
),
);
return res;
} else {
//remove entire entry
return await _popHistory();
}
}
bool _onPopVisualRoute(Route<dynamic> route, dynamic result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
final settings = route.settings;
if (settings is GetPage) {
final config = history.cast<GetNavConfig?>().firstWhere(
(element) => element?.currentPage == settings,
orElse: () => null,
);
if (config != null) {
_removeHistoryEntry(config);
}
if (_allCompleters.containsKey(settings)) {
_allCompleters[settings]?.complete(route.popped);
}
}
refresh();
return true;
}
Future<GetNavConfig?> _pop(PopMode mode) async {
switch (mode) {
case PopMode.History:
return await _popHistory();
case PopMode.Page:
return await _popPage();
default:
return null;
}
}
Future<GetNavConfig?> _popHistory() async {
if (!_canPopHistory()) return null;
return await _doPopHistory();
}
Future<GetNavConfig?> _popPage() async {
if (!_canPopPage()) return null;
return await _doPopPage();
}
Future<void> _pushHistory(GetNavConfig config) async {
if (config.currentPage!.preventDuplicates) {
final originalEntryIndex =
history.indexWhere((element) => element.location == config.location);
if (originalEntryIndex >= 0) {
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
await backUntil(config.location!, popMode: PopMode.Page);
break;
case PreventDuplicateHandlingMode.ReorderRoutes:
await _unsafeHistoryRemoveAt(originalEntryIndex);
await _unsafeHistoryAdd(config);
break;
case PreventDuplicateHandlingMode.DoNothing:
default:
break;
}
return;
}
}
await _unsafeHistoryAdd(config);
}
Future<void> _removeHistoryEntry(GetNavConfig entry) async {
await _unsafeHistoryRemove(entry);
}
Future<void> _unsafeHistoryAdd(GetNavConfig config) async {
final res = await runMiddleware(config);
if (res == null) return;
history.add(res);
}
Future<void> _unsafeHistoryRemove(GetNavConfig config) async {
var index = history.indexOf(config);
if (index >= 0) await _unsafeHistoryRemoveAt(index);
}
Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async {
if (index == history.length - 1 && history.length > 1) {
//removing WILL update the current route
final toCheck = history[history.length - 2];
final resMiddleware = await runMiddleware(toCheck);
if (resMiddleware == null) return null;
history[history.length - 2] = resMiddleware;
}
return history.removeAt(index);
}
}
class GetNavigator extends Navigator {
GetNavigator({
GlobalKey<NavigatorState>? key,
bool Function(Route<dynamic>, dynamic)? onPopPage,
required List<Page> pages,
List<NavigatorObserver>? observers,
bool reportsRouteUpdateToEngine = false,
TransitionDelegate? transitionDelegate,
}) : super(
//keys should be optional
key: key,
onPopPage: onPopPage ??
(route, result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
return true;
},
reportsRouteUpdateToEngine: reportsRouteUpdateToEngine,
pages: pages,
observers: [
// GetObserver(),
if (observers != null) ...observers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
/// Enables the user to customize the intended pop behavior
///
/// Goes to either the previous history entry or the previous page entry
///
/// e.g. if the user navigates to these pages
/// 1) /home
/// 2) /home/products/1234
///
/// when popping on [History] mode, it will emulate a browser back button.
///
/// so the new history stack will be:
/// 1) /home
///
/// when popping on [Page] mode, it will only remove the last part of the route
/// so the new history stack will be:
/// 1) /home
/// 2) /home/products
///
/// another pop will change the history stack to:
/// 1) /home
enum PopMode {
History,
Page,
}
/// Enables the user to customize the behavior when pushing multiple routes that
/// shouldn't be duplicates
enum PreventDuplicateHandlingMode {
/// Removes the history entries until it reaches the old route
PopUntilOriginalRoute,
/// Simply don't push the new route
DoNothing,
/// Recommended - Moves the old route entry to the front
///
/// With this mode, you guarantee there will be only one
/// route entry for each location
ReorderRoutes
}

View File

@@ -0,0 +1,168 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
class RouterOutlet<TDelegate extends RouterDelegate<T>, T extends Object>
extends StatefulWidget {
final TDelegate routerDelegate;
final Widget Function(
BuildContext context,
TDelegate delegate,
T? currentRoute,
) builder;
//keys
RouterOutlet.builder({
Key? key,
TDelegate? delegate,
required this.builder,
}) : routerDelegate = delegate ?? Get.delegate<TDelegate, T>()!,
super(key: key);
RouterOutlet({
Key? key,
TDelegate? delegate,
required Iterable<GetPage> Function(T currentNavStack) pickPages,
required Widget Function(
BuildContext context,
TDelegate,
Iterable<GetPage>? page,
)
pageBuilder,
}) : this.builder(
builder: (context, rDelegate, currentConfig) {
var picked =
currentConfig == null ? null : pickPages(currentConfig);
if (picked?.isEmpty ?? false) {
picked = null;
}
return pageBuilder(context, rDelegate, picked);
},
delegate: delegate,
);
@override
RouterOutletState<TDelegate, T> createState() =>
RouterOutletState<TDelegate, T>();
}
class RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object>
extends State<RouterOutlet<TDelegate, T>> {
TDelegate get delegate => widget.routerDelegate;
@override
void initState() {
super.initState();
_getCurrentRoute();
delegate.addListener(onRouterDelegateChanged);
}
@override
void dispose() {
delegate.removeListener(onRouterDelegateChanged);
super.dispose();
}
T? currentRoute;
void _getCurrentRoute() {
currentRoute = delegate.currentConfiguration;
}
void onRouterDelegateChanged() {
setState(_getCurrentRoute);
}
@override
Widget build(BuildContext context) {
return widget.builder(context, delegate, currentRoute);
}
}
class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
GetRouterOutlet({
String? anchorRoute,
required String initialRoute,
Iterable<GetPage> Function(Iterable<GetPage> afterAnchor)? filterPages,
GlobalKey<NavigatorState>? key,
GetDelegate? delegate,
}) : this.pickPages(
pickPages: (config) {
Iterable<GetPage<dynamic>> ret;
if (anchorRoute == null) {
// jump the ancestor path
final length = Uri.parse(initialRoute).pathSegments.length;
return config.currentTreeBranch
.skip(length)
.take(length)
.toList();
}
ret = config.currentTreeBranch.pickAfterRoute(anchorRoute);
if (filterPages != null) {
ret = filterPages(ret);
}
return ret;
},
emptyPage: (delegate) =>
Get.routeTree.matchRoute(initialRoute).route ??
delegate.notFoundRoute,
key: key,
delegate: delegate,
);
GetRouterOutlet.pickPages({
Widget Function(GetDelegate delegate)? emptyWidget,
GetPage Function(GetDelegate delegate)? emptyPage,
required Iterable<GetPage> Function(GetNavConfig currentNavStack) pickPages,
bool Function(Route<dynamic>, dynamic)? onPopPage,
GlobalKey<NavigatorState>? key,
GetDelegate? delegate,
}) : super(
pageBuilder: (context, rDelegate, pages) {
final pageRes = <GetPage?>[
...?pages,
if (pages == null || pages.isEmpty) emptyPage?.call(rDelegate),
].whereType<GetPage>();
if (pageRes.isNotEmpty) {
return GetNavigator(
onPopPage: onPopPage ??
(route, result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
return true;
},
pages: pageRes.toList(),
key: key,
);
}
return (emptyWidget?.call(rDelegate) ?? const SizedBox.shrink());
},
pickPages: pickPages,
delegate: delegate ?? Get.rootDelegate,
);
GetRouterOutlet.builder({
required Widget Function(
BuildContext context,
GetDelegate delegate,
GetNavConfig? currentRoute,
)
builder,
GetDelegate? routerDelegate,
}) : super.builder(
builder: builder,
delegate: routerDelegate,
);
}
extension PagesListExt on List<GetPage> {
Iterable<GetPage> pickAtRoute(String route) {
return skipWhile((value) {
return value.name != route;
});
}
Iterable<GetPage> pickAfterRoute(String route) {
return pickAtRoute(route).skip(1);
}
}