Merge remote-tracking branch 'origin/develop' into develop

# Conflicts:
#	.idea/libraries/Flutter_Plugins.xml
This commit is contained in:
2023-09-05 22:08:59 +02:00
31 changed files with 455 additions and 354 deletions

View File

@ -14,7 +14,7 @@ class ListFiles extends WebdavApi<ListFilesParams> {
@override
Future<ListFilesResponse> run() async {
List<WebDavFile> davFiles = (await (await WebdavApi.webdav).propfind(params.path)).toWebDavFiles();
List<WebDavFile> davFiles = (await (await WebdavApi.webdav).propfind(Uri.parse(params.path))).toWebDavFiles();
Set<CacheableFile> files = davFiles.map((e) => CacheableFile.fromDavFile(e)).toSet();
// webdav handles subdirectories wrong, this is a fix

View File

@ -18,7 +18,7 @@ abstract class WebdavApi<T> extends ApiRequest {
static Future<String> webdavConnectString = buildWebdavConnectString();
static Future<WebDavClient> establishWebdavConnection() async {
return NextcloudClient("https://${EndpointData().nextcloud().full()}", password: AccountData().getPassword(), loginName: AccountData().getUsername()).webdav;
return NextcloudClient(Uri.parse("https://${EndpointData().nextcloud().full()}"), password: AccountData().getPassword(), loginName: AccountData().getUsername()).webdav;
}
static Future<String> buildWebdavConnectString() async {

View File

@ -8,12 +8,12 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:jiffy/jiffy.dart';
import 'package:marianum_mobile/firebase_options.dart';
import 'package:provider/provider.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
import 'app.dart';
import 'firebase_options.dart';
import 'model/accountData.dart';
import 'model/accountModel.dart';
import 'model/breakers/Breaker.dart';
@ -43,9 +43,9 @@ Future<void> main() async {
ByteData data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List());
ErrorWidget.builder = (error) {
return PlaceholderView(icon: Icons.phonelink_erase_rounded, text: error.toString());
};
// ErrorWidget.builder = (error) {
// return PlaceholderView(icon: Icons.phonelink_erase_rounded, text: error.toString());
// };
runApp(
MultiProvider(

View File

@ -14,6 +14,7 @@ abstract class DataHolder extends ChangeNotifier {
List<ApiResponse?> properties();
bool primaryLoading() {
// log("${toString()} ${properties().map((e) => e != null ? "1" : "0").join(", ")}");
for(ApiResponse? element in properties()) {
if(element == null) return true;
}

View File

@ -1,7 +1,8 @@
import 'dart:convert';
import 'package:intl/intl.dart';
import '../../api/apiResponse.dart';
import '../../api/webuntis/queries/getHolidays/getHolidaysCache.dart';
import '../../api/webuntis/queries/getHolidays/getHolidaysResponse.dart';
import '../../api/webuntis/queries/getRooms/getRoomsCache.dart';
import '../../api/webuntis/queries/getRooms/getRoomsResponse.dart';
@ -78,12 +79,16 @@ class TimetableProps extends DataHolder {
}
);
GetHolidaysCache(
onUpdate: (GetHolidaysResponse data) => {
_getHolidaysResponse = data,
notifyListeners(),
}
);
// GetHolidaysCache( // TODO is this fixed by webuntis? miese kriese
// onUpdate: (GetHolidaysResponse data) => {
// _getHolidaysResponse = data,
// notifyListeners(),
// }
// );
_getHolidaysResponse = GetHolidaysResponse.fromJson(jsonDecode("""
{"jsonrpc":"2.0","id":"ID","result":[]}
"""));
notifyListeners();
}
DateTime getDate(DateTime d) => DateTime(d.year, d.month, d.day);

View File

@ -35,8 +35,7 @@ class LightAppTheme {
color: marianumRed,
),
checkboxTheme: CheckboxThemeData(
fillColor: MaterialStateProperty.all(marianumRed),
// visualDensity: const VisualDensity(horizontal: VisualDensity.maximumDensity),
fillColor: MaterialStateProperty.resolveWith((states) => states.contains(MaterialState.selected) ? marianumRed : Colors.white),
),
);
}

View File

@ -161,7 +161,7 @@ class _FileElementState extends State<FileElement> {
content: "Das Element wird unwiederruflich gelöscht.",
onConfirm: () {
WebdavApi.webdav
.then((value) => value.delete(widget.file.path))
.then((value) => value.delete(Uri.parse(widget.file.path)))
.then((value) => widget.refetch());
}
));

View File

@ -43,7 +43,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
setState(() {
state = FileUploadState.checkConflict;
});
List<WebDavResponse> result = (await webdavClient.propfind(widget.remotePath.join("/"))).responses;
List<WebDavResponse> result = (await webdavClient.propfind(Uri.parse(widget.remotePath.join("/")))).responses;
if(result.any((element) => element.href!.endsWith("/$targetFileName"))) {
setState(() {
state = FileUploadState.conflict;
@ -56,7 +56,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
}
}
Future<HttpClientResponse> uploadTask = webdavClient.putFile(File(widget.localPath), FileStat.statSync(widget.localPath), fullRemotePath); // TODO use onProgress from putFile
Future<HttpClientResponse> uploadTask = webdavClient.putFile(File(widget.localPath), FileStat.statSync(widget.localPath), Uri.parse(fullRemotePath)); // TODO use onProgress from putFile
uploadTask.then((value) => Future<HttpClientResponse?>.value(value)).catchError((e) {
setState(() {
state = FileUploadState.error;

View File

@ -189,7 +189,7 @@ class _FilesState extends State<Files> {
}, child: const Text("Abbrechen")),
TextButton(onPressed: () {
WebdavApi.webdav.then((webdav) {
webdav.mkcol("${widget.path.join("/")}/${inputController.text}").then((value) => _query());
webdav.mkcol(Uri.parse("${widget.path.join("/")}/${inputController.text}")).then((value) => _query());
});
Navigator.of(context).pop();
}, child: const Text("Ordner erstellen")),

View File

@ -147,7 +147,7 @@ class _HolidaysState extends State<Holidays> {
subtitle: Text(Jiffy.parse(holiday.start).fromNow()),
),
),
DebugTile(holiday.toJson()).asTile(context),
DebugTile(context).jsonData(holiday.toJson()),
],
)),
trailing: const Icon(Icons.arrow_right),

View File

@ -1,8 +1,18 @@
import 'dart:convert';
import 'dart:developer';
import 'package:http/http.dart' as http;
import 'package:crypto/crypto.dart';
import 'package:fast_rsa/fast_rsa.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:persistent_bottom_nav_bar/persistent_tab_view.dart';
import 'package:share_plus/share_plus.dart';
import '../../../model/endpointData.dart';
import '../../../widget/ListItem.dart';
import '../../../widget/debug/debugTile.dart';
import '../../settings/settings.dart';
import 'gradeAverages/gradeAverage.dart';
import 'holidays/holidays.dart';
@ -14,6 +24,7 @@ class Overhang extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Mehr"),
@ -22,11 +33,59 @@ class Overhang extends StatelessWidget {
],
),
body: ListView(
children: const [
ListItemNavigator(icon: Icons.newspaper, text: "Marianum Message", target: Message()),
ListItemNavigator(icon: Icons.room, text: "Raumplan", target: Roomplan()),
ListItemNavigator(icon: Icons.calculate, text: "Notendurschnittsrechner", target: GradeAverage()),
ListItemNavigator(icon: Icons.calendar_month, text: "Schulferien", target: Holidays()),
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.calendar_month, text: "Schulferien", target: Holidays()),
ListTile(
leading: const Icon(Icons.share_outlined),
title: const Text("Teile die App mit deiner Klasse"),
onTap: () {
Share.share( // TODO ipad needs position argument
subject: "App Teilen",
"Hol dir die inoffizielle App für's Marianum:"
"\n\nAndroid: https://play.google.com/store/apps/details?id=eu.mhsl.marianum.mobile.client "
"\nApple: https://apps.apple.com/us/app/marianum-fulda/id6458789560 "
"\n\nViel Spaß!"
);
},
),
DebugTile(context, onlyInDebug: true).callback(onTab: () async {
log("Starting");
log("Generate keys");
final rsaKey = await RSA.generate(2048);
final devicePrivateKey = rsaKey.privateKey.toString();
final devicePublicKey = rsaKey.publicKey.toString();
log("Private: \n$devicePrivateKey");
log("Public: \n$devicePublicKey");
final pushToken = await FirebaseMessaging.instance.getToken();
log("PushToken: $pushToken}");
final pushTokenHash = sha512.convert(utf8.encode(pushToken!));
log("PushTokenHash: $pushTokenHash");
final requestMap = {
"format": "json",
"pushTokenHash": pushTokenHash.toString(),
"devicePublicKey": devicePublicKey.toString(),
"proxyServer": "https://push-notifications.nextcloud.com/devices"
};
log(jsonEncode(requestMap));
http.post(
//${AccountData().buildHttpAuthString()}@
Uri.parse("https://${EndpointData().nextcloud().full()}/ocs/v2.php/apps/notifications/api/v2/push"),
headers: {
"OCS-APIRequest": "true",
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": "Bearer Fv3g7g9jW91FXNjZLaJmyprClfy8pX1jEM3hJGbXjPEFcx4oGIEVcpwEnuT4mPs39D9xT063"
},
body: jsonEncode(requestMap),
).then((response) {
log("Response: ${response.statusCode}\n${response.body}");
});
}),
],
),
);

