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