Line data Source code
1 : import 'package:beamer/src/utils.dart'; 2 : import 'package:flutter/widgets.dart'; 3 : 4 : import 'package:beamer/src/beam_location.dart'; 5 : import 'package:beamer/src/beam_page.dart'; 6 : 7 : /// Checks whether current [BeamLocation] is allowed to be beamed to 8 : /// and provides steps to be executed following a failed check. 9 : /// 10 : /// If neither [beamTo], [beamToNamed] nor [showPage] is specified, 11 : /// the guard will just block navigation, i.e. nothing will happen. 12 : class BeamGuard { 13 : /// Creates a [BeamGuard] with defined properties. 14 : /// 15 : /// [pathPatterns] and [check] must not be null. 16 1 : const BeamGuard({ 17 : required this.pathPatterns, 18 : required this.check, 19 : this.onCheckFailed, 20 : this.beamTo, 21 : this.beamToNamed, 22 : this.showPage, 23 : this.guardNonMatching = false, 24 : this.replaceCurrentStack = true, 25 : }); 26 : 27 : /// A list of path strings or regular expressions (using dart's RegExp class) that are to be guarded. 28 : /// 29 : /// For strings: 30 : /// Asterisk wildcard is supported to denote "anything". 31 : /// 32 : /// For example, '/books/*' will match '/books/1', '/books/2/genres', etc. 33 : /// but will not match '/books'. To match '/books' and everything after it, 34 : /// use '/books*'. 35 : /// 36 : /// See [_hasMatch] for more details. 37 : /// 38 : /// For RegExp: 39 : /// You can use RegExp instances and the delegate will check for a match using [RegExp.hasMatch] 40 : /// 41 : /// For example, `RegExp('/books/')` will match '/books/1', '/books/2/genres', etc. 42 : /// but will not match '/books'. To match '/books' and everything after it, 43 : /// use `RegExp('/books')` 44 : final List<Pattern> pathPatterns; 45 : 46 : /// What check should be performed on a given [BeamLocation], 47 : /// the one to which beaming has been requested. 48 : /// 49 : /// [context] is also injected to fetch data up the tree if necessary. 50 : final bool Function(BuildContext context, BeamLocation location) check; 51 : 52 : /// Arbitrary closure to execute when [check] fails. 53 : /// 54 : /// This will run before and regardless of [beamTo], [beamToNamed], [showPage]. 55 : final void Function(BuildContext context, BeamLocation location)? 56 : onCheckFailed; 57 : 58 : /// If guard [check] returns `false`, build a [BeamLocation] to be beamed to. 59 : /// 60 : /// [origin] holds the origin [BeamLocation] from where it is being beamed from, `null` if there's no origin, 61 : /// which may happen if it's the first navigation or the history was cleared. 62 : /// [target] holds the [BeamLocation] to where we tried to beam to, i.e. the one on which the check failed. 63 : /// 64 : /// [showPage] has precedence over this attribute. 65 : final BeamLocation Function( 66 : BuildContext context, BeamLocation? origin, BeamLocation target)? beamTo; 67 : 68 : /// If guard [check] returns `false`, beam to this URI string. 69 : /// 70 : /// [origin] holds the origin [BeamLocation] from where it is being beamed from. `null` if there's no origin, 71 : /// which may happen if it's the first navigation or the history was cleared. 72 : /// [target] holds the [BeamLocation] to where we tried to beam to, i.e. the one on which the check failed. 73 : /// 74 : /// [showPage] has precedence over this attribute. 75 : final String Function(BeamLocation? origin, BeamLocation target)? beamToNamed; 76 : 77 : /// If guard [check] returns `false`, put this page onto navigation stack. 78 : /// 79 : /// This has precedence over [beamTo] and [beamToNamed]. 80 : final BeamPage? showPage; 81 : 82 : /// Whether to [check] all the path blueprints defined in [pathPatterns] 83 : /// or [check] all the paths that **are not** in [pathPatterns]. 84 : /// 85 : /// `false` meaning former and `true` meaning latter. 86 : final bool guardNonMatching; 87 : 88 : /// Whether or not to replace the current [BeamLocation]'s stack of pages. 89 : final bool replaceCurrentStack; 90 : 91 : /// Matches [location]'s pathBlueprint to [pathPatterns]. 92 : /// 93 : /// If asterisk is present, it is enough that the pre-asterisk substring is 94 : /// contained within location's pathPatterns. 95 : /// Else, the path (i.e. the pre-query substring) of the location's uri 96 : /// must be equal to the pathPattern. 97 1 : bool _hasMatch(BeamLocation location) { 98 2 : for (final pathPattern in pathPatterns) { 99 : final path = 100 5 : Uri.parse(location.state.routeInformation.location ?? '/').path; 101 1 : if (pathPattern is String) { 102 1 : final asteriskIndex = pathPattern.indexOf('*'); 103 2 : if (asteriskIndex != -1) { 104 3 : if (location.state.routeInformation.location 105 1 : .toString() 106 2 : .contains(pathPattern.substring(0, asteriskIndex))) { 107 : return true; 108 : } 109 : } else { 110 1 : if (pathPattern == path) { 111 : return true; 112 : } 113 : } 114 : } else { 115 1 : final regexp = Utils.tryCastToRegExp(pathPattern); 116 1 : return regexp.hasMatch(path); 117 : } 118 : } 119 : return false; 120 : } 121 : 122 : /// Whether or not the guard should [check] the [location]. 123 1 : bool shouldGuard(BeamLocation location) { 124 3 : return guardNonMatching ? !_hasMatch(location) : _hasMatch(location); 125 : } 126 : }