View File

@ -255,7 +255,7 @@ class _ChatBubbleState extends State<ChatBubble> {
},
),
),
DebugTile(widget.bubbleData.toJson()).asTile(context),
DebugTile(context).jsonData(widget.bubbleData.toJson()),
],
);
});

View File

@ -41,7 +41,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
String filename = "${path.split("/").last.split(".").first}-${const Uuid().v4()}.${path.split(".").last}";
String shareFolder = "MarianumMobile";
WebdavApi.webdav.then((webdav) {
webdav.mkcol("/$shareFolder");
webdav.mkcol(Uri.parse("/$shareFolder"));
});
showDialog(context: context, builder: (context) => FileUploadDialog(

View File

@ -172,7 +172,7 @@ class _ChatTileState extends State<ChatTile> {
).asDialog(context);
},
),
DebugTile(widget.data.toJson()).asTile(context),
DebugTile(context).jsonData(widget.data.toJson()),
],
));
},

View File

@ -92,7 +92,7 @@ class AppointmentDetails {
leading: const Icon(Icons.people),
title: Text("Klasse(n): ${timetableData.kl.map((e) => e.name).join(", ")}"),
),
DebugTile(timetableData.toJson()).asTile(context),
DebugTile(context).jsonData(timetableData.toJson()),
],
),
)

