the-mews/app/lib/application/pages/mews_urls.dart

76 lines
2.9 KiB
Dart

/// The addresses of the client's own pages, under the `mews:` scheme.
/// One spelling of the scheme, shared by the navigator and the page builders.
library;
/// Internal addresses come in two shapes. A page address is a navigation and
/// joins the walk history; an action address under `do/` changes something and
/// leaves the reader where they are.
///
/// View state (sort, search, tag filter) rides in the query string rather than
/// in a signal, because swipe previews are built from the address alone and
/// going back should undo a sort.
abstract final class MewsUrls {
static const scheme = 'mews';
static const hub = 'mews:hub';
static const posts = 'mews:posts';
static const bookmarks = 'mews:bookmarks';
static const subscriptions = 'mews:subscriptions';
static const history = 'mews:history';
static const tags = 'mews:tags';
static const settings = 'mews:settings';
/// Whether [url] belongs to the internal scheme.
static bool isInternal(String url) => Uri.tryParse(url)?.scheme == scheme;
/// Whether [url] changes something instead of going somewhere, e.g.
/// mews:do/set-theme?value=dark.
static bool isAction(String url) {
final uri = Uri.tryParse(url);
if (uri == null || uri.scheme != scheme) return false;
final segments = uri.pathSegments;
return segments.length == 2 && segments.first == 'do';
}
/// The verb of an action address, or null when [url] is not one.
static String? verbOf(String url) =>
isAction(url) ? Uri.parse(url).pathSegments[1] : null;
/// A single spelling for every internal address, so the walk history entry
/// and the built page's url always compare equal. Bare `mews:` is the hub.
static String canonical(String url) {
if (!isInternal(url)) return url;
final uri = Uri.parse(url);
if (uri.path.isEmpty) return hub;
return uri.removeFragment().toString();
}
/// An action address carrying [args] as its query string.
static String action(String verb, [Map<String, String> args = const {}]) =>
Uri(
scheme: scheme,
path: 'do/$verb',
queryParameters: args.isEmpty ? null : args,
).toString();
/// [base] with [changes] merged over its query string. An empty or null
/// value drops the parameter, so clearing a search box returns the whole
/// list. Merging rather than replacing keeps the other parameters, which
/// `Uri.replace(queryParameters:)` would discard.
static String page(Uri base, [Map<String, String?> changes = const {}]) {
final params = {...base.queryParameters};
for (final change in changes.entries) {
final value = change.value;
if (value == null || value.isEmpty) {
params.remove(change.key);
} else {
params[change.key] = value;
}
}
return Uri(
scheme: scheme,
path: base.path,
queryParameters: params.isEmpty ? null : params,
).toString();
}
}