Line data Source code
1 : part of '../main.dart';
2 :
3 : /// [VRouteElement] is the base class for any object used in routes, stackedRoutes
4 : /// or nestedRoutes
5 : @immutable
6 : abstract class VRouteElement {
7 : /// [buildRoute] must return [VRoute] if it constitute (which its subroutes or not) a valid
8 : /// route given the input parameters
9 : /// [VRoute] should describe this valid route
10 : ///
11 : ///
12 : /// [vPathRequestData] contains all the information about the original request coming
13 : /// from [VRouter]
14 : /// It should not be changed and should be given as-is to its subroutes
15 : ///
16 : /// [parentRemainingPath] is the part on which to base any local path
17 : /// WARNING: [parentRemainingPath] is null if the parent did not match the path
18 : /// in which case only absolute path should be tested.
19 : ///
20 : /// [parentPathParameters] are the path parameters of every [VRouteElement] above this
21 : /// one in the route
22 : ///
23 : /// [buildRoute] basically just checks for a match in stackedRoutes and if any
24 : /// adds this [VRouteElement] to the [VRoute]
25 : ///
26 : /// For more info on buildRoute, see [VRouteElement.buildRoute]
27 : VRoute? buildRoute(
28 : VPathRequestData vPathRequestData, {
29 : required VPathMatch parentVPathMatch,
30 : });
31 :
32 : /// This function takes a name and tries to find the path corresponding to
33 : /// the route matching this name
34 : ///
35 : /// The deeper nested the route the better
36 : /// The given path parameters have to include at least every path parameters of the final path
37 : GetPathFromNameResult getPathFromName(
38 : String nameToMatch, {
39 : required Map<String, String> pathParameters,
40 : required GetNewParentPathResult parentPathResult,
41 : required Map<String, String> remainingPathParameters,
42 : });
43 :
44 : /// [GetPathFromPopResult.didPop] is true if this [VRouteElement] popped
45 : /// [GetPathFromPopResult.extendedPath] is null if this path can't be the right one according to
46 : /// the path parameters
47 : /// [GetPathFromPopResult] is null when this [VRouteElement] does not pop AND none of
48 : /// its stackedRoutes popped
49 : GetPathFromPopResult getPathFromPop(
50 : VRouteElement elementToPop, {
51 : required Map<String, String> pathParameters,
52 : required GetNewParentPathResult parentPathResult,
53 : });
54 :
55 : /// This is called before the url is updated if this [VRouteElement] was NOT in the
56 : /// previous route but is in the new route
57 : ///
58 : /// Use [vRedirector] if you want to redirect or stop the navigation.
59 : /// DO NOT use VRouter methods to redirect.
60 : /// [vRedirector] also has information about the route you leave and the route you go to
61 : ///
62 : /// Note that you should consider the navigation cycle to
63 : /// handle this precisely, see [https://vrouter.dev/guide/Advanced/Navigation%20Control/The%20Navigation%20Cycle]
64 : ///
65 : /// Also see:
66 : /// * [VRouter.beforeEnter] for router level beforeEnter
67 : /// * [VRedirector] to known how to redirect and have access to route information
68 : Future<void> beforeEnter(VRedirector vRedirector);
69 :
70 : /// This is called before the url is updated if this [VRouteElement] was in the previous
71 : /// route and is in the new route
72 : ///
73 : /// Use [vRedirector] if you want to redirect or stop the navigation.
74 : /// DO NOT use VRouter methods to redirect.
75 : /// [vRedirector] also has information about the route you leave and the route you go to
76 : ///
77 : /// Note that you should consider the navigation cycle to
78 : /// handle this precisely, see [https://vrouter.dev/guide/Advanced/Navigation%20Control/The%20Navigation%20Cycle]
79 : ///
80 : /// Also see:
81 : /// * [VWidgetGuard.beforeUpdate] for widget level beforeUpdate
82 : /// * [VRedirector] to known how to redirect and have access to route information
83 : Future<void> beforeUpdate(VRedirector vRedirector);
84 :
85 : /// Called when a url changes, before the url is updated
86 : /// Use [vRedirector] if you want to redirect or stop the navigation.
87 : /// DO NOT use VRouter methods to redirect.
88 : /// [vRedirector] also has information about the route you leave and the route you go to
89 : ///
90 : /// [saveHistoryState] can be used to save a history state before leaving
91 : /// This history state will be restored if the user uses the back button
92 : /// You will find the saved history state in the [VRouteElementData] using
93 : /// [VRouter.of(context).historyState]
94 : ///
95 : /// Note that you should consider the navigation cycle to
96 : /// handle this precisely, see [https://vrouter.dev/guide/Advanced/Navigation%20Control/The%20Navigation%20Cycle]
97 : ///
98 : /// Also see:
99 : /// * [VRouteElement.beforeLeave] for route level beforeLeave
100 : /// * [VWidgetGuard.beforeLeave] for widget level beforeLeave
101 : /// * [VRedirector] to known how to redirect and have access to route information
102 : Future<void> beforeLeave(
103 : VRedirector vRedirector,
104 : void Function(Map<String, String> state) saveHistoryState,
105 : );
106 :
107 : /// This is called after the url and the historyState are updated and this [VRouteElement]
108 : /// was NOT in the previous route and is in the new route
109 : /// You can't prevent the navigation anymore
110 : /// You can get the new route parameters, and queryParameters
111 : ///
112 : /// Note that you should consider the navigation cycle to
113 : /// handle this precisely, see [https://vrouter.dev/guide/Advanced/Navigation%20Control/The%20Navigation%20Cycle]
114 : ///
115 : /// Also see:
116 : /// * [VRouter.afterEnter] for router level afterEnter
117 : /// * [VWidgetGuard.afterEnter] for widget level afterEnter
118 : void afterEnter(BuildContext context, String? from, String to);
119 :
120 : /// This is called after the url and the historyState are updated and this [VRouteElement]
121 : /// was in the previous route and is in the new route
122 : /// You can't prevent the navigation anymore
123 : /// You can get the new route parameters, and queryParameters
124 : ///
125 : /// Note that you should consider the navigation cycle to
126 : /// handle this precisely, see [https://vrouter.dev/guide/Advanced/Navigation%20Control/The%20Navigation%20Cycle]
127 : ///
128 : /// Also see:
129 : /// * [VWidgetGuard.afterUpdate] for widget level afterUpdate
130 : void afterUpdate(BuildContext context, String? from, String to);
131 :
132 : /// Called when a pop event occurs
133 : /// A pop event can be called programmatically (with [VRouter.of(context).pop()])
134 : /// or by other widgets such as the appBar back button
135 : ///
136 : /// Use [vRedirector] if you want to redirect or stop the navigation.
137 : /// DO NOT use VRouter methods to redirect.
138 : /// [vRedirector] also has information about the route you leave and the route you go to
139 : ///
140 : /// The route you go to is calculated based on [VRouterState._defaultPop]
141 : ///
142 : /// Note that you should consider the pop cycle to
143 : /// handle this precisely, see [https://vrouter.dev/guide/Advanced/Pop%20Events/onPop]
144 : ///
145 : /// Also see:
146 : /// * [VRouter.onPop] for router level onPop
147 : /// * [VWidgetGuard.onPop] for widget level onPop
148 : /// * [VRedirector] to known how to redirect and have access to route information
149 : Future<void> onPop(VRedirector vRedirector);
150 :
151 : /// Called when a system pop event occurs.
152 : /// This happens on android when the system back button is pressed.
153 : ///
154 : /// Use [vRedirector] if you want to redirect or stop the navigation.
155 : /// DO NOT use VRouter methods to redirect.
156 : /// [vRedirector] also has information about the route you leave and the route you go to
157 : ///
158 : /// The route you go to is calculated based on [VRouterState._defaultPop]
159 : ///
160 : /// Note that you should consider the systemPop cycle to
161 : /// handle this precisely, see [https://vrouter.dev/guide/Advanced/Pop%20Events/onSystemPop]
162 : ///
163 : /// Also see:
164 : /// * [VRouter.onSystemPop] for route level onSystemPop
165 : /// * [VWidgetGuard.onSystemPop] for widget level onSystemPop
166 : /// * [VRedirector] to known how to redirect and have access to route information
167 : Future<void> onSystemPop(VRedirector vRedirector);
168 : }
169 :
170 : /// Return type of [VRouteElement.getPathFromPop]
171 : abstract class GetPathFromPopResult {}
172 :
173 : class FoundPopResult extends GetPathFromPopResult {
174 : /// [extendedPath] should be deducted from the parent path, [VRouteElement.path] and the path parameters,
175 : /// Note the it should be null if the path can not be deduced from the said parameters
176 : final String? path;
177 :
178 : /// [didPop] should be true if this [VRouteElement] is to be popped
179 : final bool didPop;
180 :
181 : /// List of every popping [VRouteElement]
182 : final List<VRouteElement> poppedVRouteElements;
183 :
184 9 : FoundPopResult({
185 : required this.path,
186 : required this.didPop,
187 : required this.poppedVRouteElements,
188 : });
189 : }
190 :
191 : abstract class ErrorGetPathFromPopResult extends GetPathFromPopResult implements Exception {}
192 :
193 : class ErrorNotFoundGetPathFromPopResult extends ErrorGetPathFromPopResult {
194 0 : @override
195 : String toString() =>
196 : 'The VRouteElement to pop was not found. Please open an issue, this should never happen.';
197 : }
198 :
199 : class PathParamsPopErrors extends ErrorGetPathFromPopResult {
200 : final List<MissingPathParamsError> values;
201 :
202 4 : PathParamsPopErrors({
203 : required this.values,
204 : });
205 :
206 1 : @override
207 : String toString() =>
208 : 'Could not pop because some path parameters where missing. \n'
209 1 : 'Here are the possible path parameters that were expected and the missing ones:\n' +
210 1 : [
211 1 : for (var value in values)
212 2 : ' - Path parameters: ${value.pathParams}, missing ones: ${value.missingPathParams}'
213 2 : ].join('\n');
214 : }
215 :
216 : /// Return type of [VRouteElement.getPathFromName]
217 : abstract class GetPathFromNameResult {}
218 :
219 : class ValidNameResult extends GetPathFromNameResult {
220 : /// [extendedPath] should be deducted from the parent path, [VRouteElement.path] and the path parameters,
221 : /// Note the it should be null if the path can not be deduced from the said parameters
222 : final String path;
223 :
224 6 : ValidNameResult({required this.path});
225 : }
226 :
227 : abstract class ErrorGetPathFromNameResult extends GetPathFromNameResult implements Error {
228 : String get error;
229 :
230 1 : @override
231 1 : String toString() => error;
232 :
233 0 : @override
234 0 : StackTrace? get stackTrace => StackTrace.current;
235 : }
236 :
237 : class NotFoundErrorNameResult extends ErrorGetPathFromNameResult {
238 : final String name;
239 :
240 5 : NotFoundErrorNameResult({required this.name});
241 :
242 3 : String get error => 'Could not find the VRouteElement named $name.';
243 : }
244 :
245 : class NullPathErrorNameResult extends ErrorGetPathFromNameResult {
246 : final String name;
247 :
248 1 : NullPathErrorNameResult({required this.name});
249 :
250 2 : String get error =>
251 1 : 'The VRouteElement named $name as a null path but no parent VRouteElement with a path.\n'
252 : 'No valid path can therefore be formed.';
253 : }
254 :
255 : abstract class PathParamsError implements Error {
256 : List<String> get pathParams;
257 :
258 : String get error;
259 :
260 0 : @override
261 0 : String toString() => error;
262 :
263 0 : @override
264 0 : StackTrace? get stackTrace => StackTrace.current;
265 : }
266 :
267 : class MissingPathParamsError extends PathParamsError {
268 : final List<String> missingPathParams;
269 : final List<String> pathParams;
270 :
271 5 : MissingPathParamsError({required this.pathParams, required this.missingPathParams});
272 :
273 4 : String get error => 'Path parameters given: $pathParams, missing: $missingPathParams';
274 : }
275 :
276 : class OverlyPathParamsError extends PathParamsError {
277 : final List<String> expectedPathParams;
278 : final List<String> pathParams;
279 :
280 2 : OverlyPathParamsError({required this.pathParams, required this.expectedPathParams});
281 :
282 4 : String get error => 'Path parameters given: $pathParams, expected: $expectedPathParams';
283 : }
284 :
285 : class PathParamsErrorsNameResult extends ErrorGetPathFromNameResult {
286 : final List<PathParamsError> values;
287 : final String name;
288 :
289 5 : PathParamsErrorsNameResult({required this.name, required this.values});
290 :
291 1 : @override
292 1 : String get error =>
293 1 : 'Could not find value route for name $name because of path parameters. \n'
294 1 : 'Here are the possible path parameters that were expected compared to what you gave:\n' +
295 5 : [for (var value in values) ' - ${value.error}'].join('\n');
296 : }
297 :
298 : /// Hold every information of the current route we are building
299 : /// This is used is [VRouteElement.buildRoute] and should be passed down to the next
300 : /// [VRouteElement.buildRoute] without modification.
301 : ///
302 : /// The is used for two purposes:
303 : /// 1. Giving the needed information for [VRouteElement.buildRoute] to decide how/if it should
304 : /// build its route
305 : /// 2. Holds information that are used to populate the [LocalVRouterData] attached to every
306 : /// _ [VRouteElement]
307 : class VPathRequestData {
308 : /// The previous url (which is actually the current one since when [VPathRequestData] is passed
309 : /// the url did not yet change from [VRouter] point of view)
310 : final String? previousUrl;
311 :
312 : /// The new uri. This is what should be used to determine the validity of the [VRouteElement]
313 : final Uri uri;
314 :
315 : /// The new history state, used to populate the [LocalVRouterData]
316 : final Map<String, String> historyState;
317 :
318 : /// A [BuildContext] with which we can access [RootVRouterData]
319 : final BuildContext rootVRouterContext;
320 :
321 12 : VPathRequestData({
322 : required this.previousUrl,
323 : required this.uri,
324 : required this.historyState,
325 : required this.rootVRouterContext,
326 : });
327 :
328 : /// The path contained in the uri
329 36 : String get path => uri.path;
330 :
331 : /// The query parameters contained in the uri
332 36 : Map<String, String> get queryParameters => uri.queryParameters;
333 :
334 : /// The url corresponding to the uri
335 36 : String get url => uri.toString();
336 : }
337 :
338 : /// [VRouteElementNode] is used to represent the current route configuration as a tree
339 : class VRouteElementNode {
340 : /// The [VRouteElementNode] containing the [VRouteElement] which is the current nested route
341 : /// to be valid, if any
342 : ///
343 : /// The is used be all types of [VNestedPage]
344 : final VRouteElementNode? nestedVRouteElementNode;
345 :
346 : /// The [VRouteElementNode] containing the [VRouteElement] which is the current stacked routes
347 : /// to be valid, if any
348 : final VRouteElementNode? stackedVRouteElementNode;
349 :
350 : /// The [VRouteElement] attached to this node
351 : final VRouteElement vRouteElement;
352 :
353 : /// The path of the [VRouteElement] attached to this node
354 : /// If the path has path parameters, they should be replaced
355 : final String? localPath;
356 :
357 12 : VRouteElementNode(
358 : this.vRouteElement, {
359 : required this.localPath,
360 : this.nestedVRouteElementNode,
361 : this.stackedVRouteElementNode,
362 : });
363 :
364 : /// Finding the element to pop for a [VRouteElementNode] means finding which one is at the
365 : /// end of the chain of stackedVRouteElementNode (if none then this should be popped)
366 8 : VRouteElement getVRouteElementToPop() {
367 8 : if (stackedVRouteElementNode != null) {
368 12 : return stackedVRouteElementNode!.getVRouteElementToPop();
369 : }
370 8 : return vRouteElement;
371 : }
372 :
373 : /// Get the [VRouteElementNode] associated to the given [VRouteElement]
374 : /// returns null if the [VRouteElement] is not his nor in the stackedRoutes or the subroutes
375 7 : VRouteElementNode? getVRouteElementNodeFromVRouteElement(VRouteElement vRouteElement) {
376 14 : if (vRouteElement == this.vRouteElement) return this;
377 7 : if (stackedVRouteElementNode != null) {
378 : final vRouteElementNode =
379 14 : stackedVRouteElementNode!.getVRouteElementNodeFromVRouteElement(vRouteElement);
380 : if (vRouteElementNode != null) return vRouteElementNode;
381 : }
382 2 : if (nestedVRouteElementNode != null) {
383 : final vRouteElementNode =
384 4 : nestedVRouteElementNode!.getVRouteElementNodeFromVRouteElement(vRouteElement);
385 : if (vRouteElementNode != null) return vRouteElementNode;
386 : }
387 : return null;
388 : }
389 :
390 : /// Get a flatten list of the [VRouteElement] from this + all those contained in
391 : /// stackedRoutes and subRoutes.
392 7 : List<VRouteElement> getVRouteElements() {
393 21 : return [vRouteElement] +
394 21 : (stackedVRouteElementNode?.getVRouteElements() ?? []) +
395 14 : (nestedVRouteElementNode?.getVRouteElements() ?? []);
396 : }
397 :
398 : /// This function will search this node and the nested and sub nodes to try to find the node
399 : /// that hosts [vRouteElement]
400 1 : VRouteElementNode? getChildVRouteElementNode({
401 : required VRouteElement vRouteElement,
402 : }) {
403 : // If this VRouteElementNode contains the given VRouteElement, return this
404 2 : if (vRouteElement == this.vRouteElement) {
405 : return this;
406 : }
407 :
408 : // Search if the VRouteElementNode containing the VRouteElement is in the nestedVRouteElementNode
409 1 : if (nestedVRouteElementNode != null) {
410 : VRouteElementNode? vRouteElementNode =
411 0 : nestedVRouteElementNode!.getChildVRouteElementNode(vRouteElement: vRouteElement);
412 : if (vRouteElementNode != null) {
413 : return vRouteElementNode;
414 : }
415 : }
416 :
417 : // Search if the VRouteElementNode containing the VRouteElement is in the stackedVRouteElementNode
418 1 : if (stackedVRouteElementNode != null) {
419 : VRouteElementNode? vRouteElementNode =
420 2 : stackedVRouteElementNode!.getChildVRouteElementNode(vRouteElement: vRouteElement);
421 : if (vRouteElementNode != null) {
422 : return vRouteElementNode;
423 : }
424 : }
425 :
426 : // If the VRouteElement was not find anywhere, return null
427 : return null;
428 : }
429 : }
430 :
431 : /// Part of [VRouteElement.buildRoute] that must be passed down but can be modified
432 : abstract class VPathMatch {
433 : /// The local path is the one of the current VRouteElement
434 : /// If the path has path parameters, those should be replaced
435 : String? get localPath;
436 : }
437 :
438 : class ValidVPathMatch extends VPathMatch {
439 : /// The remaining of the path after having remove the part of the path that this
440 : /// [VPath] has matched
441 : final String remainingPath;
442 :
443 : /// The path parameters of the valid [VRoute] which are
444 : /// - Empty if no valid [VRoute] has been found
445 : /// - This [VPath.pathParameters] if [VRouteElement.extendedPath] is absolute
446 : /// - This [VPath.pathParameters] and the parent pathParameters if [VRouteElement.extendedPath] is relative
447 : final Map<String, String> pathParameters;
448 :
449 : /// The local path is the one of the current VRouteElement
450 : /// If the path has path parameters, those should be replaced
451 : final String? localPath;
452 :
453 12 : ValidVPathMatch({
454 : required this.remainingPath,
455 : required this.pathParameters,
456 : required this.localPath,
457 : });
458 : }
459 :
460 : class InvalidVPathMatch extends VPathMatch implements Error {
461 : /// If there is no pathMatch but a VRouteElement needs a ValueKey
462 : /// use a constant path (which the user is likely to pop on)
463 : final String? localPath;
464 :
465 12 : InvalidVPathMatch({required this.localPath});
466 :
467 0 : @override
468 0 : StackTrace? get stackTrace => StackTrace.current;
469 : }
|