LCOV - code coverage report
Current view: top level - src - destination.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 69 71 97.2 %
Date: 2022-12-25 21:41:53 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             :     DestinationSettings? settings,
      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          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           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          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           2 :         settings = DestinationSettings.material(),
      76             :         parameters = null,
      77             :         parser = const DefaultDestinationParser(),
      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             :   /// Defines a way of how this destination will appear.
      95             :   ///
      96             :   late final DestinationSettings settings;
      97             : 
      98             :   /// Whether the destination is the home destination.
      99             :   ///
     100             :   /// The home destination matches the '/' or empty path, beside of its specific [path].
     101             :   ///
     102             :   final bool isHome;
     103             : 
     104             :   /// A child navigator.
     105             :   ///
     106             :   /// Allows to implement nested navigation. When specified, the parent navigator
     107             :   /// uses this child navigator to build content for this destination.
     108             :   ///
     109             :   final NavigationController? navigator;
     110             : 
     111             :   /// Optional parameters, that are used to build content.
     112             :   ///
     113             :   final T? parameters;
     114             : 
     115             :   /// A destination parser.
     116             :   ///
     117             :   /// Used to parse the certain destination object from the URI string, based on
     118             :   /// the current destination, and to generate a URI string from the current destination.
     119             :   ///
     120             :   final DestinationParser parser;
     121             : 
     122             :   /// Destinations and conditions to redirect.
     123             :   ///
     124             :   /// When it is not empty, the navigator will check for each [Redirection] in the list,
     125             :   /// if this destination is allowed to navigate to.
     126             :   ///
     127             :   final List<Redirection> redirections;
     128             : 
     129             :   /// An optional label to identify a destination.
     130             :   ///
     131             :   /// It will be the same for all destinations of the kind, regardless actual
     132             :   /// values of destination parameters.
     133             :   ///
     134             :   final String? tag;
     135             : 
     136             :   /// Function that returns an underlay destination.
     137             :   ///
     138             :   /// A [NavigationController] uses this method to create the underlay destination for the
     139             :   /// current one, using its parameters.
     140             :   ///
     141             :   final Destination? Function(Destination<T> destination)?
     142             :       upwardDestinationBuilder;
     143             : 
     144             :   late final Widget Function(BuildContext context, T? parameters, Widget child)?
     145             :       _transitBuilder;
     146             : 
     147             :   /// Indicates if the [upwardDestinationBuilder] is provided.
     148             :   ///
     149           0 :   bool get hasUpwardDestinationBuilder => upwardDestinationBuilder != null;
     150             : 
     151             :   /// Whether this destination is final, i.e. it builds a content
     152             :   ///
     153             :   /// Final destinations must have a [builder] function provided.
     154             :   /// Non-final destinations must have a [navigator], that manages its own destinations.
     155             :   ///
     156          12 :   bool get isFinalDestination => navigator == null;
     157             : 
     158             :   /// Return a destination that should be displayed on reverse navigation.
     159             :   ///
     160          12 :   Destination? get upwardDestination => 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           4 :   Future<Destination<T>> parse(String uri) =>
     200           8 :       parser.parseParameters(uri, this) as Future<Destination<T>>;
     201             : 
     202             :   /// Returns a copy of this destination with a different settings.
     203             :   ///
     204           4 :   Destination<T> withSettings(DestinationSettings settings) =>
     205           4 :       copyWith(
     206             :         settings: settings,
     207             :       );
     208             : 
     209             :   /// Returns a copy of this destination with different parameters.
     210             :   ///
     211             :   /// For typed parameters ensures that raw parameter values in [DestinationParameters.map] are valid.
     212             :   ///
     213           7 :   Destination<T> withParameters(T parameters) {
     214          14 :     final rawParameters = parser.toMap(parameters);
     215           7 :     return copyWith(
     216             :       parameters: parameters
     217          14 :         ..map.clear()
     218          14 :         ..map.addAll(rawParameters),
     219             :     );
     220             :   }
     221             : 
     222             :   /// Creates a copy of this destination with the given fields replaced
     223             :   /// with the new values.
     224             :   ///
     225           7 :   Destination<T> copyWith({
     226             :     DestinationSettings? settings,
     227             :     T? parameters,
     228             :   }) =>
     229           7 :       Destination<T>(
     230           7 :         path: path,
     231           7 :         builder: builder,
     232           7 :         navigator: navigator,
     233           7 :         settings: settings ?? this.settings,
     234           4 :         parameters: parameters ?? this.parameters,
     235           7 :         parser: parser,
     236           7 :         redirections: redirections,
     237           7 :         tag: tag,
     238           7 :         upwardDestinationBuilder: upwardDestinationBuilder,
     239             :       );
     240             : 
     241             :   /// Destinations are equal when their URI string are equal.
     242             :   ///
     243           8 :   @override
     244             :   bool operator ==(Object other) =>
     245             :       identical(this, other) ||
     246           8 :       other is Destination &&
     247          24 :           runtimeType == other.runtimeType &&
     248          24 :           uri == other.uri;
     249             : 
     250           5 :   @override
     251          10 :   int get hashCode => uri.hashCode;
     252             : 
     253           6 :   @override
     254           6 :   String toString() => uri;
     255             : }
     256             : 
     257             : /// Encapsulates the settings attributes which are applied when the navigation state
     258             : /// is updated with the the destination.
     259             : ///
     260             : /// There are convenient factory constructors for commonly used settings.
     261             : /// [material] - pushes the destination to the navigation stack with standard material animations.
     262             : /// [dialog] - display the destination like a dialog
     263             : /// [quiet] - replace the previous destination with the current one without animations.
     264             : ///
     265             : /// See also:
     266             : /// - [DestinationAction]
     267             : /// - [DestinationTransition]
     268             : ///
     269             : class DestinationSettings {
     270             :   /// Creates an instance of [DestinationSettings].
     271             :   ///
     272          10 :   const DestinationSettings({
     273             :     required this.action,
     274             :     required this.transition,
     275             :     this.redirectedFrom,
     276             :     this.reset = false,
     277             :     this.transitionBuilder,
     278             :   }) : assert(
     279          20 :             (transition == DestinationTransition.custom &&
     280             :                     transitionBuilder != null) ||
     281          10 :                 (transition != DestinationTransition.custom),
     282             :             'You have to provide "transitionBuilder" for "custom" transition.');
     283             : 
     284             :   /// Creates a settings to push a destination to the top of navigation
     285             :   /// stack with a standard Material animations.
     286             :   ///
     287             :   const factory DestinationSettings.material() =
     288             :       _DefaultDestinationSettings;
     289             : 
     290             :   /// Creates a settings to displays a destination as a modal dialog.
     291             :   ///
     292             :   const factory DestinationSettings.dialog() =
     293             :       _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() =
     299             :       _QuietDestinationSettings;
     300             : 
     301             :   /// How the destination will update the navigation stack.
     302             :   ///
     303             :   /// See also:
     304             :   ///  - [DestinationAction]
     305             :   ///
     306             :   final DestinationAction action;
     307             : 
     308             :   /// Visual effects that would be applied on updating the stack with the destination.
     309             :   ///
     310             :   /// See also:
     311             :   /// - [DestinationTransition]
     312             :   ///
     313             :   final DestinationTransition transition;
     314             : 
     315             :   /// In case of redirection, contains a destination from which the redirection
     316             :   /// was performed.
     317             :   ///
     318             :   final Destination? redirectedFrom;
     319             : 
     320             :   /// Whether the stack would be cleared before adding the destination.
     321             :   ///
     322             :   final bool reset;
     323             : 
     324             :   /// Function that build custom destination transitions.
     325             :   ///
     326             :   /// It is required when the [transition] value is [DestinationTransition.custom].
     327             :   ///
     328             :   /// See also
     329             :   /// - [RouteTransitionBuilder]
     330             :   ///
     331             :   final RouteTransitionsBuilder? transitionBuilder;
     332             : 
     333             :   /// Creates a copy of this settings with the given fields replaced
     334             :   /// with the new values.
     335             :   ///
     336           4 :   DestinationSettings copyWith({
     337             :     // TODO: Add other properties
     338             :     Destination? redirectedFrom,
     339             :     bool? reset,
     340             :   }) =>
     341           4 :       DestinationSettings(
     342           4 :         action: action,
     343           4 :         transition: transition,
     344           3 :         redirectedFrom: redirectedFrom ?? this.redirectedFrom,
     345           2 :         reset: reset ?? this.reset,
     346           4 :         transitionBuilder: transitionBuilder,
     347             :       );
     348             : }
     349             : 
     350             : class _DefaultDestinationSettings extends DestinationSettings {
     351          10 :   const _DefaultDestinationSettings()
     352          10 :       : super(
     353             :           action: DestinationAction.push,
     354             :           transition: DestinationTransition.material,
     355             :         );
     356             : }
     357             : 
     358             : class _DialogDestinationSettings extends DestinationSettings {
     359           2 :   const _DialogDestinationSettings()
     360           2 :       : super(
     361             :           action: DestinationAction.push,
     362             :           transition: DestinationTransition.materialDialog,
     363             :         );
     364             : }
     365             : 
     366             : class _QuietDestinationSettings extends DestinationSettings {
     367           2 :   const _QuietDestinationSettings()
     368           2 :       : super(
     369             :           action: DestinationAction.replace,
     370             :           transition: DestinationTransition.none,
     371             :         );
     372             : }
     373             : 
     374             : /// An action that is used to update the navigation stack with the destination.
     375             : ///
     376          10 : enum DestinationAction {
     377             :   /// The destination will be added to the navigation stack.
     378             :   /// On navigation back, the destination will be removed from the stack
     379             :   /// and previous destination will be restored.
     380             :   ///
     381             :   push,
     382             : 
     383             :   /// The previous destination will be removed from the navigation stack,
     384             :   /// and the current destination will be added.
     385             :   /// This means that user will not be able to return to previous destination
     386             :   /// by back navigation.
     387             :   ///
     388             :   replace,
     389             : }
     390             : 
     391             : /// Defines transition animations from the previous destination to the current one.
     392             : ///
     393          10 : enum DestinationTransition {
     394             :   /// Standard Material animations.
     395             :   ///
     396             :   material,
     397             : 
     398             :   /// Destination appears as a dialog with Material transitions and modal barrier.
     399             :   ///
     400             :   materialDialog,
     401             : 
     402             :   /// Custom animations.
     403             :   ///
     404             :   custom,
     405             : 
     406             :   /// No animations.
     407             :   ///
     408             :   none,
     409             : }
     410             : 
     411             : /// Base destination parameters.
     412             : ///
     413             : /// Extend this abstract class to define your custom parameters class.
     414             : /// Use [Destination<YourCustomDestinationParameters>()] to make a destination
     415             : /// aware of your custom parameters.
     416             : ///
     417             : /// For custom parameters you also must implement [YouCustomDestinationParser<YourCustomDestinationParameters>]
     418             : /// with [toDestinationParameters()] ans [toMap()] methods, like this:
     419             : /// ``` dart
     420             : /// class YourCustomDestinationParser
     421             : ///     extends DestinationParser<YourCustomDestinationParameters> {
     422             : ///   const YourCustomDestinationParser() : super();
     423             : ///
     424             : ///   @override
     425             : ///   YourCustomDestinationParameters toDestinationParameters(
     426             : ///       Map<String, String> map) {
     427             : ///       //...
     428             : ///   }
     429             : ///
     430             : ///   @override
     431             : ///   Map<String, String> toMap(YourCustomDestinationParameters parameters) {
     432             : ///       //...
     433             : ///   }
     434             : /// }
     435             : /// ```
     436             : ///
     437             : /// See also:
     438             : /// - [DestinationParser]
     439             : ///
     440             : class DestinationParameters {
     441             :   /// Creates a [DestinationParameters] instance.
     442             :   ///
     443           7 :   DestinationParameters([Map<String, String>? map])
     444           4 :       : map = map ?? <String, String>{};
     445             : 
     446             :   /// Contains parameter values parsed from the destination's URI.
     447             :   ///
     448             :   /// The parameter name is a [MapEntry.key], and the value is [MapEntry.value].
     449             :   ///
     450             :   late final Map<String, String> map;
     451             : }

Generated by: LCOV version