Line data Source code
1 : part of '../main.dart';
2 :
3 : @immutable
4 : class VNesterPage extends VPage {
5 : final List<VRouteElement> nestedRoutes;
6 : final Widget Function(Widget child) widgetBuilder;
7 :
8 3 : VNesterPage({
9 : required String? path,
10 : required Page Function(Widget child) pageBuilder,
11 : required this.widgetBuilder,
12 : required this.nestedRoutes,
13 : String? name,
14 : List<VRouteElement> stackedRoutes = const [],
15 : List<String> aliases = const [],
16 : bool mustMatchSubRoute = false,
17 3 : }) : assert(nestedRoutes.isNotEmpty,
18 : 'The stackedRoutes of a VNester should not be null, otherwise it can\'t nest'),
19 3 : navigatorKey = GlobalKey<NavigatorState>(),
20 3 : heroController = HeroController(),
21 3 : super(
22 : pageBuilder: pageBuilder,
23 6 : widget: widgetBuilder(Container()),
24 : path: path,
25 : name: name,
26 : stackedRoutes: stackedRoutes,
27 : aliases: aliases,
28 : mustMatchSubRoute: mustMatchSubRoute,
29 : );
30 :
31 : /// A key for the navigator
32 : /// It is created automatically
33 : final GlobalKey<NavigatorState> navigatorKey;
34 :
35 : /// A hero controller for the navigator
36 : /// It is created automatically
37 : final HeroController heroController;
38 :
39 2 : @override
40 : VRoute? buildRoute(
41 : VPathRequestData vPathRequestData, {
42 : required String? parentRemainingPath,
43 : required Map<String, String> parentPathParameters,
44 : }) {
45 : VRoute? nestedRouteVRoute;
46 : late final GetPathMatchResult getPathMatchResult;
47 :
48 : // Try to find valid VRoute from nestedRoutes
49 :
50 : // Check for the path
51 2 : final pathGetPathMatchResult = getPathMatch(
52 2 : entirePath: vPathRequestData.path,
53 : remainingPathFromParent: parentRemainingPath,
54 2 : selfPath: path,
55 2 : selfPathRegExp: pathRegExp,
56 2 : selfPathParametersKeys: pathParametersKeys,
57 : parentPathParameters: parentPathParameters,
58 : );
59 2 : final VRoute? vRoute = getVRouteFromRoutes(
60 : vPathRequestData,
61 2 : routes: nestedRoutes,
62 : getPathMatchResult: pathGetPathMatchResult,
63 : );
64 : if (vRoute != null) {
65 : // If we have a matching path with the path, keep it
66 : nestedRouteVRoute = vRoute;
67 0 : getPathMatchResult = pathGetPathMatchResult;
68 : } else {
69 : // Else check with the aliases
70 7 : for (var i = 0; i < aliases.length; i++) {
71 1 : final aliasGetPathMatchResult = getPathMatch(
72 1 : entirePath: vPathRequestData.path,
73 : remainingPathFromParent: parentRemainingPath,
74 2 : selfPath: aliases[i],
75 2 : selfPathRegExp: aliasesRegExp[i],
76 2 : selfPathParametersKeys: aliasesPathParametersKeys[i],
77 : parentPathParameters: parentPathParameters,
78 : );
79 1 : final VRoute? vRoute = getVRouteFromRoutes(
80 : vPathRequestData,
81 1 : routes: nestedRoutes,
82 : getPathMatchResult: aliasGetPathMatchResult,
83 : );
84 : if (vRoute != null) {
85 : nestedRouteVRoute = vRoute;
86 0 : getPathMatchResult = aliasGetPathMatchResult;
87 : break;
88 : }
89 : }
90 : }
91 :
92 : // If no child route match, this is not a match
93 : if (nestedRouteVRoute == null) {
94 : return null;
95 : }
96 :
97 : // Else also try to match nestedRoute with the path (or the alias) with which the nestedRoute was valid
98 2 : final VRoute? stackedRouteVRoute = getVRouteFromRoutes(
99 : vPathRequestData,
100 2 : routes: stackedRoutes,
101 0 : getPathMatchResult: getPathMatchResult,
102 : );
103 :
104 : if (stackedRouteVRoute == null) {
105 2 : final newVRouteElements = VRouteElementNode(this,
106 2 : nestedVRouteElementNode: nestedRouteVRoute.vRouteElementNode);
107 :
108 : // If stackedRouteVRoute is null, create a VRoute with nestedRouteVRoute
109 2 : return VRoute(
110 : vRouteElementNode: newVRouteElements,
111 2 : pages: [
112 2 : buildPage(
113 4 : widget: widgetBuilder(
114 2 : Builder(
115 2 : builder: (BuildContext context) {
116 2 : return VRouterHelper(
117 4 : pages: nestedRouteVRoute!.pages.isNotEmpty
118 2 : ? nestedRouteVRoute.pages
119 0 : : [
120 0 : MaterialPage(child: Center(child: CircularProgressIndicator())),
121 : ],
122 2 : navigatorKey: navigatorKey,
123 4 : observers: [heroController],
124 : backButtonDispatcher:
125 6 : ChildBackButtonDispatcher(Router.of(context).backButtonDispatcher!),
126 1 : onPopPage: (_, __) {
127 2 : RootVRouterData.of(context).pop(
128 2 : nestedRouteVRoute!.vRouteElementNode.getVRouteElementToPop(),
129 2 : pathParameters: VRouter.of(context).pathParameters,
130 : );
131 :
132 : // We always prevent popping because we handle it in VRouter
133 : return false;
134 : },
135 0 : onSystemPopPage: () async {
136 0 : await RootVRouterData.of(context).systemPop(
137 0 : nestedRouteVRoute!.vRouteElementNode.getVRouteElementToPop(),
138 0 : pathParameters: VRouter.of(context).pathParameters,
139 : );
140 :
141 : // We always prevent popping because we handle it in VRouter
142 : return true;
143 : },
144 : );
145 : },
146 : ),
147 : ),
148 : vPathRequestData: vPathRequestData,
149 2 : pathParameters: nestedRouteVRoute.pathParameters,
150 : vRouteElementNode: newVRouteElements,
151 : ),
152 : ],
153 2 : pathParameters: nestedRouteVRoute.pathParameters,
154 6 : vRouteElements: <VRouteElement>[this] + nestedRouteVRoute.vRouteElements,
155 : );
156 : } else {
157 : // If stackedRouteVRoute is NOT null, create a VRoute by mixing nestedRouteVRoute and stackedRouteVRoute
158 :
159 1 : final allPathParameters = {
160 1 : ...nestedRouteVRoute.pathParameters,
161 1 : ...stackedRouteVRoute.pathParameters,
162 : };
163 1 : final newVRouteElementNode = VRouteElementNode(
164 : this,
165 1 : nestedVRouteElementNode: nestedRouteVRoute.vRouteElementNode,
166 1 : stackedVRouteElementNode: stackedRouteVRoute.vRouteElementNode,
167 : );
168 :
169 1 : return VRoute(
170 : vRouteElementNode: newVRouteElementNode,
171 1 : pages: [
172 1 : buildPage(
173 1 : widget: Builder(
174 1 : builder: (BuildContext context) {
175 1 : return VRouterHelper(
176 1 : pages: nestedRouteVRoute!.pages,
177 1 : navigatorKey: navigatorKey,
178 2 : observers: [heroController],
179 : backButtonDispatcher:
180 3 : ChildBackButtonDispatcher(Router.of(context).backButtonDispatcher!),
181 0 : onPopPage: (_, __) {
182 0 : RootVRouterData.of(context).pop(
183 0 : newVRouteElementNode.getVRouteElementToPop(),
184 0 : pathParameters: VRouter.of(context).pathParameters,
185 : );
186 : return false;
187 : },
188 0 : onSystemPopPage: () async {
189 0 : await RootVRouterData.of(context).systemPop(
190 0 : newVRouteElementNode.getVRouteElementToPop(),
191 0 : pathParameters: VRouter.of(context).pathParameters,
192 : );
193 : return true;
194 : },
195 : );
196 : },
197 : ),
198 : vPathRequestData: vPathRequestData,
199 : pathParameters: allPathParameters,
200 : vRouteElementNode: newVRouteElementNode,
201 : ),
202 1 : ...stackedRouteVRoute.pages,
203 : ],
204 : pathParameters: allPathParameters,
205 2 : vRouteElements: <VRouteElement>[this] +
206 2 : nestedRouteVRoute.vRouteElements +
207 1 : stackedRouteVRoute.vRouteElements,
208 : );
209 : }
210 : }
211 :
212 1 : String? getPathFromName(
213 : String nameToMatch, {
214 : required Map<String, String> pathParameters,
215 : required String? parentPath,
216 : required Map<String, String> remainingPathParameters,
217 : }) {
218 : // A variable to store the new parentPath from the path
219 : late final String? newParentPathFromPath;
220 : late final Map<String, String> newRemainingPathParametersFromPath;
221 :
222 : // A variable to store the new parent path from the aliases
223 1 : final List<String?> newParentPathFromAliases = [];
224 1 : final List<Map<String, String>> newRemainingPathParametersFromAliases = [];
225 :
226 : // Get the new parent path by taking this path into account
227 1 : newParentPathFromPath = getNewParentPath(parentPath,
228 2 : path: path, pathParametersKeys: pathParametersKeys, pathParameters: pathParameters);
229 :
230 1 : newRemainingPathParametersFromPath = Map<String, String>.from(remainingPathParameters)
231 1 : ..removeWhere((key, value) => pathParametersKeys.contains(key));
232 :
233 : // Check if any nested route matches the name using path
234 2 : for (var vRouteElement in nestedRoutes) {
235 1 : String? childPathFromName = vRouteElement.getPathFromName(
236 : nameToMatch,
237 : pathParameters: pathParameters,
238 0 : parentPath: newParentPathFromPath,
239 0 : remainingPathParameters: newRemainingPathParametersFromPath,
240 : );
241 : if (childPathFromName != null) {
242 : return childPathFromName;
243 : }
244 : }
245 :
246 : // Check if any subroute matches the name using path
247 1 : for (var vRouteElement in stackedRoutes) {
248 0 : String? childPathFromName = vRouteElement.getPathFromName(
249 : nameToMatch,
250 : pathParameters: pathParameters,
251 0 : parentPath: newParentPathFromPath,
252 0 : remainingPathParameters: newRemainingPathParametersFromPath,
253 : );
254 : if (childPathFromName != null) {
255 : return childPathFromName;
256 : }
257 : }
258 :
259 4 : for (var i = 0; i < aliases.length; i++) {
260 : // Get the new parent path by taking this alias into account
261 2 : newParentPathFromAliases.add(getNewParentPath(
262 : parentPath,
263 2 : path: aliases[i],
264 2 : pathParametersKeys: aliasesPathParametersKeys[i],
265 : pathParameters: pathParameters,
266 : ));
267 1 : newRemainingPathParametersFromAliases.add(
268 1 : Map<String, String>.from(remainingPathParameters)
269 1 : ..removeWhere((key, value) => pathParametersKeys.contains(key)),
270 : );
271 :
272 : // Check if any nested route matches the name using aliases
273 2 : for (var vRouteElement in nestedRoutes) {
274 1 : String? childPathFromName = vRouteElement.getPathFromName(
275 : nameToMatch,
276 : pathParameters: pathParameters,
277 1 : parentPath: newParentPathFromAliases[i],
278 1 : remainingPathParameters: newRemainingPathParametersFromAliases[i],
279 : );
280 : if (childPathFromName != null) {
281 : return childPathFromName;
282 : }
283 : }
284 :
285 : // Check if any subroute matches the name using aliases
286 1 : for (var vRouteElement in stackedRoutes) {
287 0 : String? childPathFromName = vRouteElement.getPathFromName(
288 : nameToMatch,
289 : pathParameters: pathParameters,
290 0 : parentPath: newParentPathFromAliases[i],
291 0 : remainingPathParameters: newRemainingPathParametersFromAliases[i],
292 : );
293 : if (childPathFromName != null) {
294 : return childPathFromName;
295 : }
296 : }
297 : }
298 :
299 : // If no subroute matches the name, try to match this name
300 2 : if (name == nameToMatch) {
301 : // Note that newParentPath will be null if this path can't be included so the return value
302 : // is the right one
303 1 : if (newParentPathFromPath != null && newRemainingPathParametersFromPath.isEmpty) {
304 0 : return newParentPathFromPath;
305 : }
306 3 : for (var i = 0; i < aliases.length; i++) {
307 1 : if (newParentPathFromAliases[i] != null &&
308 2 : newRemainingPathParametersFromAliases[i].isEmpty) {
309 1 : return newParentPathFromAliases[i];
310 : }
311 : }
312 : }
313 :
314 : // Else we return null
315 : return null;
316 : }
317 :
318 3 : GetPathFromPopResult? getPathFromPop(
319 : VRouteElement elementToPop, {
320 : required Map<String, String> pathParameters,
321 : required String? parentPath,
322 : }) {
323 : // If vRouteElement is this, then this is the element to pop so we return null
324 3 : if (elementToPop == this) {
325 0 : return GetPathFromPopResult(path: parentPath, didPop: true);
326 : }
327 :
328 : // Try to match the path given the path parameters
329 3 : final newParentPathFromPath = getNewParentPath(
330 : parentPath,
331 3 : path: path,
332 3 : pathParametersKeys: pathParametersKeys,
333 : pathParameters: pathParameters,
334 : );
335 :
336 6 : print('newParentPathFromPath: $newParentPathFromPath');
337 :
338 : // If the path matched and produced a non null newParentPath, try to pop from the stackedRoutes or the nestedRoutes
339 : if (newParentPathFromPath != null) {
340 : // Try to pop from the stackedRoutes
341 4 : for (var vRouteElement in stackedRoutes) {
342 1 : final childPopResult = vRouteElement.getPathFromPop(
343 : elementToPop,
344 : pathParameters: pathParameters,
345 : parentPath: newParentPathFromPath,
346 : );
347 : if (childPopResult != null) {
348 2 : return GetPathFromPopResult(path: childPopResult.path, didPop: false);
349 : }
350 : }
351 :
352 : // Try to pop from the nestedRoutes
353 6 : for (var vRouteElement in nestedRoutes) {
354 3 : final childPopResult = vRouteElement.getPathFromPop(
355 : elementToPop,
356 : pathParameters: pathParameters,
357 : parentPath: newParentPathFromPath,
358 : );
359 : if (childPopResult != null) {
360 3 : if (childPopResult.didPop) {
361 : // if the nestedRoute popped, we should pop too
362 3 : return GetPathFromPopResult(path: parentPath, didPop: true);
363 : } else {
364 9 : print('childPopResult.path: ${childPopResult.path}');
365 6 : return GetPathFromPopResult(path: childPopResult.path, didPop: false);
366 : }
367 : }
368 : }
369 : }
370 :
371 : // Try to match the aliases given the path parameters
372 3 : for (var i = 0; i < aliases.length; i++) {
373 1 : final newParentPathFromAlias = getNewParentPath(
374 : parentPath,
375 2 : path: aliases[i],
376 2 : pathParametersKeys: aliasesPathParametersKeys[i],
377 : pathParameters: pathParameters,
378 : );
379 :
380 : // If an alias matched and produced a non null newParentPath, try to pop from the stackedRoutes or the nestedRoutes
381 : if (newParentPathFromAlias != null) {
382 : // Try to pop from the stackedRoutes
383 1 : for (var vRouteElement in stackedRoutes) {
384 0 : final childPopResult = vRouteElement.getPathFromPop(
385 : elementToPop,
386 : pathParameters: pathParameters,
387 : parentPath: newParentPathFromAlias,
388 : );
389 : if (childPopResult != null) {
390 0 : return GetPathFromPopResult(path: childPopResult.path, didPop: false);
391 : }
392 : }
393 :
394 : // Try to pop from the nested routes
395 2 : for (var vRouteElement in nestedRoutes) {
396 1 : final childPopResult = vRouteElement.getPathFromPop(
397 : elementToPop,
398 : pathParameters: pathParameters,
399 : parentPath: newParentPathFromAlias,
400 : );
401 : if (childPopResult != null) {
402 1 : if (childPopResult.didPop) {
403 : // if the nestedRoute popped, we should pop too
404 1 : return GetPathFromPopResult(path: parentPath, didPop: true);
405 : } else {
406 2 : return GetPathFromPopResult(path: childPopResult.path, didPop: false);
407 : }
408 : }
409 : }
410 : }
411 : }
412 :
413 : // If none of the stackedRoutes nor the nestedRoutes popped and this did not pop, return a null result
414 : return null;
415 : }
416 : }
|