Line data Source code
1 : import 'package:flutter/cupertino.dart';
2 : import 'package:flutter/material.dart';
3 :
4 : import '../beamer.dart';
5 :
6 : /// Types for how to route should be built.
7 10 : enum BeamPageType {
8 : material,
9 : cupertino,
10 : fadeTransition,
11 : slideTransition,
12 : scaleTransition,
13 : noTransition,
14 : }
15 :
16 : /// A wrapper for screens in a navigation stack.
17 : class BeamPage extends Page {
18 17 : const BeamPage({
19 : LocalKey? key,
20 : String? name,
21 : required this.child,
22 : this.title,
23 : this.onPopPage = defaultOnPopPage,
24 : this.popToNamed,
25 : this.type = BeamPageType.material,
26 : this.routeBuilder,
27 : this.fullScreenDialog = false,
28 : this.keepQueryOnPop = false,
29 6 : }) : super(key: key, name: name);
30 :
31 : /// The default pop behavior for [BeamPage].
32 3 : static bool defaultOnPopPage(
33 : BuildContext context,
34 : BeamerDelegate delegate,
35 : BeamPage poppedPage,
36 : ) {
37 3 : final beamLocation = delegate.currentBeamLocation;
38 9 : final previousRouteInformation = delegate.routeHistory.length > 1
39 15 : ? delegate.routeHistory[delegate.routeHistory.length - 2]
40 : : null;
41 : final previousUri = previousRouteInformation != null
42 6 : ? Uri.parse(previousRouteInformation.location ?? '/')
43 : : null;
44 :
45 9 : final location = beamLocation.state.routeInformation.location ?? '/';
46 6 : final pathSegments = Uri.parse(location).pathSegments;
47 6 : final queryParameters = Uri.parse(location).queryParameters;
48 3 : var popUri = Uri(
49 6 : pathSegments: List.from(pathSegments)..removeLast(),
50 3 : queryParameters: poppedPage.keepQueryOnPop ? queryParameters : null,
51 : );
52 6 : final popUriPath = '/' + popUri.path;
53 :
54 3 : popUri = Uri(
55 3 : pathSegments: popUri.pathSegments,
56 : queryParameters:
57 8 : (popUriPath == previousUri?.path && !poppedPage.keepQueryOnPop)
58 2 : ? previousUri?.queryParameters
59 3 : : popUri.queryParameters,
60 : );
61 :
62 3 : delegate.removeLastRouteInformation();
63 3 : delegate.update(
64 6 : configuration: delegate.configuration.copyWith(
65 : location:
66 11 : popUriPath + (popUri.query.isNotEmpty ? '?${popUri.query}' : ''),
67 : ),
68 : );
69 :
70 : return true;
71 : }
72 :
73 : /// The concrete Widget representing app's screen.
74 : final Widget child;
75 :
76 : /// The BeamPage's title. On the web, this is used for the browser tab title.
77 : final String? title;
78 :
79 : /// Overrides the default pop by executing an arbitrary closure.
80 : /// Mainly used to manually update the [delegate.currentBeamLocation] state.
81 : ///
82 : /// [poppedPage] is this [BeamPage].
83 : ///
84 : /// Return `false` (rarely used) to prevent **any** navigation from happening,
85 : /// otherwise return `true`.
86 : ///
87 : /// More powerful than [popToNamed].
88 : final bool Function(
89 : BuildContext context,
90 : BeamerDelegate delegate,
91 : BeamPage poppedPage,
92 : ) onPopPage;
93 :
94 : /// Overrides the default pop by beaming to specified URI string.
95 : ///
96 : /// Less powerful than [onPopPage].
97 : final String? popToNamed;
98 :
99 : /// The type to determine how a route should be built.
100 : ///
101 : /// See [BeamPageType] for available types.
102 : final BeamPageType type;
103 :
104 : /// A builder for custom [Route] to use in [createRoute].
105 : ///
106 : /// [settings] must be passed to [PageRoute.settings].
107 : /// [child] is the child of this [BeamPage]
108 : final Route Function(RouteSettings settings, Widget child)? routeBuilder;
109 :
110 : /// Whether to present current [BeamPage] as a fullscreen dialog
111 : ///
112 : /// On iOS, dialog transitions animate differently and are also not closeable with the back swipe gesture
113 : final bool fullScreenDialog;
114 :
115 : /// When this [BeamPage] pops from [Navigator] stack, whether to keep the
116 : /// query parameters within current [BeamLocation].
117 : ///
118 : /// Defaults to `false`.
119 : final bool keepQueryOnPop;
120 :
121 6 : @override
122 : Route createRoute(BuildContext context) {
123 6 : if (routeBuilder != null) {
124 2 : return routeBuilder!(this, child);
125 : }
126 6 : switch (type) {
127 6 : case BeamPageType.cupertino:
128 1 : return CupertinoPageRoute(
129 1 : title: title,
130 1 : fullscreenDialog: fullScreenDialog,
131 : settings: this,
132 2 : builder: (context) => child,
133 : );
134 6 : case BeamPageType.fadeTransition:
135 1 : return PageRouteBuilder(
136 1 : fullscreenDialog: fullScreenDialog,
137 : settings: this,
138 2 : pageBuilder: (_, __, ___) => child,
139 2 : transitionsBuilder: (_, animation, __, child) => FadeTransition(
140 : opacity: animation,
141 : child: child,
142 : ),
143 : );
144 6 : case BeamPageType.slideTransition:
145 1 : return PageRouteBuilder(
146 1 : fullscreenDialog: fullScreenDialog,
147 : settings: this,
148 2 : pageBuilder: (_, __, ___) => child,
149 2 : transitionsBuilder: (_, animation, __, child) => SlideTransition(
150 1 : position: animation.drive(
151 1 : Tween(begin: const Offset(0, 1), end: const Offset(0, 0))
152 2 : .chain(CurveTween(curve: Curves.ease))),
153 : child: child,
154 : ),
155 : );
156 6 : case BeamPageType.scaleTransition:
157 1 : return PageRouteBuilder(
158 1 : fullscreenDialog: fullScreenDialog,
159 : settings: this,
160 2 : pageBuilder: (_, __, ___) => child,
161 2 : transitionsBuilder: (_, animation, __, child) => ScaleTransition(
162 : scale: animation,
163 : child: child,
164 : ),
165 : );
166 6 : case BeamPageType.noTransition:
167 1 : return PageRouteBuilder(
168 1 : fullscreenDialog: fullScreenDialog,
169 : settings: this,
170 2 : pageBuilder: (context, animation, secondaryAnimation) => child,
171 : );
172 : default:
173 6 : return MaterialPageRoute(
174 6 : fullscreenDialog: fullScreenDialog,
175 : settings: this,
176 12 : builder: (context) => child,
177 : );
178 : }
179 : }
180 : }
|