LCOV - code coverage report
Current view: top level - src - beam_page.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 73 75 97.3 %
Date: 2021-12-12 20:09:16 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:flutter/cupertino.dart';
       2             : import 'package:flutter/material.dart';
       3             : 
       4             : import 'package:beamer/beamer.dart';
       5             : 
       6             : /// Types for how to route should be built.
       7             : ///
       8             : /// See [BeamPage.type]
       9          12 : enum BeamPageType {
      10             :   /// An enum for Material page type.
      11             :   material,
      12             : 
      13             :   /// An enum for Cupertino page type.
      14             :   cupertino,
      15             : 
      16             :   /// An enum for a page type with fade transition.
      17             :   fadeTransition,
      18             : 
      19             :   /// An enum for a page type with slide transition.
      20             :   slideTransition,
      21             : 
      22             :   /// An enum for a page type with scale transition.
      23             :   scaleTransition,
      24             : 
      25             :   /// An enum for a page type with no transition.
      26             :   noTransition,
      27             : }
      28             : 
      29             : /// A wrapper for screens in a navigation stack.
      30             : class BeamPage extends Page {
      31             :   /// Creates a [BeamPage] with specified properties.
      32             :   ///
      33             :   /// [child] is required and typicially represents a screen of the app.
      34          19 :   const BeamPage({
      35             :     LocalKey? key,
      36             :     String? name,
      37             :     required this.child,
      38             :     this.title,
      39             :     this.onPopPage = pathSegmentPop,
      40             :     this.popToNamed,
      41             :     this.type = BeamPageType.material,
      42             :     this.routeBuilder,
      43             :     this.fullScreenDialog = false,
      44             :     this.keepQueryOnPop = false,
      45           6 :   }) : super(key: key, name: name);
      46             : 
      47             :   /// The default pop behavior for [BeamPage].
      48             :   ///
      49             :   /// Pops the last path segment from URI and calls [BeamerDelegate.update].
      50           3 :   static bool pathSegmentPop(
      51             :     BuildContext context,
      52             :     BeamerDelegate delegate,
      53             :     RouteInformationSerializable state,
      54             :     BeamPage poppedPage,
      55             :   ) {
      56           6 :     if (!delegate.navigator.canPop()) {
      57             :       return false;
      58             :     }
      59             : 
      60             :     // take the data in case we remove the BeamLocation from history
      61             :     // and generate a new one (but the same).
      62           6 :     final data = delegate.currentBeamLocation.data;
      63             : 
      64             :     // Take the history element that is being popped and the one before
      65             :     // as they will be compared later on to fine-tune the pop experience.
      66           3 :     final poppedHistoryElement = delegate.removeLastHistoryElement()!;
      67           6 :     final previousHistoryElement = delegate.beamingHistory.isNotEmpty
      68          12 :         ? delegate.beamingHistory.last.history.last
      69             :         : null;
      70             : 
      71             :     // Convert both to Uri as their path and query will be compared.
      72           3 :     final poppedUri = Uri.parse(
      73           6 :       poppedHistoryElement.routeInformation.location ?? '/',
      74             :     );
      75           3 :     final previousUri = Uri.parse(
      76             :       previousHistoryElement != null
      77           6 :           ? previousHistoryElement.routeInformation.location ?? '/'
      78           0 :           : delegate.initialPath,
      79             :     );
      80             : 
      81           3 :     final poppedPathSegments = poppedUri.pathSegments;
      82           3 :     final poppedQueryParameters = poppedUri.queryParameters;
      83             : 
      84             :     // Pop path is obtained via removing the last path segment from path
      85             :     // that is beeing popped.
      86           6 :     final popPathSegments = List.from(poppedPathSegments)..removeLast();
      87           6 :     final popPath = '/' + popPathSegments.join('/');
      88           3 :     final popUri = Uri(
      89             :       path: popPath,
      90           3 :       queryParameters: poppedPage.keepQueryOnPop
      91           0 :           ? poppedQueryParameters.isEmpty
      92             :               ? null
      93             :               : poppedQueryParameters
      94           6 :           : (popPath == previousUri.path)
      95           4 :               ? previousUri.queryParameters.isEmpty
      96             :                   ? null
      97           1 :                   : previousUri.queryParameters
      98             :               : null,
      99             :     );
     100             : 
     101             :     // We need the route information from the route we are trying to pop to.
     102             :     //
     103             :     // Remove the last history element if it's the same as the path
     104             :     // we're trying to pop to, because `update` will add it to history.
     105             :     // This is `false` in case we deep-linked.
     106             :     //
     107             :     // Otherwise, find the route information with popPath in history.
     108             :     RouteInformation? lastRouteInformation;
     109           6 :     if (popPath == previousUri.path) {
     110             :       lastRouteInformation =
     111           4 :           delegate.removeLastHistoryElement()?.routeInformation;
     112             :     } else {
     113             :       // find the last
     114             :       var found = false;
     115           6 :       for (var beamLocation in delegate.beamingHistory.reversed) {
     116             :         if (found) {
     117             :           break;
     118             :         }
     119           6 :         for (var historyElement in beamLocation.history.reversed) {
     120             :           final uri =
     121           6 :               Uri.parse(historyElement.routeInformation.location ?? '/');
     122           4 :           if (uri.path == popPath) {
     123           1 :             lastRouteInformation = historyElement.routeInformation;
     124             :             found = true;
     125             :             break;
     126             :           }
     127             :         }
     128             :       }
     129             :     }
     130             : 
     131           3 :     delegate.update(
     132           6 :       configuration: delegate.configuration.copyWith(
     133           3 :         location: popUri.toString(),
     134           2 :         state: lastRouteInformation?.state,
     135             :       ),
     136             :       data: data,
     137             :     );
     138             : 
     139             :     return true;
     140             :   }
     141             : 
     142             :   /// Pops the last route from history and calls [BeamerDelegate.update].
     143           1 :   static bool routePop(
     144             :     BuildContext context,
     145             :     BeamerDelegate delegate,
     146             :     RouteInformationSerializable state,
     147             :     BeamPage poppedPage,
     148             :   ) {
     149           2 :     if (delegate.beamingHistoryCompleteLength < 2) {
     150             :       return false;
     151             :     }
     152             : 
     153           1 :     delegate.removeLastHistoryElement();
     154           1 :     final previousHistoryElement = delegate.removeLastHistoryElement()!;
     155             : 
     156           1 :     delegate.update(
     157           2 :       configuration: previousHistoryElement.routeInformation.copyWith(),
     158             :     );
     159             : 
     160             :     return true;
     161             :   }
     162             : 
     163             :   /// The concrete Widget representing app's screen.
     164             :   final Widget child;
     165             : 
     166             :   /// The BeamPage's title. On the web, this is used for the browser tab title.
     167             :   final String? title;
     168             : 
     169             :   /// Overrides the default pop by executing an arbitrary closure.
     170             :   /// Mainly used to manually update the [delegate.currentBeamLocation] state.
     171             :   ///
     172             :   /// [poppedPage] is this [BeamPage].
     173             :   ///
     174             :   /// Return `false` (rarely used) to prevent **any** navigation from happening,
     175             :   /// otherwise return `true`.
     176             :   ///
     177             :   /// More powerful than [popToNamed].
     178             :   final bool Function(
     179             :     BuildContext context,
     180             :     BeamerDelegate delegate,
     181             :     RouteInformationSerializable state,
     182             :     BeamPage poppedPage,
     183             :   ) onPopPage;
     184             : 
     185             :   /// Overrides the default pop by beaming to specified URI string.
     186             :   ///
     187             :   /// Less powerful than [onPopPage].
     188             :   final String? popToNamed;
     189             : 
     190             :   /// The type to determine how a route should be built.
     191             :   ///
     192             :   /// See [BeamPageType] for available types.
     193             :   final BeamPageType type;
     194             : 
     195             :   /// A builder for custom [Route] to use in [createRoute].
     196             :   ///
     197             :   /// [context] is the build context.
     198             :   /// [child] is the child of this [BeamPage]
     199             :   /// [settings] must be passed to [PageRoute.settings].
     200             :   final Route Function(
     201             :       BuildContext context, RouteSettings settings, Widget child)? routeBuilder;
     202             : 
     203             :   /// Whether to present current [BeamPage] as a fullscreen dialog
     204             :   ///
     205             :   /// On iOS, dialog transitions animate differently and are also not closeable with the back swipe gesture
     206             :   final bool fullScreenDialog;
     207             : 
     208             :   /// When this [BeamPage] pops from [Navigator] stack, whether to keep the
     209             :   /// query parameters within current [BeamLocation].
     210             :   ///
     211             :   /// Defaults to `false`.
     212             :   final bool keepQueryOnPop;
     213             : 
     214           6 :   @override
     215             :   Route createRoute(BuildContext context) {
     216           6 :     if (routeBuilder != null) {
     217           3 :       return routeBuilder!(context, this, child);
     218             :     }
     219           6 :     switch (type) {
     220           6 :       case BeamPageType.cupertino:
     221           1 :         return CupertinoPageRoute(
     222           1 :           title: title,
     223           1 :           fullscreenDialog: fullScreenDialog,
     224             :           settings: this,
     225           2 :           builder: (context) => child,
     226             :         );
     227           6 :       case BeamPageType.fadeTransition:
     228           1 :         return PageRouteBuilder(
     229           1 :           fullscreenDialog: fullScreenDialog,
     230             :           settings: this,
     231           2 :           pageBuilder: (_, __, ___) => child,
     232           2 :           transitionsBuilder: (_, animation, __, child) => FadeTransition(
     233             :             opacity: animation,
     234             :             child: child,
     235             :           ),
     236             :         );
     237           6 :       case BeamPageType.slideTransition:
     238           1 :         return PageRouteBuilder(
     239           1 :           fullscreenDialog: fullScreenDialog,
     240             :           settings: this,
     241           2 :           pageBuilder: (_, __, ___) => child,
     242           2 :           transitionsBuilder: (_, animation, __, child) => SlideTransition(
     243           1 :             position: animation.drive(
     244           1 :                 Tween(begin: const Offset(0, 1), end: const Offset(0, 0))
     245           2 :                     .chain(CurveTween(curve: Curves.ease))),
     246             :             child: child,
     247             :           ),
     248             :         );
     249           6 :       case BeamPageType.scaleTransition:
     250           1 :         return PageRouteBuilder(
     251           1 :           fullscreenDialog: fullScreenDialog,
     252             :           settings: this,
     253           2 :           pageBuilder: (_, __, ___) => child,
     254           2 :           transitionsBuilder: (_, animation, __, child) => ScaleTransition(
     255             :             scale: animation,
     256             :             child: child,
     257             :           ),
     258             :         );
     259           6 :       case BeamPageType.noTransition:
     260           1 :         return PageRouteBuilder(
     261           1 :           fullscreenDialog: fullScreenDialog,
     262             :           settings: this,
     263           2 :           pageBuilder: (context, animation, secondaryAnimation) => child,
     264             :         );
     265             :       default:
     266           6 :         return MaterialPageRoute(
     267           6 :           fullscreenDialog: fullScreenDialog,
     268             :           settings: this,
     269          12 :           builder: (context) => child,
     270             :         );
     271             :     }
     272             :   }
     273             : }

Generated by: LCOV version 1.14