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