Line data Source code
1 : part of '../main.dart';
2 :
3 : /// A [VRouteElement] similar to [VNester] but which allows you to specify your own page
4 : /// thanks to [pageBuilder]
5 : class VNesterPageBase extends VRouteElement with VoidVGuard, VoidVPopHandler {
6 : /// A list of [VRouteElement] which widget will be accessible in [widgetBuilder]
7 : final List<VRouteElement> nestedRoutes;
8 :
9 : /// The list of possible routes to stack on top of this [VRouteElement]
10 : final List<VRouteElement> stackedRoutes;
11 :
12 : /// A function which creates the [VRouteElement.widget] associated to this [VRouteElement]
13 : ///
14 : /// [child] will be the [VRouteElement.widget] of the matched [VRouteElement] in
15 : /// [nestedRoutes]
16 : final Widget Function(Widget child) widgetBuilder;
17 :
18 : final LocalKey? key;
19 :
20 : final Page Function(LocalKey key, Widget child) pageBuilder;
21 :
22 4 : VNesterPageBase({
23 : required this.nestedRoutes,
24 : required this.widgetBuilder,
25 : required this.pageBuilder,
26 : this.stackedRoutes = const [],
27 : this.key,
28 : }) {
29 8 : assert(nestedRoutes.isNotEmpty,
30 : 'The nestedRoutes of a VNester should not be null, otherwise it can\'t nest');
31 : }
32 :
33 : /// A key for the navigator
34 : /// It is created automatically
35 3 : late final GlobalKey<NavigatorState> navigatorKey =
36 9 : GlobalKey<NavigatorState>(debugLabel: 'This is the key of VNesterBase with key $key');
37 :
38 : /// A hero controller for the navigator
39 : /// It is created automatically
40 : final HeroController heroController = HeroController();
41 :
42 3 : @override
43 : VRoute? buildRoute(
44 : VPathRequestData vPathRequestData, {
45 : required VPathMatch parentVPathMatch,
46 : }) {
47 : // Set localPath to null since a VNesterPageBase marks a limit between localPaths
48 3 : VPathMatch newVPathMatch = (parentVPathMatch is ValidVPathMatch)
49 3 : ? ValidVPathMatch(
50 3 : remainingPath: parentVPathMatch.remainingPath,
51 3 : pathParameters: parentVPathMatch.pathParameters,
52 : localPath: null,
53 : )
54 2 : : InvalidVPathMatch(localPath: null);
55 :
56 : // Try to find valid VRoute from nestedRoutes
57 : VRoute? nestedRouteVRoute;
58 6 : for (var vRouteElement in nestedRoutes) {
59 3 : nestedRouteVRoute = vRouteElement.buildRoute(
60 : vPathRequestData,
61 : parentVPathMatch: newVPathMatch,
62 : );
63 : if (nestedRouteVRoute != null) {
64 : break;
65 : }
66 : }
67 :
68 : // If no child route match, this is not a match
69 : if (nestedRouteVRoute == null) {
70 : return null;
71 : }
72 :
73 : // Else also try to match stackedRoutes
74 : VRoute? stackedRouteVRoute;
75 5 : for (var vRouteElement in stackedRoutes) {
76 2 : stackedRouteVRoute = vRouteElement.buildRoute(
77 : vPathRequestData,
78 : parentVPathMatch: newVPathMatch,
79 : );
80 : if (stackedRouteVRoute != null) {
81 : break;
82 : }
83 : }
84 :
85 3 : final vRouteElementNode = VRouteElementNode(
86 : this,
87 : localPath: null,
88 3 : nestedVRouteElementNode: nestedRouteVRoute.vRouteElementNode,
89 2 : stackedVRouteElementNode: stackedRouteVRoute?.vRouteElementNode,
90 : );
91 :
92 3 : final pathParameters = {
93 3 : ...nestedRouteVRoute.pathParameters,
94 8 : ...stackedRouteVRoute?.pathParameters ?? {},
95 : };
96 :
97 3 : return VRoute(
98 : vRouteElementNode: vRouteElementNode,
99 3 : pages: [
100 6 : pageBuilder(
101 9 : key ?? ValueKey(parentVPathMatch.localPath),
102 3 : LocalVRouterData(
103 3 : child: NotificationListener<VWidgetGuardMessage>(
104 : // This listen to [VWidgetGuardNotification] which is a notification
105 : // that a [VWidgetGuard] sends when it is created
106 : // When this happens, we store the VWidgetGuard and its context
107 : // This will be used to call its afterUpdate and beforeLeave in particular.
108 1 : onNotification: (VWidgetGuardMessage vWidgetGuardMessage) {
109 1 : VWidgetGuardMessageRoot(
110 1 : vWidgetGuard: vWidgetGuardMessage.vWidgetGuard,
111 1 : localContext: vWidgetGuardMessage.localContext,
112 : associatedVRouteElement: this,
113 2 : ).dispatch(vPathRequestData.rootVRouterContext);
114 :
115 : return true;
116 : },
117 6 : child: widgetBuilder(
118 3 : Builder(
119 3 : builder: (BuildContext context) {
120 3 : return VRouterHelper(
121 6 : pages: nestedRouteVRoute!.pages.isNotEmpty
122 3 : ? nestedRouteVRoute.pages
123 0 : : [
124 0 : MaterialPage(child: Center(child: CircularProgressIndicator())),
125 : ],
126 3 : navigatorKey: navigatorKey,
127 6 : observers: [heroController],
128 : backButtonDispatcher:
129 9 : ChildBackButtonDispatcher(Router.of(context).backButtonDispatcher!),
130 2 : onPopPage: (_, __) {
131 4 : RootVRouterData.of(context).popFromElement(
132 4 : nestedRouteVRoute!.vRouteElementNode.getVRouteElementToPop(),
133 4 : pathParameters: VRouter.of(context).pathParameters,
134 : );
135 :
136 : // We always prevent popping because we handle it in VRouter
137 : return false;
138 : },
139 0 : onSystemPopPage: () async {
140 0 : await RootVRouterData.of(context).systemPopFromElement(
141 0 : nestedRouteVRoute!.vRouteElementNode.getVRouteElementToPop(),
142 0 : pathParameters: VRouter.of(context).pathParameters,
143 : );
144 :
145 : // We always prevent popping because we handle it in VRouter
146 : return true;
147 : },
148 : );
149 : },
150 : ),
151 : ),
152 : ),
153 : vRouteElementNode: vRouteElementNode,
154 3 : url: vPathRequestData.url,
155 3 : previousUrl: vPathRequestData.previousUrl,
156 3 : historyState: vPathRequestData.historyState,
157 : pathParameters: pathParameters,
158 3 : queryParameters: vPathRequestData.queryParameters,
159 3 : context: vPathRequestData.rootVRouterContext,
160 : ),
161 : ),
162 8 : ...stackedRouteVRoute?.pages ?? [],
163 : ],
164 3 : pathParameters: nestedRouteVRoute.pathParameters,
165 6 : vRouteElements: <VRouteElement>[this] +
166 6 : nestedRouteVRoute.vRouteElements +
167 5 : (stackedRouteVRoute?.vRouteElements ?? []),
168 : );
169 : }
170 :
171 2 : GetPathFromNameResult getPathFromName(
172 : String nameToMatch, {
173 : required Map<String, String> pathParameters,
174 : required GetNewParentPathResult parentPathResult,
175 : required Map<String, String> remainingPathParameters,
176 : }) {
177 2 : final childNameResults = <GetPathFromNameResult>[];
178 :
179 : // Check if any nestedRoute matches the name
180 4 : for (var vRouteElement in nestedRoutes) {
181 2 : childNameResults.add(
182 2 : vRouteElement.getPathFromName(
183 : nameToMatch,
184 : pathParameters: pathParameters,
185 : parentPathResult: parentPathResult,
186 : remainingPathParameters: remainingPathParameters,
187 : ),
188 : );
189 4 : if (childNameResults.last is ValidNameResult) {
190 1 : return childNameResults.last;
191 : }
192 : }
193 :
194 : // Check if any subroute matches the name
195 3 : for (var vRouteElement in stackedRoutes) {
196 1 : childNameResults.add(
197 1 : vRouteElement.getPathFromName(
198 : nameToMatch,
199 : pathParameters: pathParameters,
200 : parentPathResult: parentPathResult,
201 : remainingPathParameters: remainingPathParameters,
202 : ),
203 : );
204 2 : if (childNameResults.last is ValidNameResult) {
205 1 : return childNameResults.last;
206 : }
207 : }
208 :
209 : // If we don't have any valid result
210 :
211 : // If some stackedRoute returned PathParamsPopError, aggregate them
212 2 : final pathParamsNameErrors = PathParamsErrorsNameResult(
213 : name: nameToMatch,
214 2 : values: childNameResults.fold<List<PathParamsError>>(
215 2 : <PathParamsError>[],
216 2 : (previousValue, element) {
217 2 : return previousValue +
218 4 : ((element is PathParamsErrorsNameResult) ? element.values : []);
219 : },
220 : ),
221 : );
222 :
223 : // If there was any PathParamsPopError, we have some pathParamsPopErrors.values
224 : // and therefore should return this
225 4 : if (pathParamsNameErrors.values.isNotEmpty) {
226 : return pathParamsNameErrors;
227 : }
228 :
229 : // Else try to find a NullPathError
230 : if (childNameResults
231 8 : .indexWhere((childNameResult) => childNameResult is NullPathErrorNameResult) !=
232 2 : -1) {
233 0 : return NullPathErrorNameResult(name: nameToMatch);
234 : }
235 :
236 : // Else return a NotFoundError
237 2 : return NotFoundErrorNameResult(name: nameToMatch);
238 : }
239 :
240 3 : GetPathFromPopResult getPathFromPop(
241 : VRouteElement elementToPop, {
242 : required Map<String, String> pathParameters,
243 : required GetNewParentPathResult parentPathResult,
244 : }) {
245 : // If vRouteElement is this, then this is the element to pop so we return null
246 3 : if (elementToPop == this) {
247 0 : if (parentPathResult is ValidParentPathResult) {
248 0 : return ValidPopResult(
249 0 : path: parentPathResult.path,
250 : didPop: true,
251 0 : poppedVRouteElements: [this],
252 : );
253 : } else {
254 0 : assert(parentPathResult is PathParamsErrorNewParentPath);
255 0 : return PathParamsPopErrors(
256 0 : values: [
257 0 : MissingPathParamsError(
258 0 : pathParams: pathParameters.keys.toList(),
259 0 : missingPathParams: (parentPathResult as PathParamsErrorNewParentPath).pathParameters,
260 : ),
261 : ],
262 : );
263 : }
264 : }
265 :
266 3 : final popErrorResults = <GetPathFromPopResult>[];
267 :
268 : // Try to pop from the nestedRoutes
269 6 : for (var vRouteElement in nestedRoutes) {
270 3 : GetPathFromPopResult childPopResult = vRouteElement.getPathFromPop(
271 : elementToPop,
272 : pathParameters: pathParameters,
273 : parentPathResult: parentPathResult,
274 : );
275 3 : if (childPopResult is ValidPopResult) {
276 3 : if (childPopResult.didPop) {
277 : // if the nestedRoute popped, we should pop too
278 3 : if (parentPathResult is ValidParentPathResult) {
279 3 : return ValidPopResult(
280 3 : path: parentPathResult.path,
281 : didPop: true,
282 : poppedVRouteElements:
283 9 : <VRouteElement>[this] + childPopResult.poppedVRouteElements,
284 : );
285 : } else {
286 0 : assert(parentPathResult is PathParamsErrorNewParentPath);
287 0 : popErrorResults.add(
288 0 : PathParamsPopErrors(
289 0 : values: [
290 0 : MissingPathParamsError(
291 0 : pathParams: pathParameters.keys.toList(),
292 : missingPathParams:
293 0 : (parentPathResult as PathParamsErrorNewParentPath).pathParameters,
294 : ),
295 : ],
296 : ),
297 : );
298 : }
299 : } else {
300 3 : return ValidPopResult(
301 3 : path: childPopResult.path,
302 : didPop: false,
303 3 : poppedVRouteElements: childPopResult.poppedVRouteElements,
304 : );
305 : }
306 : } else {
307 2 : popErrorResults.add(childPopResult);
308 : }
309 : }
310 :
311 : // Try to pop from the subRoutes
312 4 : for (var vRouteElement in stackedRoutes) {
313 2 : GetPathFromPopResult childPopResult = vRouteElement.getPathFromPop(
314 : elementToPop,
315 : pathParameters: pathParameters,
316 : parentPathResult: parentPathResult,
317 : );
318 2 : if (childPopResult is ValidPopResult) {
319 2 : return ValidPopResult(
320 2 : path: childPopResult.path,
321 : didPop: false,
322 2 : poppedVRouteElements: childPopResult.poppedVRouteElements,
323 : );
324 : } else {
325 0 : popErrorResults.add(childPopResult);
326 : }
327 : }
328 :
329 : // If we don't have any valid result
330 :
331 : // If some stackedRoute returned PathParamsPopError, aggregate them
332 2 : final pathParamsPopErrors = PathParamsPopErrors(
333 2 : values: popErrorResults.fold<List<MissingPathParamsError>>(
334 2 : <MissingPathParamsError>[],
335 2 : (previousValue, element) {
336 6 : return previousValue + ((element is PathParamsPopErrors) ? element.values : []);
337 : },
338 : ),
339 : );
340 :
341 : // If there was any PathParamsPopError, we have some pathParamsPopErrors.values
342 : // and therefore should return this
343 4 : if (pathParamsPopErrors.values.isNotEmpty) {
344 : return pathParamsPopErrors;
345 : }
346 :
347 : // If none of the stackedRoutes popped, this did not pop, and there was no path parameters issue, return not found
348 : // This should never happen
349 0 : return ErrorNotFoundGetPathFromPopResult();
350 : }
351 : }
|