Line data Source code
1 : import 'dart:convert';
2 :
3 : import 'package:flutter/foundation.dart';
4 : import 'package:flutter/widgets.dart';
5 :
6 : import './utils.dart';
7 : import './beam_location.dart';
8 :
9 : /// A class to mix with when defining a custom state for [BeamLocation].
10 : ///
11 : /// [fromRouteInformation] and [toRouteInformation] need to be implemented in
12 : /// order to notify the platform of current [RouteInformation] that corresponds
13 : /// to the state.
14 : mixin RouteInformationSerializable<T> {
15 : T fromRouteInformation(RouteInformation routeInformation);
16 : RouteInformation toRouteInformation();
17 18 : RouteInformation get routeInformation => toRouteInformation();
18 : }
19 :
20 : /// A pre-made state for [BeamLocation].
21 : ///
22 : /// This can be used when one does not desire to define its own state.
23 : class BeamState with RouteInformationSerializable<BeamState> {
24 9 : BeamState({
25 : this.pathPatternSegments = const <String>[],
26 : this.pathParameters = const <String, String>{},
27 : this.queryParameters = const <String, String>{},
28 : this.routeState,
29 9 : }) : assert(() {
30 9 : json.encode(routeState);
31 : return true;
32 : }()) {
33 9 : configure();
34 : }
35 :
36 : /// Creates a [BeamState] from given [uri] and optional [data].
37 : ///
38 : /// If [beamLocation] is given, then it will take into consideration
39 : /// its `pathPatterns` to populate the [pathParameters] attribute.
40 : ///
41 : /// See [Utils.createBeamState].
42 9 : factory BeamState.fromUri(
43 : Uri uri, {
44 : BeamLocation? beamLocation,
45 : Object? routeState,
46 : }) {
47 9 : return Utils.createBeamState(
48 : uri,
49 : beamLocation: beamLocation,
50 : routeState: routeState,
51 : );
52 : }
53 :
54 : /// Creates a [BeamState] from given [uriString] and optional [data].
55 : ///
56 : /// If [beamLocation] is given, then it will take into consideration
57 : /// its path blueprints to populate the [pathParameters] attribute.
58 : ///
59 : /// See [BeamState.fromUri].
60 1 : factory BeamState.fromUriString(
61 : String uriString, {
62 : BeamLocation? beamLocation,
63 : Object? routeState,
64 : }) {
65 1 : uriString = Utils.trimmed(uriString);
66 1 : final uri = Uri.parse(uriString);
67 1 : return BeamState.fromUri(
68 : uri,
69 : beamLocation: beamLocation,
70 : routeState: routeState,
71 : );
72 : }
73 :
74 : /// Creates a [BeamState] from given [routeInformation].
75 : ///
76 : /// If [beamLocation] is given, then it will take into consideration
77 : /// its path blueprints to populate the [pathParameters] attribute.
78 : ///
79 : /// See [BeamState.fromUri].
80 9 : factory BeamState.fromRouteInformation(
81 : RouteInformation routeInformation, {
82 : BeamLocation? beamLocation,
83 : }) {
84 9 : return BeamState.fromUri(
85 18 : Uri.parse(routeInformation.location ?? '/'),
86 : beamLocation: beamLocation,
87 9 : routeState: routeInformation.state,
88 : );
89 : }
90 :
91 : /// Path segments of the current URI,
92 : /// in the form as it's defined in [BeamLocation.pathPatterns].
93 : ///
94 : /// If current URI is '/books/1', this will be `['books', ':bookId']`.
95 : final List<String> pathPatternSegments;
96 :
97 : /// Path parameters from the URI,
98 : /// in the form as it's defined in [BeamLocation.pathPatterns].
99 : ///
100 : /// If current URI is '/books/1', this will be `{'bookId': '1'}`.
101 : final Map<String, String> pathParameters;
102 :
103 : /// Query parameters of the current URI.
104 : ///
105 : /// If current URI is '/books?title=str', this will be `{'title': 'str'}`.
106 : final Map<String, String> queryParameters;
107 :
108 : /// An object that will be passed to [RouteInformation.state]
109 : /// that is stored as a part of browser history entry.
110 : ///
111 : /// This needs to be serializable.
112 : final Object? routeState;
113 :
114 : late Uri _uriBlueprint;
115 :
116 : /// Current URI object in the "blueprint form",
117 : /// as it's defined in [BeamLocation.pathPatterns].
118 : ///
119 : /// This is constructed from [pathPatternSegments] and [queryParameters].
120 : /// See more at [configure].
121 4 : Uri get uriBlueprint => _uriBlueprint;
122 :
123 : late Uri _uri;
124 :
125 : /// Current URI object in the "real form",
126 : /// as it should be shown in browser's URL bar.
127 : ///
128 : /// This is constructed from [pathPatternSegments] and [queryParameters],
129 : /// with the addition of replacing each pathPatternSegment of the form ':*'
130 : /// with a coresponding value from [pathParameters].
131 : ///
132 : /// See more at [configure].
133 18 : Uri get uri => _uri;
134 :
135 : /// Copies this with configuration for specific [BeamLocation].
136 1 : BeamState copyForLocation(BeamLocation beamLocation, Object? routeState) {
137 1 : return Utils.createBeamState(
138 1 : uri,
139 : beamLocation: beamLocation,
140 : routeState: routeState,
141 : );
142 : }
143 :
144 : /// Returns a configured copy of this.
145 3 : BeamState copyWith({
146 : List<String>? pathPatternSegments,
147 : Map<String, String>? pathParameters,
148 : Map<String, String>? queryParameters,
149 : Object? routeState,
150 : }) =>
151 3 : BeamState(
152 2 : pathPatternSegments: pathPatternSegments ?? this.pathPatternSegments,
153 1 : pathParameters: pathParameters ?? this.pathParameters,
154 2 : queryParameters: queryParameters ?? this.queryParameters,
155 3 : routeState: routeState ?? this.routeState,
156 3 : )..configure();
157 :
158 : /// Constructs [uriBlueprint] and [uri].
159 9 : void configure() {
160 18 : _uriBlueprint = Uri(
161 27 : path: '/' + pathPatternSegments.join('/'),
162 24 : queryParameters: queryParameters.isEmpty ? null : queryParameters,
163 : );
164 18 : final pathSegments = pathPatternSegments.toList();
165 27 : for (int i = 0; i < pathSegments.length; i++) {
166 45 : if (pathSegments[i].isNotEmpty && pathSegments[i][0] == ':') {
167 12 : final key = pathSegments[i].substring(1);
168 12 : if (pathParameters.containsKey(key)) {
169 18 : pathSegments[i] = pathParameters[key]!;
170 : }
171 : }
172 : }
173 18 : _uri = Uri(
174 18 : path: '/' + pathSegments.join('/'),
175 24 : queryParameters: queryParameters.isEmpty ? null : queryParameters,
176 : );
177 : }
178 :
179 1 : @override
180 : BeamState fromRouteInformation(RouteInformation routeInformation) =>
181 1 : BeamState.fromRouteInformation(routeInformation);
182 :
183 7 : @override
184 7 : RouteInformation toRouteInformation() => RouteInformation(
185 14 : location: uri.toString(),
186 7 : state: routeState,
187 : );
188 :
189 1 : @override
190 4 : int get hashCode => hashValues(uri, json.encode(routeState));
191 :
192 1 : @override
193 : bool operator ==(Object other) {
194 1 : return other is BeamState &&
195 3 : other.uri == uri &&
196 5 : json.encode(other.routeState) == json.encode(routeState);
197 : }
198 : }
|