91 lines
2.5 KiB
Dart
91 lines
2.5 KiB
Dart
/// A backend-independent emergency notice loaded from a foreign server URL —
|
|
/// frontmatter (control fields) plus a free Markdown body, so it stays
|
|
/// hand-writable in an outage. See [parse] for the format.
|
|
class EmergencyNotice {
|
|
/// `false` renders full-screen and blocks back/barrier taps.
|
|
final bool dismissible;
|
|
final String? title;
|
|
final String body;
|
|
|
|
const EmergencyNotice({
|
|
required this.dismissible,
|
|
required this.title,
|
|
required this.body,
|
|
});
|
|
|
|
/// Parses the raw file, or returns `null` when there is nothing to show.
|
|
/// Never throws — malformed input yields `null` so a broken file can't break
|
|
/// the app.
|
|
///
|
|
/// ```
|
|
/// ---
|
|
/// active: true # required truthy, else null; # lines are comments
|
|
/// dismissible: true # default true
|
|
/// title: Störung # optional
|
|
/// ---
|
|
/// Free **markdown** body (everything after the closing ---).
|
|
/// ```
|
|
static EmergencyNotice? parse(String raw) {
|
|
final lines = raw
|
|
.replaceAll('\r\n', '\n')
|
|
.replaceAll('\r', '\n')
|
|
.split('\n');
|
|
|
|
var i = 0;
|
|
while (i < lines.length && lines[i].trim().isEmpty) {
|
|
i++;
|
|
}
|
|
if (i >= lines.length || lines[i].trim() != '---') return null;
|
|
final openIndex = i;
|
|
|
|
var closeIndex = -1;
|
|
for (var j = openIndex + 1; j < lines.length; j++) {
|
|
if (lines[j].trim() == '---') {
|
|
closeIndex = j;
|
|
break;
|
|
}
|
|
}
|
|
if (closeIndex == -1) return null;
|
|
|
|
final meta = <String, String>{};
|
|
for (var j = openIndex + 1; j < closeIndex; j++) {
|
|
final line = lines[j].trim();
|
|
if (line.isEmpty || line.startsWith('#')) continue;
|
|
final sep = line.indexOf(':');
|
|
if (sep <= 0) continue;
|
|
final key = line.substring(0, sep).trim().toLowerCase();
|
|
final value = line.substring(sep + 1).trim();
|
|
meta[key] = value;
|
|
}
|
|
|
|
if (_parseBool(meta['active']) != true) return null;
|
|
|
|
final body = lines.sublist(closeIndex + 1).join('\n').trim();
|
|
if (body.isEmpty) return null;
|
|
|
|
final title = meta['title'];
|
|
return EmergencyNotice(
|
|
dismissible: _parseBool(meta['dismissible']) ?? true,
|
|
title: (title == null || title.isEmpty) ? null : title,
|
|
body: body,
|
|
);
|
|
}
|
|
|
|
static bool? _parseBool(String? value) {
|
|
switch (value?.trim().toLowerCase()) {
|
|
case 'true':
|
|
case 'yes':
|
|
case '1':
|
|
case 'on':
|
|
return true;
|
|
case 'false':
|
|
case 'no':
|
|
case '0':
|
|
case 'off':
|
|
return false;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
}
|