LCOV - code coverage report
Current view: top level - src/vroute_elements - vnester_page_base.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 117 157 74.5 %
Date: 2021-04-29 14:25:52 Functions: 0 0 -

          Line data    Source code
       1             : part of '../main.dart';
       2             : 
       3             : /// A [VRouteElement] similar to [VNester] but which allows you to specify your own page
       4             : /// thanks to [pageBuilder]
       5             : class VNesterPageBase extends VRouteElement with VoidVGuard, VoidVPopHandler {
       6             :   /// A list of [VRouteElement] which widget will be accessible in [widgetBuilder]
       7             :   final List<VRouteElement> nestedRoutes;
       8             : 
       9             :   /// The list of possible routes to stack on top of this [VRouteElement]
      10             :   final List<VRouteElement> stackedRoutes;
      11             : 
      12             :   /// A function which creates the [VRouteElement._rootVRouter] associated to this [VRouteElement]
      13             :   ///
      14             :   /// [child] will be the [VRouteElement._rootVRouter] of the matched [VRouteElement] in
      15             :   /// [nestedRoutes]
      16             :   final Widget Function(Widget child) widgetBuilder;
      17             : 
      18             :   /// A LocalKey that will be given to the page which contains the given [_rootVRouter]
      19             :   ///
      20             :   /// This key mostly controls the page animation. If a page remains the same but the key is changes,
      21             :   /// the page gets animated
      22             :   /// The key is by default the value of the current [path] (or [aliases]) with
      23             :   /// the path parameters replaced
      24             :   ///
      25             :   /// Do provide a constant [key] if you don't want this page to animate even if [path] or
      26             :   /// [aliases] path parameters change
      27             :   final LocalKey? key;
      28             : 
      29             :   /// A name for the route which will allow you to easily navigate to it
      30             :   /// using [VRouter.of(context).pushNamed]
      31             :   ///
      32             :   /// Note that [name] should be unique w.r.t every [VRouteElement]
      33             :   final String? name;
      34             : 
      35             :   /// Function which returns a page that will wrap [widget]
      36             :   ///   - key and name should be given to your [Page]
      37             :   ///   - child should be placed as the last child in [Route]
      38             :   final Page Function(LocalKey key, Widget child, String? name) pageBuilder;
      39             : 
      40           4 :   VNesterPageBase({
      41             :     required this.nestedRoutes,
      42             :     required this.widgetBuilder,
      43             :     required this.pageBuilder,
      44             :     this.stackedRoutes = const [],
      45             :     this.key,
      46             :     this.name,
      47             :   }) {
      48           8 :     assert(nestedRoutes.isNotEmpty,
      49             :         'The nestedRoutes of a VNester should not be null, otherwise it can\'t nest');
      50             :   }
      51             : 
      52             :   /// A key for the navigator
      53             :   /// It is created automatically
      54           6 :   late final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(
      55           6 :       debugLabel: 'This is the key of VNesterBase with key $key');
      56             : 
      57             :   /// A hero controller for the navigator
      58             :   /// It is created automatically
      59             :   final HeroController heroController = HeroController();
      60             : 
      61           3 :   @override
      62             :   VRoute? buildRoute(
      63             :     VPathRequestData vPathRequestData, {
      64             :     required VPathMatch parentVPathMatch,
      65             :   }) {
      66             :     // Set localPath to null since a VNesterPageBase marks a limit between localPaths
      67           3 :     VPathMatch newVPathMatch = (parentVPathMatch is ValidVPathMatch)
      68           3 :         ? ValidVPathMatch(
      69           3 :             remainingPath: parentVPathMatch.remainingPath,
      70           3 :             pathParameters: parentVPathMatch.pathParameters,
      71             :             localPath: null,
      72             :           )
      73           2 :         : InvalidVPathMatch(localPath: null);
      74             : 
      75             :     // Try to find valid VRoute from nestedRoutes
      76             :     VRoute? nestedRouteVRoute;
      77           6 :     for (var vRouteElement in nestedRoutes) {
      78           3 :       nestedRouteVRoute = vRouteElement.buildRoute(
      79             :         vPathRequestData,
      80             :         parentVPathMatch: newVPathMatch,
      81             :       );
      82             :       if (nestedRouteVRoute != null) {
      83             :         break;
      84             :       }
      85             :     }
      86             : 
      87             :     // If no child route match, this is not a match
      88             :     if (nestedRouteVRoute == null) {
      89             :       return null;
      90             :     }
      91             : 
      92             :     // Else also try to match stackedRoutes
      93             :     VRoute? stackedRouteVRoute;
      94           5 :     for (var vRouteElement in stackedRoutes) {
      95           2 :       stackedRouteVRoute = vRouteElement.buildRoute(
      96             :         vPathRequestData,
      97             :         parentVPathMatch: newVPathMatch,
      98             :       );
      99             :       if (stackedRouteVRoute != null) {
     100             :         break;
     101             :       }
     102             :     }
     103             : 
     104           3 :     final vRouteElementNode = VRouteElementNode(
     105             :       this,
     106             :       localPath: null,
     107           3 :       nestedVRouteElementNode: nestedRouteVRoute.vRouteElementNode,
     108           2 :       stackedVRouteElementNode: stackedRouteVRoute?.vRouteElementNode,
     109             :     );
     110             : 
     111           3 :     final pathParameters = {
     112           3 :       ...nestedRouteVRoute.pathParameters,
     113           8 :       ...stackedRouteVRoute?.pathParameters ?? {},
     114             :     };
     115             : 
     116           3 :     return VRoute(
     117             :       vRouteElementNode: vRouteElementNode,
     118           3 :       pages: [
     119           6 :         pageBuilder(
     120           9 :           key ?? ValueKey(parentVPathMatch.localPath),
     121           3 :           LocalVRouterData(
     122           3 :             child: NotificationListener<VWidgetGuardMessage>(
     123             :               // This listen to [VWidgetGuardNotification] which is a notification
     124             :               // that a [VWidgetGuard] sends when it is created
     125             :               // When this happens, we store the VWidgetGuard and its context
     126             :               // This will be used to call its afterUpdate and beforeLeave in particular.
     127           1 :               onNotification: (VWidgetGuardMessage vWidgetGuardMessage) {
     128           1 :                 VWidgetGuardMessageRoot(
     129           1 :                   vWidgetGuard: vWidgetGuardMessage.vWidgetGuard,
     130           1 :                   localContext: vWidgetGuardMessage.localContext,
     131             :                   associatedVRouteElement: this,
     132           2 :                 ).dispatch(vPathRequestData.rootVRouterContext);
     133             : 
     134             :                 return true;
     135             :               },
     136           6 :               child: widgetBuilder(
     137           3 :                 Builder(
     138           3 :                   builder: (BuildContext context) {
     139           3 :                     return VRouterHelper(
     140           6 :                       pages: nestedRouteVRoute!.pages.isNotEmpty
     141           3 :                           ? nestedRouteVRoute.pages
     142           0 :                           : [
     143           0 :                               MaterialPage(
     144           0 :                                   child: Center(
     145           0 :                                       child: CircularProgressIndicator())),
     146             :                             ],
     147           3 :                       navigatorKey: navigatorKey,
     148           9 :                       observers: <NavigatorObserver>[heroController] +
     149           9 :                           RootVRouterData.of(context)._state.navigatorObservers,
     150           3 :                       backButtonDispatcher: ChildBackButtonDispatcher(
     151           6 :                           Router.of(context).backButtonDispatcher!),
     152           2 :                       onPopPage: (_, __) {
     153           4 :                         RootVRouterData.of(context).popFromElement(
     154           2 :                           nestedRouteVRoute!.vRouteElementNode
     155           2 :                               .getVRouteElementToPop(),
     156           4 :                           pathParameters: VRouter.of(context).pathParameters,
     157             :                         );
     158             : 
     159             :                         // We always prevent popping because we handle it in VRouter
     160             :                         return false;
     161             :                       },
     162           0 :                       onSystemPopPage: () async {
     163           0 :                         await RootVRouterData.of(context).systemPopFromElement(
     164           0 :                           nestedRouteVRoute!.vRouteElementNode
     165           0 :                               .getVRouteElementToPop(),
     166           0 :                           pathParameters: VRouter.of(context).pathParameters,
     167             :                         );
     168             : 
     169             :                         // We always prevent popping because we handle it in VRouter
     170             :                         return true;
     171             :                       },
     172             :                     );
     173             :                   },
     174             :                 ),
     175             :               ),
     176             :             ),
     177             :             vRouteElementNode: vRouteElementNode,
     178           3 :             url: vPathRequestData.url,
     179           3 :             previousUrl: vPathRequestData.previousUrl,
     180           3 :             historyState: vPathRequestData.historyState,
     181             :             pathParameters: pathParameters,
     182           3 :             queryParameters: vPathRequestData.queryParameters,
     183           3 :             context: vPathRequestData.rootVRouterContext,
     184             :           ),
     185           6 :           name ?? parentVPathMatch.localPath,
     186             :         ),
     187           8 :         ...stackedRouteVRoute?.pages ?? [],
     188             :       ],
     189           3 :       pathParameters: nestedRouteVRoute.pathParameters,
     190           6 :       vRouteElements: <VRouteElement>[this] +
     191           6 :           nestedRouteVRoute.vRouteElements +
     192           5 :           (stackedRouteVRoute?.vRouteElements ?? []),
     193             :     );
     194             :   }
     195             : 
     196           2 :   GetPathFromNameResult getPathFromName(
     197             :     String nameToMatch, {
     198             :     required Map<String, String> pathParameters,
     199             :     required GetNewParentPathResult parentPathResult,
     200             :     required Map<String, String> remainingPathParameters,
     201             :   }) {
     202           2 :     final childNameResults = <GetPathFromNameResult>[];
     203             : 
     204             :     // Check if any nestedRoute matches the name
     205           4 :     for (var vRouteElement in nestedRoutes) {
     206           2 :       childNameResults.add(
     207           2 :         vRouteElement.getPathFromName(
     208             :           nameToMatch,
     209             :           pathParameters: pathParameters,
     210             :           parentPathResult: parentPathResult,
     211             :           remainingPathParameters: remainingPathParameters,
     212             :         ),
     213             :       );
     214           4 :       if (childNameResults.last is ValidNameResult) {
     215           1 :         return childNameResults.last;
     216             :       }
     217             :     }
     218             : 
     219             :     // Check if any subroute matches the name
     220           3 :     for (var vRouteElement in stackedRoutes) {
     221           1 :       childNameResults.add(
     222           1 :         vRouteElement.getPathFromName(
     223             :           nameToMatch,
     224             :           pathParameters: pathParameters,
     225             :           parentPathResult: parentPathResult,
     226             :           remainingPathParameters: remainingPathParameters,
     227             :         ),
     228             :       );
     229           2 :       if (childNameResults.last is ValidNameResult) {
     230           1 :         return childNameResults.last;
     231             :       }
     232             :     }
     233             : 
     234             :     // If no subroute or stackedRoute matches the name, try to match this name
     235           4 :     if (name == nameToMatch) {
     236             :       // If path or any alias is valid considering the given path parameters, return this
     237           2 :       if (parentPathResult is ValidParentPathResult) {
     238           2 :         if (parentPathResult.path == null) {
     239             :           // If this path is null, we add a NullPathErrorNameResult
     240           0 :           childNameResults.add(NullPathErrorNameResult(name: nameToMatch));
     241             :         } else {
     242           2 :           if (remainingPathParameters.isNotEmpty) {
     243             :             // If there are path parameters remaining, wee add a PathParamsErrorsNameResult
     244           0 :             childNameResults.add(
     245           0 :               PathParamsErrorsNameResult(
     246             :                 name: nameToMatch,
     247           0 :                 values: [
     248           0 :                   OverlyPathParamsError(
     249           0 :                     pathParams: pathParameters.keys.toList(),
     250             :                     expectedPathParams:
     251           0 :                         parentPathResult.pathParameters.keys.toList(),
     252             :                   ),
     253             :                 ],
     254             :               ),
     255             :             );
     256             :           } else {
     257             :             // Else the result is valid
     258           4 :             return ValidNameResult(path: parentPathResult.path!);
     259             :           }
     260             :         }
     261             :       } else {
     262           2 :         assert(parentPathResult is PathParamsErrorNewParentPath);
     263           2 :         childNameResults.add(
     264           2 :           PathParamsErrorsNameResult(
     265             :             name: nameToMatch,
     266           2 :             values: [
     267           2 :               MissingPathParamsError(
     268           4 :                 pathParams: pathParameters.keys.toList(),
     269             :                 missingPathParams:
     270             :                     (parentPathResult as PathParamsErrorNewParentPath)
     271           2 :                         .pathParameters,
     272             :               ),
     273             :             ],
     274             :           ),
     275             :         );
     276             :       }
     277             :     }
     278             : 
     279             :     // If we don't have any valid result
     280             : 
     281             :     // If some stackedRoute returned PathParamsPopError, aggregate them
     282           2 :     final pathParamsNameErrors = PathParamsErrorsNameResult(
     283             :       name: nameToMatch,
     284           2 :       values: childNameResults.fold<List<PathParamsError>>(
     285           2 :         <PathParamsError>[],
     286           2 :         (previousValue, element) {
     287           2 :           return previousValue +
     288           6 :               ((element is PathParamsErrorsNameResult) ? element.values : []);
     289             :         },
     290             :       ),
     291             :     );
     292             : 
     293             :     // If there was any PathParamsPopError, we have some pathParamsPopErrors.values
     294             :     // and therefore should return this
     295           4 :     if (pathParamsNameErrors.values.isNotEmpty) {
     296             :       return pathParamsNameErrors;
     297             :     }
     298             : 
     299             :     // Else try to find a NullPathError
     300           0 :     if (childNameResults.indexWhere(
     301           0 :             (childNameResult) => childNameResult is NullPathErrorNameResult) !=
     302           0 :         -1) {
     303           0 :       return NullPathErrorNameResult(name: nameToMatch);
     304             :     }
     305             : 
     306             :     // Else return a NotFoundError
     307           0 :     return NotFoundErrorNameResult(name: nameToMatch);
     308             :   }
     309             : 
     310           3 :   GetPathFromPopResult getPathFromPop(
     311             :     VRouteElement elementToPop, {
     312             :     required Map<String, String> pathParameters,
     313             :     required GetNewParentPathResult parentPathResult,
     314             :   }) {
     315             :     // If vRouteElement is this, then this is the element to pop so we return null
     316           3 :     if (elementToPop == this) {
     317           0 :       if (parentPathResult is ValidParentPathResult) {
     318           0 :         return FoundPopResult(
     319           0 :           path: parentPathResult.path,
     320             :           didPop: true,
     321           0 :           poppedVRouteElements: [this],
     322             :         );
     323             :       } else {
     324           0 :         assert(parentPathResult is PathParamsErrorNewParentPath);
     325           0 :         return PathParamsPopErrors(
     326           0 :           values: [
     327           0 :             MissingPathParamsError(
     328           0 :               pathParams: pathParameters.keys.toList(),
     329             :               missingPathParams:
     330             :                   (parentPathResult as PathParamsErrorNewParentPath)
     331           0 :                       .pathParameters,
     332             :             ),
     333             :           ],
     334             :         );
     335             :       }
     336             :     }
     337             : 
     338           3 :     final popErrorResults = <GetPathFromPopResult>[];
     339             : 
     340             :     // Try to pop from the nestedRoutes
     341           6 :     for (var vRouteElement in nestedRoutes) {
     342           3 :       GetPathFromPopResult childPopResult = vRouteElement.getPathFromPop(
     343             :         elementToPop,
     344             :         pathParameters: pathParameters,
     345             :         parentPathResult: parentPathResult,
     346             :       );
     347           3 :       if (childPopResult is FoundPopResult) {
     348           3 :         if (childPopResult.didPop) {
     349             :           // if the nestedRoute popped, we should pop too
     350           3 :           if (parentPathResult is ValidParentPathResult) {
     351           3 :             return FoundPopResult(
     352           3 :               path: parentPathResult.path,
     353             :               didPop: true,
     354             :               poppedVRouteElements:
     355           9 :                   <VRouteElement>[this] + childPopResult.poppedVRouteElements,
     356             :             );
     357             :           } else {
     358           0 :             assert(parentPathResult is PathParamsErrorNewParentPath);
     359           0 :             popErrorResults.add(
     360           0 :               PathParamsPopErrors(
     361           0 :                 values: [
     362           0 :                   MissingPathParamsError(
     363           0 :                     pathParams: pathParameters.keys.toList(),
     364             :                     missingPathParams:
     365             :                         (parentPathResult as PathParamsErrorNewParentPath)
     366           0 :                             .pathParameters,
     367             :                   ),
     368             :                 ],
     369             :               ),
     370             :             );
     371             :           }
     372             :         } else {
     373           3 :           return FoundPopResult(
     374           3 :             path: childPopResult.path,
     375             :             didPop: false,
     376           3 :             poppedVRouteElements: childPopResult.poppedVRouteElements,
     377             :           );
     378             :         }
     379             :       } else {
     380           2 :         popErrorResults.add(childPopResult);
     381             :       }
     382             :     }
     383             : 
     384             :     // Try to pop from the subRoutes
     385           4 :     for (var vRouteElement in stackedRoutes) {
     386           2 :       GetPathFromPopResult childPopResult = vRouteElement.getPathFromPop(
     387             :         elementToPop,
     388             :         pathParameters: pathParameters,
     389             :         parentPathResult: parentPathResult,
     390             :       );
     391           2 :       if (childPopResult is FoundPopResult) {
     392           2 :         return FoundPopResult(
     393           2 :           path: childPopResult.path,
     394             :           didPop: false,
     395           2 :           poppedVRouteElements: childPopResult.poppedVRouteElements,
     396             :         );
     397             :       } else {
     398           0 :         popErrorResults.add(childPopResult);
     399             :       }
     400             :     }
     401             : 
     402             :     // If we don't have any valid result
     403             : 
     404             :     // If some stackedRoute returned PathParamsPopError, aggregate them
     405           2 :     final pathParamsPopErrors = PathParamsPopErrors(
     406           2 :       values: popErrorResults.fold<List<MissingPathParamsError>>(
     407           2 :         <MissingPathParamsError>[],
     408           2 :         (previousValue, element) {
     409           2 :           return previousValue +
     410           4 :               ((element is PathParamsPopErrors) ? element.values : []);
     411             :         },
     412             :       ),
     413             :     );
     414             : 
     415             :     // If there was any PathParamsPopError, we have some pathParamsPopErrors.values
     416             :     // and therefore should return this
     417           4 :     if (pathParamsPopErrors.values.isNotEmpty) {
     418             :       return pathParamsPopErrors;
     419             :     }
     420             : 
     421             :     // If none of the stackedRoutes popped, this did not pop, and there was no path parameters issue, return not found
     422             :     // This should never happen
     423           0 :     return ErrorNotFoundGetPathFromPopResult();
     424             :   }
     425             : }

Generated by: LCOV version 1.14