Line data Source code
1 : part of '../main.dart';
2 :
3 : /// If the [VRouteElement] does have a page to display, it should extend this class
4 : ///
5 : /// What is does is:
6 : /// - Requiring attribute [widget]
7 : /// - implementing [buildRoute] methods
8 : @immutable
9 : mixin VRouteElementWithPage on VRouteElement {
10 : List<VRouteElement> get stackedRoutes;
11 :
12 : /// The widget which will be displayed for the given [path]
13 : Page Function(LocalKey key, Widget child, String? name) get pageBuilder;
14 :
15 : /// The widget which will be put inside the page
16 : Widget get widget;
17 :
18 : /// The key associated to the page
19 : LocalKey? get key;
20 :
21 : /// A name for the route which will allow you to easily navigate to it
22 : /// using [VRouter.of(context).pushNamed]
23 : ///
24 : /// Note that [name] should be unique w.r.t every [VRouteElement]
25 : String? get name;
26 :
27 : /// This is basically the same as [VPath.buildRoute] except that
28 : /// we add the page of this [VRouteElement] as a page to [VRoute.pages]
29 12 : @override
30 : VRoute? buildRoute(
31 : VPathRequestData vPathRequestData, {
32 : required VPathMatch parentVPathMatch,
33 : }) {
34 : // Set localPath to null since a VRouteElementWithPage marks a limit between localPaths
35 12 : VPathMatch newVPathMatch = (parentVPathMatch is ValidVPathMatch)
36 12 : ? ValidVPathMatch(
37 12 : remainingPath: parentVPathMatch.remainingPath,
38 12 : pathParameters: parentVPathMatch.pathParameters,
39 : localPath: null,
40 : )
41 12 : : InvalidVPathMatch(localPath: null);
42 :
43 : VRoute? childVRoute;
44 24 : for (var vRouteElement in stackedRoutes) {
45 12 : childVRoute = vRouteElement.buildRoute(
46 : vPathRequestData,
47 : parentVPathMatch: newVPathMatch,
48 : );
49 : if (childVRoute != null) {
50 : break;
51 : }
52 : }
53 :
54 12 : final bool validParentVRoute = !(parentVPathMatch is InvalidVPathMatch) &&
55 24 : (parentVPathMatch as ValidVPathMatch).remainingPath.isEmpty;
56 : if (childVRoute == null && !validParentVRoute) {
57 : return null;
58 : }
59 :
60 12 : final VRouteElementNode vRouteElementNode = VRouteElementNode(
61 : this,
62 : localPath: null,
63 12 : stackedVRouteElementNode: childVRoute?.vRouteElementNode,
64 : );
65 :
66 12 : Map<String, String> pathParameters = childVRoute?.pathParameters ??
67 12 : (parentVPathMatch as ValidVPathMatch).pathParameters;
68 :
69 12 : return VRoute(
70 : vRouteElementNode: vRouteElementNode,
71 12 : pages: [
72 24 : pageBuilder(
73 36 : key ?? ValueKey(parentVPathMatch.localPath),
74 12 : LocalVRouterData(
75 12 : child: NotificationListener<VWidgetGuardMessage>(
76 : // This listen to [VWidgetGuardNotification] which is a notification
77 : // that a [VWidgetGuard] sends when it is created
78 : // When this happens, we store the VWidgetGuard and its context
79 : // This will be used to call its afterUpdate and beforeLeave in particular.
80 1 : onNotification: (VWidgetGuardMessage vWidgetGuardMessage) {
81 1 : VWidgetGuardMessageRoot(
82 1 : vWidgetGuard: vWidgetGuardMessage.vWidgetGuard,
83 1 : localContext: vWidgetGuardMessage.localContext,
84 : associatedVRouteElement: this,
85 2 : ).dispatch(vPathRequestData.rootVRouterContext);
86 :
87 : return true;
88 : },
89 12 : child: widget,
90 : ),
91 : vRouteElementNode: vRouteElementNode,
92 12 : url: vPathRequestData.url,
93 12 : previousUrl: vPathRequestData.previousUrl,
94 12 : historyState: vPathRequestData.historyState,
95 : pathParameters: pathParameters,
96 12 : queryParameters: vPathRequestData.queryParameters,
97 12 : context: vPathRequestData.rootVRouterContext,
98 : ),
99 24 : name ?? parentVPathMatch.localPath,
100 : ),
101 36 : ...childVRoute?.pages ?? []
102 : ],
103 : pathParameters: pathParameters,
104 : vRouteElements:
105 48 : <VRouteElement>[this] + (childVRoute?.vRouteElements ?? []),
106 : );
107 : }
108 :
109 : /// Tries to find a path from a name
110 : ///
111 : /// This first asks its stackedRoutes if they have a match
112 : /// Else is tries to see if this [VRouteElement] matches the name
113 : /// Else return null
114 : ///
115 : /// Note that not only the name must match but the path parameters must be able to form a
116 : /// valid path afterward too
117 7 : GetPathFromNameResult getPathFromName(
118 : String nameToMatch, {
119 : required Map<String, String> pathParameters,
120 : required GetNewParentPathResult parentPathResult,
121 : required Map<String, String> remainingPathParameters,
122 : }) {
123 7 : final List<GetPathFromNameResult> childNameResults = [];
124 :
125 : // Check if any subroute matches the name
126 13 : for (var vRouteElement in stackedRoutes) {
127 6 : childNameResults.add(
128 6 : vRouteElement.getPathFromName(
129 : nameToMatch,
130 : pathParameters: pathParameters,
131 : parentPathResult: parentPathResult,
132 : remainingPathParameters: remainingPathParameters,
133 : ),
134 : );
135 12 : if (childNameResults.last is ValidNameResult) {
136 6 : return childNameResults.last;
137 : }
138 : }
139 :
140 : // If no subroute matches the name, try to match this name
141 14 : if (name == nameToMatch) {
142 : // If path or any alias is valid considering the given path parameters, return this
143 6 : if (parentPathResult is ValidParentPathResult) {
144 6 : if (parentPathResult.path == null) {
145 : // If this path is null, we add a NullPathErrorNameResult
146 2 : childNameResults.add(NullPathErrorNameResult(name: nameToMatch));
147 : } else {
148 6 : if (remainingPathParameters.isNotEmpty) {
149 : // If there are path parameters remaining, wee add a PathParamsErrorsNameResult
150 2 : childNameResults.add(
151 2 : PathParamsErrorsNameResult(
152 : name: nameToMatch,
153 2 : values: [
154 2 : OverlyPathParamsError(
155 4 : pathParams: pathParameters.keys.toList(),
156 : expectedPathParams:
157 6 : parentPathResult.pathParameters.keys.toList(),
158 : ),
159 : ],
160 : ),
161 : );
162 : } else {
163 : // Else the result is valid
164 10 : return ValidNameResult(path: parentPathResult.path!);
165 : }
166 : }
167 : } else {
168 2 : assert(parentPathResult is PathParamsErrorNewParentPath);
169 2 : childNameResults.add(
170 2 : PathParamsErrorsNameResult(
171 : name: nameToMatch,
172 2 : values: [
173 2 : MissingPathParamsError(
174 4 : pathParams: pathParameters.keys.toList(),
175 : missingPathParams:
176 : (parentPathResult as PathParamsErrorNewParentPath)
177 2 : .pathParameters,
178 : ),
179 : ],
180 : ),
181 : );
182 : }
183 : }
184 :
185 : // If we don't have any valid result
186 :
187 : // If some stackedRoute returned PathParamsPopError, aggregate them
188 5 : final pathParamsNameErrors = PathParamsErrorsNameResult(
189 : name: nameToMatch,
190 5 : values: childNameResults.fold<List<PathParamsError>>(
191 5 : <PathParamsError>[],
192 2 : (previousValue, element) {
193 2 : return previousValue +
194 5 : ((element is PathParamsErrorsNameResult) ? element.values : []);
195 : },
196 : ),
197 : );
198 :
199 : // If there was any PathParamsPopError, we have some pathParamsPopErrors.values
200 : // and therefore should return this
201 10 : if (pathParamsNameErrors.values.isNotEmpty) {
202 : return pathParamsNameErrors;
203 : }
204 :
205 : // Else try to find a NullPathError
206 5 : if (childNameResults.indexWhere(
207 7 : (childNameResult) => childNameResult is NullPathErrorNameResult) !=
208 5 : -1) {
209 1 : return NullPathErrorNameResult(name: nameToMatch);
210 : }
211 :
212 : // Else return a NotFoundError
213 5 : return NotFoundErrorNameResult(name: nameToMatch);
214 : }
215 : }
|