LCOV - code coverage report
Current view: top level - src - destination.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 58 63 92.1 %
Date: 2022-10-22 16:29:45 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:flutter/widgets.dart';
       2             : import 'package:theseus_navigator/theseus_navigator.dart';
       3             : 
       4             : import 'destination_parser.dart';
       5             : import 'navigation_controller.dart';
       6             : import 'redirection.dart';
       7             : 
       8             : /// A class that contains all required information about navigation target.
       9             : ///
      10             : /// The destination is identified by its [path]. Optionally, [parameters] can be provided.
      11             : /// Either content [builder] or nested [navigator] must be provided for the destination.
      12             : ///
      13             : /// The navigator uses a destination's [configuration] to apply a certain logic of
      14             : /// updating the navigation stack and transition animations.
      15             : ///
      16             : /// The [parser] is used to parse destination from the URI and generate a URI string
      17             : /// for the destination.
      18             : ///
      19             : /// Optional [upwardDestinationBuilder] builder function can be used to implement custom
      20             : /// logic of upward navigation from the current destination.
      21             : ///
      22             : /// If [redirections] are specified, they will be applied on navigation to this destination.
      23             : ///
      24             : /// See also:
      25             : /// - [DestinationConfiguration]
      26             : /// - [DestinationParameters]
      27             : /// - [DestinationParser]
      28             : /// - [Redirection]
      29             : ///
      30             : class Destination<T extends DestinationParameters> {
      31             :   /// Creates a destination.
      32             :   ///
      33           9 :   Destination({
      34             :     required this.path,
      35             :     this.builder,
      36             :     DestinationConfiguration? configuration,
      37             :     this.isHome = false,
      38             :     this.navigator,
      39             :     this.parameters,
      40             :     this.parser = const DefaultDestinationParser(),
      41             :     this.redirections = const <Redirection>[],
      42             :     this.tag,
      43             :     this.upwardDestinationBuilder,
      44           9 :   })  : assert(navigator != null || builder != null,
      45             :             'Either "builder" or "navigator" must be specified.'),
      46             :         assert(
      47           9 :             (navigator != null && builder == null) ||
      48             :                 (builder != null && navigator == null),
      49             :             'If the "navigator" is provided, the "builder" must be null, or vice versa.'),
      50             :         assert(
      51          18 :             ((T == DestinationParameters) &&
      52           9 :                     (parser is DefaultDestinationParser)) ||
      53           5 :                 ((T != DestinationParameters) &&
      54           5 :                     parser is! DefaultDestinationParser),
      55           0 :             'Custom "parser" must be provided when using the parameters of type $T, but ${parser.runtimeType} was provided.') {
      56          18 :     this.configuration = configuration ?? DestinationConfiguration.material();
      57             :   }
      58             : 
      59             :   /// Creates a destination that provides a navigator with nested destinations.
      60             :   ///
      61           0 :   Destination.intermediate({
      62             :     required this.path,
      63             :     required this.navigator,
      64             :     this.isHome = false,
      65             :     this.redirections = const <Redirection>[],
      66             :     this.tag,
      67             :   })  : builder = null,
      68           0 :         configuration = DestinationConfiguration.material(),
      69             :         parameters = null,
      70             :         parser = const DefaultDestinationParser(),
      71             :         upwardDestinationBuilder = null;
      72             : 
      73             :   /// Path identifies the destination.
      74             :   ///
      75             :   /// Usually it follows the common url pattern with optional parameters.
      76             :   /// Example: `/catalog/{id}`
      77             :   ///
      78             :   final String path;
      79             : 
      80             :   /// A content builder.
      81             :   ///
      82             :   /// Returns a widget (basically a screen) that should be rendered for this destination.
      83             :   ///
      84             :   final Widget Function(BuildContext context, T? parameters)? builder;
      85             : 
      86             :   /// Defines a way of how this destination will appear.
      87             :   ///
      88             :   late final DestinationConfiguration configuration;
      89             : 
      90             :   /// Whether the destination is the home destination.
      91             :   ///
      92             :   /// The home destination matches the '/' or empty path, beside of its specific [path].
      93             :   ///
      94             :   final bool isHome;
      95             : 
      96             :   /// A child navigator.
      97             :   ///
      98             :   /// Allows to implement nested navigation. When specified, the parent navigator
      99             :   /// uses this child navigator to build content for this destination.
     100             :   ///
     101             :   final NavigationController? navigator;
     102             : 
     103             :   /// Optional parameters, that are used to build content.
     104             :   ///
     105             :   final T? parameters;
     106             : 
     107             :   /// A destination parser.
     108             :   ///
     109             :   /// Used to parse the certain destination object from the URI string, based on
     110             :   /// the current destination, and to generate a URI string from the current destination.
     111             :   ///
     112             :   final DestinationParser parser;
     113             : 
     114             :   /// Destinations and conditions to redirect.
     115             :   ///
     116             :   /// When it is not empty, the navigator will check for each [Redirection] in the list,
     117             :   /// if this destination is allowed to navigate to.
     118             :   ///
     119             :   final List<Redirection> redirections;
     120             : 
     121             :   /// An optional label to identify a destination.
     122             :   ///
     123             :   /// It will be the same for all destinations of the kind, regardless actual
     124             :   /// values of destination parameters.
     125             :   ///
     126             :   final String? tag;
     127             : 
     128             :   /// Function that returns an underlay destination.
     129             :   ///
     130             :   /// A [NavigationController] uses this method to create the underlay destination for the
     131             :   /// current one, using its parameters.
     132             :   ///
     133             :   final Destination? Function(Destination<T> destination)?
     134             :       upwardDestinationBuilder;
     135             : 
     136             :   /// Indicates if the [upwardDestinationBuilder] is provided.
     137             :   ///
     138           6 :   bool get hasUpwardDestinationBuilder => upwardDestinationBuilder != null;
     139             : 
     140             :   /// Whether this destination is final, i.e. it builds a content
     141             :   ///
     142             :   /// Final destinations must have a [builder] function provided.
     143             :   /// Non-final destinations must have a [navigator], that manages its own destinations.
     144             :   ///
     145          10 :   bool get isFinalDestination => navigator == null;
     146             : 
     147             :   /// A full URI of the destination, with parameters placeholders replaced with
     148             :   /// actual parameter values.
     149             :   ///
     150          24 :   String get uri => parser.uri(this);
     151             : 
     152             :   /// Check if the destination matches the provided URI string
     153             :   ///
     154          21 :   bool isMatch(String uri) => parser.isMatch(uri, this);
     155             : 
     156             :   /// Parses the destination from the provided URI string.
     157             :   ///
     158             :   /// Returns a copy of the current destination with updated parameters, parsed
     159             :   /// from the URI.
     160             :   /// If the URI doesn't match this destination, throws an [DestinationNotMatchException].
     161             :   ///
     162           4 :   Future<Destination<T>> parse(String uri) =>
     163           8 :       parser.parseParameters(uri, this) as Future<Destination<T>>;
     164             : 
     165             :   /// Return a widget that display destination's content.
     166             :   ///
     167             :   /// If the destination is final, then [builder] is called to build a content.
     168             :   /// Otherwise [navigator.build] is called to build nested navigator's content.
     169             :   ///
     170           4 :   Widget build(BuildContext context) => isFinalDestination
     171           6 :       ? builder!(context, parameters)
     172           0 :       : navigator!.build(context);
     173             : 
     174             :   /// Return a destination that should be displayed on reverse navigation.
     175             :   ///
     176          12 :   Destination? get upwardDestination => upwardDestinationBuilder?.call(this);
     177             : 
     178             :   /// Returns a copy of this destination with a different configuration.
     179             :   ///
     180           3 :   Destination<T> withConfiguration(DestinationConfiguration configuration) =>
     181           3 :       copyWith(
     182             :         configuration: configuration,
     183             :       );
     184             : 
     185             :   /// Returns a copy of this destination with different parameters.
     186             :   ///
     187             :   /// For typed parameters ensures that raw parameter values in [DestinationParameters.map] are valid.
     188             :   ///
     189           7 :   Destination<T> withParameters(T parameters) {
     190          14 :     final rawParameters = parser.toMap(parameters);
     191           7 :     return copyWith(
     192             :       parameters: parameters
     193          14 :         ..map.clear()
     194          14 :         ..map.addAll(rawParameters),
     195             :     );
     196             :   }
     197             : 
     198             :     /// Creates a copy of this destination with the given fields replaced
     199             :     /// with the new values.
     200             :     ///
     201           7 :     Destination<T> copyWith({
     202             :       DestinationConfiguration? configuration,
     203             :       T? parameters,
     204             :     }) =>
     205           7 :         Destination<T>(
     206           7 :           path: path,
     207           7 :           builder: builder,
     208           7 :           navigator: navigator,
     209           7 :           configuration: configuration ?? this.configuration,
     210           3 :           parameters: parameters ?? this.parameters,
     211           7 :           parser: parser,
     212           7 :           tag: tag,
     213           7 :           upwardDestinationBuilder: upwardDestinationBuilder,
     214             :         );
     215             : 
     216             :   /// Destinations are equal when their URI string are equal.
     217             :   ///
     218           8 :   @override
     219             :   bool operator ==(Object other) =>
     220             :       identical(this, other) ||
     221           8 :       other is Destination &&
     222          24 :           runtimeType == other.runtimeType &&
     223          24 :           uri == other.uri;
     224             : 
     225           5 :   @override
     226          10 :   int get hashCode => uri.hashCode;
     227             : 
     228           5 :   @override
     229           5 :   String toString() => uri;
     230             : }
     231             : 
     232             : /// Encapsulates the configuration attributes which are used for navigating to
     233             : /// the destination.
     234             : ///
     235             : /// There are convenient factory constructors of commonly used configurations.
     236             : /// [defaultMaterial] - pushes the destination to the navigation stack with standard material animations.
     237             : /// [quiet] - replace the previous destination with the current one without animations.
     238             : ///
     239             : /// See also:
     240             : /// - [DestinationAction]
     241             : /// - [DestinationTransition]
     242             : ///
     243             : class DestinationConfiguration {
     244             :   /// Creates configuration of a destination.
     245             :   ///
     246           9 :   const DestinationConfiguration({
     247             :     required this.action,
     248             :     required this.transition,
     249             :     this.reset = false,
     250             :     this.transitionBuilder,
     251             :   }) : assert(
     252          18 :             (transition == DestinationTransition.custom &&
     253             :                     transitionBuilder != null) ||
     254           9 :                 (transition != DestinationTransition.custom),
     255             :             'You have to provide "transitionBuilder" for "custom" transition.');
     256             : 
     257             :   /// Creates a configuration that pushes a destination to the top of navigation
     258             :   /// stack with a standard Material animations.
     259             :   ///
     260             :   const factory DestinationConfiguration.material() =
     261             :       _DefaultDestinationConfiguration;
     262             : 
     263             :   /// Creates a configuration that replaces the current destination with a new one
     264             :   /// with no animations.
     265             :   ///
     266             :   const factory DestinationConfiguration.quiet() =
     267             :       _QuietDestinationConfiguration;
     268             : 
     269             :   /// How the destination will update the navigation stack.
     270             :   ///
     271             :   /// See also:
     272             :   ///  - [DestinationAction]
     273             :   ///
     274             :   final DestinationAction action;
     275             : 
     276             :   /// Visual effects that would be applied on updating the stack with the destination.
     277             :   ///
     278             :   /// See also:
     279             :   /// - [DestinationTransition]
     280             :   ///
     281             :   final DestinationTransition transition;
     282             : 
     283             :   /// Whether the stack would be cleared before adding the destination.
     284             :   ///
     285             :   final bool reset;
     286             : 
     287             :   /// Function that build custom destination transitions.
     288             :   ///
     289             :   /// It is required when the [transition] value is [DestinationTransition.custom].
     290             :   ///
     291             :   /// See also
     292             :   /// - [RouteTransitionBuilder]
     293             :   ///
     294             :   final RouteTransitionsBuilder? transitionBuilder;
     295             : 
     296             :   /// Creates a copy of this configuration with the given fields replaced
     297             :   /// with the new values.
     298             :   ///
     299           3 :   DestinationConfiguration copyWith({
     300             :     // TODO: Add other properties
     301             :     bool? reset,
     302             :   }) =>
     303           3 :       DestinationConfiguration(
     304           3 :         action: action,
     305           3 :         transition: transition,
     306           0 :         reset: reset ?? this.reset,
     307           3 :         transitionBuilder: transitionBuilder,
     308             :       );
     309             : }
     310             : 
     311             : class _DefaultDestinationConfiguration extends DestinationConfiguration {
     312           9 :   const _DefaultDestinationConfiguration()
     313           9 :       : super(
     314             :           action: DestinationAction.push,
     315             :           transition: DestinationTransition.material,
     316             :         );
     317             : }
     318             : 
     319             : class _QuietDestinationConfiguration extends DestinationConfiguration {
     320           2 :   const _QuietDestinationConfiguration()
     321           2 :       : super(
     322             :           action: DestinationAction.replace,
     323             :           transition: DestinationTransition.none,
     324             :         );
     325             : }
     326             : 
     327             : /// An action that is used to update the navigation stack with the destination.
     328             : ///
     329           9 : enum DestinationAction {
     330             :   /// The destination will be added to the navigation stack.
     331             :   /// On navigation back, the destination will be removed from the stack
     332             :   /// and previous destination will be restored.
     333             :   ///
     334             :   push,
     335             : 
     336             :   /// The previous destination will be removed from the navigation stack,
     337             :   /// and the current destination will be added.
     338             :   /// This means that user will not be able to return to previous destination
     339             :   /// by back navigation.
     340             :   ///
     341             :   replace,
     342             : }
     343             : 
     344             : /// Defines transition animations from the previous destination to the current one.
     345             : ///
     346           9 : enum DestinationTransition {
     347             :   /// Standard Material animations.
     348             :   ///
     349             :   material,
     350             : 
     351             :   /// Custom animations.
     352             :   ///
     353             :   custom,
     354             : 
     355             :   /// No animations.
     356             :   ///
     357             :   none,
     358             : }
     359             : 
     360             : /// Base destination parameters.
     361             : ///
     362             : /// Extend this abstract class to define your custom parameters class.
     363             : /// Use [Destination<YourCustomDestinationParameters>()] to make a destination
     364             : /// aware of your custom parameters.
     365             : ///
     366             : /// For custom parameters you also must implement [YouCustomDestinationParser<YourCustomDestinationParameters>]
     367             : /// with [toDestinationParameters()] ans [toMap()] methods, like this:
     368             : /// ```
     369             : /// class YourCustomDestinationParser
     370             : ///     extends DestinationParser<YourCustomDestinationParameters> {
     371             : ///   const YourCustomDestinationParser() : super();
     372             : ///
     373             : ///   @override
     374             : ///   YourCustomDestinationParameters toDestinationParameters(
     375             : ///       Map<String, String> map) {
     376             : ///       ...
     377             : ///   }
     378             : ///
     379             : ///   @override
     380             : ///   Map<String, String> toMap(YourCustomDestinationParameters parameters) {
     381             : ///       ...
     382             : ///   }
     383             : /// }
     384             : /// ```
     385             : ///
     386             : /// See also:
     387             : /// - [DestinationParser]
     388             : ///
     389             : class DestinationParameters {
     390             :   /// Creates a [DestinationParameters] instance.
     391             :   ///
     392           7 :   DestinationParameters([Map<String, String>? map])
     393           4 :     : map = map ?? <String, String>{};
     394             : 
     395             :   /// Contains parameter values parsed from the destination's URI.
     396             :   ///
     397             :   /// The parameter name is a [MapEntry.key], and the value is [MapEntry.value].
     398             :   ///
     399             :   late final Map<String, String> map;
     400             : }

Generated by: LCOV version