Jonny Borges
Committed by GitHub

Merge pull request #1511 from Bdaya-Dev/router-outlet

More Improvements to the new RouterOutlet
... ... @@ -20,6 +20,13 @@
"cwd": "example_nav2",
"request": "launch",
"type": "dart"
},
{
"name": "example_nav2 WEB",
"cwd": "example_nav2",
"request": "launch",
"type": "dart",
"deviceId": "Chrome"
}
]
}
\ No newline at end of file
... ...
import 'package:example_nav2/app/modules/home/views/dashboard_view.dart';
import 'package:example_nav2/app/routes/app_pages.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_navigation/src/nav2/get_router_delegate.dart';
import 'package:get/get_navigation/src/nav2/router_outlet.dart';
import '../../../routes/app_pages.dart';
import '../controllers/home_controller.dart';
import 'dashboard_view.dart';
class HomeView extends GetView<HomeController> {
@override
... ... @@ -14,26 +11,21 @@ class HomeView extends GetView<HomeController> {
return GetRouterOutlet.builder(
builder: (context, delegate, currentRoute) {
//This router outlet handles the appbar and the bottom navigation bar
final title = currentRoute?.title;
final currentName = currentRoute?.name;
final currentLocation = currentRoute?.location;
var currentIndex = 0;
if (currentName?.startsWith(Routes.PRODUCTS) == true) currentIndex = 2;
if (currentName?.startsWith(Routes.PROFILE) == true) currentIndex = 1;
if (currentLocation?.startsWith(Routes.PRODUCTS) == true) {
currentIndex = 2;
}
if (currentLocation?.startsWith(Routes.PROFILE) == true) {
currentIndex = 1;
}
return Scaffold(
appBar: title == null
? null
: AppBar(
title: Text(title),
centerTitle: true,
),
body: GetRouterOutlet(
emptyPage: (delegate) => DashboardView(),
pickPages: (currentNavStack) {
// will take any route after home
final res = currentNavStack.pickAfterRoute(Routes.HOME);
// print('''RouterOutlet rebuild:
// currentStack: $currentNavStack
// pickedStack: $res''');
final res =
currentNavStack.currentTreeBranch.pickAfterRoute(Routes.HOME);
return res;
},
),
... ... @@ -42,7 +34,7 @@ class HomeView extends GetView<HomeController> {
onTap: (value) {
switch (value) {
case 0:
delegate.offUntil(Routes.HOME);
delegate.until(Routes.HOME);
break;
case 1:
delegate.toNamed(Routes.PROFILE);
... ...
import 'package:get/get.dart';
import '../controllers/root_controller.dart';
class RootBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<RootController>(
() => RootController(),
);
}
}
... ...
import 'package:get/get.dart';
class RootController extends GetxController {
//TODO: Implement RootController
final count = 0.obs;
@override
void onInit() {
super.onInit();
}
@override
void onReady() {
super.onReady();
}
@override
void onClose() {}
void increment() => count.value++;
}
... ...
import 'package:example_nav2/app/routes/app_pages.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class DrawerWidget extends StatelessWidget {
const DrawerWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: [
Container(
height: 100,
color: Colors.red,
),
ListTile(
title: Text('Home'),
onTap: () {
Get.getDelegate()?.toNamed(Routes.HOME);
//to close the drawer
Navigator.of(context).pop();
},
),
ListTile(
title: Text('Settings'),
onTap: () {
Get.getDelegate()?.toNamed(Routes.SETTINGS);
//to close the drawer
Navigator.of(context).pop();
},
),
],
),
);
}
}
... ...
import 'package:example_nav2/app/routes/app_pages.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/root_controller.dart';
import 'drawer.dart';
class RootView extends GetView<RootController> {
@override
Widget build(BuildContext context) {
return GetRouterOutlet.builder(
builder: (context, rDelegate, currentRoute) {
final title = currentRoute?.location;
return Scaffold(
drawer: DrawerWidget(),
appBar: AppBar(
title: Text(title ?? ''),
centerTitle: true,
),
body: GetRouterOutlet(
emptyPage: (delegate) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('<<<< Select something from the drawer on the left'),
Builder(
builder: (context) => MaterialButton(
child: Icon(Icons.open_in_new_outlined),
onPressed: () {
Scaffold.of(context).openDrawer();
},
),
)
],
),
);
},
pickPages: (currentNavStack) {
//show all routes here except the root view
print('Root RouterOutlet: $currentNavStack');
return currentNavStack.currentTreeBranch.skip(1).take(1).toList();
},
),
);
},
);
}
}
... ...
... ... @@ -8,10 +8,6 @@ class SettingsView extends GetView<SettingsController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SettingsView'),
centerTitle: true,
),
body: Center(
child: Text(
'SettingsView is working',
... ...
import 'package:example_nav2/app/modules/root/bindings/root_binding.dart';
import 'package:example_nav2/app/modules/root/views/root_view.dart';
import 'package:get/get.dart';
import 'package:get/get_navigation/src/nav2/router_outlet.dart';
import '../modules/home/bindings/home_binding.dart';
... ... @@ -20,15 +22,21 @@ class AppPages {
static final routes = [
GetPage(
name: '/',
page: () => RootView(),
middlewares: [
RouterOutletContainerMiddleWare('/'),
],
binding: RootBinding(),
children: [
GetPage(
name: _Paths.HOME,
preventDuplicates: true,
page: () => HomeView(),
bindings: [
HomeBinding(),
],
title: null,
middlewares: [
RouterOutletContainerMiddleWare(_Paths.HOME),
],
children: [
GetPage(
name: _Paths.PROFILE,
... ... @@ -56,5 +64,7 @@ class AppPages {
page: () => SettingsView(),
binding: SettingsBinding(),
),
],
),
];
}
... ...
... ... @@ -10,8 +10,14 @@ void main() {
GetMaterialApp.router(
title: "Application",
getPages: AppPages.routes,
routeInformationParser: GetInformationParser(),
routerDelegate: GetDelegate(),
routeInformationParser: GetInformationParser(
// initialRoute: Routes.HOME,
),
routerDelegate: GetDelegate(
backButtonPopMode: PopMode.History,
preventDuplicateHandlingMode:
PreventDuplicateHandlingMode.PopUntilOriginalRoute,
),
),
);
}
... ...
import 'package:flutter/widgets.dart';
import 'package:get/get_navigation/src/nav2/get_router_delegate.dart';
import '../../get_core/src/get_interface.dart';
import '../../route_manager.dart';
import 'get_instance.dart';
... ... @@ -130,5 +128,5 @@ extension Inst on GetInterface {
TDelegate? delegate<TDelegate extends RouterDelegate<TPage>, TPage>() =>
routerDelegate as TDelegate?;
GetDelegate? getDelegate() => delegate<GetDelegate, GetPage>();
GetDelegate? getDelegate() => delegate<GetDelegate, GetNavConfig>();
}
... ...
... ... @@ -3,6 +3,9 @@ library get_navigation;
export 'src/bottomsheet/bottomsheet.dart';
export 'src/extension_navigation.dart';
export 'src/nav2/get_information_parser.dart';
export 'src/nav2/get_nav_config.dart';
export 'src/nav2/get_router_delegate.dart';
export 'src/nav2/router_outlet.dart';
export 'src/root/get_cupertino_app.dart';
export 'src/root/get_material_app.dart';
export 'src/root/internacionalization.dart';
... ...
... ... @@ -2,28 +2,44 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../../../get.dart';
class GetInformationParser extends RouteInformationParser<GetPage> {
class GetInformationParser extends RouteInformationParser<GetNavConfig> {
final String initialRoute;
GetInformationParser({
this.initialRoute = '/',
});
@override
SynchronousFuture<GetPage> parseRouteInformation(
SynchronousFuture<GetNavConfig> parseRouteInformation(
RouteInformation routeInformation,
) {
if (routeInformation.location == '/') {
return SynchronousFuture(Get.routeTree.routes.first);
print('GetInformationParser: route location: ${routeInformation.location}');
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;
}
}
print('route location: ${routeInformation.location}');
final page = Get.routeTree.matchRoute(routeInformation.location!);
print(page.parameters);
final val = page.route!.copy(
name: routeInformation.location,
parameter: Map.from(page.parameters),
final matchResult = Get.routeTree.matchRoute(location ?? initialRoute);
return SynchronousFuture(
GetNavConfig(
currentTreeBranch: matchResult.treeBranch,
location: location,
state: routeInformation.state,
),
);
return SynchronousFuture(val);
}
@override
RouteInformation restoreRouteInformation(GetPage uri) {
print('restore $uri');
RouteInformation restoreRouteInformation(GetNavConfig config) {
print('restore $config');
return RouteInformation(location: uri.name);
return RouteInformation(
location: config.location,
state: config.state,
);
}
}
... ...
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
/// 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,
GetPage? currentPage,
required String? location,
required Object? state,
}) {
return GetNavConfig(
currentTreeBranch: currentTreeBranch ?? this.currentTreeBranch,
location: location ?? this.location,
state: state ?? this.state,
);
}
@override
String toString() => '''
======GetNavConfig=====
currentTreeBranch: $currentTreeBranch
currentPage: $currentPage
======GetNavConfig=====''';
}
... ...
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/nav2/router_outlet.dart';
import '../../../get.dart';
import '../../../get_state_manager/src/simple/list_notifier.dart';
class GetDelegate extends RouterDelegate<GetPage>
with ListenableMixin, ListNotifierMixin {
final List<GetPage> routes = <GetPage>[];
/// 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,
final pageRoutes = <GetPage, GetPageRoute>{};
/// Simply don't push the new route
DoNothing,
}
class GetDelegate extends RouterDelegate<GetNavConfig>
with ListenableMixin, ListNotifierMixin {
final List<GetNavConfig> history = <GetNavConfig>[];
final PopMode backButtonPopMode;
final PreventDuplicateHandlingMode preventDuplicateHandlingMode;
final pageRoutes = <String, GetPageRoute>{};
GetPage? notFoundRoute;
final List<NavigatorObserver>? dipNavObservers;
final List<NavigatorObserver>? navigatorObservers;
final TransitionDelegate<dynamic>? transitionDelegate;
final _resultCompleter = <GetNavConfig, Completer<Object?>>{};
GlobalKey<NavigatorState> get navigatorKey =>
GetNavigation.getxController.key;
GetDelegate(
{this.notFoundRoute, this.dipNavObservers, this.transitionDelegate});
GetDelegate({
this.notFoundRoute,
this.navigatorObservers,
this.transitionDelegate,
this.backButtonPopMode = PopMode.History,
this.preventDuplicateHandlingMode = PreventDuplicateHandlingMode.DoNothing,
});
/// Adds a new history entry and waits for the result
Future<T?> pushHistory<T>(
GetNavConfig config, {
bool rebuildStack = true,
}) {
//this changes the currentConfiguration
final completer = Completer<T?>();
_resultCompleter[config] = completer;
_pushHistory(config);
if (rebuildStack) {
refresh();
}
return completer.future;
}
void _removeHistoryEntry(GetNavConfig entry) {
history.remove(entry);
pageRoutes.remove(entry.location);
final lastCompleter = _resultCompleter.remove(entry);
lastCompleter?.complete(entry);
}
void _pushHistory(GetNavConfig config) {
if (config.currentPage!.preventDuplicates) {
if (history.any((element) => element.location == config.location)) {
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
until(config.location!, popMode: PopMode.History);
return;
case PreventDuplicateHandlingMode.DoNothing:
default:
return;
}
}
}
history.add(config);
pageRoutes[config.location!] =
PageRedirect(config.currentPage!, _notFound()).page();
}
GetNavConfig? _popHistory() {
if (!_canPopHistory()) return null;
return _doPopHistory();
}
GetNavConfig _doPopHistory() {
final res = history.removeLast();
pageRoutes.remove(res.location);
return res;
}
GetNavConfig? _popPage() {
if (!_canPopPage()) return null;
return _doPopPage();
}
GetNavConfig? _pop(PopMode mode) {
switch (mode) {
case PopMode.History:
return _popHistory();
case PopMode.Page:
return _popPage();
default:
return null;
}
}
// returns the popped page
GetNavConfig? _doPopPage() {
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 _popHistory();
}
}
//create a new route with the remaining tree branch
final res = _popHistory();
_pushHistory(
GetNavConfig(
currentTreeBranch: remaining.toList(),
location: remaining.last.name,
state: null, //TOOD: persist state??
),
);
return res;
} else {
//remove entire entry
return _popHistory();
}
}
Future<GetNavConfig?> popHistory() {
return SynchronousFuture(_popHistory());
}
bool _canPopHistory() {
return history.length > 1;
}
Future<bool> canPopHistory() {
return SynchronousFuture(_canPopHistory());
}
List<GetPage> getVisiblePages() {
return routes.where((r) {
bool _canPopPage() {
final currentTreeBranch = currentConfiguration?.currentTreeBranch;
if (currentTreeBranch == null) return false;
return currentTreeBranch.length > 1 ? true : _canPopHistory();
}
Future<bool> canPopPage() {
return SynchronousFuture(_canPopPage());
}
/// gets the visual pages from the current history entry
///
/// visual pages must have the [RouterOutletContainerMiddleWare] middleware
/// with `stayAt` equal to the route name of the visual page
List<GetPage> getVisualPages() {
final currentHistory = currentConfiguration;
if (currentHistory == null) return <GetPage>[];
return currentHistory.currentTreeBranch.where((r) {
final mware =
(r.middlewares ?? []).whereType<RouterOutletContainerMiddleWare>();
if (mware.length == 0) return true;
... ... @@ -31,63 +205,77 @@ class GetDelegate extends RouterDelegate<GetPage>
}).toList();
}
/// Called by the [Router] at startup with the structure that the
/// [RouteInformationParser] obtained from parsing the initial route.
@override
Widget build(BuildContext context) {
final pages = getVisiblePages();
final pages = getVisualPages();
final extraObservers = navigatorObservers;
return Navigator(
key: navigatorKey,
onPopPage: _onPopPage,
onPopPage: _onPopVisualRoute,
pages: pages,
observers: [
GetObserver(),
if (extraObservers != null) ...extraObservers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
final _resultCompleter = <GetPage, Completer<Object?>>{};
@override
Future<void> setInitialRoutePath(GetPage configuration) async {
await pushRoute(configuration);
Future<void> setInitialRoutePath(GetNavConfig configuration) async {
history.clear();
pageRoutes.clear();
_resultCompleter.clear();
await pushHistory(configuration);
}
@override
Future<void> setNewRoutePath(GetPage configuration) {
routes.clear();
pageRoutes.clear();
return pushRoute(configuration);
Future<void> setNewRoutePath(GetNavConfig configuration) async {
await pushHistory(configuration);
}
/// Called by the [Router] when it detects a route information may have
/// changed as a result of rebuild.
@override
GetPage get currentConfiguration {
final route = routes.last;
GetNavConfig? get currentConfiguration {
if (history.isEmpty) return null;
final route = history.last;
return route;
}
GetPageRoute? get currentRoute => pageRoutes[currentConfiguration];
Future<T?> toNamed<T>(String route) {
final page = Get.routeTree.matchRoute(route);
if (page.route != null) {
return pushRoute(page.route!.copy(name: route));
} else {
return pushRoute(_notFound());
GetPageRoute? get currentRoute {
final curPage = currentConfiguration?.currentPage;
return curPage == null ? null : pageRoutes[curPage];
}
Future<T?> toNamed<T>(String fullRoute) {
final decoder = Get.routeTree.matchRoute(fullRoute);
return pushHistory<T>(
GetNavConfig(
currentTreeBranch: decoder.treeBranch,
location: fullRoute,
state: null, //TODO: persist state?
),
);
}
Future<T?> offUntil<T>(String route) {
final page = Get.routeTree.matchRoute(route);
if (page.route != null) {
return pushRoute(page.route!.copy(name: route), removeUntil: true);
} else {
return pushRoute(_notFound());
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
void until(
String fullRoute, {
PopMode popMode = PopMode.History,
}) {
// remove history or page entries until you meet route
final currentEntry = currentConfiguration;
var iterator = currentEntry;
while (history.length > 0 &&
iterator != null &&
iterator.location != fullRoute) {
_pop(popMode);
// replace iterator
iterator = currentConfiguration;
}
refresh();
}
GetPage _notFound() {
... ... @@ -99,33 +287,6 @@ class GetDelegate extends RouterDelegate<GetPage>
);
}
Future<T?> pushRoute<T>(
GetPage page, {
bool removeUntil = false,
bool replaceCurrent = false,
bool rebuildStack = true,
}) {
final completer = Completer<T?>();
_resultCompleter[page] = completer;
page = page.copy(unknownRoute: _notFound());
assert(!(removeUntil && replaceCurrent),
'Only removeUntil or replaceCurrent should by true!');
if (removeUntil) {
routes.clear();
pageRoutes.clear();
} else if (replaceCurrent && routes.isNotEmpty) {
final lastPage = routes.removeLast();
pageRoutes.remove(lastPage);
}
addPage(page);
if (rebuildStack) {
refresh();
}
//emulate the old push with result
return completer.future;
}
Future<bool> handlePopupRoutes({
Object? result,
}) async {
... ... @@ -143,79 +304,38 @@ class GetDelegate extends RouterDelegate<GetPage>
@override
Future<bool> popRoute({
Object? result,
PopMode popMode = PopMode.History,
}) async {
//Returning false will cause the entire app to be popped.
final wasPopup = await handlePopupRoutes(result: result);
if (wasPopup) return true;
if (canPop()) {
final _popped = _pop(popMode);
refresh();
if (_popped != null) {
//emulate the old pop with result
final lastRoute = routes.last;
final lastCompleter = _resultCompleter.remove(lastRoute);
final lastCompleter = _resultCompleter.remove(_popped);
lastCompleter?.complete(result);
//route to be removed
removePage(lastRoute);
return Future.value(true);
}
return Future.value(false);
}
bool canPop() {
return routes.length > 1;
}
bool _onPopPage(Route<dynamic> route, dynamic result) {
bool _onPopVisualRoute(Route<dynamic> route, dynamic result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
final settings = route.settings;
if (settings is GetPage) {
removePage(settings);
}
refresh();
return true;
}
void removePage(GetPage page) {
final isLast = routes.last == page;
//check if it's last
routes.remove(page);
final oldPageRoute = pageRoutes.remove(page);
if (isLast && oldPageRoute != null) {
_currentRoutePopped(oldPageRoute);
final newPageRoute = pageRoutes[routes.last];
if (newPageRoute != null) _currentRouteChanged(newPageRoute);
}
refresh();
}
void addPage(GetPage route) {
routes.add(
route,
final config = history.cast<GetNavConfig?>().firstWhere(
(element) => element?.currentPage == settings,
orElse: () => null,
);
final pageRoute =
pageRoutes[route] = PageRedirect(route, _notFound()).page();
_currentRouteChanged(pageRoute);
refresh();
if (config != null) {
_removeHistoryEntry(config);
}
void addRoutes(List<GetPage> pages) {
routes.addAll(pages);
for (var item in pages) {
pageRoutes[item] = PageRedirect(item, _notFound()).page();
}
final pageRoute = pageRoutes[routes.last];
if (pageRoute != null) _currentRouteChanged(pageRoute);
refresh();
}
void _currentRoutePopped(GetPageRoute route) {
route.dispose();
}
void _currentRouteChanged(GetPageRoute route) {
//is this method useful ?
//transition? -> in router outlet ??
//buildPage? -> in router outlet
return true;
}
}
... ...
... ... @@ -20,16 +20,21 @@ class RouterOutlet<TDelegate extends RouterDelegate<T>, T extends Object>
RouterOutlet({
TDelegate? delegate,
required List<T> Function(TDelegate routerDelegate) currentNavStack,
required List<T> Function(List<T> currentNavStack) pickPages,
required Widget Function(BuildContext context, TDelegate, T? page)
required List<RouteSettings> Function(T currentNavStack) pickPages,
required Widget Function(
BuildContext context,
TDelegate,
RouteSettings? page,
)
pageBuilder,
}) : this.builder(
builder: (context, rDelegate, currentConfig) {
final currentStack = currentNavStack(rDelegate);
final picked = pickPages(currentStack);
if (picked.length == 0)
final picked = currentConfig == null
? <RouteSettings>[]
: pickPages(currentConfig);
if (picked.length == 0) {
return pageBuilder(context, rDelegate, null);
}
return pageBuilder(context, rDelegate, picked.last);
},
delegate: delegate,
... ... @@ -68,12 +73,12 @@ class _RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object>
}
}
class GetRouterOutlet extends RouterOutlet<GetDelegate, GetPage> {
class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
GetRouterOutlet.builder({
required Widget Function(
BuildContext context,
GetDelegate delegate,
GetPage? currentRoute,
GetNavConfig? currentRoute,
)
builder,
GetDelegate? routerDelegate,
... ... @@ -84,10 +89,10 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetPage> {
GetRouterOutlet({
Widget Function(GetDelegate delegate)? emptyPage,
required List<GetPage> Function(List<GetPage> currentNavStack) pickPages,
required List<GetPage> Function(GetNavConfig currentNavStack) pickPages,
}) : super(
pageBuilder: (context, rDelegate, page) {
final pageRoute = rDelegate.pageRoutes[page];
final pageRoute = rDelegate.pageRoutes[page?.name];
if (pageRoute != null) {
//TODO: transitions go here !
return pageRoute.buildPage(
... ... @@ -102,21 +107,18 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetPage> {
rDelegate.notFoundRoute?.page()) ??
SizedBox.shrink();
},
currentNavStack: (routerDelegate) => routerDelegate.routes,
pickPages: pickPages,
delegate: Get.getDelegate(),
);
}
/// A marker outlet to identify which pages are visual
/// (handled by the navigator) and which are logical
/// (handled by the delegate)
class RouterOutletContainerMiddleWare extends GetMiddleware {
final String stayAt;
RouterOutletContainerMiddleWare(this.stayAt);
// @override
// RouteSettings? redirect(String? route) {
// print('RouterOutletContainerMiddleWare: Redirect called ($route)');
// return null;
// }
}
extension PagesListExt on List<GetPage> {
... ...
import '../../../get_core/src/get_main.dart';
import '../../get_navigation.dart';
import '../routes/get_route.dart';
class RouteDecoder {
final GetPage? route;
final Map<String, String?> parameters;
const RouteDecoder(this.route, this.parameters);
final List<GetPage> treeBranch;
GetPage? get route => treeBranch.isEmpty ? null : treeBranch.last;
final Map<String, String> parameters;
const RouteDecoder(
this.treeBranch,
this.parameters,
);
}
class ParseRouteTree {
... ... @@ -17,20 +20,53 @@ class ParseRouteTree {
RouteDecoder matchRoute(String name) {
final uri = Uri.parse(name);
final route = _findRoute(uri.path);
final params = Map<String, String?>.from(uri.queryParameters);
if (route != null) {
final parsedParams = _parseParams(name, route.path);
// /home/profile/123 => home,profile,123 => /,/home,/home/profile,/home/profile/123
final split = uri.path.split('/').where((element) => element.isNotEmpty);
var curPath = '/';
final cumulativePaths = <String>[
'/',
];
for (var item in split) {
if (curPath.endsWith('/')) {
curPath += '$item';
} else {
curPath += '/$item';
}
cumulativePaths.add(curPath);
}
final treeBranch = cumulativePaths
.map((e) => MapEntry(e, _findRoute(e)))
.where((element) => element.value != null)
.toList();
final params = Map<String, String>.from(uri.queryParameters);
if (treeBranch.isNotEmpty) {
//route is found, do further parsing to get nested query params
final lastRoute = treeBranch.last;
final parsedParams = _parseParams(name, lastRoute.value!.path);
if (parsedParams.isNotEmpty) {
params.addAll(parsedParams);
}
//copy parameters to all pages.
final mappedTreeBranch = treeBranch
.map(
(e) => e.value!.copy(
parameter: params,
),
)
.toList();
return RouteDecoder(
mappedTreeBranch,
params,
);
}
// This logger sends confusing messages
// else {
// // Get.log('Route "${uri.path}" not found');
// }
return RouteDecoder(route, params);
//route not found
return RouteDecoder(
treeBranch.map((e) => e.value!).toList(),
params,
);
}
void addRoutes(List<GetPage> getPages) {
... ... @@ -88,6 +124,7 @@ class ParseRouteTree {
opaque: origin.opaque,
parameter: origin.parameter,
popGesture: origin.popGesture,
// settings: origin.settings,
transitionDuration: origin.transitionDuration,
middlewares: middlewares,
... ... @@ -99,8 +136,8 @@ class ParseRouteTree {
);
}
Map<String, String?> _parseParams(String path, PathDecoded routePath) {
final params = <String, String?>{};
Map<String, String> _parseParams(String path, PathDecoded routePath) {
final params = <String, String>{};
var idx = path.indexOf('?');
if (idx > -1) {
path = path.substring(0, idx);
... ...
... ... @@ -41,7 +41,7 @@ class GetPage<T> extends Page<T> {
final CustomTransition? customTransition;
final Duration? transitionDuration;
final bool fullscreenDialog;
final bool preventDuplicates;
// @override
// final LocalKey? key;
... ... @@ -79,6 +79,7 @@ class GetPage<T> extends Page<T> {
this.children,
this.middlewares,
this.unknownRoute,
this.preventDuplicates = false,
}) : path = _nameToRegex(name),
super(
key: ValueKey(name),
... ... @@ -128,8 +129,10 @@ class GetPage<T> extends Page<T> {
List<GetPage>? children,
GetPage? unknownRoute,
List<GetMiddleware>? middlewares,
bool? preventDuplicates,
}) {
return GetPage(
preventDuplicates: preventDuplicates ?? this.preventDuplicates,
name: name ?? this.name,
page: page ?? this.page,
popGesture: popGesture ?? this.popGesture,
... ... @@ -145,7 +148,6 @@ class GetPage<T> extends Page<T> {
customTransition: customTransition ?? this.customTransition,
transitionDuration: transitionDuration ?? this.transitionDuration,
fullscreenDialog: fullscreenDialog ?? this.fullscreenDialog,
// settings: settings ?? this.settings,
children: children ?? this.children,
unknownRoute: unknownRoute ?? this.unknownRoute,
middlewares: middlewares ?? this.middlewares,
... ... @@ -171,6 +173,8 @@ class GetPage<T> extends Page<T> {
other.page.runtimeType == page.runtimeType &&
other.popGesture == popGesture &&
// mapEquals(other.parameter, parameter) &&
other.preventDuplicates == preventDuplicates &&
other.title == title &&
other.transition == transition &&
other.curve == curve &&
... ... @@ -194,6 +198,7 @@ class GetPage<T> extends Page<T> {
return //page.hashCode ^
popGesture.hashCode ^
// parameter.hashCode ^
preventDuplicates.hashCode ^
title.hashCode ^
transition.hashCode ^
curve.hashCode ^
... ...