Merge branch 'develop' into develop-connectedDoubleLessons

This commit is contained in:
2024-03-30 22:05:54 +01:00
130 changed files with 1030 additions and 874 deletions

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import '../../storage/base/settings.dart';
import '../../storage/devTools/devToolsSettings.dart';
import '../../storage/file/fileSettings.dart';
import '../../storage/fileView/fileViewSettings.dart';
import '../../storage/gradeAverages/gradeAveragesSettings.dart';
@ -45,7 +46,12 @@ class DefaultSettings {
notificationSettings: NotificationSettings(
askUsageDismissed: false,
enabled: false,
)
),
devToolsSettings: DevToolsSettings(
checkerboardOffscreenLayers: false,
checkerboardRasterCacheImages: false,
showPerformanceOverlay: false,
),
);
}
}

View File

@ -0,0 +1,120 @@
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../storage/base/settingsProvider.dart';
import '../../widget/centeredLeading.dart';
import '../../widget/confirmDialog.dart';
import '../../widget/debug/cacheView.dart';
import '../../widget/debug/jsonViewer.dart';
class DevToolsSettingsDialog extends StatefulWidget {
final SettingsProvider settings;
const DevToolsSettingsDialog({required this.settings, super.key});
@override
State<DevToolsSettingsDialog> createState() => _DevToolsSettingsDialogState();
}
class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.speed_outlined)),
title: const Text('Performance overlays'),
trailing: const Icon(Icons.arrow_right),
onTap: () {
showDialog(context: context, builder: (context) => SimpleDialog(
children: [
ListTile(
leading: const Icon(Icons.auto_graph_outlined),
title: const Text('Performance graph'),
trailing: Checkbox(
value: widget.settings.val().devToolsSettings.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: widget.settings.val().devToolsSettings.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: widget.settings.val().devToolsSettings.checkerboardRasterCacheImages,
onChanged: (e) => widget.settings.val(write: true).devToolsSettings.checkerboardRasterCacheImages = e!,
),
),
],
));
},
),
ListTile(
leading: const CenteredLeading(Icon(Icons.image_outlined)),
title: const Text('Cached Thumbnails löschen'),
subtitle: Text('etwa ${filesize(PaintingBinding.instance.imageCache.currentSizeBytes)}'),
onTap: () {
ConfirmDialog(
title: 'Thumbs cache löschen',
content: 'Alle zwischengespeicherten Bilder werden gelöscht.',
confirmButton: 'Unwiederruflich löschen',
onConfirm: () => PaintingBinding.instance.imageCache.clear(),
).asDialog(context);
},
trailing: const Icon(Icons.arrow_right),
),
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: 'App-Speicher löschen',
content: 'Alle Einstellungen gehen verloren! Accountdaten sowie App-Daten sind nicht betroffen.',
confirmButton: 'Unwiederruflich Löschen',
onConfirm: () {
Provider.of<SettingsProvider>(context, listen: false).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) {
return Text("etwa ${snapshot.hasError ? "?" : snapshot.hasData ? filesize(snapshot.data) : "..."}\nLange tippen um zu löschen");
},
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const CacheView();
}));
},
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),
),
],
);
}
}

View File

@ -13,17 +13,17 @@ class PrivacyInfo {
void showPopup(BuildContext context) {
showDialog(context: context, builder: (context) {
return SimpleDialog(
title: Text("Betreiberinformation | $providerText"),
title: Text('Betreiberinformation | $providerText'),
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.person_pin_outlined)),
title: const Text("Impressum"),
title: const Text('Impressum'),
subtitle: Text(imprintUrl),
onTap: () => ConfirmDialog.openBrowser(context, imprintUrl),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.privacy_tip_outlined)),
title: const Text("Datenschutzerklärung"),
title: const Text('Datenschutzerklärung'),
subtitle: Text(privacyUrl),
onTap: () => ConfirmDialog.openBrowser(context, privacyUrl),
),

View File

