implemented a backend-independent emergency notice system

This commit is contained in:
2026-07-15 20:30:20 +02:00
parent e8c6ac1c65
commit 7c1f5c06df
11 changed files with 599 additions and 3 deletions
@@ -75,6 +75,7 @@ class DefaultSettings {
showPerformanceOverlay: false,
marianumConnectEndpoint: MarianumConnectEndpoint.live,
marianumConnectCustomUrl: '',
emergencyNoticeUrl: '',
),
hapticSettings: HapticSettings(level: HapticLevel.full),
);
@@ -5,6 +5,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import '../../../../routing/app_routes.dart';
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../../storage/dev_tools_settings.dart';
import '../../../../storage/settings.dart' as model;
import '../../../../widget/centered_leading.dart';
import '../../../../widget/confirm_dialog.dart';
@@ -88,6 +89,27 @@ class _DevToolsSectionState extends State<DevToolsSection> {
onTap: () =>
MarianumConnectEndpointPicker.show(context, widget.settings),
),
BlocBuilder<SettingsCubit, model.Settings>(
bloc: widget.settings,
builder: (_, _) {
final override = widget.settings
.val()
.devToolsSettings
.emergencyNoticeUrl
.trim();
return ListTile(
leading: const CenteredLeading(Icon(Icons.emergency_outlined)),
title: const Text('Notfall-Nachricht (Quelle)'),
subtitle: Text(
override.isEmpty
? 'Standardquelle (im Code hinterlegt)'
: override,
),
trailing: const Icon(Icons.arrow_right),
onTap: () => _EmergencyNoticeUrlEditor.show(context, widget.settings),
);
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.image_outlined)),
title: const Text('Thumb-storage'),
@@ -170,3 +192,94 @@ class _DevToolsSectionState extends State<DevToolsSection> {
],
);
}
/// Bottom-sheet editor for the backend-independent emergency-notice override
/// URL. Empty clears the override so the compiled-in default source is used.
class _EmergencyNoticeUrlEditor extends StatefulWidget {
final SettingsCubit settings;
const _EmergencyNoticeUrlEditor({required this.settings});
static void show(BuildContext context, SettingsCubit settings) {
showDetailsBottomSheet(
context,
header: const ListTile(title: Text('Notfall-Nachricht (Quelle)')),
children: (sheetCtx) => [_EmergencyNoticeUrlEditor(settings: settings)],
);
}
@override
State<_EmergencyNoticeUrlEditor> createState() =>
_EmergencyNoticeUrlEditorState();
}
class _EmergencyNoticeUrlEditorState extends State<_EmergencyNoticeUrlEditor> {
late final TextEditingController _controller;
String? _error;
@override
void initState() {
super.initState();
_controller = TextEditingController(
text: widget.settings.val().devToolsSettings.emergencyNoticeUrl,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _save() {
final raw = _controller.text.trim();
// Empty is valid: it clears the override and falls back to the default.
if (raw.isNotEmpty &&
DevToolsSettings.sanitizeCustomUrl(raw) == null) {
setState(
() => _error = DevToolsSettings.allowsHttpCustomEndpoint
? 'Ungültige URL (http(s)://host[:port]/...)'
: 'Ungültige URL — nur HTTPS erlaubt',
);
return;
}
widget.settings.val(write: true).devToolsSettings.emergencyNoticeUrl = raw;
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Padding(
padding: EdgeInsets.only(bottom: 12),
child: Text(
'Überschreibt die im Code hinterlegte Standardquelle. Leer '
'lassen, um die Standardquelle zu verwenden.',
style: TextStyle(fontSize: 12),
),
),
TextField(
controller: _controller,
keyboardType: TextInputType.url,
decoration: InputDecoration(
labelText: 'https://...',
errorText: _error,
),
onChanged: (_) {
if (_error != null) setState(() => _error = null);
},
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: FilledButton(
onPressed: _save,
child: const Text('Übernehmen'),
),
),
],
),
);
}