Merge branch 'develop' into feature-highEduGraduationCalculator

# Conflicts:
#	lib/view/pages/overhang.dart
This commit is contained in:
2024-03-29 18:32:59 +01:00
115 changed files with 554 additions and 554 deletions

View File

@ -21,7 +21,7 @@ class _LoginState extends State<Login> {
bool displayDisclaimerText = true;
String? _checkInput(value){
return (value ?? "").length == 0 ? "Eingabe erforderlich" : null;
return (value ?? '').length == 0 ? 'Eingabe erforderlich' : null;
}
Future<String?> _login(LoginData data) async {
@ -42,7 +42,7 @@ class _LoginState extends State<Login> {
} catch(e) {
await AccountData().removeData();
log(e.toString());
return "Benutzername oder Password falsch! (${e.toString()})";
return 'Benutzername oder Password falsch! (${e.toString()})';
}
await Future.delayed(const Duration(seconds: 1));
@ -51,14 +51,14 @@ class _LoginState extends State<Login> {
Future<String> _resetPassword(String name) {
return Future.delayed(Duration.zero).then((_) {
return "Diese Funktion steht nicht zur Verfügung!";
return 'Diese Funktion steht nicht zur Verfügung!';
});
}
@override
Widget build(BuildContext context) {
return FlutterLogin(
logo: Image.asset("assets/logo/icon.png").image,
logo: Image.asset('assets/logo/icon.png').image,
userValidator: _checkInput,
passwordValidator: _checkInput,
@ -84,9 +84,9 @@ class _LoginState extends State<Login> {
),
messages: LoginMessages(
loginButton: "Anmelden",
userHint: "Nutzername",
passwordHint: "Passwort",
loginButton: 'Anmelden',
userHint: 'Nutzername',
passwordHint: 'Passwort',
),
disableCustomPageTransformer: true,
@ -97,15 +97,15 @@ class _LoginState extends State<Login> {
child: Visibility(
visible: displayDisclaimerText,
child: const Text(
"Dies ist ein Inoffizieller Nextcloud & Webuntis Client und wird nicht vom Marianum selbst betrieben.\nKeinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!",
'Dies ist ein Inoffizieller Nextcloud & Webuntis Client und wird nicht vom Marianum selbst betrieben.\nKeinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!',
textAlign: TextAlign.center,
),
),
),
),
footer: "Marianum Fulda - Die persönliche Schule",
title: "Marianum Fulda",
footer: 'Marianum Fulda - Die persönliche Schule',
title: 'Marianum Fulda',
userType: LoginUserType.name,
);

View File

@ -6,7 +6,7 @@ import 'package:flowder/flowder.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:marianum_mobile/widget/infoDialog.dart';
import '../../../widget/infoDialog.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:path_provider/path_provider.dart';
@ -29,7 +29,7 @@ class FileElement extends StatefulWidget {
Directory paths = await getTemporaryDirectory();
var encodedPath = Uri.encodeComponent(remotePath);
encodedPath = encodedPath.replaceAll("%2F", "/");
encodedPath = encodedPath.replaceAll('%2F', '/');
String local = paths.path + Platform.pathSeparator + name;
@ -44,7 +44,7 @@ class FileElement extends StatefulWidget {
onDone: () {
//Future<OpenResult> result = OpenFile.open(local); // TODO legacy - refactor: remove onDone parameter
Navigator.of(context).push(MaterialPageRoute(builder: (context) => FileViewer(path: local)));
onDone(OpenResult(message: "File viewer opened", type: ResultType.done));
onDone(OpenResult(message: 'File viewer opened', type: ResultType.done));
// result.then((value) => {
// onDone(value)
// });
@ -71,21 +71,21 @@ class _FileElementState extends State<FileElement> {
children: [
Container(
margin: const EdgeInsets.only(right: 10),
child: const Text("Download:"),
child: const Text('Download:'),
),
Expanded(
child: LinearProgressIndicator(value: percent/100),
),
Container(
margin: const EdgeInsets.only(left: 10),
child: Text("${percent.round()}%"),
child: Text('${percent.round()}%'),
),
],
);
}
return widget.file.isDirectory
? Text("geändert ${Jiffy.parseFromDateTime(widget.file.modifiedAt ?? DateTime.now()).fromNow()}")
: Text("${filesize(widget.file.size)}, ${Jiffy.parseFromDateTime(widget.file.modifiedAt ?? DateTime.now()).fromNow()}");
? Text('geändert ${Jiffy.parseFromDateTime(widget.file.modifiedAt ?? DateTime.now()).fromNow()}')
: Text('${filesize(widget.file.size)}, ${Jiffy.parseFromDateTime(widget.file.modifiedAt ?? DateTime.now()).fromNow()}');
}
@override
@ -106,17 +106,17 @@ class _FileElementState extends State<FileElement> {
));
} else {
if(EndpointData().getEndpointMode() == EndpointMode.stage) {
InfoDialog.show(context, "Virtuelle Dateien im Staging Prozess können nicht heruntergeladen werden!");
InfoDialog.show(context, 'Virtuelle Dateien im Staging Prozess können nicht heruntergeladen werden!');
return;
}
if(widget.file.currentlyDownloading) {
showDialog(
context: context,
builder: (context) => ConfirmDialog(
title: "Download abbrechen?",
content: "Möchtest du den Download abbrechen?",
cancelButton: "Nein",
confirmButton: "Ja, Abbrechen",
title: 'Download abbrechen?',
content: 'Möchtest du den Download abbrechen?',
cancelButton: 'Nein',
confirmButton: 'Ja, Abbrechen',
onConfirm: () {
downloadCore?.then((value) {
if(!value.isCancelled) value.cancel();
@ -143,7 +143,7 @@ class _FileElementState extends State<FileElement> {
if(result.type != ResultType.done) {
showDialog(context: context, builder: (context) {
return AlertDialog(
title: const Text("Download"),
title: const Text('Download'),
content: Text(result.message),
);
});
@ -163,12 +163,12 @@ class _FileElementState extends State<FileElement> {
children: [
ListTile(
leading: const Icon(Icons.delete_outline),
title: const Text("Löschen"),
title: const Text('Löschen'),
onTap: () {
Navigator.of(context).pop();
showDialog(context: context, builder: (context) => ConfirmDialog(
title: "Element löschen?",
content: "Das Element wird unwiederruflich gelöscht.",
title: 'Element löschen?',
content: 'Das Element wird unwiederruflich gelöscht.',
onConfirm: () {
WebdavApi.webdav
.then((value) => value.delete(PathUri.parse(widget.file.path)))
@ -181,7 +181,7 @@ class _FileElementState extends State<FileElement> {
visible: !kReleaseMode,
child: ListTile(
leading: const Icon(Icons.share_outlined),
title: const Text("Teilen"),
title: const Text('Teilen'),
onTap: () {
Navigator.of(context).pop();
UnimplementedDialog.show(context);

View File

@ -43,8 +43,8 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
setState(() {
state = FileUploadState.checkConflict;
});
List<WebDavResponse> result = (await webdavClient.propfind(PathUri.parse(widget.remotePath.join("/")))).responses;
if(result.any((element) => element.href!.endsWith("/$targetFileName"))) {
List<WebDavResponse> result = (await webdavClient.propfind(PathUri.parse(widget.remotePath.join('/')))).responses;
if(result.any((element) => element.href!.endsWith('/$targetFileName'))) {
setState(() {
state = FileUploadState.conflict;
});
@ -57,7 +57,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
}
Future<HttpClientResponse> uploadTask = webdavClient.putFile(File(widget.localPath), FileStat.statSync(widget.localPath), PathUri.parse(fullRemotePath)); // TODO use onProgress from putFile
uploadTask.then((value) => Future<HttpClientResponse?>.value(value)).catchError((e) {
uploadTask.then(Future<HttpClientResponse?>.value).catchError((e) {
setState(() {
state = FileUploadState.error;
});
@ -67,7 +67,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
cancelableOperation = CancelableOperation<HttpClientResponse>.fromFuture(
uploadTask,
onCancel: () => log("Upload cancelled"),
onCancel: () => log('Upload cancelled'),
);
cancelableOperation!.then((value) {
@ -88,7 +88,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
void initState() {
super.initState();
targetFileName = widget.fileName;
remoteFolderName = widget.remotePath.isNotEmpty ? widget.remotePath.last : "/";
remoteFolderName = widget.remotePath.isNotEmpty ? widget.remotePath.last : '/';
fileNameController.text = widget.fileName;
}
@ -96,7 +96,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
Widget build(BuildContext context) {
if(state == FileUploadState.naming) {
return AlertDialog(
title: const Text("Datei hochladen"),
title: const Text('Datei hochladen'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -107,7 +107,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
},
autocorrect: false,
decoration: const InputDecoration(
labelText: "Dateiname",
labelText: 'Dateiname',
),
),
],
@ -115,10 +115,10 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
actions: [
TextButton(onPressed: () {
Navigator.of(context).pop();
}, child: const Text("Abbrechen")),
}, child: const Text('Abbrechen')),
TextButton(onPressed: () async {
upload();
}, child: const Text("Hochladen")),
}, child: const Text('Hochladen')),
],
);
@ -127,7 +127,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
if(state == FileUploadState.conflict) {
return AlertDialog(
icon: const Icon(Icons.error_outline),
title: const Text("Datei konflikt"),
title: const Text('Datei konflikt'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -139,10 +139,10 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
setState(() {
state = FileUploadState.naming;
});
}, child: const Text("Datei umbenennen")),
}, child: const Text('Datei umbenennen')),
TextButton(onPressed: () {
upload(override: true);
}, child: const Text("Datei überschreiben")),
}, child: const Text('Datei überschreiben')),
],
);
@ -151,15 +151,15 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
if(state == FileUploadState.upload || state == FileUploadState.checkConflict) {
return AlertDialog(
icon: const Icon(Icons.upload),
title: const Text("Hochladen"),
title: const Text('Hochladen'),
content: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Visibility(
visible: state == FileUploadState.upload,
replacement: const Text("Prüfe auf dateikonflikte..."),
child: const Text("Upload läuft!\nDies kann je nach Dateigröße einige Zeit dauern...", textAlign: TextAlign.center),
replacement: const Text('Prüfe auf dateikonflikte...'),
child: const Text('Upload läuft!\nDies kann je nach Dateigröße einige Zeit dauern...', textAlign: TextAlign.center),
),
const SizedBox(height: 30),
const CircularProgressIndicator()
@ -183,7 +183,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
}
return AlertDialog(
icon: const Icon(Icons.done),
title: const Text("Upload fertig"),
title: const Text('Upload fertig'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -193,7 +193,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
actions: [
TextButton(onPressed: () {
Navigator.of(context).pop();
}, child: const Text("Fertig")),
}, child: const Text('Fertig')),
],
);
@ -202,23 +202,23 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
if(state == FileUploadState.error) {
return AlertDialog(
icon: const Icon(Icons.error_outline),
title: const Text("Fehler"),
title: const Text('Fehler'),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Es ist ein Fehler aufgetreten!", textAlign: TextAlign.center),
Text('Es ist ein Fehler aufgetreten!', textAlign: TextAlign.center),
],
),
actions: [
TextButton(onPressed: () {
Navigator.of(context).pop();
}, child: const Text("Schlißen")),
}, child: const Text('Schlißen')),
],
);
}
throw UnimplementedError("Invalid state");
throw UnimplementedError('Invalid state');
}
}

View File

@ -43,17 +43,17 @@ enum SortOption {
class SortOptions {
static Map<SortOption, BetterSortOption> options = {
SortOption.name: BetterSortOption(
displayName: "Name",
displayName: 'Name',
icon: Icons.sort_by_alpha_outlined,
compare: (CacheableFile a, CacheableFile b) => a.name.compareTo(b.name)
),
SortOption.date: BetterSortOption(
displayName: "Datum",
displayName: 'Datum',
icon: Icons.history_outlined,
compare: (CacheableFile a, CacheableFile b) => a.modifiedAt!.compareTo(b.modifiedAt!)
),
SortOption.size: BetterSortOption(
displayName: "Größe",
displayName: 'Größe',
icon: Icons.sd_card_outlined,
compare: (CacheableFile a, CacheableFile b) {
if(a.isDirectory || b.isDirectory) return a.isDirectory ? 1 : 0;
@ -88,7 +88,7 @@ class _FilesState extends State<Files> {
void _query() {
ListFilesCache(
path: widget.path.isEmpty ? "/" : widget.path.join("/"),
path: widget.path.isEmpty ? '/' : widget.path.join('/'),
onUpdate: (ListFilesResponse d) {
if(!context.mounted) return; // prevent setState when widget is possibly already disposed
d.files.removeWhere((element) => element.name.isEmpty || element.name == widget.path.lastOrNull());
@ -109,7 +109,7 @@ class _FilesState extends State<Files> {
return Scaffold(
appBar: AppBar(
title: Text(widget.path.isNotEmpty ? widget.path.last : "Dateien"),
title: Text(widget.path.isNotEmpty ? widget.path.last : 'Dateien'),
actions: [
// IconButton(
// icon: const Icon(Icons.search),
@ -127,7 +127,7 @@ class _FilesState extends State<Files> {
children: [
Icon(e ? Icons.text_rotate_up : Icons.text_rotation_down, color: Theme.of(context).colorScheme.onSurface),
const SizedBox(width: 15),
Text(e ? "Aufsteigend" : "Absteigend")
Text(e ? 'Aufsteigend' : 'Absteigend')
],
)
)).toList();
@ -164,7 +164,7 @@ class _FilesState extends State<Files> {
],
),
floatingActionButton: FloatingActionButton(
heroTag: "uploadFile",
heroTag: 'uploadFile',
backgroundColor: Theme.of(context).primaryColor,
onPressed: () {
showDialog(context: context, builder: (context) {
@ -172,29 +172,29 @@ class _FilesState extends State<Files> {
children: [
ListTile(
leading: const Icon(Icons.create_new_folder_outlined),
title: const Text("Ordner erstellen"),
title: const Text('Ordner erstellen'),
onTap: () {
Navigator.of(context).pop();
showDialog(context: context, builder: (context) {
var inputController = TextEditingController();
return AlertDialog(
title: const Text("Neuer Ordner"),
title: const Text('Neuer Ordner'),
content: TextField(
controller: inputController,
decoration: const InputDecoration(
labelText: "Name",
labelText: 'Name',
),
),
actions: [
TextButton(onPressed: () {
Navigator.of(context).pop();
}, child: const Text("Abbrechen")),
}, child: const Text('Abbrechen')),
TextButton(onPressed: () {
WebdavApi.webdav.then((webdav) {
webdav.mkcol(PathUri.parse("${widget.path.join("/")}/${inputController.text}")).then((value) => _query());
});
Navigator.of(context).pop();
}, child: const Text("Ordner erstellen")),
}, child: const Text('Ordner erstellen')),
],
);
});
@ -202,12 +202,10 @@ class _FilesState extends State<Files> {
),
ListTile(
leading: const Icon(Icons.upload_file),
title: const Text("Aus Dateien hochladen"),
title: const Text('Aus Dateien hochladen'),
onTap: () {
context.loaderOverlay.show();
FilePick.documentPick().then((value) {
mediaUpload(value);
});
FilePick.documentPick().then(mediaUpload);
Navigator.of(context).pop();
},
),
@ -215,7 +213,7 @@ class _FilesState extends State<Files> {
visible: !Platform.isIOS,
child: ListTile(
leading: const Icon(Icons.add_a_photo_outlined),
title: const Text("Aus Gallerie hochladen"),
title: const Text('Aus Gallerie hochladen'),
onTap: () {
context.loaderOverlay.show();
FilePick.galleryPick().then((value) {
@ -231,7 +229,7 @@ class _FilesState extends State<Files> {
},
child: const Icon(Icons.add),
),
body: data == null ? const LoadingSpinner() : data!.files.isEmpty ? const PlaceholderView(icon: Icons.folder_off_rounded, text: "Der Ordner ist leer") : LoaderOverlay(
body: data == null ? const LoadingSpinner() : data!.files.isEmpty ? const PlaceholderView(icon: Icons.folder_off_rounded, text: 'Der Ordner ist leer') : LoaderOverlay(
child: RefreshIndicator(
onRefresh: () {
_query();
@ -258,6 +256,6 @@ class _FilesState extends State<Files> {
}
var fileName = path.split(Platform.pathSeparator).last;
showDialog(context: context, builder: (context) => FileUploadDialog(localPath: path, remotePath: widget.path, fileName: fileName, onUploadFinished: () => _query()), barrierDismissible: false);
showDialog(context: context, builder: (context) => FileUploadDialog(localPath: path, remotePath: widget.path, fileName: fileName, onUploadFinished: _query), barrierDismissible: false);
}
}

View File

@ -22,20 +22,20 @@ class _FeedbackDialogState extends State<FeedbackDialog> {
Widget build(BuildContext context) {
return AlertDialog(
title: const Text("Feedback"),
title: const Text('Feedback'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("Feedback, Anregungen, Ideen, Fehler und Verbesserungen"),
const Text('Feedback, Anregungen, Ideen, Fehler und Verbesserungen'),
const SizedBox(height: 10),
const Text("Bitte gib keine geheimen Daten wie z.B. Passwörter weiter.", style: TextStyle(fontSize: 10)),
const Text('Bitte gib keine geheimen Daten wie z.B. Passwörter weiter.', style: TextStyle(fontSize: 10)),
const SizedBox(height: 10),
TextField(
controller: _feedbackInput,
autofocus: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
label: Text("Feedback und Verbesserungen")
label: Text('Feedback und Verbesserungen')
),
// style: TextStyle(),
// expands: true,
@ -44,12 +44,12 @@ class _FeedbackDialogState extends State<FeedbackDialog> {
),
Visibility(
visible: _error != null,
child: Text("Senden fehlgeschlagen: $_error", style: const TextStyle(color: Colors.red))
child: Text('Senden fehlgeschlagen: $_error', style: const TextStyle(color: Colors.red))
)
],
),
actions: [
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("Abbrechen")),
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Abbrechen')),
TextButton(
onPressed: () async {
AddFeedback(
@ -62,7 +62,7 @@ class _FeedbackDialogState extends State<FeedbackDialog> {
.run()
.then((value) {
Navigator.of(context).pop();
InfoDialog.show(context, "Danke für dein Feedback!");
InfoDialog.show(context, 'Danke für dein Feedback!');
})
.catchError((error, trace) {
setState(() {
@ -70,7 +70,7 @@ class _FeedbackDialogState extends State<FeedbackDialog> {
});
});
},
child: const Text("Senden"),
child: const Text('Senden'),
)
],
);

View File

@ -24,7 +24,7 @@ class _GradeAverageState extends State<GradeAverage> {
String getGradeDisplay(int grade) {
if(gradeSystem) {
return "Note $grade";
return 'Note $grade';
} else {
return "$grade Punkt${grade > 1 ? "e" : ""}";
}
@ -48,22 +48,22 @@ class _GradeAverageState extends State<GradeAverage> {
if(!settings.val().gradeAveragesSettings.askedForPreferredGradeSystem) {
settings.val(write: true).gradeAveragesSettings.askedForPreferredGradeSystem = true;
showDialog(context: context, builder: (context) => AlertDialog(
title: const Text("Notensystem"),
content: const Text("Wähle dein bevorzugtes Schulnotensystem"),
title: const Text('Notensystem'),
content: const Text('Wähle dein bevorzugtes Schulnotensystem'),
actions: [
TextButton(
onPressed: () {
switchSystem(true);
Navigator.of(context).pop();
},
child: const Text("Realschule"),
child: const Text('Realschule'),
),
TextButton(
onPressed: () {
switchSystem(false);
Navigator.of(context).pop();
},
child: const Text("Oberstufe"),
child: const Text('Oberstufe'),
),
],
));
@ -81,7 +81,7 @@ class _GradeAverageState extends State<GradeAverage> {
return Scaffold(
appBar: AppBar(
title: const Text("Notendurschnittsrechner"),
title: const Text('Notendurschnittsrechner'),
actions: [
Visibility(
visible: grades.isNotEmpty,
@ -89,9 +89,9 @@ class _GradeAverageState extends State<GradeAverage> {
showDialog(
context: context,
builder: (context) => ConfirmDialog(
title: "Zurücksetzen?",
content: "Alle Einträge werden entfernt.",
confirmButton: "Zurücksetzen",
title: 'Zurücksetzen?',
content: 'Alle Einträge werden entfernt.',
confirmButton: 'Zurücksetzen',
onConfirm: () {
grades.clear();
setState(() {});
@ -109,7 +109,7 @@ class _GradeAverageState extends State<GradeAverage> {
children: [
Icon(e ? Icons.calculate_outlined : Icons.school_outlined, color: Theme.of(context).colorScheme.onSurface),
const SizedBox(width: 15),
Text(e ? "Notensystem" : "Punktesystem"),
Text(e ? 'Notensystem' : 'Punktesystem'),
],
),
)).toList(),
@ -120,9 +120,9 @@ class _GradeAverageState extends State<GradeAverage> {
showDialog(
context: context,
builder: (context) => ConfirmDialog(
title: "Notensystem wechseln",
content: "Beim wechsel des Notensystems werden alle Einträge zurückgesetzt.",
confirmButton: "Fortfahren",
title: 'Notensystem wechseln',
content: 'Beim wechsel des Notensystems werden alle Einträge zurückgesetzt.',
confirmButton: 'Fortfahren',
onConfirm: () => switchSystem(e),
),
);
@ -142,7 +142,7 @@ class _GradeAverageState extends State<GradeAverage> {
const SizedBox(height: 10),
const Divider(),
const SizedBox(height: 10),
Text(gradeSystem ? "Wähle unten die Anzahl deiner jewiligen Noten aus" : "Wähle unten die Anzahl deiner jeweiligen Punkte aus"),
Text(gradeSystem ? 'Wähle unten die Anzahl deiner jewiligen Noten aus' : 'Wähle unten die Anzahl deiner jeweiligen Punkte aus'),
const SizedBox(height: 10),
Expanded(
child: ListView.builder(
@ -169,7 +169,7 @@ class _GradeAverageState extends State<GradeAverage> {
icon: const Icon(Icons.remove),
color: Theme.of(context).colorScheme.onSurface,
),
Text("${grades.where(isThis).length}", style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
Text('${grades.where(isThis).length}', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
IconButton(
onPressed: () {
setState(() {

View File

@ -42,23 +42,23 @@ class _HolidaysState extends State<Holidays> {
}
String parseString(String enDate) {
return Jiffy.parse(enDate).format(pattern: "dd.MM.yyyy");
return Jiffy.parse(enDate).format(pattern: 'dd.MM.yyyy');
}
void showDisclaimer() {
showDialog(context: context, builder: (context) {
return AlertDialog(
title: const Text("Richtigkeit und Bereitstellung der Daten"),
title: const Text('Richtigkeit und Bereitstellung der Daten'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(""
"Sämtliche Datumsangaben sind ohne Gewähr.\n"
"Ich übernehme weder Verantwortung für die Richtigkeit der Daten noch hafte ich für wirtschaftliche Schäden die aus der Verwendung dieser Daten entstehen können.\n\n"
"Die Daten stammen von https://ferien-api.de/"),
const Text(''
'Sämtliche Datumsangaben sind ohne Gewähr.\n'
'Ich übernehme weder Verantwortung für die Richtigkeit der Daten noch hafte ich für wirtschaftliche Schäden die aus der Verwendung dieser Daten entstehen können.\n\n'
'Die Daten stammen von https://ferien-api.de/'),
const SizedBox(height: 30),
ListTile(
title: const Text("Diese Meldung nicht mehr anzeigen"),
title: const Text('Diese Meldung nicht mehr anzeigen'),
trailing: Checkbox(
value: settings.val().holidaysSettings.dismissedDisclaimer,
onChanged: (value) => settings.val(write: true).holidaysSettings.dismissedDisclaimer = value!,
@ -67,8 +67,8 @@ class _HolidaysState extends State<Holidays> {
],
),
actions: [
TextButton(child: const Text("ferien-api.de besuchen"), onPressed: () => ConfirmDialog.openBrowser(context, "https://ferien-api.de/")),
TextButton(child: const Text("Okay"), onPressed: () => Navigator.of(context).pop()),
TextButton(child: const Text('ferien-api.de besuchen'), onPressed: () => ConfirmDialog.openBrowser(context, 'https://ferien-api.de/')),
TextButton(child: const Text('Okay'), onPressed: () => Navigator.of(context).pop()),
],
);
});
@ -78,11 +78,11 @@ class _HolidaysState extends State<Holidays> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Schulferien in Hessen"),
title: const Text('Schulferien in Hessen'),
actions: [
IconButton(
icon: const Icon(Icons.warning_amber_outlined),
onPressed: () => showDisclaimer(),
onPressed: showDisclaimer,
),
PopupMenuButton<bool>(
initialValue: settings.val().holidaysSettings.showPastEvents,
@ -95,7 +95,7 @@ class _HolidaysState extends State<Holidays> {
children: [
Icon(e ? Icons.history_outlined : Icons.history_toggle_off_outlined, color: Theme.of(context).colorScheme.onSurface),
const SizedBox(width: 15),
Text(e ? "Alle anzeigen" : "Nur zukünftige anzeigen")
Text(e ? 'Alle anzeigen' : 'Nur zukünftige anzeigen')
],
)
)).toList();
@ -115,19 +115,19 @@ class _HolidaysState extends State<Holidays> {
List<GetHolidaysResponseObject> holidays = value.getHolidaysResponse.data;
if(!showPastEvents) holidays = holidays.where((element) => DateTime.parse(element.end).isAfter(DateTime.now())).toList();
if(holidays.isEmpty) return const PlaceholderView(icon: Icons.search_off, text: "Es wurden keine Ferieneinträge gefunden!");
if(holidays.isEmpty) return const PlaceholderView(icon: Icons.search_off, text: 'Es wurden keine Ferieneinträge gefunden!');
return ListView.builder(
itemCount: holidays.length,
itemBuilder: (context, index) {
GetHolidaysResponseObject holiday = holidays[index];
String holidayType = holiday.name.split(" ").first.capitalize();
String holidayType = holiday.name.split(' ').first.capitalize();
return ListTile(
leading: const CenteredLeading(Icon(Icons.calendar_month)),
title: Text("$holidayType ab ${parseString(holiday.start)}"),
subtitle: Text("bis ${parseString(holiday.end)}"),
title: Text('$holidayType ab ${parseString(holiday.start)}'),
subtitle: Text('bis ${parseString(holiday.end)}'),
onTap: () => showDialog(context: context, builder: (context) => SimpleDialog(
title: Text("$holidayType ${holiday.year} in Hessen"),
title: Text('$holidayType ${holiday.year} in Hessen'),
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.signpost_outlined)),
@ -136,11 +136,11 @@ class _HolidaysState extends State<Holidays> {
),
ListTile(
leading: const Icon(Icons.arrow_forward),
title: Text("vom ${parseString(holiday.start)}"),
title: Text('vom ${parseString(holiday.start)}'),
),
ListTile(
leading: const Icon(Icons.arrow_back),
title: Text("bis zum ${parseString(holiday.end)}"),
title: Text('bis zum ${parseString(holiday.end)}'),
),
Visibility(
visible: !DateTime.parse(holiday.start).difference(DateTime.now()).isNegative,

View File

@ -27,7 +27,7 @@ class _MessageState extends State<Message> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Marianum Message"),
title: const Text('Marianum Message'),
),
body: Consumer<MessageProps>(builder: (context, value, child) {
if(value.primaryLoading()) return const LoadingSpinner();
@ -43,7 +43,7 @@ class _MessageState extends State<Message> {
children: [Icon(Icons.newspaper)],
),
title: Text(message.name, overflow: TextOverflow.ellipsis),
subtitle: Text("vom ${message.date}"),
subtitle: Text('vom ${message.date}'),
trailing: const Icon(Icons.arrow_right),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => MessageView(basePath: value.getMessagesResponse.base, message: message)));

View File

@ -29,12 +29,12 @@ class _MessageViewState extends State<MessageView> {
Navigator.of(context).pop();
showDialog(context: context, builder: (context) {
return AlertDialog(
title: const Text("Fehler beim öffnen"),
title: const Text('Fehler beim öffnen'),
content: Text("Dokument '${widget.message.name}' konnte nicht geladen werden:\n${e.description}"),
actions: [
TextButton(onPressed: () {
Navigator.of(context).pop();
}, child: const Text("Ok"))
}, child: const Text('Ok'))
],
);
});
@ -43,9 +43,9 @@ class _MessageViewState extends State<MessageView> {
showDialog(
context: context,
builder: (context) => ConfirmDialog(
title: "Link öffnen",
content: "Möchtest du den folgenden Link öffnen?\n${e.uri}",
confirmButton: "Öffnen",
title: 'Link öffnen',
content: 'Möchtest du den folgenden Link öffnen?\n${e.uri}',
confirmButton: 'Öffnen',
onConfirm: () => launchUrl(Uri.parse(e.uri), mode: LaunchMode.externalApplication),
),
);

View File

@ -8,10 +8,10 @@ class Roomplan extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Raumplan"),
title: const Text('Raumplan'),
),
body: PhotoView(
imageProvider: Image.asset("assets/img/raumplan.jpg").image,
imageProvider: Image.asset('assets/img/raumplan.jpg').image,
minScale: 0.5,
maxScale: 2.0,
backgroundDecoration: BoxDecoration(color: Theme.of(context).colorScheme.background),

View File

@ -16,18 +16,18 @@ class _QrShareViewState extends State<QrShareView> {
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text("Teile die App"),
title: const Text('Teile die App'),
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.android_outlined), text: "Android"),
Tab(icon: Icon(Icons.apple_outlined), text: "iOS & iPadOS"),
Tab(icon: Icon(Icons.android_outlined), text: 'Android'),
Tab(icon: Icon(Icons.apple_outlined), text: 'iOS & iPadOS'),
],
),
),
body: const TabBarView(
children: [
AppSharePlatformView("Für Android", "https://play.google.com/store/apps/details?id=eu.mhsl.marianum.mobile.client"),
AppSharePlatformView("Für iOS & iPad", "https://apps.apple.com/us/app/marianum-fulda/id6458789560"),
AppSharePlatformView('Für Android', 'https://play.google.com/store/apps/details?id=eu.mhsl.marianum.mobile.client'),
AppSharePlatformView('Für iOS & iPad', 'https://apps.apple.com/us/app/marianum-fulda/id6458789560'),
],
),
),

View File

@ -13,7 +13,7 @@ class SelectShareTypeDialog extends StatelessWidget {
children: [
ListTile(
leading: const Icon(Icons.qr_code_2_outlined),
title: const Text("Per QR-Code"),
title: const Text('Per QR-Code'),
trailing: const Icon(Icons.arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const QrShareView()));
@ -21,16 +21,16 @@ class SelectShareTypeDialog extends StatelessWidget {
),
ListTile(
leading: const Icon(Icons.link_outlined),
title: const Text("Per Link teilen"),
title: const Text('Per Link teilen'),
trailing: const Icon(Icons.arrow_right),
onTap: () {
Share.share(
sharePositionOrigin: SharePositionOrigin.get(context),
subject: "App Teilen",
"Hol dir die für das Marianum maßgeschneiderte App:"
"\n\nAndroid: https://play.google.com/store/apps/details?id=eu.mhsl.marianum.mobile.client "
"\niOS: https://apps.apple.com/us/app/marianum-fulda/id6458789560 "
"\n\nViel Spaß!"
subject: 'App Teilen',
'Hol dir die für das Marianum maßgeschneiderte App:'
'\n\nAndroid: https://play.google.com/store/apps/details?id=eu.mhsl.marianum.mobile.client '
'\niOS: https://apps.apple.com/us/app/marianum-fulda/id6458789560 '
'\n\nViel Spaß!'
);
},
)

View File

@ -3,7 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:in_app_review/in_app_review.dart';
import 'package:marianum_mobile/extensions/renderNotNull.dart';
import '../../extensions/renderNotNull.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import '../../widget/ListItem.dart';
@ -26,23 +26,23 @@ class Overhang extends StatelessWidget {
return Scaffold(
appBar: AppBar(
title: const Text("Mehr"),
title: const Text('Mehr'),
actions: [
IconButton(onPressed: () => pushScreen(context, screen: const Settings(), withNavBar: false), icon: const Icon(Icons.settings))
],
),
body: ListView(
children: [
const ListItemNavigator(icon: Icons.newspaper, text: "Marianum Message", target: Message()),
const ListItemNavigator(icon: Icons.room, text: "Raumplan", target: Roomplan()),
const ListItemNavigator(icon: Icons.calculate, text: "Notendurschnittsrechner", target: GradeAverage()),
const ListItemNavigator(icon: Icons.newspaper, text: 'Marianum Message', target: Message()),
const ListItemNavigator(icon: Icons.room, text: 'Raumplan', target: Roomplan()),
const ListItemNavigator(icon: Icons.calculate, text: 'Notendurschnittsrechner', target: GradeAverage()),
const ListItemNavigator(icon: Icons.school_outlined, text: "Abiturrechner", target: AbiturCalculatorView()),
const ListItemNavigator(icon: Icons.calendar_month, text: "Schulferien", target: Holidays()),
const ListItemNavigator(icon: Icons.calendar_month, text: 'Schulferien', target: Holidays()),
const Divider(),
ListTile(
leading: const Icon(Icons.share_outlined),
title: const Text("Teile die App"),
subtitle: const Text("Mit Freunden und deiner Klasse teilen"),
title: const Text('Teile die App'),
subtitle: const Text('Mit Freunden und deiner Klasse teilen'),
trailing: const Icon(Icons.arrow_right),
onTap: () => showDialog(context: context, builder: (context) => const SelectShareTypeDialog())
),
@ -52,19 +52,19 @@ class Overhang extends StatelessWidget {
if(!snapshot.hasData) return const SizedBox.shrink();
String? getPlatformStoreName() {
if(Platform.isAndroid) return "Play store";
if(Platform.isIOS) return "App store";
if(Platform.isAndroid) return 'Play store';
if(Platform.isIOS) return 'App store';
return null;
}
return ListTile(
leading: const CenteredLeading(Icon(Icons.star_rate_outlined)),
title: const Text("App Bewerten"),
subtitle: getPlatformStoreName().wrapNullable((data) => Text("Im $data")),
title: const Text('App bewerten'),
subtitle: getPlatformStoreName().wrapNullable((data) => Text('Im $data')),
trailing: const Icon(Icons.arrow_right),
onTap: () {
InAppReview.instance.openStoreListing(appStoreId: "6458789560").then(
(value) => InfoDialog.show(context, "Vielen Dank!"),
InAppReview.instance.openStoreListing(appStoreId: '6458789560').then(
(value) => InfoDialog.show(context, 'Vielen Dank!'),
onError: (error) => InfoDialog.show(context, error.toString())
);
},
@ -73,8 +73,8 @@ class Overhang extends StatelessWidget {
),
ListTile(
leading: const CenteredLeading(Icon(Icons.feedback_outlined)),
title: const Text("Du hast eine Idee?"),
subtitle: const Text("Fehler und Verbessungsvorschläge"),
title: const Text('Du hast eine Idee?'),
subtitle: const Text('Fehler und Verbessungsvorschläge'),
trailing: const Icon(Icons.arrow_right),
onTap: () => showDialog(context: context, barrierDismissible: false, builder: (context) => const FeedbackDialog()),
),

View File

@ -66,7 +66,7 @@ class _ChatInfoState extends State<ChatInfo> {
if(participants != null) ...[
ListTile(
leading: const Icon(Icons.supervised_user_circle),
title: Text("${participants!.data.length} Teilnehmer"),
title: Text('${participants!.data.length} Teilnehmer'),
trailing: const Icon(Icons.arrow_right),
onTap: () => TalkNavigator.pushSplitView(context, ParticipantsListView(participants!)),
),

View File

@ -16,7 +16,7 @@ class _ParticipantsListViewState extends State<ParticipantsListView> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Teilnehmende"),
title: const Text('Teilnehmende'),
),
body: ListView(
children: widget.participantsResponse.data.map((participant) {

View File

@ -40,9 +40,9 @@ class _ChatListState extends State<ChatList> {
ConfirmDialog(
icon: Icons.notifications_active_outlined,
title: "Benachrichtigungen aktivieren",
content: "Auf wunsch kannst du Push-Benachrichtigungen aktivieren. Deine Einstellungen kannst du jederzeit ändern.",
confirmButton: "Weiter",
title: 'Benachrichtigungen aktivieren',
content: 'Auf wunsch kannst du Push-Benachrichtigungen aktivieren. Deine Einstellungen kannst du jederzeit ändern.',
confirmButton: 'Weiter',
onConfirm: () {
FirebaseMessaging.instance.requestPermission(
provisional: false
@ -53,7 +53,7 @@ class _ChatListState extends State<ChatList> {
break;
case AuthorizationStatus.denied:
showDialog(context: context, builder: (context) => const AlertDialog(
content: Text("Du kannst die Benachrichtigungen später jederzeit in den App-Einstellungen aktivieren."),
content: Text('Du kannst die Benachrichtigungen später jederzeit in den App-Einstellungen aktivieren.'),
));
break;
default:
@ -79,7 +79,7 @@ class _ChatListState extends State<ChatList> {
breakpoint: 1000,
child: Scaffold(
appBar: AppBar(
title: const Text("Talk"),
title: const Text('Talk'),
actions: [
IconButton(
icon: const Icon(Icons.search),
@ -91,16 +91,16 @@ class _ChatListState extends State<ChatList> {
],
),
floatingActionButton: FloatingActionButton(
heroTag: "createChat",
heroTag: 'createChat',
backgroundColor: Theme.of(context).primaryColor,
onPressed: () async {
showSearch(context: context, delegate: JoinChat()).then((username) {
if(username == null) return;
ConfirmDialog(
title: "Chat starten",
title: 'Chat starten',
content: "Möchtest du einen Chat mit Nutzer '$username' starten?",
confirmButton: "Chat starten",
confirmButton: 'Chat starten',
onConfirm: () {
CreateRoom(CreateRoomParams(
roomType: 1,

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:marianum_mobile/extensions/dateTime.dart';
import '../../../extensions/dateTime.dart';
import 'package:provider/provider.dart';
import '../../../api/marianumcloud/talk/chat/getChatResponse.dart';
@ -51,8 +51,8 @@ class _ChatViewState extends State<ChatView> {
data.getChatResponse.sortByTimestamp().forEach((element) {
DateTime elementDate = DateTime.fromMillisecondsSinceEpoch(element.timestamp * 1000);
if(element.systemMessage.contains("reaction")) return;
int commonRead = int.parse(data.getChatResponse.headers?['x-chat-last-common-read'] ?? "0");
if(element.systemMessage.contains('reaction')) return;
int commonRead = int.parse(data.getChatResponse.headers?['x-chat-last-common-read'] ?? '0');
if(!elementDate.isSameDay(lastDate)) {
lastDate = elementDate;
@ -81,8 +81,8 @@ class _ChatViewState extends State<ChatView> {
context: context,
isSender: false,
bubbleData: GetChatResponseObject.getTextDummy(
"Zurzeit können in dieser App nur die letzten 200 vergangenen Nachrichten angezeigt werden. "
"Um ältere Nachrichten abzurufen verwende die Webversion unter https://cloud.marianum-fulda.de"
'Zurzeit können in dieser App nur die letzten 200 vergangenen Nachrichten angezeigt werden. '
'Um ältere Nachrichten abzurufen verwende die Webversion unter https://cloud.marianum-fulda.de'
),
chatData: widget.room,
refetch: _query,
@ -111,7 +111,7 @@ class _ChatViewState extends State<ChatView> {
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: const AssetImage("assets/background/chat.png"),
image: const AssetImage('assets/background/chat.png'),
scale: 1.5,
opacity: 1,
repeat: ImageRepeat.repeat,

View File

@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:jiffy/jiffy.dart';
import 'package:marianum_mobile/extensions/text.dart';
import '../../../../extensions/text.dart';
import 'package:provider/provider.dart';
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
@ -114,7 +114,7 @@ class _ChatBubbleState extends State<ChatBubble> {
);
Text timeText = Text(
Jiffy.parseFromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).format(pattern: "HH:mm"),
Jiffy.parseFromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).format(pattern: 'HH:mm'),
textAlign: TextAlign.end,
style: TextStyle(color: widget.timeIconColor, fontSize: widget.timeIconSize),
);
@ -184,7 +184,7 @@ class _ChatBubbleState extends State<ChatBubble> {
),
onLongPress: () {
showDialog(context: context, builder: (context) {
List<String> commonReactions = ["👍", "👎", "😆", "❤️", "👀", "🤔"];
List<String> commonReactions = ['👍', '👎', '😆', '❤️', '👀', '🤔'];
bool canReact = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment;
return SimpleDialog(
children: [
@ -222,7 +222,7 @@ class _ChatBubbleState extends State<ChatBubble> {
visible: canReact,
child: ListTile(
leading: const Icon(Icons.add_reaction_outlined),
title: const Text("Reaktionen"),
title: const Text('Reaktionen'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MessageReactions(
token: widget.chatData.token,
@ -235,7 +235,7 @@ class _ChatBubbleState extends State<ChatBubble> {
visible: !message.containsFile,
child: ListTile(
leading: const Icon(Icons.copy),
title: const Text("Nachricht kopieren"),
title: const Text('Nachricht kopieren'),
onTap: () => {
Clipboard.setData(ClipboardData(text: widget.bubbleData.message)),
Navigator.of(context).pop(),
@ -256,7 +256,7 @@ class _ChatBubbleState extends State<ChatBubble> {
visible: widget.isSender && DateTime.fromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).add(const Duration(hours: 6)).isAfter(DateTime.now()),
child: ListTile(
leading: const Icon(Icons.delete_outline),
title: const Text("Nachricht löschen"),
title: const Text('Nachricht löschen'),
onTap: () {
DeleteMessage(widget.chatData.token, widget.bubbleData.id).run().then((value) {
Provider.of<ChatProps>(context, listen: false).run();
@ -276,12 +276,12 @@ class _ChatBubbleState extends State<ChatBubble> {
if(downloadProgress > 0) {
showDialog(context: context, builder: (context) {
return AlertDialog(
title: const Text("Download abbrechen?"),
content: const Text("Möchtest du den Download abbrechen?"),
title: const Text('Download abbrechen?'),
content: const Text('Möchtest du den Download abbrechen?'),
actions: [
TextButton(onPressed: () {
Navigator.of(context).pop();
}, child: const Text("Nein")),
}, child: const Text('Nein')),
TextButton(onPressed: () {
downloadCore?.then((value) {
if(!value.isCancelled) value.cancel();
@ -291,7 +291,7 @@ class _ChatBubbleState extends State<ChatBubble> {
downloadProgress = 0;
downloadCore = null;
});
}, child: const Text("Ja, Abbrechen"))
}, child: const Text('Ja, Abbrechen'))
],
);
});
@ -336,7 +336,7 @@ class _ChatBubbleState extends State<ChatBubble> {
return Container(
margin: const EdgeInsets.only(right: 2.5, left: 2.5),
child: ActionChip(
label: Text("${e.key} ${e.value}"),
label: Text('${e.key} ${e.value}'),
visualDensity: const VisualDensity(vertical: VisualDensity.minimumDensity, horizontal: VisualDensity.minimumDensity),
padding: EdgeInsets.zero,
backgroundColor: hasSelfReacted ? Theme.of(context).primaryColor : null,

View File

@ -14,14 +14,14 @@ class ChatMessage {
Map<String, RichObjectString>? originalData;
RichObjectString? file;
String content = "";
String content = '';
bool get containsFile => file != null;
ChatMessage({required this.originalMessage, this.originalData}) {
if(originalData?.containsKey("file") ?? false) {
if(originalData?.containsKey('file') ?? false) {
file = originalData?['file'];
content = file?.name ?? "Datei";
content = file?.name ?? 'Datei';
} else {
content = RichObjectStringProcessor.parseToString(originalMessage, originalData);
}
@ -56,7 +56,7 @@ class ChatMessage {
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
errorListener: (value) {},
imageUrl: "https://${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().full()}/index.php/core/preview?fileId=${file!.id}&x=100&y=-1&a=1",
imageUrl: 'https://${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().full()}/index.php/core/preview?fileId=${file!.id}&x=100&y=-1&a=1',
);
}

View File

@ -41,9 +41,9 @@ class _ChatTextfieldState extends State<ChatTextfield> {
}
String filename = "${path.split("/").last.split(".").first}-${const Uuid().v4()}.${path.split(".").last}";
String shareFolder = "MarianumMobile";
String shareFolder = 'MarianumMobile';
WebdavApi.webdav.then((webdav) {
webdav.mkcol(PathUri.parse("/$shareFolder"));
webdav.mkcol(PathUri.parse('/$shareFolder'));
});
showDialog(context: context, builder: (context) => FileUploadDialog(
@ -55,7 +55,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
FileSharingApi().share(FileSharingApiParams(
shareType: 10,
shareWith: widget.sendToToken,
path: "$shareFolder/$filename",
path: '$shareFolder/$filename',
)).then((value) => _query());
},
), barrierDismissible: false);
@ -77,7 +77,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
@override
Widget build(BuildContext context) {
_textBoxController.text = settings.val().talkSettings.drafts[widget.sendToToken] ?? "";
_textBoxController.text = settings.val().talkSettings.drafts[widget.sendToToken] ?? '';
return Stack(
children: <Widget>[
@ -95,12 +95,10 @@ class _ChatTextfieldState extends State<ChatTextfield> {
children: [
ListTile(
leading: const Icon(Icons.file_open),
title: const Text("Aus Dateien auswählen"),
title: const Text('Aus Dateien auswählen'),
onTap: () {
context.loaderOverlay.show();
FilePick.documentPick().then((value) {
mediaUpload(value);
});
FilePick.documentPick().then(mediaUpload);
Navigator.of(context).pop();
},
),
@ -108,7 +106,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
visible: !Platform.isIOS,
child: ListTile(
leading: const Icon(Icons.image),
title: const Text("Aus Gallerie auswählen"),
title: const Text('Aus Gallerie auswählen'),
onTap: () {
context.loaderOverlay.show();
FilePick.galleryPick().then((value) {
@ -147,12 +145,12 @@ class _ChatTextfieldState extends State<ChatTextfield> {
maxLines: 7,
minLines: 1,
decoration: const InputDecoration(
hintText: "Nachricht schreiben...",
hintText: 'Nachricht schreiben...',
border: InputBorder.none,
),
onChanged: (String text) {
if(text.trim().toLowerCase() == "marbot marbot marbot") {
var newText = "Roboter sind cool und so, aber Marbots sind besser!";
if(text.trim().toLowerCase() == 'marbot marbot marbot') {
var newText = 'Roboter sind cool und so, aber Marbots sind besser!';
_textBoxController.text = newText;
text = newText;
}
@ -175,8 +173,8 @@ class _ChatTextfieldState extends State<ChatTextfield> {
setState(() {
isLoading = false;
});
_textBoxController.text = "";
setDraft("");
_textBoxController.text = '';
setDraft('');
});
},
backgroundColor: Theme.of(context).primaryColor,

View File

@ -37,7 +37,7 @@ class _ChatTileState extends State<ChatTile> {
void initState() {
super.initState();
SharedPreferences.getInstance().then((value) => {
username = value.getString("username")!
username = value.getString('username')!
});
bool isGroup = widget.data.type != GetRoomResponseObjectConversationType.oneToOne;
@ -110,7 +110,7 @@ class _ChatTileState extends State<ChatTile> {
minHeight: 20,
),
child: Text(
"${widget.data.unreadMessages}",
'${widget.data.unreadMessages}',
style: const TextStyle(
color: Colors.white,
fontSize: 15,
@ -132,7 +132,7 @@ class _ChatTileState extends State<ChatTile> {
visible: widget.data.unreadMessages > 0,
replacement: ListTile(
leading: const Icon(Icons.mark_chat_unread_outlined),
title: const Text("Als ungelesen markieren"),
title: const Text('Als ungelesen markieren'),
onTap: () {
SetReadMarker(widget.data.token, false).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
@ -140,7 +140,7 @@ class _ChatTileState extends State<ChatTile> {
),
child: ListTile(
leading: const Icon(Icons.mark_chat_read_outlined),
title: const Text("Als gelesen markieren"),
title: const Text('Als gelesen markieren'),
onTap: () {
setCurrentAsRead();
Navigator.of(context).pop();
@ -151,7 +151,7 @@ class _ChatTileState extends State<ChatTile> {
visible: widget.data.isFavorite,
replacement: ListTile(
leading: const Icon(Icons.star_outline),
title: const Text("Zu Favoriten hinzufügen"),
title: const Text('Zu Favoriten hinzufügen'),
onTap: () {
SetFavorite(widget.data.token, true).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
@ -159,7 +159,7 @@ class _ChatTileState extends State<ChatTile> {
),
child: ListTile(
leading: const Icon(Icons.stars_outlined),
title: const Text("Von Favoriten entfernen"),
title: const Text('Von Favoriten entfernen'),
onTap: () {
SetFavorite(widget.data.token, false).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
@ -168,12 +168,12 @@ class _ChatTileState extends State<ChatTile> {
),
ListTile(
leading: const Icon(Icons.delete_outline),
title: const Text("Konversation verlassen"),
title: const Text('Konversation verlassen'),
onTap: () {
ConfirmDialog(
title: "Chat verlassen",
content: "Du benötigst ggf. eine Einladung um erneut beizutreten.",
confirmButton: "Löschen",
title: 'Chat verlassen',
content: 'Du benötigst ggf. eine Einladung um erneut beizutreten.',
confirmButton: 'Löschen',
onConfirm: () {
LeaveRoom(widget.data.token).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();

View File

@ -17,10 +17,10 @@ class SplitViewPlaceholder extends StatelessWidget {
data: MediaQuery.of(context).copyWith(
invertColors: !AppTheme.isDarkMode(context),
),
child: Image.asset("assets/logo/icon.png", height: 200),
child: Image.asset('assets/logo/icon.png', height: 200),
),
const SizedBox(height: 30),
const Text("Marianum Fulda\nTalk", textAlign: TextAlign.center, style: TextStyle(fontSize: 30)),
const Text('Marianum Fulda\nTalk', textAlign: TextAlign.center, style: TextStyle(fontSize: 30)),
],
),
)

View File

@ -33,7 +33,7 @@ class JoinChat extends SearchDelegate<String> {
return const SizedBox.shrink();
},
),
if(query.isNotEmpty) IconButton(onPressed: () => query = "", icon: const Icon(Icons.delete)),
if(query.isNotEmpty) IconButton(onPressed: () => query = '', icon: const Icon(Icons.delete)),
];
}
@ -48,7 +48,7 @@ class JoinChat extends SearchDelegate<String> {
if(query.isEmpty) {
return const PlaceholderView(
text: "Suche nach benutzern",
text: 'Suche nach benutzern',
icon: Icons.person_search_outlined,
);
}
@ -63,7 +63,7 @@ class JoinChat extends SearchDelegate<String> {
itemBuilder: (context, index) {
AutocompleteResponseObject object = snapshot.data!.data[index];
CircleAvatar circleAvatar = CircleAvatar(
foregroundImage: Image.network("https://${EndpointData().nextcloud().full()}/avatar/${object.id}/128").image,
foregroundImage: Image.network('https://${EndpointData().nextcloud().full()}/avatar/${object.id}/128').image,
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
child: const Icon(Icons.person),
@ -80,7 +80,7 @@ class JoinChat extends SearchDelegate<String> {
}
);
} else if(snapshot.hasError) {
return const PlaceholderView(icon: Icons.search_off, text: "Ein fehler ist aufgetreten. Bist du mit dem Internet verbunden?");
return const PlaceholderView(icon: Icons.search_off, text: 'Ein fehler ist aufgetreten. Bist du mit dem Internet verbunden?');
}
return const Center(child: CircularProgressIndicator());

View File

@ -33,13 +33,13 @@ class _MessageReactionsState extends State<MessageReactions> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Reaktionen"),
title: const Text('Reaktionen'),
),
body: FutureBuilder(
future: data,
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.waiting) return const LoadingSpinner();
if(snapshot.data == null) return const PlaceholderView(icon: Icons.search_off_outlined, text: "Keine Reaktionen gefunden!");
if(snapshot.data == null) return const PlaceholderView(icon: Icons.search_off_outlined, text: 'Keine Reaktionen gefunden!');
return ListView(
children: [
...snapshot.data!.data.entries.map<Widget>((entry) {
@ -49,17 +49,17 @@ class _MessageReactionsState extends State<MessageReactions> {
iconColor: Theme.of(context).colorScheme.onSurface,
collapsedIconColor: Theme.of(context).colorScheme.onSurface,
subtitle: const Text("Tippe für mehr"),
subtitle: const Text('Tippe für mehr'),
leading: CenteredLeading(Text(entry.key)),
title: Text("${entry.value.length} mal reagiert"),
title: Text('${entry.value.length} mal reagiert'),
children: entry.value.map((e) {
bool isSelf = AccountData().getUsername() == e.actorId;
return ListTile(
leading: UserAvatar(id: e.actorId, isGroup: false),
title: Text(e.actorDisplayName),
subtitle: isSelf
? const Text("Du")
: e.actorType == GetReactionsResponseObjectActorType.guests ? const Text("Gast") : null,
? const Text('Du')
: e.actorType == GetReactionsResponseObjectActorType.guests ? const Text('Gast') : null,
trailing: isSelf
? null
: Visibility(

View File

@ -11,7 +11,7 @@ class SearchChat extends SearchDelegate {
@override
List<Widget>? buildActions(BuildContext context) {
return [
if(query.isNotEmpty) IconButton(onPressed: () => query = "", icon: const Icon(Icons.delete)),
if(query.isNotEmpty) IconButton(onPressed: () => query = '', icon: const Icon(Icons.delete)),
];
}

View File

@ -53,7 +53,7 @@ class _AppointmentComponentState extends State<AppointmentComponent> {
FittedBox(
fit: BoxFit.fitWidth,
child: Text(
(meeting.location == null || meeting.location!.isEmpty ? " " : meeting.location!),
(meeting.location == null || meeting.location!.isEmpty ? ' ' : meeting.location!),
maxLines: 3,
overflow: TextOverflow.ellipsis,
softWrap: true,

View File

@ -27,9 +27,9 @@ import 'customTimetableEventEditDialog.dart';
class AppointmentDetails {
static String _getEventPrefix(String? code) {
if(code == "cancelled") return "Entfällt: ";
if(code == "irregular") return "Änderung: ";
return code ?? "";
if(code == 'cancelled') return 'Entfällt: ';
if(code == 'irregular') return 'Änderung: ';
return code ?? '';
}
static void show(BuildContext context, TimetableProps webuntisData, Appointment appointment) {
@ -65,13 +65,13 @@ class AppointmentDetails {
try {
subject = webuntisData.getSubjectsResponse.result.firstWhere((subject) => subject.id == timetableData.su[0].id);
} catch(e) {
subject = GetSubjectsResponseObject(0, "?", "Unbekannt", "?", true);
subject = GetSubjectsResponseObject(0, '?', 'Unbekannt', '?', true);
}
try {
room = webuntisData.getRoomsResponse.result.firstWhere((room) => room.id == timetableData.ro[0].id);
} catch(e) {
room = GetRoomsResponseObject(0, "?", "Unbekannt", true, "?");
room = GetRoomsResponseObject(0, '?', 'Unbekannt', true, '?');
}
_bottomSheet(
@ -99,7 +99,7 @@ class AppointmentDetails {
),
ListTile(
leading: const Icon(Icons.room),
title: Text("Raum: ${room.name} (${room.longName})"),
title: Text('Raum: ${room.name} (${room.longName})'),
trailing: IconButton(
icon: const Icon(Icons.house_outlined),
onPressed: () {
@ -111,7 +111,7 @@ class AppointmentDetails {
leading: const Icon(Icons.person),
title: timetableData.te.isNotEmpty
? Text("Lehrkraft: ${timetableData.te[0].name} ${timetableData.te[0].longname.isNotEmpty ? "(${timetableData.te[0].longname})" : ""}")
: const Text("?"),
: const Text('?'),
trailing: Visibility(
visible: !kReleaseMode,
child: IconButton(
@ -124,7 +124,7 @@ class AppointmentDetails {
),
ListTile(
leading: const Icon(Icons.abc),
title: Text("Typ: ${timetableData.activityType}"),
title: Text('Typ: ${timetableData.activityType}'),
),
ListTile(
leading: const Icon(Icons.people),
@ -140,9 +140,9 @@ class AppointmentDetails {
static Completer deleteCustomEvent(BuildContext context, CustomTimetableEvent appointment) {
Completer future = Completer();
ConfirmDialog(
title: "Termin löschen",
title: 'Termin löschen',
content: "Der ${appointment.rrule.isEmpty ? "Termin" : "Serientermin"} wird unwiederruflich gelöscht.",
confirmButton: "Löschen",
confirmButton: 'Löschen',
onConfirm: () {
RemoveCustomTimetableEvent(
RemoveCustomTimetableEventParams(
@ -186,14 +186,14 @@ class AppointmentDetails {
builder: (context) => CustomTimetableEventEditDialog(existingEvent: appointment),
);
},
label: const Text("Bearbeiten"),
label: const Text('Bearbeiten'),
icon: const Icon(Icons.edit_outlined),
),
TextButton.icon(
onPressed: () {
deleteCustomEvent(context, appointment).future.then((value) => Navigator.of(context).pop());
},
label: const Text("Löschen"),
label: const Text('Löschen'),
icon: const Icon(Icons.delete_outline),
),
],
@ -202,7 +202,7 @@ class AppointmentDetails {
const Divider(),
ListTile(
leading: const Icon(Icons.info_outline),
title: Text(appointment.description.isEmpty ? "Keine Beschreibung" : appointment.description),
title: Text(appointment.description.isEmpty ? 'Keine Beschreibung' : appointment.description),
),
ListTile(
leading: const CenteredLeading(Icon(Icons.repeat_outlined)),
@ -210,10 +210,10 @@ class AppointmentDetails {
subtitle: FutureBuilder(
future: RruleL10nEn.create(),
builder: (context, snapshot) {
if(appointment.rrule.isEmpty) return const Text("Keine weiteren vorkomnisse");
if(snapshot.data == null) return const Text("...");
if(appointment.rrule.isEmpty) return const Text('Keine weiteren vorkomnisse');
if(snapshot.data == null) return const Text('...');
RecurrenceRule rrule = RecurrenceRule.fromString(appointment.rrule);
if(!rrule.canFullyConvertToText) return const Text("Keine genauere Angabe möglich.");
if(!rrule.canFullyConvertToText) return const Text('Keine genauere Angabe möglich.');
return Text(rrule.toText(l10n: snapshot.data!));
},
)
@ -221,8 +221,8 @@ class AppointmentDetails {
DebugTile(context).child(
ListTile(
leading: const CenteredLeading(Icon(Icons.rule)),
title: const Text("RRule"),
subtitle: Text(appointment.rrule.isEmpty ? "Keine" : appointment.rrule),
title: const Text('RRule'),
subtitle: Text(appointment.rrule.isEmpty ? 'Keine' : appointment.rrule),
)
),
DebugTile(context).jsonData(appointment.toJson()),

View File

@ -15,16 +15,16 @@ class TimetableColors {
static ColorModeDisplay getDisplayOptions(CustomTimetableColors color) {
switch(color) {
case CustomTimetableColors.green:
return ColorModeDisplay(color: Colors.green, displayName: "Grün");
return ColorModeDisplay(color: Colors.green, displayName: 'Grün');
case CustomTimetableColors.blue:
return ColorModeDisplay(color: Colors.blue, displayName: "Blau");
return ColorModeDisplay(color: Colors.blue, displayName: 'Blau');
case CustomTimetableColors.orange:
return ColorModeDisplay(color: Colors.orange.shade800, displayName: "Orange");
return ColorModeDisplay(color: Colors.orange.shade800, displayName: 'Orange');
case CustomTimetableColors.red:
return ColorModeDisplay(color: DarkAppTheme.marianumRed, displayName: "Rot");
return ColorModeDisplay(color: DarkAppTheme.marianumRed, displayName: 'Rot');
}
}

View File

@ -3,7 +3,7 @@ import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:marianum_mobile/extensions/dateTime.dart';
import '../../../extensions/dateTime.dart';
import 'package:provider/provider.dart';
import 'package:rrule_generator/rrule_generator.dart';
import 'package:time_range_picker/time_range_picker.dart';
@ -32,7 +32,7 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
late TimeOfDay _endTime = widget.existingEvent?.endDate.toTimeOfDay() ?? const TimeOfDay(hour: 09, minute: 30);
late final TextEditingController _eventName = TextEditingController(text: widget.existingEvent?.title);
late final TextEditingController _eventDescription = TextEditingController(text: widget.existingEvent?.description);
late String _recurringRule = widget.existingEvent?.rrule ?? "";
late String _recurringRule = widget.existingEvent?.rrule ?? '';
late CustomTimetableColors _customTimetableColor = CustomTimetableColors.values.firstWhere(
(element) => element.name == widget.existingEvent?.color,
orElse: () => TimetableColors.defaultColor
@ -64,7 +64,7 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
controller: _eventName,
autofocus: true,
decoration: const InputDecoration(
labelText: "Terminname",
labelText: 'Terminname',
border: OutlineInputBorder()
),
),
@ -75,7 +75,7 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
maxLines: 2,
minLines: 2,
decoration: const InputDecoration(
labelText: "Beschreibung",
labelText: 'Beschreibung',
border: OutlineInputBorder()
),
),
@ -84,7 +84,7 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
ListTile(
leading: const Icon(Icons.date_range_outlined),
title: Text(Jiffy.parseFromDateTime(_date).yMMMd),
subtitle: const Text("Datum"),
subtitle: const Text('Datum'),
onTap: () async {
final DateTime? pickedDate = await showDatePicker(
context: context,
@ -101,8 +101,8 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
),
ListTile(
leading: const Icon(Icons.access_time_outlined),
title: Text("${_startTime.format(context).toString()} - ${_endTime.format(context).toString()}"),
subtitle: const Text("Zeitraum"),
title: Text('${_startTime.format(context).toString()} - ${_endTime.format(context).toString()}'),
subtitle: const Text('Zeitraum'),
onTap: () async {
TimeRange timeRange = await showTimeRangePicker(
context: context,
@ -112,8 +112,8 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
disabledColor: Colors.grey,
paintingStyle: PaintingStyle.fill,
interval: const Duration(minutes: 5),
fromText: "Beginnend",
toText: "Endend",
fromText: 'Beginnend',
toText: 'Endend',
strokeColor: Theme.of(context).colorScheme.secondary,
minDuration: const Duration(minutes: 15),
selectedColor: Theme.of(context).primaryColor,
@ -129,7 +129,7 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
const Divider(),
ListTile(
leading: const Icon(Icons.color_lens_outlined),
title: const Text("Farbgebung"),
title: const Text('Farbgebung'),
trailing: DropdownButton<CustomTimetableColors>(
value: _customTimetableColor,
icon: const Icon(Icons.arrow_drop_down),
@ -162,7 +162,7 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
initialRRule: _recurringRule,
textDelegate: const GermanRRuleTextDelegate(),
onChange: (String newValue) {
log("Rule: $newValue");
log('Rule: $newValue');
setState(() {
_recurringRule = newValue;
});
@ -183,7 +183,7 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
if(!validate()) return;
CustomTimetableEvent editedEvent = CustomTimetableEvent(
id: "",
id: '',
title: _eventName.text,
description: _eventDescription.text,
startDate: _date.withTime(_startTime),
@ -210,7 +210,7 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
} else {
UpdateCustomTimetableEvent(
UpdateCustomTimetableEventParams(
widget.existingEvent?.id ?? "",
widget.existingEvent?.id ?? '',
editedEvent
)
).run().then((value) {
@ -224,7 +224,7 @@ class _AddCustomTimetableEventDialogState extends State<CustomTimetableEventEdit
},
child: Text(isEditingExisting ? "Speichern" : "Erstellen"),
child: Text(isEditingExisting ? 'Speichern' : 'Erstellen'),
),
],
);

View File

@ -33,12 +33,12 @@ class _TimeRegionComponentState extends State<TimeRegionComponent> {
children: [
const SizedBox(height: 15),
const Icon(Icons.cake),
const Text("FREI"),
const Text('FREI'),
const SizedBox(height: 10),
RotatedBox(
quarterTurns: 1,
child: Text(
text.split(":").last,
text.split(':').last,
maxLines: 1,
style: const TextStyle(
fontWeight: FontWeight.bold,

View File

@ -2,7 +2,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:marianum_mobile/extensions/dateTime.dart';
import '../../../extensions/dateTime.dart';
import 'package:provider/provider.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
@ -57,7 +57,7 @@ class _TimetableState extends State<Timetable> {
return Scaffold(
appBar: AppBar(
title: const Text("Stunden & Vertretungsplan"),
title: const Text('Stunden & Vertretungsplan'),
actions: [
IconButton(
icon: const Icon(Icons.home_outlined),
@ -74,11 +74,11 @@ class _TimetableState extends State<Timetable> {
Icon icon;
switch(e) {
case CalendarActions.addEvent:
title = "Kalendereintrag hinzufügen";
title = 'Kalendereintrag hinzufügen';
icon = const Icon(Icons.add);
case CalendarActions.viewEvents:
default:
title = "Kalendereinträge anzeigen";
title = 'Kalendereinträge anzeigen';
icon = const Icon(Icons.perm_contact_calendar_outlined);
}
return PopupMenuItem<CalendarActions>(
@ -112,9 +112,9 @@ class _TimetableState extends State<Timetable> {
if(value.hasError) {
return PlaceholderView(
icon: Icons.calendar_month,
text: "Webuntis error: ${value.error.toString()}",
text: 'Webuntis error: ${value.error.toString()}',
button: TextButton(
child: const Text("Neu laden"),
child: const Text('Neu laden'),
onPressed: () {
controller.displayDate = DateTime.now().add(const Duration(days: 2));
Provider.of<TimetableProps>(context, listen: false).resetWeek();
@ -155,8 +155,8 @@ class _TimetableState extends State<Timetable> {
startHour: 07.5,
endHour: 16.5,
timeInterval: Duration(minutes: 30),
timeFormat: "HH:mm",
dayFormat: "EE",
timeFormat: 'HH:mm',
dayFormat: 'EE',
timeIntervalHeight: 40,
),
@ -259,10 +259,10 @@ class _TimetableState extends State<Timetable> {
startTime: startTime,
endTime: endTime,
subject: subjects.result.firstWhere((subject) => subject.id == element.su[0].id).name,
location: ""
"${rooms.result.firstWhere((room) => room.id == element.ro[0].id).name}"
"\n"
"${element.te.first.longname}",
location: ''
'${rooms.result.firstWhere((room) => room.id == element.ro[0].id).name}'
'\n'
'${element.te.first.longname}',
notes: element.activityType,
color: _getEventColor(element, startTime, endTime),
);
@ -272,7 +272,7 @@ class _TimetableState extends State<Timetable> {
id: ArbitraryAppointment(webuntis: element),
startTime: _parseWebuntisTimestamp(element.date, element.startTime),
endTime: endTime,
subject: "Änderung",
subject: 'Änderung',
notes: element.info,
location: 'Unbekannt',
color: endTime.isBefore(DateTime.now()) ? Theme.of(context).primaryColor.withAlpha(100) : Theme.of(context).primaryColor,
@ -309,10 +309,10 @@ class _TimetableState extends State<Timetable> {
int alpha = endTime.isBefore(DateTime.now()) ? 100 : 255;
// Cancelled
if(webuntisElement.code == "cancelled") return const Color(0xff000000).withAlpha(alpha);
if(webuntisElement.code == 'cancelled') return const Color(0xff000000).withAlpha(alpha);
// Any changes or no teacher at this element
if(webuntisElement.code == "irregular" || webuntisElement.te.first.id == 0) return const Color(0xff8F19B3).withAlpha(alpha);
if(webuntisElement.code == 'irregular' || webuntisElement.te.first.id == 0) return const Color(0xff8F19B3).withAlpha(alpha);
// Event was in the past
if(endTime.isBefore(DateTime.now())) return Theme.of(context).primaryColor.withAlpha(alpha);
@ -327,7 +327,7 @@ class _TimetableState extends State<Timetable> {
bool _isCrossedOut(CalendarAppointmentDetails calendarEntry) {
ArbitraryAppointment appointment = calendarEntry.appointments.first.id as ArbitraryAppointment;
if(appointment.hasWebuntis()) {
return appointment.webuntis!.code == "cancelled";
return appointment.webuntis!.code == 'cancelled';
}
return false;
}

View File

@ -37,7 +37,7 @@ class _ViewCustomTimetableEventsState extends State<ViewCustomTimetableEvents> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Eigene Termine"),
title: const Text('Eigene Termine'),
actions: [
IconButton(
icon: const Icon(Icons.add),
@ -77,10 +77,10 @@ class _ViewCustomTimetableEventsState extends State<ViewCustomTimetableEvents> {
var placeholder = PlaceholderView(
icon: Icons.calendar_today_outlined,
text: "Keine Einträge vorhanden",
text: 'Keine Einträge vorhanden',
button: TextButton(
onPressed: _openCreateDialog,
child: const Text("Termin erstellen"),
child: const Text('Termin erstellen'),
),
);

View File

@ -24,14 +24,14 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.speed_outlined)),
title: const Text("Performance overlays"),
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"),
title: const Text('Performance graph'),
trailing: Checkbox(
value: widget.settings.val().devToolsSettings.showPerformanceOverlay,
onChanged: (e) => widget.settings.val(write: true).devToolsSettings.showPerformanceOverlay = e!,
@ -39,7 +39,7 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
),
ListTile(
leading: const Icon(Icons.screen_search_desktop_outlined),
title: const Text("Indicate offscreen layers"),
title: const Text('Indicate offscreen layers'),
trailing: Checkbox(
value: widget.settings.val().devToolsSettings.checkerboardOffscreenLayers,
onChanged: (e) => widget.settings.val(write: true).devToolsSettings.checkerboardOffscreenLayers = e!,
@ -47,7 +47,7 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
),
ListTile(
leading: const Icon(Icons.imagesearch_roller_outlined),
title: const Text("Indicate raster cache images"),
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!,
@ -59,13 +59,13 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
),
ListTile(
leading: const CenteredLeading(Icon(Icons.image_outlined)),
title: const Text("Cached Thumbnails löschen"),
subtitle: Text("etwa ${filesize(PaintingBinding.instance.imageCache.currentSizeBytes)}"),
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",
title: 'Thumbs cache löschen',
content: 'Alle zwischengespeicherten Bilder werden gelöscht.',
confirmButton: 'Unwiederruflich löschen',
onConfirm: () => PaintingBinding.instance.imageCache.clear(),
).asDialog(context);
},
@ -73,16 +73,16 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
),
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"),
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",
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();
},
@ -92,7 +92,7 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
),
ListTile(
leading: const CenteredLeading(Icon(Icons.data_object)),
title: const Text("Cache-storage JSON dump"),
title: const Text('Cache-storage JSON dump'),
subtitle: FutureBuilder(
future: const CacheView().totalSize(),
builder: (context, snapshot) {
@ -106,9 +106,9 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
},
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",
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);
},

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

@ -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),
@ -100,7 +100,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) {
@ -111,7 +111,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) {
@ -124,7 +124,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) {
@ -137,7 +137,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) {
@ -150,8 +150,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) {
@ -163,16 +163,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'))
],
)),
),
@ -181,18 +181,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',
);
});
},
@ -201,31 +201,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),
),
],
);
@ -240,15 +240,15 @@ 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("Entwicklermodus"),
title: const Text('Entwicklermodus'),
trailing: Checkbox(
value: settings.val().devToolsEnabled,
onChanged: (state) {
@ -264,15 +264,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);
},
),