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