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