Line data Source code
1 : import 'package:beamer/beamer.dart'; 2 : import 'package:flutter/material.dart'; 3 : 4 : /// Some useful methods for beaming process configuration. 5 : abstract class Utils { 6 : /// Traverses [beamLocations] and returns the one whose one of 7 : /// `pathPatterns` contains the [uri], ignoring concrete path parameters. 8 : /// 9 : /// Upon finding such [BeamLocation], configures it with 10 : /// `pathParameters` and `queryParameters` from [uri]. 11 : /// 12 : /// If [beamLocations] don't contain a match, [NotFound] will be returned 13 : /// configured with [uri]. 14 3 : static BeamLocation chooseBeamLocation( 15 : Uri uri, 16 : List<BeamLocation> beamLocations, { 17 : Object? data, 18 : Object? routeState, 19 : }) { 20 6 : for (final beamLocation in beamLocations) { 21 3 : if (canBeamLocationHandleUri(beamLocation, uri)) { 22 3 : final routeInformation = RouteInformation( 23 3 : location: uri.toString(), 24 : state: routeState, 25 : ); 26 3 : return beamLocation..create(routeInformation); 27 : } 28 : } 29 4 : return NotFound(path: uri.path); 30 : } 31 : 32 : /// Can a [beamLocation], depending on its `pathPatterns`, 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.pathPatterns) { 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 : Object? routeState, 81 : }) { 82 : if (beamLocation != null) { 83 : // TODO: abstract this and reuse in canBeamLocationHandleUri 84 18 : for (final pathBlueprint in beamLocation.pathPatterns) { 85 9 : if (pathBlueprint is String) { 86 27 : if (pathBlueprint == uri.path || pathBlueprint == '/*') { 87 7 : BeamState( 88 7 : pathPatternSegments: uri.pathSegments, 89 7 : queryParameters: uri.queryParameters, 90 : routeState: routeState, 91 : ); 92 : } 93 18 : final uriPathSegments = uri.pathSegments.toList(); 94 36 : 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 24 : 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 : pathPatternSegments: pathSegments, 128 : pathParameters: pathParameters, 129 9 : queryParameters: uri.queryParameters, 130 : routeState: routeState, 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 : pathPatternSegments: uri.pathSegments, 146 : pathParameters: pathParameters, 147 2 : queryParameters: uri.queryParameters, 148 : routeState: routeState, 149 : ); 150 : } 151 : } 152 : } 153 : } 154 9 : return BeamState( 155 9 : pathPatternSegments: uri.pathSegments, 156 9 : queryParameters: uri.queryParameters, 157 : routeState: routeState, 158 : ); 159 : } 160 : 161 : /// Whether the [pattern] can match the [exact] URI. 162 6 : static bool urisMatch(Pattern pattern, Uri exact) { 163 6 : if (pattern is String) { 164 6 : final uriPattern = Uri.parse(pattern); 165 6 : final patternSegments = uriPattern.pathSegments; 166 6 : final exactSegment = exact.pathSegments; 167 18 : if (patternSegments.length != exactSegment.length) { 168 : return false; 169 : } 170 18 : for (int i = 0; i < patternSegments.length; i++) { 171 12 : if (patternSegments[i].startsWith(':')) { 172 : continue; 173 : } 174 18 : if (patternSegments[i] != exactSegment[i]) { 175 : return false; 176 : } 177 : } 178 : return true; 179 : } else { 180 1 : final regExpPattern = tryCastToRegExp(pattern); 181 2 : return regExpPattern.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 : }