LCOV - code coverage report
Current view: top level - src - destination.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 71 72 98.6 %
Date: 2023-03-02 15:31:08 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:flutter/widgets.dart';
       2             : 
       3             : import 'destination_parser.dart';
       4             : import 'navigation_controller.dart';
       5             : import 'redirection.dart';
       6             : 
       7             : /// A class that contains all required information about navigation target.
       8             : ///
       9             : /// The destination is identified by its [path]. Optionally, [parameters] can be provided.
      10             : /// Either content [builder] or nested [navigator] must be provided for the destination.
      11             : ///
      12             : /// The navigator uses a destination's [settings] to determine a way of changing
      13             : /// the navigation stack, apply transition animations and other aspects of updating
      14             : /// the navigation state.
      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             : /// - [DestinationSettings]
      26             : /// - [DestinationParameters]
      27             : /// - [DestinationParser]
      28             : /// - [Redirection]
      29             : ///
      30             : class Destination<T extends DestinationParameters> {
      31             :   /// Creates a destination.
      32             :   ///
      33          10 :   Destination({
      34             :     required this.path,
      35             :     this.builder,
      36             :     this.isHome = false,
      37             :     this.navigator,
      38             :     this.parameters,
      39             :     this.parser = const DefaultDestinationParser(),
      40             :     this.redirections = const <Redirection>[],
      41             :     DestinationSettings? settings,
      42             :     this.tag,
      43             :     this.upwardDestinationBuilder,
      44          10 :   })  : assert(navigator != null || builder != null,
      45             :             'Either "builder" or "navigator" must be specified.'),
      46             :         assert(
      47          10 :             (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          20 :             ((T == DestinationParameters) &&
      52          10 :                     (parser is DefaultDestinationParser)) ||
      53           6 :                 ((T != DestinationParameters) &&
      54           6 :                     parser is! DefaultDestinationParser),
      55           0 :             'Custom "parser" must be provided when using the parameters of type $T, but ${parser.runtimeType} was provided.') {
      56          20 :     this.settings = settings ?? DestinationSettings.material();
      57          10 :     _transitBuilder = null;
      58             :   }
      59             : 
      60             :   /// Creates a destination that provides a navigator with nested destinations.
      61             :   ///
      62             :   /// An optional [builder] parameter is basically the same like normal [Destination.builder],
      63             :   /// but has additional [child] parameter, which contains the nested content that built by [navigator].
      64             :   /// The implementation of [builder] function must include this child widget sub-tree
      65             :   /// in the result for correct displaying the nested content.
      66             :   ///
      67           2 :   Destination.transit({
      68             :     required this.path,
      69             :     required this.navigator,
      70             :     Widget Function(BuildContext context, T? parameters, Widget child)? builder,
      71             :     this.isHome = false,
      72             :     this.redirections = const <Redirection>[],
      73             :     this.tag,
      74             :   })  : builder = null,
      75             :         parameters = null,
      76             :         parser = const DefaultDestinationParser(),
      77           2 :         settings = DestinationSettings.material(),
      78             :         upwardDestinationBuilder = null,
      79             :         _transitBuilder = builder;
      80             : 
      81             :   /// Path identifies the destination.
      82             :   ///
      83             :   /// Usually it follows the common url pattern with optional parameters.
      84             :   /// Example: `/catalog/{id}`
      85             :   ///
      86             :   final String path;
      87             : 
      88             :   /// A content builder.
      89             :   ///
      90             :   /// Returns a widget (basically a screen) that should be rendered for this destination.
      91             :   ///
      92             :   final Widget Function(BuildContext context, T? parameters)? builder;
      93             : 
      94             :   /// Whether the destination is the home destination.
      95             :   ///
      96             :   /// The home destination matches the '/' or empty path, beside of its specific [path].
      97             :   ///
      98             :   final bool isHome;
      99             : 
     100             :   /// A child navigator.
     101             :   ///
     102             :   /// Allows to implement nested navigation. When specified, the parent navigator
     103             :   /// uses this child navigator to build content for this destination.
     104             :   ///
     105             :   final NavigationController? navigator;
     106             : 
     107             :   /// Parameters of the destination.
     108             :   ///
     109             :   /// If the type *T* is not specified for the destination, then default [DestinationParameters]
     110             :   /// type is used.
     111             :   ///
     112             :   final T? parameters;
     113             : 
     114             :   /// A destination parser.
     115             :   ///
     116             :   /// Used to parse the certain destination object from the URI string, based on
     117             :   /// the current destination, and to generate a URI string from the current destination.
     118             :   ///
     119             :   final DestinationParser parser;
     120             : 
     121             :   /// Destinations and conditions to redirect.
     122             :   ///
     123             :   /// When it is not empty, the navigator will check for each [Redirection] in the list,
     124             :   /// if this destination is allowed to navigate to.
     125             :   ///
     126             :   final List<Redirection> redirections;
     127             : 
     128             :   /// Defines a way of how this destination will appear.
     129             :   ///
     130             :   late final DestinationSettings settings;
     131             : 
     132             :   /// An optional label to identify a destination.
     133             :   ///
     134             :   /// It will be the same for all destinations of the kind, regardless actual
     135             :   /// values of destination parameters.
     136             :   ///
     137             :   final String? tag;
     138             : 
     139             :   /// Function that returns an underlay destination.
     140             :   ///
     141             :   /// A [NavigationController] uses this method to create the underlay destination for the
     142             :   /// current one, using its parameters.
     143             :   ///
     144             :   final Future<Destination?> Function(Destination<T> destination)?
     145             :       upwardDestinationBuilder;
     146             : 
     147             :   late final Widget Function(BuildContext context, T? parameters, Widget child)?
     148             :       _transitBuilder;
     149             : 
     150             :   /// Whether this destination is final, i.e. it builds a content
     151             :   ///
     152             :   /// Final destinations must have a [builder] function provided.
     153             :   /// Non-final destinations must have a [navigator], that manages its own destinations.
     154             :   ///
     155          12 :   bool get isFinalDestination => navigator == null;
     156             : 
     157             :   /// Return a destination that should be displayed on reverse navigation.
     158             :   ///
     159           5 :   Future<Destination?> get upwardDestination async =>
     160           8 :       upwardDestinationBuilder?.call(this);
     161             : 
     162             :   /// A full URI of the destination, with parameters placeholders replaced with
     163             :   /// actual parameter values.
     164             :   ///
     165          27 :   String get uri => parser.uri(this);
     166             : 
     167             :   /// Return a widget that display destination's content.
     168             :   ///
     169             :   /// If the destination is final, then [builder] is called to build the content.
     170             :   ///
     171             :   /// Otherwise [navigator.build] is called to build nested navigator's content.
     172             :   /// In case the destination was created by [Destination.transit] constructor,
     173             :   /// and [builder] parameter was specified, the nested content is also wrapped in
     174             :   /// the widget sub-tree returned by that builder.
     175             :   ///
     176           3 :   Widget build(BuildContext context) {
     177           3 :     if (isFinalDestination) {
     178           9 :       return builder!(context, parameters);
     179             :     } else {
     180           4 :       final nestedContent = navigator!.build(context);
     181           2 :       if (_transitBuilder != null) {
     182           6 :         return _transitBuilder!(context, parameters, nestedContent);
     183             :       } else {
     184             :         return nestedContent;
     185             :       }
     186             :     }
     187             :   }
     188             : 
     189             :   /// Check if the destination matches the provided URI string
     190             :   ///
     191          21 :   bool isMatch(String uri) => parser.isMatch(uri, this);
     192             : 
     193             :   /// Parses the destination from the provided URI string.
     194             :   ///
     195             :   /// Returns a copy of the current destination with updated parameters, parsed
     196             :   /// from the URI.
     197             :   /// If the URI doesn't match this destination, throws an [DestinationNotMatchException].
     198             :   ///
     199           5 :   Future<Destination<T>> parse(String uri) =>
     200          10 :       parser.parseParameters(uri, this) as Future<Destination<T>>;
     201             : 
     202             :   /// Returns a copy of this destination with a different settings.
     203             :   ///
     204          10 :   Destination<T> withSettings(DestinationSettings settings) => copyWith(
     205             :         settings: settings,
     206             :       );
     207             : 
     208             :   /// Returns a copy of this destination with different parameters.
     209             :   ///
     210             :   /// For typed parameters ensures that raw parameter values in [DestinationParameters.map]
     211             :   /// are updated as well.
     212             :   ///
     213           8 :   Destination<T> withParameters(T parameters) {
     214          16 :     final rawParameters = parser.parametersToMap(parameters);
     215           8 :     return copyWith(
     216          16 :       parameters: parameters..map.addAll(rawParameters),
     217             :     );
     218             :   }
     219             : 
     220             :   /// Creates a copy of this destination with the given fields replaced
     221             :   /// with the new values.
     222             :   ///
     223           8 :   Destination<T> copyWith({
     224             :     T? parameters,
     225             :     DestinationSettings? settings,
     226             :     Future<Destination?> Function(Destination<T> destination)?
     227             :         upwardDestinationBuilder,
     228             :   }) =>
     229           8 :       Destination<T>(
     230           8 :         path: path,
     231           8 :         builder: builder,
     232           8 :         navigator: navigator,
     233           5 :         parameters: parameters ?? this.parameters,
     234           8 :         parser: parser,
     235           8 :         redirections: redirections,
     236           8 :         settings: settings ?? this.settings,
     237           8 :         tag: tag,
     238             :         upwardDestinationBuilder:
     239           8 :             upwardDestinationBuilder ?? this.upwardDestinationBuilder,
     240             :       );
     241             : 
     242             :   /// Destinations are equal when their URI string are equal.
     243             :   ///
     244           8 :   @override
     245             :   bool operator ==(Object other) =>
     246             :       identical(this, other) ||
     247           8 :       other is Destination &&
     248          24 :           runtimeType == other.runtimeType &&
     249          24 :           uri == other.uri;
     250             : 
     251           5 :   @override
     252          10 :   int get hashCode => uri.hashCode;
     253             : 
     254           6 :   @override
     255           6 :   String toString() => uri;
     256             : }
     257             : 
     258             : /// Encapsulates the settings attributes which are applied when the navigation state
     259             : /// is updated with the the destination.
     260             : ///
     261             : /// There are convenient factory constructors for commonly used settings.
     262             : /// [material] - pushes the destination to the navigation stack with standard material animations.
     263             : /// [dialog] - display the destination like a dialog
     264             : /// [quiet] - replace the previous destination with the current one without animations.
     265             : ///
     266             : /// See also:
     267             : /// - [DestinationAction]
     268             : /// - [DestinationTransition]
     269             : ///
     270             : class DestinationSettings {
     271             :   /// Creates an instance of [DestinationSettings].
     272             :   ///
     273          10 :   const DestinationSettings({
     274             :     required this.action,
     275             :     required this.transition,
     276             :     this.redirectedFrom,
     277             :     this.reset = false,
     278             :     this.transitionBuilder,
     279             :     this.updateHistory = true,
     280             :   }) : assert(
     281          20 :             (transition == DestinationTransition.custom &&
     282             :                     transitionBuilder != null) ||
     283          10 :                 (transition != DestinationTransition.custom),
     284             :             'You have to provide "transitionBuilder" for "custom" transition.');
     285             : 
     286             :   /// Creates a settings to push a destination to the top of navigation
     287             :   /// stack with a standard Material animations.
     288             :   ///
     289             :   const factory DestinationSettings.material() = _DefaultDestinationSettings;
     290             : 
     291             :   /// Creates a settings to displays a destination as a modal dialog.
     292             :   ///
     293             :   const factory DestinationSettings.dialog() = _DialogDestinationSettings;
     294             : 
     295             :   /// Creates a settings to replaces the current destination with a new one
     296             :   /// with no animations.
     297             :   ///
     298             :   const factory DestinationSettings.quiet() = _QuietDestinationSettings;
     299             : 
     300             :   /// How the destination will update the navigation stack.
     301             :   ///
     302             :   /// See also:
     303             :   ///  - [DestinationAction]
     304             :   ///
     305             :   final DestinationAction action;
     306             : 
     307             :   /// Visual effects that would be applied on updating the stack with the destination.
     308             :   ///
     309             :   /// See also:
     310             :   /// - [DestinationTransition]
     311             :   ///
     312             :   final DestinationTransition transition;
     313             : 
     314             :   /// In case of redirection, contains a destination from which the redirection
     315             :   /// was performed.
     316             :   ///
     317             :   final Destination? redirectedFrom;
     318             : 
     319             :   /// Whether the stack would be cleared before adding the destination.
     320             :   ///
     321             :   final bool reset;
     322             : 
     323             :   /// Function that build custom destination transitions.
     324             :   ///
     325             :   /// It is required when the [transition] value is [DestinationTransition.custom].
     326             :   ///
     327             :   /// See also
     328             :   /// - [RouteTransitionBuilder]
     329             :   ///
     330             :   final RouteTransitionsBuilder? transitionBuilder;
     331             : 
     332             :   /// Controls if the destination will be added to the navigation history.
     333             :   ///
     334             :   /// Currently it only affects to web applications. When set to *true*, which is
     335             :   /// default, the url in the web browser address field will be updated with the [Destination.uri].
     336             :   ///
     337             :   final bool updateHistory;
     338             : 
     339             :   /// Creates a copy of this settings with the given fields replaced
     340             :   /// with the new values.
     341             :   ///
     342           5 :   DestinationSettings copyWith({
     343             :     // TODO: Add other properties
     344             :     DestinationAction? action,
     345             :     Destination? redirectedFrom,
     346             :     bool? reset,
     347             :     bool? updateHistory,
     348             :   }) =>
     349           5 :       DestinationSettings(
     350           4 :         action: action ?? this.action,
     351           5 :         transition: transition,
     352           5 :         redirectedFrom: redirectedFrom ?? this.redirectedFrom,
     353           4 :         reset: reset ?? this.reset,
     354           5 :         transitionBuilder: transitionBuilder,
     355           4 :         updateHistory: updateHistory ?? this.updateHistory,
     356             :       );
     357             : }
     358             : 
     359             : class _DefaultDestinationSettings extends DestinationSettings {
     360          10 :   const _DefaultDestinationSettings()
     361          10 :       : super(
     362             :           action: DestinationAction.push,
     363             :           transition: DestinationTransition.material,
     364             :         );
     365             : }
     366             : 
     367             : class _DialogDestinationSettings extends DestinationSettings {
     368           2 :   const _DialogDestinationSettings()
     369           2 :       : super(
     370             :           action: DestinationAction.push,
     371             :           transition: DestinationTransition.materialDialog,
     372             :         );
     373             : }
     374             : 
     375             : class _QuietDestinationSettings extends DestinationSettings {
     376           2 :   const _QuietDestinationSettings()
     377           2 :       : super(
     378             :           action: DestinationAction.replace,
     379             :           transition: DestinationTransition.none,
     380             :         );
     381             : }
     382             : 
     383             : /// An action that is used to update the navigation stack with the destination.
     384             : ///
     385          11 : enum DestinationAction {
     386             :   /// The destination will be added to the navigation stack.
     387             :   /// On navigation back, the destination will be removed from the stack
     388             :   /// and previous destination will be restored.
     389             :   ///
     390             :   push,
     391             : 
     392             :   /// The previous destination will be removed from the navigation stack,
     393             :   /// and the current destination will be added.
     394             :   /// This means that user will not be able to return to previous destination
     395             :   /// by back navigation.
     396             :   ///
     397             :   replace,
     398             : }
     399             : 
     400             : /// Defines transition animations from the previous destination to the current one.
     401             : ///
     402          11 : enum DestinationTransition {
     403             :   /// Standard Material animations.
     404             :   ///
     405             :   material,
     406             : 
     407             :   /// Destination appears as a dialog with Material transitions and modal barrier.
     408             :   ///
     409             :   materialDialog,
     410             : 
     411             :   /// Custom animations.
     412             :   ///
     413             :   custom,
     414             : 
     415             :   /// No animations.
     416             :   ///
     417             :   none,
     418             : }
     419             : 
     420             : /// Base destination parameters.
     421             : ///
     422             : /// Extend this class to define your custom parameters class.
     423             : /// Use [Destination<YourCustomDestinationParameters>()] to make a destination
     424             : /// aware of your custom parameters.
     425             : ///
     426             : /// For custom parameters you also must implement [YouCustomDestinationParser<YourCustomDestinationParameters>]
     427             : /// with [toDestinationParameters()] ans [toMap()] methods, like this:
     428             : /// ``` dart
     429             : /// class YourCustomDestinationParser
     430             : ///     extends DestinationParser<YourCustomDestinationParameters> {
     431             : ///   const YourCustomDestinationParser() : super();
     432             : ///
     433             : ///   @override
     434             : ///   YourCustomDestinationParameters toDestinationParameters(
     435             : ///       Map<String, String> map) {
     436             : ///       //...
     437             : ///   }
     438             : ///
     439             : ///   @override
     440             : ///   Map<String, String> toMap(YourCustomDestinationParameters parameters) {
     441             : ///       //...
     442             : ///   }
     443             : /// }
     444             : /// ```
     445             : ///
     446             : /// See also:
     447             : /// - [DestinationParser]
     448             : ///
     449             : class DestinationParameters {
     450             :   /// Creates a [DestinationParameters] instance.
     451             :   ///
     452           8 :   DestinationParameters([Map<String, String>? map])
     453           5 :       : map = map ?? <String, String>{};
     454             : 
     455             :   /// Reserved query parameter name.
     456             :   ///
     457             :   /// It is used for automatic persisting of navigation state.
     458             :   /// Do not use this name for your custom parameters.
     459             :   ///
     460             :   static const String stateParameterName = 'state';
     461             : 
     462             :   static const _reservedParameterNames = <String>{
     463             :     stateParameterName,
     464             :   };
     465             : 
     466             :   /// Contains parameter values parsed from the destination's URI.
     467             :   ///
     468             :   /// The parameter name is a [MapEntry.key], and the value is [MapEntry.value].
     469             :   ///
     470             :   late final Map<String, String> map;
     471             : 
     472             :   /// Check if a provided parameter name is reserved
     473             :   ///
     474             :   /// This function is used by [DestinationParser] to synchronize internal raw parameter
     475             :   /// values with parsed parameter object properties and build destination URI.
     476             :   ///
     477           6 :   static bool isReservedParameter(String parameterName) =>
     478           6 :       _reservedParameterNames.contains(parameterName);
     479             : }

Generated by: LCOV version