@ -1,6 +1,5 @@
import 'package:filesize/filesize.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
@ -15,7 +14,8 @@ import '../../theming/appTheme.dart';
import '../../widget/centeredLeading.dart';
import '../../widget/confirmDialog.dart';
import '../../widget/debug/cacheView.dart';
import '../../widget/debug/jsonViewer.dart';
import 'defaultSettings.dart';
import 'devToolsSettingsDialog.dart';
import 'privacyInfo.dart';
class Settings extends StatefulWidget {
@ -40,21 +40,21 @@ class _SettingsState extends State<Settings> {
return Consumer<SettingsProvider>(builder: (context, settings, child) {
return Scaffold(
appBar: AppBar(
title: const Text("Einstellungen"),
title: const Text('Einstellungen'),
),
body: ListView(
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.logout_outlined)),
title: const Text("Konto abmelden"),
subtitle: Text("Angemeldet als ${AccountData().getUsername()}"),
title: const Text('Konto abmelden'),
subtitle: Text('Angemeldet als ${AccountData().getUsername()}'),
onTap: () {
showDialog(
context: context,
builder: (context) => ConfirmDialog(
title: "Abmelden?",
content: "Möchtest du dich wirklich abmelden?",
confirmButton: "Abmelden",
title: 'Abmelden?',
content: 'Möchtest du dich wirklich abmelden?',
confirmButton: 'Abmelden',
onConfirm: () {
SharedPreferences.getInstance().then((value) => {
value.clear(),
@ -75,7 +75,7 @@ class _SettingsState extends State<Settings> {
ListTile(
leading: const Icon(Icons.dark_mode_outlined),
title: const Text("Farbgebung"),
title: const Text('Farbgebung'),
trailing: DropdownButton<ThemeMode>(
value: settings.val().appTheme,
icon: const Icon(Icons.arrow_drop_down),
@ -113,7 +113,7 @@ class _SettingsState extends State<Settings> {
ListTile(
leading: const Icon(Icons.star_border),
title: const Text("Favoriten im Talk nach oben sortieren"),
title: const Text('Favoriten im Talk nach oben sortieren'),
trailing: Checkbox(
value: settings.val().talkSettings.sortFavoritesToTop,
onChanged: (e) {
@ -124,7 +124,7 @@ class _SettingsState extends State<Settings> {
ListTile(
leading: const Icon(Icons.mark_email_unread_outlined),
title: const Text("Ungelesene Chats nach oben sortieren"),
title: const Text('Ungelesene Chats nach oben sortieren'),
trailing: Checkbox(
value: settings.val().talkSettings.sortUnreadToTop,
onChanged: (e) {
@ -137,7 +137,7 @@ class _SettingsState extends State<Settings> {
ListTile(
leading: const Icon(Icons.drive_folder_upload_outlined),
title: const Text("Ordner in Dateien nach oben sortieren"),
title: const Text('Ordner in Dateien nach oben sortieren'),
trailing: Checkbox(
value: settings.val().fileSettings.sortFoldersToTop,
onChanged: (e) {
@ -150,7 +150,7 @@ class _SettingsState extends State<Settings> {
ListTile(
leading: const Icon(Icons.open_in_new_outlined),
title: const Text("Dateien immer mit Systemdialog öffnen"),
title: const Text('Dateien immer mit Systemdialog öffnen'),
trailing: Checkbox(
value: settings.val().fileViewSettings.alwaysOpenExternally,
onChanged: (e) {
@ -163,8 +163,8 @@ class _SettingsState extends State<Settings> {
ListTile(
leading: const CenteredLeading(Icon(Icons.notifications_active_outlined)),
title: const Text("Push-Benachrichtigungen aktivieren"),
subtitle: const Text("Lange tippen für mehr Informationen"),
title: const Text('Push-Benachrichtigungen aktivieren'),
subtitle: const Text('Lange tippen für mehr Informationen'),
trailing: Checkbox(
value: settings.val().notificationSettings.enabled,
onChanged: (e) {
@ -176,16 +176,16 @@ class _SettingsState extends State<Settings> {
},
),
onLongPress: () => showDialog(context: context, builder: (context) => AlertDialog(
title: const Text("Info über Push"),
content: const SingleChildScrollView(child: Text(""
title: const Text('Info über Push'),
content: const SingleChildScrollView(child: Text(''
"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 trots bester Absichten ein Sicherheitsrisiko sein kann!"
'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 trots bester Absichten ein Sicherheitsrisiko sein kann!'
)),
actions: [
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("Zurück"))
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Zurück'))
],
)),
),
@ -194,18 +194,18 @@ class _SettingsState extends State<Settings> {
ListTile(
leading: const Icon(Icons.live_help_outlined),
title: const Text("Informationen und Lizenzen"),
title: const Text('Informationen und Lizenzen'),
onTap: () {
PackageInfo.fromPlatform().then((appInfo) {
showAboutDialog(
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"
"Keinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!\n\n"
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'
'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",
'Marianum Fulda 2023-${Jiffy.now().year}\nElias Müller',
);
});
},
@ -214,31 +214,31 @@ class _SettingsState extends State<Settings> {
ListTile(
leading: const Icon(Icons.policy_outlined),
title: const Text("Impressum & Datenschutz"),
title: const Text('Impressum & Datenschutz'),
onTap: () {
showDialog(context: context, builder: (context) {
return SimpleDialog(
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.school_outlined)),
title: const Text("Infos zum Marianum Fulda"),
subtitle: const Text("Für Talk-Chats und Dateien"),
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(context)
onTap: () => PrivacyInfo(providerText: 'Marianum', imprintUrl: 'https://www.marianum-fulda.de/impressum', privacyUrl: 'https://www.marianum-fulda.de/datenschutz').showPopup(context)
),
ListTile(
leading: const CenteredLeading(Icon(Icons.date_range_outlined)),
title: const Text("Infos zu Web-/ Untis"),
subtitle: const Text("Für den Vertretungsplan"),
title: const Text('Infos zu Web-/ Untis'),
subtitle: const Text('Für den Vertretungsplan'),
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(context)
onTap: () => PrivacyInfo(providerText: 'Untis', imprintUrl: 'https://www.untis.at/impressum', privacyUrl: 'https://www.untis.at/datenschutz-wu-apps').showPopup(context)
),
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"),
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(context),
onTap: () => PrivacyInfo(providerText: 'mhsl', imprintUrl: 'https://mhsl.eu/id.html', privacyUrl: 'https://mhsl.eu/datenschutz.html').showPopup(context),
),
],
);
@ -253,19 +253,23 @@ class _SettingsState extends State<Settings> {
visible: !kReleaseMode,
child: ListTile(
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"),
title: const Text('Quellcode MarianumMobile/Client'),
subtitle: const Text('GNU GPL v3'),
onTap: () => ConfirmDialog.openBrowser(context, 'https://mhsl.eu/gitea/MarianumMobile/Client'),
),
),
ListTile(
leading: const Icon(Icons.developer_mode_outlined),
title: const Text("Entwickleransicht"),
title: const Text('Entwicklermodus'),
trailing: Checkbox(
value: settings.val().devToolsEnabled,
onChanged: (state) {
changeView() => settings.val(write: true).devToolsEnabled = state ?? false;
changeView() {
var enabled = state ?? false;
settings.val(write: true).devToolsEnabled = enabled;
if(!enabled) settings.val(write: true).devToolsSettings = DefaultSettings.get().devToolsSettings;
}
if(!state!) {
changeView();
@ -273,15 +277,13 @@ class _SettingsState extends State<Settings> {
}
ConfirmDialog(
title: "Entwicklermodus",
content: ""
"Die Entwickleransicht bietet erweiterte Funktionen, die für den üblichen Gebrauch nicht benötigt werden.\n\nDie 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",
cancelButton: "Nein, zurück zur App",
onConfirm: () {
changeView();
},
title: 'Entwicklermodus',
content: ''
'Die Entwickleransicht bietet erweiterte Funktionen, die für den üblichen Gebrauch nicht benötigt werden.\n\nDie 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',
cancelButton: 'Nein, zurück zur App',
onConfirm: changeView,
).asDialog(context);
},
),
@ -289,67 +291,7 @@ class _SettingsState extends State<Settings> {
Visibility(
visible: settings.val().devToolsEnabled,
child: Column(
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.image_outlined)),
title: const Text("Cached Thumbnails löschen"),
subtitle: Text("etwa ${filesize(PaintingBinding.instance.imageCache.currentSizeBytes)}"),
onTap: () {
ConfirmDialog(
title: "Thumbs cache löschen",
content: "Alle zwischengespeicherten Bilder werden gelöscht.",
confirmButton: "Unwiederruflich löschen",
onConfirm: () => PaintingBinding.instance.imageCache.clear(),
).asDialog(context);
},
trailing: const Icon(Icons.arrow_right),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.settings_applications_outlined)),
title: const Text("Settings-storage JSON dump"),
subtitle: Text("etwa ${filesize(settings.val().toJson().toString().length * 8)}\nLange tippen um zu löschen"),
onTap: () {
JsonViewer.asDialog(context, settings.val().toJson());
},
onLongPress: () {
ConfirmDialog(
title: "App-Speicher löschen",
content: "Alle Einstellungen gehen verloren! Accountdaten sowie App-Daten sind nicht betroffen.",
confirmButton: "Unwiederruflich Löschen",
onConfirm: () {
Provider.of<SettingsProvider>(context, listen: false).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) {
return Text("etwa ${snapshot.hasError ? "?" : snapshot.hasData ? filesize(snapshot.data) : "..."}\nLange tippen um zu löschen");
},
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const CacheView();
}));
},
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),
),
],
),
child: DevToolsSettingsDialog(settings: settings),
),
],
),