dart format

This commit is contained in:
2026-05-08 20:12:40 +02:00
parent 9e139b5704
commit 3b8da1d3d6
295 changed files with 6404 additions and 4161 deletions
@@ -37,14 +37,18 @@ class AboutSection extends StatelessWidget {
leading: const CenteredLeading(Icon(Icons.code)),
title: const Text('Quellcode MarianumMobile/Client'),
subtitle: const Text('GNU GPL v3'),
onTap: () => ConfirmDialog.openBrowser(context, 'https://mhsl.eu/gitea/MarianumMobile/Client'),
onTap: () => ConfirmDialog.openBrowser(
context,
'https://mhsl.eu/gitea/MarianumMobile/Client',
),
),
ListTile(
leading: const Icon(Icons.developer_mode_outlined),
title: const Text('Entwicklermodus'),
trailing: Checkbox(
value: settings.val().devToolsEnabled,
onChanged: (state) => _toggleDeveloperMode(context, settings, state),
onChanged: (state) =>
_toggleDeveloperMode(context, settings, state),
),
),
Visibility(
@@ -62,8 +66,10 @@ class AboutSection extends StatelessWidget {
context: context,
applicationIcon: const Icon(Icons.apps),
applicationName: 'MarianumMobile',
applicationVersion: '${appInfo.appName}\n\nPackage: ${appInfo.packageName}\nVersion: ${appInfo.version}\nBuild: ${appInfo.buildNumber}',
applicationLegalese: 'Dies ist ein Inoffizieller Nextcloud & Webuntis Client und wird nicht vom Marianum selbst betrieben.\n'
applicationVersion:
'${appInfo.appName}\n\nPackage: ${appInfo.packageName}\nVersion: ${appInfo.version}\nBuild: ${appInfo.buildNumber}',
applicationLegalese:
'Dies ist ein Inoffizieller Nextcloud & Webuntis Client und wird nicht vom Marianum selbst betrieben.\n'
'Keinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!\n\n'
"${kReleaseMode ? "Production" : "Development"} build\n"
'Marianum Fulda 2023-${Jiffy.now().year}\nElias Müller',
@@ -71,49 +77,58 @@ class AboutSection extends StatelessWidget {
}
void _showPrivacyDialog(BuildContext context) => showDetailsBottomSheet(
context,
children: (sheetCtx) => [
ListTile(
leading: const CenteredLeading(Icon(Icons.school_outlined)),
title: const Text('Infos zum Marianum Fulda'),
subtitle: const Text('Für Talk-Chats und Dateien'),
trailing: const Icon(Icons.arrow_right),
onTap: () => PrivacyInfo(
providerText: 'Marianum',
imprintUrl: 'https://www.marianum-fulda.de/impressum',
privacyUrl: 'https://www.marianum-fulda.de/datenschutz',
).showPopup(sheetCtx),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.date_range_outlined)),
title: const Text('Infos zu Web-/ Untis'),
subtitle: const Text('Für den Stundenplan'),
trailing: const Icon(Icons.arrow_right),
onTap: () => PrivacyInfo(
providerText: 'Untis',
imprintUrl: 'https://www.untis.at/impressum',
privacyUrl: 'https://www.untis.at/datenschutz-wu-apps',
).showPopup(sheetCtx),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.send_time_extension_outlined)),
title: const Text('Infos zu mhsl'),
subtitle: const Text('Für Countdowns, Marianum Message und mehr'),
trailing: const Icon(Icons.arrow_right),
onTap: () => PrivacyInfo(
providerText: 'mhsl',
imprintUrl: 'https://mhsl.eu/id.html',
privacyUrl: 'https://mhsl.eu/datenschutz.html',
).showPopup(sheetCtx),
),
],
);
context,
children: (sheetCtx) => [
ListTile(
leading: const CenteredLeading(Icon(Icons.school_outlined)),
title: const Text('Infos zum Marianum Fulda'),
subtitle: const Text('Für Talk-Chats und Dateien'),
trailing: const Icon(Icons.arrow_right),
onTap: () => PrivacyInfo(
providerText: 'Marianum',
imprintUrl: 'https://www.marianum-fulda.de/impressum',
privacyUrl: 'https://www.marianum-fulda.de/datenschutz',
).showPopup(sheetCtx),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.date_range_outlined)),
title: const Text('Infos zu Web-/ Untis'),
subtitle: const Text('Für den Stundenplan'),
trailing: const Icon(Icons.arrow_right),
onTap: () => PrivacyInfo(
providerText: 'Untis',
imprintUrl: 'https://www.untis.at/impressum',
privacyUrl: 'https://www.untis.at/datenschutz-wu-apps',
).showPopup(sheetCtx),
),
ListTile(
leading: const CenteredLeading(
Icon(Icons.send_time_extension_outlined),
),
title: const Text('Infos zu mhsl'),
subtitle: const Text('Für Countdowns, Marianum Message und mehr'),
trailing: const Icon(Icons.arrow_right),
onTap: () => PrivacyInfo(
providerText: 'mhsl',
imprintUrl: 'https://mhsl.eu/id.html',
privacyUrl: 'https://mhsl.eu/datenschutz.html',
).showPopup(sheetCtx),
),
],
);
void _toggleDeveloperMode(BuildContext context, SettingsCubit settings, bool? state) {
void _toggleDeveloperMode(
BuildContext context,
SettingsCubit settings,
bool? state,
) {
void apply() {
final enabled = state ?? false;
settings.val(write: true).devToolsEnabled = enabled;
if (!enabled) settings.val(write: true).devToolsSettings = DefaultSettings.get().devToolsSettings;
if (!enabled) {
settings.val(write: true).devToolsSettings =
DefaultSettings.get().devToolsSettings;
}
}
if (!state!) {
@@ -123,7 +138,8 @@ class AboutSection extends StatelessWidget {
ConfirmDialog(
title: 'Entwicklermodus',
content: 'Die Entwickleransicht bietet erweiterte Funktionen, die für den üblichen Gebrauch nicht benötigt werden.\n\n'
content:
'Die Entwickleransicht bietet erweiterte Funktionen, die für den üblichen Gebrauch nicht benötigt werden.\n\n'
'Die Verwendung der Tools kann darüber hinaus bei falscher Verwendung zu Fehlern führen.\n\n'
'Aktivieren auf eigene Verantwortung.',
confirmButton: 'Ja, ich verstehe das Risiko',
@@ -12,11 +12,11 @@ class AccountSection extends StatelessWidget {
@override
Widget build(BuildContext context) => ListTile(
leading: const CenteredLeading(Icon(Icons.logout_outlined)),
title: const Text('Konto abmelden'),
subtitle: Text('Angemeldet als ${AccountData().getUsername()}'),
onTap: () => _showLogoutDialog(context),
);
leading: const CenteredLeading(Icon(Icons.logout_outlined)),
title: const Text('Konto abmelden'),
subtitle: Text('Angemeldet als ${AccountData().getUsername()}'),
onTap: () => _showLogoutDialog(context),
);
Future<void> _showLogoutDialog(BuildContext context) async {
// Sequential logout flow: dialog wipes secure storage, dialog closes
@@ -17,17 +17,19 @@ class AppearanceSection extends StatelessWidget {
value: settings.val().appTheme,
icon: const Icon(Icons.arrow_drop_down),
items: ThemeMode.values
.map((e) => DropdownMenuItem<ThemeMode>(
value: e,
enabled: e != settings.val().appTheme,
child: Row(
children: [
Icon(AppTheme.getDisplayOptions(e).icon),
const SizedBox(width: 10),
Text(AppTheme.getDisplayOptions(e).displayName),
],
),
))
.map(
(e) => DropdownMenuItem<ThemeMode>(
value: e,
enabled: e != settings.val().appTheme,
child: Row(
children: [
Icon(AppTheme.getDisplayOptions(e).icon),
const SizedBox(width: 10),
Text(AppTheme.getDisplayOptions(e).displayName),
],
),
),
)
.toList(),
onChanged: (e) => settings.val(write: true).appTheme = e!,
),
@@ -1,4 +1,3 @@
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -24,117 +23,150 @@ class DevToolsSection extends StatefulWidget {
class _DevToolsSectionState extends State<DevToolsSection> {
@override
Widget build(BuildContext context) => Column(
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.speed_outlined)),
title: const Text('Performance overlays'),
trailing: const Icon(Icons.arrow_right),
onTap: () {
showDetailsBottomSheet(
context,
children: (sheetCtx) => [
BlocBuilder<SettingsCubit, model.Settings>(
bloc: widget.settings,
builder: (_, _) {
final dev = widget.settings.val().devToolsSettings;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.auto_graph_outlined),
title: const Text('Performance graph'),
trailing: Checkbox(
value: dev.showPerformanceOverlay,
onChanged: (e) => widget.settings.val(write: true).devToolsSettings.showPerformanceOverlay = e!,
),
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.speed_outlined)),
title: const Text('Performance overlays'),
trailing: const Icon(Icons.arrow_right),
onTap: () {
showDetailsBottomSheet(
context,
children: (sheetCtx) => [
BlocBuilder<SettingsCubit, model.Settings>(
bloc: widget.settings,
builder: (_, _) {
final dev = widget.settings.val().devToolsSettings;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.auto_graph_outlined),
title: const Text('Performance graph'),
trailing: Checkbox(
value: dev.showPerformanceOverlay,
onChanged: (e) =>
widget.settings
.val(write: true)
.devToolsSettings
.showPerformanceOverlay =
e!,
),
ListTile(
leading: const Icon(Icons.screen_search_desktop_outlined),
title: const Text('Indicate offscreen layers'),
trailing: Checkbox(
value: dev.checkerboardOffscreenLayers,
onChanged: (e) => widget.settings.val(write: true).devToolsSettings.checkerboardOffscreenLayers = e!,
),
),
ListTile(
leading: const Icon(
Icons.screen_search_desktop_outlined,
),
ListTile(
leading: const Icon(Icons.imagesearch_roller_outlined),
title: const Text('Indicate raster cache images'),
trailing: Checkbox(
value: dev.checkerboardRasterCacheImages,
onChanged: (e) => widget.settings.val(write: true).devToolsSettings.checkerboardRasterCacheImages = e!,
),
title: const Text('Indicate offscreen layers'),
trailing: Checkbox(
value: dev.checkerboardOffscreenLayers,
onChanged: (e) =>
widget.settings
.val(write: true)
.devToolsSettings
.checkerboardOffscreenLayers =
e!,
),
],
);
},
),
],
);
},
),
ListTile(
leading: const Icon(Icons.imagesearch_roller_outlined),
title: const Text('Indicate raster cache images'),
trailing: Checkbox(
value: dev.checkerboardRasterCacheImages,
onChanged: (e) =>
widget.settings
.val(write: true)
.devToolsSettings
.checkerboardRasterCacheImages =
e!,
),
),
],
);
},
),
],
);
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.image_outlined)),
title: const Text('Thumb-storage'),
subtitle: Text(
'etwa ${filesize(PaintingBinding.instance.imageCache.currentSizeBytes)}\nLange tippen um zu löschen',
),
ListTile(
leading: const CenteredLeading(Icon(Icons.image_outlined)),
title: const Text('Thumb-storage'),
subtitle: Text('etwa ${filesize(PaintingBinding.instance.imageCache.currentSizeBytes)}\nLange tippen um zu löschen'),
onLongPress: () {
ConfirmDialog(
title: 'Thumbs cache löschen',
content: 'Alle zwischengespeicherten Bilder werden gelöscht.',
confirmButton: 'Unwiederruflich löschen',
onConfirm: () => PaintingBinding.instance.imageCache.clear(),
).asDialog(context);
},
onLongPress: () {
ConfirmDialog(
title: 'Thumbs cache löschen',
content: 'Alle zwischengespeicherten Bilder werden gelöscht.',
confirmButton: 'Unwiederruflich löschen',
onConfirm: () => PaintingBinding.instance.imageCache.clear(),
).asDialog(context);
},
),
ListTile(
leading: const CenteredLeading(
Icon(Icons.settings_applications_outlined),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.settings_applications_outlined)),
title: const Text('Settings-storage JSON dump'),
subtitle: Text('etwa ${filesize(widget.settings.val().toJson().toString().length * 8)}\nLange tippen um zu löschen'),
onTap: () {
JsonViewer.asDialog(context, widget.settings.val().toJson());
},
onLongPress: () {
ConfirmDialog(
title: 'Einstellungen löschen',
content: 'Alle Einstellungen gehen verloren! Accountdaten sowie App-Daten sind nicht betroffen.',
confirmButton: 'Unwiederruflich Löschen',
onConfirm: () {
context.read<SettingsCubit>().reset();
},
).asDialog(context);
},
trailing: const Icon(Icons.arrow_right),
title: const Text('Settings-storage JSON dump'),
subtitle: Text(
'etwa ${filesize(widget.settings.val().toJson().toString().length * 8)}\nLange tippen um zu löschen',
),
ListTile(
leading: const CenteredLeading(Icon(Icons.data_object)),
title: const Text('Cache-storage JSON dump'),
subtitle: FutureBuilder(
future: const CacheView().totalSize(),
builder: (context, snapshot) => Text("etwa ${snapshot.hasError ? "?" : snapshot.hasData ? filesize(snapshot.data) : "..."}\nLange tippen um zu löschen"),
onTap: () {
JsonViewer.asDialog(context, widget.settings.val().toJson());
},
onLongPress: () {
ConfirmDialog(
title: 'Einstellungen löschen',
content:
'Alle Einstellungen gehen verloren! Accountdaten sowie App-Daten sind nicht betroffen.',
confirmButton: 'Unwiederruflich Löschen',
onConfirm: () {
context.read<SettingsCubit>().reset();
},
).asDialog(context);
},
trailing: const Icon(Icons.arrow_right),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.data_object)),
title: const Text('Cache-storage JSON dump'),
subtitle: FutureBuilder(
future: const CacheView().totalSize(),
builder: (context, snapshot) => Text(
"etwa ${snapshot.hasError
? "?"
: snapshot.hasData
? filesize(snapshot.data)
: "..."}\nLange tippen um zu löschen",
),
onTap: () => AppRoutes.openCacheView(context),
onLongPress: () {
ConfirmDialog(
title: 'App-Cache löschen',
content: 'Alle cache Einträge werden gelöscht. Der Cache wird bei Nutzung der App automatisch erneut aufgebaut',
confirmButton: 'Unwiederruflich löschen',
onConfirm: () => const CacheView().clear().then((value) => setState((){})),
).asDialog(context);
},
trailing: const Icon(Icons.arrow_right),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.data_object)),
title: const Text('BLOC-storage state cache'),
subtitle: const Text('Lange tippen um zu löschen'),
onLongPress: () {
ConfirmDialog(
title: 'BLOC-Cache löschen',
content: 'Alle cache Einträge werden gelöscht. Der Cache wird bei Nutzung der App automatisch erneut aufgebaut',
confirmButton: 'Unwiederruflich löschen',
onConfirm: () => HydratedBloc.storage.clear(),
).asDialog(context);
},
),
],
);
onTap: () => AppRoutes.openCacheView(context),
onLongPress: () {
ConfirmDialog(
title: 'App-Cache löschen',
content:
'Alle cache Einträge werden gelöscht. Der Cache wird bei Nutzung der App automatisch erneut aufgebaut',
confirmButton: 'Unwiederruflich löschen',
onConfirm: () =>
const CacheView().clear().then((value) => setState(() {})),
).asDialog(context);
},
trailing: const Icon(Icons.arrow_right),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.data_object)),
title: const Text('BLOC-storage state cache'),
subtitle: const Text('Lange tippen um zu löschen'),
onLongPress: () {
ConfirmDialog(
title: 'BLOC-Cache löschen',
content:
'Alle cache Einträge werden gelöscht. Der Cache wird bei Nutzung der App automatisch erneut aufgebaut',
confirmButton: 'Unwiederruflich löschen',
onConfirm: () => HydratedBloc.storage.clear(),
).asDialog(context);
},
),
],
);
}
@@ -16,7 +16,8 @@ class FilesSection extends StatelessWidget {
title: const Text('Ordner in Dateien nach oben sortieren'),
trailing: Checkbox(
value: settings.val().fileSettings.sortFoldersToTop,
onChanged: (e) => settings.val(write: true).fileSettings.sortFoldersToTop = e!,
onChanged: (e) =>
settings.val(write: true).fileSettings.sortFoldersToTop = e!,
),
),
ListTile(
@@ -24,7 +25,12 @@ class FilesSection extends StatelessWidget {
title: const Text('Dateien immer mit Systemdialog öffnen'),
trailing: Checkbox(
value: settings.val().fileViewSettings.alwaysOpenExternally,
onChanged: (e) => settings.val(write: true).fileViewSettings.alwaysOpenExternally = e!,
onChanged: (e) =>
settings
.val(write: true)
.fileViewSettings
.alwaysOpenExternally =
e!,
),
),
],
@@ -7,10 +7,10 @@ class ModulesSection extends StatelessWidget {
@override
Widget build(BuildContext context) => ListTile(
leading: const Icon(Icons.apps_outlined),
title: const Text('Module'),
subtitle: const Text('Reihenfolge, Sichtbarkeit und Modulleiste anpassen'),
trailing: const Icon(Icons.arrow_right),
onTap: () => AppRoutes.openModulesSettings(context),
);
leading: const Icon(Icons.apps_outlined),
title: const Text('Module'),
subtitle: const Text('Reihenfolge, Sichtbarkeit und Modulleiste anpassen'),
trailing: const Icon(Icons.arrow_right),
onTap: () => AppRoutes.openModulesSettings(context),
);
}
@@ -21,7 +21,8 @@ class TalkSection extends StatelessWidget {
title: const Text('Favoriten im Talk nach oben sortieren'),
trailing: Checkbox(
value: talkSettings.sortFavoritesToTop,
onChanged: (e) => settings.val(write: true).talkSettings.sortFavoritesToTop = e!,
onChanged: (e) =>
settings.val(write: true).talkSettings.sortFavoritesToTop = e!,
),
),
ListTile(
@@ -29,11 +30,14 @@ class TalkSection extends StatelessWidget {
title: const Text('Ungelesene Chats nach oben sortieren'),
trailing: Checkbox(
value: talkSettings.sortUnreadToTop,
onChanged: (e) => settings.val(write: true).talkSettings.sortUnreadToTop = e!,
onChanged: (e) =>
settings.val(write: true).talkSettings.sortUnreadToTop = e!,
),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.notifications_active_outlined)),
leading: const CenteredLeading(
Icon(Icons.notifications_active_outlined),
),
title: const Text('Push-Benachrichtigungen aktivieren'),
subtitle: const Text('Lange tippen für mehr Informationen'),
trailing: Checkbox(
@@ -53,12 +57,12 @@ class TalkSection extends StatelessWidget {
}
void _showInfoDialog(BuildContext context) => InfoDialog.show(
context,
"Aufgrund technischer Limitationen müssen Push-Nachrichten über einen externen Server - hier 'mhsl.eu' (Author dieser App) - erfolgen.\n\n"
'Wenn Push aktiviert wird, werden deine Zugangsdaten und ein Token verschlüsselt an den Betreiber gesendet und von ihm unverschlüsselt gespeichert.\n\n'
'Der extene Server verwendet die Zugangsdaten um sich maschinell in Nextcloud Talk anzumelden und via Websockets auf neue Nachrichten zu warten.\n\n'
'Wenn eine neue Nachricht eintrifft wird dein Telefon via FBC-Messaging (Google Firebase Push) vom externen Server benachrichtigt.\n\n'
'Behalte im Hinterkopf, dass deine Zugangsdaten auf einem externen Server gespeichert werden und dies trotz bester Absichten ein Sicherheitsrisiko sein kann!',
title: 'Info über Push',
);
context,
"Aufgrund technischer Limitationen müssen Push-Nachrichten über einen externen Server - hier 'mhsl.eu' (Author dieser App) - erfolgen.\n\n"
'Wenn Push aktiviert wird, werden deine Zugangsdaten und ein Token verschlüsselt an den Betreiber gesendet und von ihm unverschlüsselt gespeichert.\n\n'
'Der extene Server verwendet die Zugangsdaten um sich maschinell in Nextcloud Talk anzumelden und via Websockets auf neue Nachrichten zu warten.\n\n'
'Wenn eine neue Nachricht eintrifft wird dein Telefon via FBC-Messaging (Google Firebase Push) vom externen Server benachrichtigt.\n\n'
'Behalte im Hinterkopf, dass deine Zugangsdaten auf einem externen Server gespeichert werden und dies trotz bester Absichten ein Sicherheitsrisiko sein kann!',
title: 'Info über Push',
);
}
@@ -20,20 +20,25 @@ class TimetableSection extends StatelessWidget {
value: timetableSettings.timetableNameMode,
icon: const Icon(Icons.arrow_drop_down),
items: TimetableNameMode.values
.map((e) => DropdownMenuItem(
value: e,
enabled: e != timetableSettings.timetableNameMode,
child: Row(
children: [
Icon(TimetableNameModes.getDisplayOptions(e).icon),
const SizedBox(width: 10),
Text(TimetableNameModes.getDisplayOptions(e).displayName),
],
),
))
.map(
(e) => DropdownMenuItem(
value: e,
enabled: e != timetableSettings.timetableNameMode,
child: Row(
children: [
Icon(TimetableNameModes.getDisplayOptions(e).icon),
const SizedBox(width: 10),
Text(
TimetableNameModes.getDisplayOptions(e).displayName,
),
],
),
),
)
.toList(),
onChanged: (value) =>
settings.val(write: true).timetableSettings.timetableNameMode = value!,
settings.val(write: true).timetableSettings.timetableNameMode =
value!,
),
),
ListTile(
@@ -42,7 +47,11 @@ class TimetableSection extends StatelessWidget {
trailing: Checkbox(
value: timetableSettings.connectDoubleLessons,
onChanged: (e) =>
settings.val(write: true).timetableSettings.connectDoubleLessons = e!,
settings
.val(write: true)
.timetableSettings
.connectDoubleLessons =
e!,
),
),
],