Line data Source code
1 : import 'package:beamer/beamer.dart';
2 : import 'package:flutter/material.dart';
3 :
4 : abstract class Utils {
5 : /// Traverses [beamLocations] and returns the one whose one of
6 : /// `pathBlueprints` contains the [uri], ignoring concrete path parameters.
7 : ///
8 : /// Upon finding such [BeamLocation], configures it with
9 : /// `pathParameters` and `queryParameters` from [uri].
10 : ///
11 : /// If [beamLocations] don't contain a match, [NotFound] will be returned
12 : /// configured with [uri].
13 3 : static BeamLocation chooseBeamLocation(
14 : Uri uri,
15 : List<BeamLocation> beamLocations, {
16 : Map<String, dynamic> data = const <String, dynamic>{},
17 : }) {
18 6 : for (final beamLocation in beamLocations) {
19 3 : if (canBeamLocationHandleUri(beamLocation, uri)) {
20 : return beamLocation
21 6 : ..state = beamLocation.createState(
22 3 : RouteInformation(
23 3 : location: uri.toString(),
24 : state: data,
25 : ),
26 : );
27 : }
28 : }
29 4 : return NotFound(path: uri.path);
30 : }
31 :
32 : /// Can a [beamLocation], depending on its `pathBlueprints`, handle the [uri].
33 : ///
34 : /// Used in [BeamLocation.canHandle] and [chooseBeamLocation].
35 3 : static bool canBeamLocationHandleUri(BeamLocation beamLocation, Uri uri) {
36 6 : for (final pathBlueprint in beamLocation.pathBlueprints) {
37 3 : if (pathBlueprint is String) {
38 9 : if (pathBlueprint == uri.path || pathBlueprint == '/*') {
39 : return true;
40 : }
41 6 : final uriPathSegments = uri.pathSegments.toList();
42 12 : if (uriPathSegments.length > 1 && uriPathSegments.last == '') {
43 1 : uriPathSegments.removeLast();
44 : }
45 : final beamLocationPathBlueprintSegments =
46 6 : Uri.parse(pathBlueprint).pathSegments;
47 9 : if (uriPathSegments.length > beamLocationPathBlueprintSegments.length &&
48 1 : !beamLocationPathBlueprintSegments.contains('*')) {
49 : continue;
50 : }
51 : var checksPassed = true;
52 9 : for (int i = 0; i < uriPathSegments.length; i++) {
53 6 : if (beamLocationPathBlueprintSegments[i] == '*') {
54 : checksPassed = true;
55 : break;
56 : }
57 9 : if (uriPathSegments[i] != beamLocationPathBlueprintSegments[i] &&
58 9 : beamLocationPathBlueprintSegments[i][0] != ':') {
59 : checksPassed = false;
60 : break;
61 : }
62 : }
63 : if (checksPassed) {
64 : return true;
65 : }
66 : } else {
67 1 : final regexp = tryCastToRegExp(pathBlueprint);
68 2 : return regexp.hasMatch(uri.toString());
69 : }
70 : }
71 : return false;
72 : }
73 :
74 : /// Creates a state for [BeamLocation] based on incoming [uri].
75 : ///
76 : /// Used in [BeamState.copyForLocation].
77 9 : static BeamState createBeamState(
78 : Uri uri, {
79 : BeamLocation? beamLocation,
80 : Map<String, dynamic> data = const <String, dynamic>{},
81 : }) {
82 : if (beamLocation != null) {
83 : // TODO: abstract this and reuse in canBeamLocationHandleUri
84 18 : for (final pathBlueprint in beamLocation.pathBlueprints) {
85 9 : if (pathBlueprint is String) {
86 27 : if (pathBlueprint == uri.path || pathBlueprint == '/*') {
87 7 : BeamState(
88 7 : pathBlueprintSegments: uri.pathSegments,
89 7 : queryParameters: uri.queryParameters,
90 : data: data,
91 : );
92 : }
93 18 : final uriPathSegments = uri.pathSegments.toList();
94 34 : if (uriPathSegments.length > 1 && uriPathSegments.last == '') {
95 1 : uriPathSegments.removeLast();
96 : }
97 : final beamLocationPathBlueprintSegments =
98 18 : Uri.parse(pathBlueprint).pathSegments;
99 9 : var pathSegments = <String>[];
100 9 : final pathParameters = <String, String>{};
101 18 : if (uriPathSegments.length >
102 9 : beamLocationPathBlueprintSegments.length &&
103 6 : !beamLocationPathBlueprintSegments.contains('*')) {
104 : continue;
105 : }
106 : var checksPassed = true;
107 27 : for (int i = 0; i < uriPathSegments.length; i++) {
108 18 : if (beamLocationPathBlueprintSegments[i] == '*') {
109 5 : pathSegments = uriPathSegments.toList();
110 : checksPassed = true;
111 : break;
112 : }
113 27 : if (uriPathSegments[i] != beamLocationPathBlueprintSegments[i] &&
114 18 : beamLocationPathBlueprintSegments[i][0] != ':') {
115 : checksPassed = false;
116 : break;
117 27 : } else if (beamLocationPathBlueprintSegments[i][0] == ':') {
118 12 : pathParameters[beamLocationPathBlueprintSegments[i]
119 12 : .substring(1)] = uriPathSegments[i];
120 12 : pathSegments.add(beamLocationPathBlueprintSegments[i]);
121 : } else {
122 18 : pathSegments.add(uriPathSegments[i]);
123 : }
124 : }
125 : if (checksPassed) {
126 9 : return BeamState(
127 : pathBlueprintSegments: pathSegments,
128 : pathParameters: pathParameters,
129 9 : queryParameters: uri.queryParameters,
130 : data: data,
131 : );
132 : }
133 : } else {
134 2 : final regexp = tryCastToRegExp(pathBlueprint);
135 2 : final pathParameters = <String, String>{};
136 2 : final url = uri.toString();
137 :
138 2 : if (regexp.hasMatch(url)) {
139 6 : regexp.allMatches(url).forEach((match) {
140 3 : for (final groupName in match.groupNames) {
141 2 : pathParameters[groupName] = match.namedGroup(groupName) ?? '';
142 : }
143 : });
144 2 : return BeamState(
145 2 : pathBlueprintSegments: uri.pathSegments,
146 : pathParameters: pathParameters,
147 2 : queryParameters: uri.queryParameters,
148 : data: data,
149 : );
150 : }
151 : }
152 : }
153 : }
154 9 : return BeamState(
155 9 : pathBlueprintSegments: uri.pathSegments,
156 9 : queryParameters: uri.queryParameters,
157 : data: data,
158 : );
159 : }
160 :
161 6 : static bool urisMatch(Pattern blueprint, Uri exact) {
162 6 : if (blueprint is String) {
163 6 : final uriBlueprint = Uri.parse(blueprint);
164 6 : final blueprintSegments = uriBlueprint.pathSegments;
165 6 : final exactSegment = exact.pathSegments;
166 18 : if (blueprintSegments.length != exactSegment.length) {
167 : return false;
168 : }
169 18 : for (int i = 0; i < blueprintSegments.length; i++) {
170 12 : if (blueprintSegments[i].startsWith(':')) {
171 : continue;
172 : }
173 18 : if (blueprintSegments[i] != exactSegment[i]) {
174 : return false;
175 : }
176 : }
177 : return true;
178 : } else {
179 1 : final regExpBlueprint = tryCastToRegExp(blueprint);
180 2 : return regExpBlueprint.hasMatch(exact.toString());
181 : }
182 : }
183 :
184 : /// Wraps the casting of pathBlueprint to RegExp inside a try-catch
185 : /// and throws a nice FlutterError.
186 3 : static RegExp tryCastToRegExp(Pattern pathBlueprint) {
187 : try {
188 : return pathBlueprint as RegExp;
189 1 : } on TypeError catch (_) {
190 2 : throw FlutterError.fromParts([
191 1 : DiagnosticsNode.message('Path blueprint can either be:',
192 : level: DiagnosticLevel.summary),
193 1 : DiagnosticsNode.message('1. String'),
194 1 : DiagnosticsNode.message('2. RegExp instance')
195 : ]);
196 : }
197 : }
198 :
199 : /// Removes the trailing / in an URI String and returns the result.
200 : ///
201 : /// If there is no trailing /, returns the input.
202 7 : static String trimmed(String? uri) {
203 : if (uri == null) {
204 : return '/';
205 : }
206 21 : if (uri.length > 1 && uri.endsWith('/')) {
207 3 : return uri.substring(0, uri.length - 1);
208 : }
209 : return uri;
210 : }
211 : }
|