View File

@ -1,4 +1,6 @@
import 'dart:io';
import 'package:filesize/filesize.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -145,45 +147,48 @@ class _SettingsState extends State<Settings> {
const Divider(),
ListTile(
leading: const CenteredLeading(Icon(Icons.notifications_active_outlined)),
title: const Text("Push-Benachrichtigungen aktivieren"),
subtitle: const Text("Lange tippen für mehr Informationen"),
trailing: Checkbox(
value: settings.val().notificationSettings.enabled,
onChanged: (e) {
if(e!) {
ConfirmDialog(
title: "Warnung",
icon: Icons.warning_amber,
content: ""
"Die Push-Benachrichtigungen werden durch mhsl.eu versendet.\n\n"
"Durch das aktivieren dieser Funktion wird dein Nutzername, dein Password und eine Geräte-ID von mhsl dauerhaft gespeichert und verarbeitet.\n\n"
"Für mehr Informationen drücke lange auf die Einstellungsoption!",
confirmButton: "Aktivieren",
onConfirm: () {
settings.val(write: true).notificationSettings.enabled = e;
NotifyUpdater.registerToServer();
},
).asDialog(context);
} else {
settings.val(write: true).notificationSettings.enabled = e;
}
},
),
onLongPress: () => showDialog(context: context, builder: (context) => AlertDialog(
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!"
Visibility(
visible: Platform.isAndroid,
child: ListTile(
leading: const CenteredLeading(Icon(Icons.notifications_active_outlined)),
title: const Text("Push-Benachrichtigungen aktivieren"),
subtitle: const Text("Lange tippen für mehr Informationen"),
trailing: Checkbox(
value: settings.val().notificationSettings.enabled,
onChanged: (e) {
if(e!) {
ConfirmDialog(
title: "Warnung",
icon: Icons.warning_amber,
content: ""
"Die Push-Benachrichtigungen werden durch mhsl.eu versendet.\n\n"
"Durch das aktivieren dieser Funktion wird dein Nutzername, dein Password und eine Geräte-ID von mhsl dauerhaft gespeichert und verarbeitet.\n\n"
"Für mehr Informationen drücke lange auf die Einstellungsoption!",
confirmButton: "Aktivieren",
onConfirm: () {
settings.val(write: true).notificationSettings.enabled = e;
NotifyUpdater.registerToServer();
},
).asDialog(context);
} else {
settings.val(write: true).notificationSettings.enabled = e;
}
},
),
onLongPress: () => showDialog(context: context, builder: (context) => AlertDialog(
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!"
)),
actions: [
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("Zurück"))
],
)),
actions: [
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("Zurück"))
],
)),
),
),
const Divider(),

