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