View File

@ -1,26 +1,38 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../storage/base/settingsProvider.dart';
import '../centeredLeading.dart';
import 'jsonViewer.dart';
class DebugTile {
Map<String, dynamic> data;
BuildContext context;
bool onlyInDebug;
DebugTile(this.context, {this.onlyInDebug = false});
DebugTile(this.data);
Widget jsonData(Map<String, dynamic> data, {bool ignoreConfig = false}) {
return callback(
title: "JSON daten anzeigen",
onTab: () => JsonViewer.asDialog(context, data)
);
}
Widget asTile(BuildContext context, {bool ignoreConfig = false}) {
return Visibility(
visible: Provider.of<SettingsProvider>(context).val().devToolsEnabled || ignoreConfig,
child: ListTile(
leading: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [Icon(Icons.data_object)],
),
title: const Text("JSON daten anzeigen"),
Widget callback({String title = "Debugaktion", required void Function() onTab}) {
return child(
ListTile(
leading: const CenteredLeading(Icon(Icons.developer_mode_outlined)),
title: Text(title),
subtitle: const Text("Entwicklermodus aktiviert"),
onTap: () => JsonViewer.asDialog(context, data),
),
onTap: onTab,
)
);
}
Widget child(Widget child) {
return Visibility(
visible: Provider.of<SettingsProvider>(context).val().devToolsEnabled && (onlyInDebug ? kDebugMode : true),
child: child,
);
}
}

View File

@ -3,11 +3,11 @@ import 'dart:math';
import 'package:better_open_file/better_open_file.dart';
import 'package:flutter/material.dart';
import 'package:marianum_mobile/storage/base/settingsProvider.dart';
import 'package:photo_view/photo_view.dart';
import 'package:provider/provider.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
import '../storage/base/settingsProvider.dart';
import 'placeholderView.dart';
class FileViewer extends StatefulWidget {

View File

@ -16,7 +16,7 @@ class _LoadingSpinnerState extends State<LoadingSpinner> {
@override
void initState() {
timer = Timer(const Duration(seconds: 15), () {
timer = Timer(const Duration(seconds: 30), () {
setState(() {
textVisible = true;
});
@ -33,13 +33,16 @@ class _LoadingSpinnerState extends State<LoadingSpinner> {
children: [
Visibility(
visible: !textVisible,
replacement: const Icon(Icons.signal_wifi_connected_no_internet_4_outlined),
replacement: const Icon(Icons.sentiment_dissatisfied_outlined),
child: const CircularProgressIndicator(),
),
const SizedBox(height: 30),
Visibility(
visible: textVisible,
child: const Text("Etwas scheint nicht zu funktionieren!\nBist du mit dem Internet verbunden?\n\nVersuche die App neuzustarten"),
child: const Text(
textAlign: TextAlign.center,
"Irgendetwas funktioniert nicht!\nBist du mit dem Internet verbunden?\n\nVersuche die App neuzustarten"
),
),
],
),