From db87cf5f3956832c0d1fa9f94be3098768b095b7 Mon Sep 17 00:00:00 2001 From: Alessio Caputo <84250128+alessioC42@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:44:01 +0100 Subject: [PATCH 1/6] basic datastorage functionality --- .../client/client_submodules/datastorage.dart | 11 ++- app/lib/home_page.dart | 14 ++- app/lib/shared/launch_file.dart | 44 +++++++++ app/lib/shared/types/dateispeicher_node.dart | 3 +- app/lib/view/data_storage/data_storage.dart | 20 ++++ app/lib/view/data_storage/node_view.dart | 97 +++++++++++++++++++ .../view/mein_unterricht/course_overview.dart | 44 +-------- app/pubspec.lock | 8 ++ app/pubspec.yaml | 1 + 9 files changed, 199 insertions(+), 43 deletions(-) create mode 100644 app/lib/shared/launch_file.dart create mode 100644 app/lib/view/data_storage/data_storage.dart create mode 100644 app/lib/view/data_storage/node_view.dart diff --git a/app/lib/client/client_submodules/datastorage.dart b/app/lib/client/client_submodules/datastorage.dart index 86f527e7..6f4f9819 100644 --- a/app/lib/client/client_submodules/datastorage.dart +++ b/app/lib/client/client_submodules/datastorage.dart @@ -23,6 +23,12 @@ class DataStorageParser { .toList(); for (var file in document.querySelectorAll("table#files tbody tr")) { final fields = file.querySelectorAll("td"); + String? hinweis = fields[headers.indexOf("Name")] + .querySelector("small")?.text.trim(); + if (hinweis != null) { + fields[headers.indexOf("Name")] + .querySelector("small")?.text = ""; + } var name = fields[headers.indexOf("Name")].text.trim(); var aenderung = fields[headers.indexOf("Änderung")].text.trim(); var groesse = fields[headers.indexOf("Größe")].text.trim(); @@ -32,8 +38,11 @@ class DataStorageParser { id, "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id", aenderung, - groesse)); + groesse, + hinweis: hinweis + )); } + List folders = []; for (var folder in document.querySelectorAll(".folder")) { var name = folder.querySelector(".caption")!.text.trim(); diff --git a/app/lib/home_page.dart b/app/lib/home_page.dart index 5d1cef31..6605fb98 100644 --- a/app/lib/home_page.dart +++ b/app/lib/home_page.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:sph_plan/client/client.dart'; import 'package:sph_plan/view/calendar/calendar.dart'; import 'package:sph_plan/view/conversations/conversations.dart'; +import 'package:sph_plan/view/data_storage/data_storage.dart'; import 'package:sph_plan/view/mein_unterricht/mein_unterricht.dart'; import 'package:sph_plan/view/settings/settings.dart'; import 'package:sph_plan/view/bug_report/send_bugreport.dart'; @@ -86,6 +87,17 @@ class _HomePageState extends State { enableBottomNavigation: true, enableDrawer: true, body: const MeinUnterrichtAnsicht()), + Destination( + label: "Dateispeicher", + icon: const Icon(Icons.folder_copy), + selectedIcon: const Icon(Icons.folder_copy_outlined), + isSupported: client.doesSupportFeature(SPHAppEnum.dateispeicher), + enableBottomNavigation: false, + enableDrawer: true, + action: (context) => Navigator.push( + context, + MaterialPageRoute(builder: (context) => const DataStorageAnsicht()), + )), Destination( label: "Lanis im Browser öffnen", icon: const Icon(Icons.open_in_new), @@ -307,7 +319,7 @@ class _HomePageState extends State { indexNavbarTranslationLayer.add(null); } } - + debugPrint(selectedDestinationDrawer.toString()); return NavigationBar( destinations: barDestinations, selectedIndex: indexNavbarTranslationLayer[selectedDestinationDrawer]!, diff --git a/app/lib/shared/launch_file.dart b/app/lib/shared/launch_file.dart new file mode 100644 index 00000000..b04e7e7b --- /dev/null +++ b/app/lib/shared/launch_file.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:open_file/open_file.dart'; + +import '../client/client.dart'; + +void launchFile(context, url, filename, filesize) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text("Download... $filesize"), + content: const Center( + heightFactor: 1.1, + child: CircularProgressIndicator(), + ), + ); + }); + client + .downloadFile(url, filename) + .then((filepath) { + Navigator.of(context).pop(); + + if (filepath == "") { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text("Fehler!"), + content: Text( + "Beim Download der Datei $filename ist ein unerwarteter Fehler aufgetreten. Wenn dieses Problem besteht, senden Sie uns bitte einen Fehlerbericht."), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + )); + } else { + OpenFile.open(filepath); + } + }); +} \ No newline at end of file diff --git a/app/lib/shared/types/dateispeicher_node.dart b/app/lib/shared/types/dateispeicher_node.dart index 78cc2434..2abe8e12 100644 --- a/app/lib/shared/types/dateispeicher_node.dart +++ b/app/lib/shared/types/dateispeicher_node.dart @@ -4,8 +4,9 @@ class FileNode { String downloadUrl; String groesse; String aenderung; + String? hinweis; - FileNode(this.name, this.id, this.downloadUrl, this.aenderung, this.groesse); + FileNode(this.name, this.id, this.downloadUrl, this.aenderung, this.groesse, {this.hinweis}); } class FolderNode { diff --git a/app/lib/view/data_storage/data_storage.dart b/app/lib/view/data_storage/data_storage.dart new file mode 100644 index 00000000..4f2e8c9e --- /dev/null +++ b/app/lib/view/data_storage/data_storage.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'node_view.dart'; + +class DataStorageAnsicht extends StatefulWidget { + const DataStorageAnsicht({super.key}); + + @override + State createState() => _DataStorageAnsichtState(); +} + +class _DataStorageAnsichtState extends State { + + + + @override + Widget build(BuildContext context) { + return const DataStorageNodeView(nodeID: 0, title: "Datenspeicher"); + } +} + diff --git a/app/lib/view/data_storage/node_view.dart b/app/lib/view/data_storage/node_view.dart new file mode 100644 index 00000000..0298c2ae --- /dev/null +++ b/app/lib/view/data_storage/node_view.dart @@ -0,0 +1,97 @@ +import 'package:file_icon/file_icon.dart'; +import 'package:flutter/material.dart'; +import 'package:sph_plan/shared/widgets/marquee.dart'; + +import '../../client/client.dart'; +import '../../shared/launch_file.dart'; +import '../../shared/types/dateispeicher_node.dart'; + +class DataStorageNodeView extends StatefulWidget { + final int nodeID; + final String title; + + const DataStorageNodeView({super.key, required this.nodeID, required this.title}); + + @override + State createState() => _DataStorageNodeViewState(); +} + +class _DataStorageNodeViewState extends State { + var loading = true; + late List files; + late List folders; + + @override + void dispose() { + super.dispose(); + } + + @override + void initState() { + super.initState(); + loadItems(); + } + + void loadItems() async { + var items = await client.dataStorage.getNode(widget.nodeID); + var (fileList, folderList) = items; + files = fileList; + folders = folderList; + + setState(() { + loading = false; + }); + } + + List getListTiles() { + var listTiles = []; + + for (var folder in folders) { + listTiles.add(ListTile( + title: Text(folder.name), + subtitle: Text(folder.desc, maxLines: 2, overflow: TextOverflow.ellipsis,), + leading: const Icon(Icons.folder_outlined), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DataStorageNodeView(nodeID: folder.id, title: folder.name), + ), + ); + } + )); + } + + for (var file in files) { + listTiles.add(ListTile( + title: MarqueeWidget(child: Text(file.name)), + subtitle: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (file.hinweis != null) MarqueeWidget(child: Text( file.hinweis!)) else Text(file.groesse), + Text(file.aenderung), + ], + ), + leading: FileIcon(file.name, ), + onTap: () => launchFile(context, file.downloadUrl, file.name, file.groesse), + )); + } + + return listTiles; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: loading ? const Center( + child: CircularProgressIndicator(), + ) : ListView( + children: getListTiles(), + ), + ); + } +} + diff --git a/app/lib/view/mein_unterricht/course_overview.dart b/app/lib/view/mein_unterricht/course_overview.dart index 88edca90..1196d2d4 100644 --- a/app/lib/view/mein_unterricht/course_overview.dart +++ b/app/lib/view/mein_unterricht/course_overview.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:open_file/open_file.dart'; import 'package:sph_plan/view/mein_unterricht/upload_page.dart'; import '../../client/client.dart'; +import '../../shared/launch_file.dart'; import '../../shared/widgets/error_view.dart'; import '../../shared/widgets/format_text.dart'; @@ -118,45 +118,9 @@ class _CourseOverviewAnsichtState extends State { data["historie"][index]["files"].forEach((file) { files.add(ActionChip( label: Text(file["filename"]), - onPressed: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return AlertDialog( - title: Text("Download... ${file['filesize']}"), - content: const Center( - heightFactor: 1.1, - child: CircularProgressIndicator(), - ), - ); - }); - client - .downloadFile(file["url"], file["filename"]) - .then((filepath) { - Navigator.of(context).pop(); - - if (filepath == "") { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Fehler!"), - content: Text( - "Beim Download der Datei ${file["filename"]} ist ein unerwarteter Fehler aufgetreten. Wenn dieses Problem besteht, senden Sie uns bitte einen Fehlerbericht."), - actions: [ - TextButton( - child: const Text('OK'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - )); - } else { - OpenFile.open(filepath); - } - }); - }, + onPressed: () => launchFile( + context, file["url"], file["filename"], file["size"] + ), )); }); diff --git a/app/pubspec.lock b/app/pubspec.lock index d19a5088..ee8457e8 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -257,6 +257,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_icon: + dependency: "direct main" + description: + name: file_icon + sha256: c46b6c24d9595d18995758b90722865baeda407f56308eadd757e1ab913f50a1 + url: "https://pub.dev" + source: hosted + version: "1.0.0" file_picker: dependency: "direct main" description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index a4150bb6..20aee7c6 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -68,6 +68,7 @@ dependencies: table_calendar: ^3.0.9 intl: ^0.18.1 cached_network_image: ^3.3.1 + file_icon: ^1.0.0 flutter_launcher_icons: android: false From 429b2f40cd7741972485af39d69c4398bec533ea Mon Sep 17 00:00:00 2001 From: Alessio Caputo <84250128+alessioC42@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:18:20 +0100 Subject: [PATCH 2/6] Error handling --- .../client/client_submodules/datastorage.dart | 101 ++++++++++-------- app/lib/view/data_storage/node_view.dart | 19 +++- 2 files changed, 74 insertions(+), 46 deletions(-) diff --git a/app/lib/client/client_submodules/datastorage.dart b/app/lib/client/client_submodules/datastorage.dart index 6f4f9819..117f4855 100644 --- a/app/lib/client/client_submodules/datastorage.dart +++ b/app/lib/client/client_submodules/datastorage.dart @@ -1,6 +1,7 @@ import 'package:dio/dio.dart'; import 'package:html/parser.dart'; +import '../../shared/exceptions/client_status_exceptions.dart'; import '../../shared/types/dateispeicher_node.dart'; import '../client.dart'; @@ -12,54 +13,64 @@ class DataStorageParser { dio = dioClient; } - Future getNode(int nodeID) async { - final response = await dio.get( - "https://start.schulportal.hessen.de/dateispeicher.php?a=view&folder=$nodeID"); - var document = parse(response.data); - List files = []; - List headers = document - .querySelectorAll("table#files thead th") - .map((e) => e.text) - .toList(); - for (var file in document.querySelectorAll("table#files tbody tr")) { - final fields = file.querySelectorAll("td"); - String? hinweis = fields[headers.indexOf("Name")] - .querySelector("small")?.text.trim(); - if (hinweis != null) { - fields[headers.indexOf("Name")] - .querySelector("small")?.text = ""; + Future<(List, List)> getNode(int nodeID) async { + try { + late final Response response; + try { + response = await dio.get( + "https://start.schulportal.hessen.de/dateispeicher.php?a=view&folder=$nodeID"); + } catch (e) { + throw NoConnectionException(); + } + + var document = parse(response.data); + List files = []; + List headers = document + .querySelectorAll("table#files thead th") + .map((e) => e.text) + .toList(); + for (var file in document.querySelectorAll("table#files tbody tr")) { + final fields = file.querySelectorAll("td"); + String? hinweis = + fields[headers.indexOf("Name")].querySelector("small")?.text.trim(); + if (hinweis != null) { + fields[headers.indexOf("Name")].querySelector("small")?.text = ""; + } + var name = fields[headers.indexOf("Name")].text.trim(); + var aenderung = fields[headers.indexOf("Änderung")].text.trim(); + var groesse = fields[headers.indexOf("Größe")].text.trim(); + var id = int.parse(file.attributes["data-id"]!.trim()); + files.add(FileNode( + name, + id, + "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id", + aenderung, + groesse, + hinweis: hinweis)); } - var name = fields[headers.indexOf("Name")].text.trim(); - var aenderung = fields[headers.indexOf("Änderung")].text.trim(); - var groesse = fields[headers.indexOf("Größe")].text.trim(); - var id = int.parse(file.attributes["data-id"]!.trim()); - files.add(FileNode( - name, - id, - "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id", - aenderung, - groesse, - hinweis: hinweis - )); - } - List folders = []; - for (var folder in document.querySelectorAll(".folder")) { - var name = folder.querySelector(".caption")!.text.trim(); - var desc = folder.querySelector(".desc")!.text.trim(); - var subfolders = int.tryParse(RegExp(r"\d+") - .firstMatch(folder - .querySelector("[title=\"Anzahl Ordner\"]") - ?.text - .trim() ?? - "") - ?.group(0) ?? - "") ?? - 0; - var id = int.parse(folder.attributes["data-id"]!); - folders.add(FolderNode(name, id, subfolders, desc)); + List folders = []; + for (var folder in document.querySelectorAll(".folder")) { + var name = folder.querySelector(".caption")!.text.trim(); + var desc = folder.querySelector(".desc")!.text.trim(); + var subfolders = int.tryParse(RegExp(r"\d+") + .firstMatch(folder + .querySelector("[title=\"Anzahl Ordner\"]") + ?.text + .trim() ?? + "") + ?.group(0) ?? + "") ?? + 0; + var id = int.parse(folder.attributes["data-id"]!); + folders.add(FolderNode(name, id, subfolders, desc)); + } + return (files, folders); + } on NoConnectionException { + rethrow; + } catch (e) { + throw LoggedOffOrUnknownException(); } - return (files, folders); } Future getRoot() async { diff --git a/app/lib/view/data_storage/node_view.dart b/app/lib/view/data_storage/node_view.dart index 0298c2ae..e7c649de 100644 --- a/app/lib/view/data_storage/node_view.dart +++ b/app/lib/view/data_storage/node_view.dart @@ -1,8 +1,10 @@ import 'package:file_icon/file_icon.dart'; import 'package:flutter/material.dart'; +import 'package:sph_plan/shared/widgets/error_view.dart'; import 'package:sph_plan/shared/widgets/marquee.dart'; import '../../client/client.dart'; +import '../../shared/exceptions/client_status_exceptions.dart'; import '../../shared/launch_file.dart'; import '../../shared/types/dateispeicher_node.dart'; @@ -18,6 +20,7 @@ class DataStorageNodeView extends StatefulWidget { class _DataStorageNodeViewState extends State { var loading = true; + var error = false; late List files; late List folders; @@ -33,6 +36,7 @@ class _DataStorageNodeViewState extends State { } void loadItems() async { + try { var items = await client.dataStorage.getNode(widget.nodeID); var (fileList, folderList) = items; files = fileList; @@ -41,6 +45,12 @@ class _DataStorageNodeViewState extends State { setState(() { loading = false; }); + } on LanisException catch (e) { + setState(() { + error = true; + loading = false; + }); + } } List getListTiles() { @@ -88,10 +98,17 @@ class _DataStorageNodeViewState extends State { ), body: loading ? const Center( child: CircularProgressIndicator(), + ) : error ? const Center( + child: Column( + children: [ + Icon(Icons.error_outline, size: 100), + SizedBox(height: 10), + Text("Fehler beim Laden der Dateien"), + ], + ) ) : ListView( children: getListTiles(), ), ); } } - From 1ce709fcb263828226be0d48c1715136a5eb60ae Mon Sep 17 00:00:00 2001 From: Alessio Caputo <84250128+alessioC42@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:44:01 +0100 Subject: [PATCH 3/6] basic datastorage functionality --- .../client/client_submodules/datastorage.dart | 11 ++- app/lib/home_page.dart | 14 ++- app/lib/shared/launch_file.dart | 44 +++++++++ app/lib/shared/types/dateispeicher_node.dart | 3 +- app/lib/view/data_storage/data_storage.dart | 20 ++++ app/lib/view/data_storage/node_view.dart | 97 +++++++++++++++++++ .../view/mein_unterricht/course_overview.dart | 44 +-------- app/pubspec.lock | 8 ++ app/pubspec.yaml | 1 + 9 files changed, 199 insertions(+), 43 deletions(-) create mode 100644 app/lib/shared/launch_file.dart create mode 100644 app/lib/view/data_storage/data_storage.dart create mode 100644 app/lib/view/data_storage/node_view.dart diff --git a/app/lib/client/client_submodules/datastorage.dart b/app/lib/client/client_submodules/datastorage.dart index 86f527e7..6f4f9819 100644 --- a/app/lib/client/client_submodules/datastorage.dart +++ b/app/lib/client/client_submodules/datastorage.dart @@ -23,6 +23,12 @@ class DataStorageParser { .toList(); for (var file in document.querySelectorAll("table#files tbody tr")) { final fields = file.querySelectorAll("td"); + String? hinweis = fields[headers.indexOf("Name")] + .querySelector("small")?.text.trim(); + if (hinweis != null) { + fields[headers.indexOf("Name")] + .querySelector("small")?.text = ""; + } var name = fields[headers.indexOf("Name")].text.trim(); var aenderung = fields[headers.indexOf("Änderung")].text.trim(); var groesse = fields[headers.indexOf("Größe")].text.trim(); @@ -32,8 +38,11 @@ class DataStorageParser { id, "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id", aenderung, - groesse)); + groesse, + hinweis: hinweis + )); } + List folders = []; for (var folder in document.querySelectorAll(".folder")) { var name = folder.querySelector(".caption")!.text.trim(); diff --git a/app/lib/home_page.dart b/app/lib/home_page.dart index 5d1cef31..6605fb98 100644 --- a/app/lib/home_page.dart +++ b/app/lib/home_page.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:sph_plan/client/client.dart'; import 'package:sph_plan/view/calendar/calendar.dart'; import 'package:sph_plan/view/conversations/conversations.dart'; +import 'package:sph_plan/view/data_storage/data_storage.dart'; import 'package:sph_plan/view/mein_unterricht/mein_unterricht.dart'; import 'package:sph_plan/view/settings/settings.dart'; import 'package:sph_plan/view/bug_report/send_bugreport.dart'; @@ -86,6 +87,17 @@ class _HomePageState extends State { enableBottomNavigation: true, enableDrawer: true, body: const MeinUnterrichtAnsicht()), + Destination( + label: "Dateispeicher", + icon: const Icon(Icons.folder_copy), + selectedIcon: const Icon(Icons.folder_copy_outlined), + isSupported: client.doesSupportFeature(SPHAppEnum.dateispeicher), + enableBottomNavigation: false, + enableDrawer: true, + action: (context) => Navigator.push( + context, + MaterialPageRoute(builder: (context) => const DataStorageAnsicht()), + )), Destination( label: "Lanis im Browser öffnen", icon: const Icon(Icons.open_in_new), @@ -307,7 +319,7 @@ class _HomePageState extends State { indexNavbarTranslationLayer.add(null); } } - + debugPrint(selectedDestinationDrawer.toString()); return NavigationBar( destinations: barDestinations, selectedIndex: indexNavbarTranslationLayer[selectedDestinationDrawer]!, diff --git a/app/lib/shared/launch_file.dart b/app/lib/shared/launch_file.dart new file mode 100644 index 00000000..b04e7e7b --- /dev/null +++ b/app/lib/shared/launch_file.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:open_file/open_file.dart'; + +import '../client/client.dart'; + +void launchFile(context, url, filename, filesize) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text("Download... $filesize"), + content: const Center( + heightFactor: 1.1, + child: CircularProgressIndicator(), + ), + ); + }); + client + .downloadFile(url, filename) + .then((filepath) { + Navigator.of(context).pop(); + + if (filepath == "") { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text("Fehler!"), + content: Text( + "Beim Download der Datei $filename ist ein unerwarteter Fehler aufgetreten. Wenn dieses Problem besteht, senden Sie uns bitte einen Fehlerbericht."), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + )); + } else { + OpenFile.open(filepath); + } + }); +} \ No newline at end of file diff --git a/app/lib/shared/types/dateispeicher_node.dart b/app/lib/shared/types/dateispeicher_node.dart index 78cc2434..2abe8e12 100644 --- a/app/lib/shared/types/dateispeicher_node.dart +++ b/app/lib/shared/types/dateispeicher_node.dart @@ -4,8 +4,9 @@ class FileNode { String downloadUrl; String groesse; String aenderung; + String? hinweis; - FileNode(this.name, this.id, this.downloadUrl, this.aenderung, this.groesse); + FileNode(this.name, this.id, this.downloadUrl, this.aenderung, this.groesse, {this.hinweis}); } class FolderNode { diff --git a/app/lib/view/data_storage/data_storage.dart b/app/lib/view/data_storage/data_storage.dart new file mode 100644 index 00000000..4f2e8c9e --- /dev/null +++ b/app/lib/view/data_storage/data_storage.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'node_view.dart'; + +class DataStorageAnsicht extends StatefulWidget { + const DataStorageAnsicht({super.key}); + + @override + State createState() => _DataStorageAnsichtState(); +} + +class _DataStorageAnsichtState extends State { + + + + @override + Widget build(BuildContext context) { + return const DataStorageNodeView(nodeID: 0, title: "Datenspeicher"); + } +} + diff --git a/app/lib/view/data_storage/node_view.dart b/app/lib/view/data_storage/node_view.dart new file mode 100644 index 00000000..0298c2ae --- /dev/null +++ b/app/lib/view/data_storage/node_view.dart @@ -0,0 +1,97 @@ +import 'package:file_icon/file_icon.dart'; +import 'package:flutter/material.dart'; +import 'package:sph_plan/shared/widgets/marquee.dart'; + +import '../../client/client.dart'; +import '../../shared/launch_file.dart'; +import '../../shared/types/dateispeicher_node.dart'; + +class DataStorageNodeView extends StatefulWidget { + final int nodeID; + final String title; + + const DataStorageNodeView({super.key, required this.nodeID, required this.title}); + + @override + State createState() => _DataStorageNodeViewState(); +} + +class _DataStorageNodeViewState extends State { + var loading = true; + late List files; + late List folders; + + @override + void dispose() { + super.dispose(); + } + + @override + void initState() { + super.initState(); + loadItems(); + } + + void loadItems() async { + var items = await client.dataStorage.getNode(widget.nodeID); + var (fileList, folderList) = items; + files = fileList; + folders = folderList; + + setState(() { + loading = false; + }); + } + + List getListTiles() { + var listTiles = []; + + for (var folder in folders) { + listTiles.add(ListTile( + title: Text(folder.name), + subtitle: Text(folder.desc, maxLines: 2, overflow: TextOverflow.ellipsis,), + leading: const Icon(Icons.folder_outlined), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DataStorageNodeView(nodeID: folder.id, title: folder.name), + ), + ); + } + )); + } + + for (var file in files) { + listTiles.add(ListTile( + title: MarqueeWidget(child: Text(file.name)), + subtitle: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (file.hinweis != null) MarqueeWidget(child: Text( file.hinweis!)) else Text(file.groesse), + Text(file.aenderung), + ], + ), + leading: FileIcon(file.name, ), + onTap: () => launchFile(context, file.downloadUrl, file.name, file.groesse), + )); + } + + return listTiles; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: loading ? const Center( + child: CircularProgressIndicator(), + ) : ListView( + children: getListTiles(), + ), + ); + } +} + diff --git a/app/lib/view/mein_unterricht/course_overview.dart b/app/lib/view/mein_unterricht/course_overview.dart index 88edca90..1196d2d4 100644 --- a/app/lib/view/mein_unterricht/course_overview.dart +++ b/app/lib/view/mein_unterricht/course_overview.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:open_file/open_file.dart'; import 'package:sph_plan/view/mein_unterricht/upload_page.dart'; import '../../client/client.dart'; +import '../../shared/launch_file.dart'; import '../../shared/widgets/error_view.dart'; import '../../shared/widgets/format_text.dart'; @@ -118,45 +118,9 @@ class _CourseOverviewAnsichtState extends State { data["historie"][index]["files"].forEach((file) { files.add(ActionChip( label: Text(file["filename"]), - onPressed: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return AlertDialog( - title: Text("Download... ${file['filesize']}"), - content: const Center( - heightFactor: 1.1, - child: CircularProgressIndicator(), - ), - ); - }); - client - .downloadFile(file["url"], file["filename"]) - .then((filepath) { - Navigator.of(context).pop(); - - if (filepath == "") { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Fehler!"), - content: Text( - "Beim Download der Datei ${file["filename"]} ist ein unerwarteter Fehler aufgetreten. Wenn dieses Problem besteht, senden Sie uns bitte einen Fehlerbericht."), - actions: [ - TextButton( - child: const Text('OK'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - )); - } else { - OpenFile.open(filepath); - } - }); - }, + onPressed: () => launchFile( + context, file["url"], file["filename"], file["size"] + ), )); }); diff --git a/app/pubspec.lock b/app/pubspec.lock index d19a5088..ee8457e8 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -257,6 +257,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_icon: + dependency: "direct main" + description: + name: file_icon + sha256: c46b6c24d9595d18995758b90722865baeda407f56308eadd757e1ab913f50a1 + url: "https://pub.dev" + source: hosted + version: "1.0.0" file_picker: dependency: "direct main" description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index a4150bb6..20aee7c6 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -68,6 +68,7 @@ dependencies: table_calendar: ^3.0.9 intl: ^0.18.1 cached_network_image: ^3.3.1 + file_icon: ^1.0.0 flutter_launcher_icons: android: false From 692a5101193e8746e0386c2fe124ec70c519b1fb Mon Sep 17 00:00:00 2001 From: Alessio Caputo <84250128+alessioC42@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:18:20 +0100 Subject: [PATCH 4/6] Error handling --- .../client/client_submodules/datastorage.dart | 101 ++++++++++-------- app/lib/view/data_storage/node_view.dart | 19 +++- 2 files changed, 74 insertions(+), 46 deletions(-) diff --git a/app/lib/client/client_submodules/datastorage.dart b/app/lib/client/client_submodules/datastorage.dart index 6f4f9819..117f4855 100644 --- a/app/lib/client/client_submodules/datastorage.dart +++ b/app/lib/client/client_submodules/datastorage.dart @@ -1,6 +1,7 @@ import 'package:dio/dio.dart'; import 'package:html/parser.dart'; +import '../../shared/exceptions/client_status_exceptions.dart'; import '../../shared/types/dateispeicher_node.dart'; import '../client.dart'; @@ -12,54 +13,64 @@ class DataStorageParser { dio = dioClient; } - Future getNode(int nodeID) async { - final response = await dio.get( - "https://start.schulportal.hessen.de/dateispeicher.php?a=view&folder=$nodeID"); - var document = parse(response.data); - List files = []; - List headers = document - .querySelectorAll("table#files thead th") - .map((e) => e.text) - .toList(); - for (var file in document.querySelectorAll("table#files tbody tr")) { - final fields = file.querySelectorAll("td"); - String? hinweis = fields[headers.indexOf("Name")] - .querySelector("small")?.text.trim(); - if (hinweis != null) { - fields[headers.indexOf("Name")] - .querySelector("small")?.text = ""; + Future<(List, List)> getNode(int nodeID) async { + try { + late final Response response; + try { + response = await dio.get( + "https://start.schulportal.hessen.de/dateispeicher.php?a=view&folder=$nodeID"); + } catch (e) { + throw NoConnectionException(); + } + + var document = parse(response.data); + List files = []; + List headers = document + .querySelectorAll("table#files thead th") + .map((e) => e.text) + .toList(); + for (var file in document.querySelectorAll("table#files tbody tr")) { + final fields = file.querySelectorAll("td"); + String? hinweis = + fields[headers.indexOf("Name")].querySelector("small")?.text.trim(); + if (hinweis != null) { + fields[headers.indexOf("Name")].querySelector("small")?.text = ""; + } + var name = fields[headers.indexOf("Name")].text.trim(); + var aenderung = fields[headers.indexOf("Änderung")].text.trim(); + var groesse = fields[headers.indexOf("Größe")].text.trim(); + var id = int.parse(file.attributes["data-id"]!.trim()); + files.add(FileNode( + name, + id, + "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id", + aenderung, + groesse, + hinweis: hinweis)); } - var name = fields[headers.indexOf("Name")].text.trim(); - var aenderung = fields[headers.indexOf("Änderung")].text.trim(); - var groesse = fields[headers.indexOf("Größe")].text.trim(); - var id = int.parse(file.attributes["data-id"]!.trim()); - files.add(FileNode( - name, - id, - "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id", - aenderung, - groesse, - hinweis: hinweis - )); - } - List folders = []; - for (var folder in document.querySelectorAll(".folder")) { - var name = folder.querySelector(".caption")!.text.trim(); - var desc = folder.querySelector(".desc")!.text.trim(); - var subfolders = int.tryParse(RegExp(r"\d+") - .firstMatch(folder - .querySelector("[title=\"Anzahl Ordner\"]") - ?.text - .trim() ?? - "") - ?.group(0) ?? - "") ?? - 0; - var id = int.parse(folder.attributes["data-id"]!); - folders.add(FolderNode(name, id, subfolders, desc)); + List folders = []; + for (var folder in document.querySelectorAll(".folder")) { + var name = folder.querySelector(".caption")!.text.trim(); + var desc = folder.querySelector(".desc")!.text.trim(); + var subfolders = int.tryParse(RegExp(r"\d+") + .firstMatch(folder + .querySelector("[title=\"Anzahl Ordner\"]") + ?.text + .trim() ?? + "") + ?.group(0) ?? + "") ?? + 0; + var id = int.parse(folder.attributes["data-id"]!); + folders.add(FolderNode(name, id, subfolders, desc)); + } + return (files, folders); + } on NoConnectionException { + rethrow; + } catch (e) { + throw LoggedOffOrUnknownException(); } - return (files, folders); } Future getRoot() async { diff --git a/app/lib/view/data_storage/node_view.dart b/app/lib/view/data_storage/node_view.dart index 0298c2ae..e7c649de 100644 --- a/app/lib/view/data_storage/node_view.dart +++ b/app/lib/view/data_storage/node_view.dart @@ -1,8 +1,10 @@ import 'package:file_icon/file_icon.dart'; import 'package:flutter/material.dart'; +import 'package:sph_plan/shared/widgets/error_view.dart'; import 'package:sph_plan/shared/widgets/marquee.dart'; import '../../client/client.dart'; +import '../../shared/exceptions/client_status_exceptions.dart'; import '../../shared/launch_file.dart'; import '../../shared/types/dateispeicher_node.dart'; @@ -18,6 +20,7 @@ class DataStorageNodeView extends StatefulWidget { class _DataStorageNodeViewState extends State { var loading = true; + var error = false; late List files; late List folders; @@ -33,6 +36,7 @@ class _DataStorageNodeViewState extends State { } void loadItems() async { + try { var items = await client.dataStorage.getNode(widget.nodeID); var (fileList, folderList) = items; files = fileList; @@ -41,6 +45,12 @@ class _DataStorageNodeViewState extends State { setState(() { loading = false; }); + } on LanisException catch (e) { + setState(() { + error = true; + loading = false; + }); + } } List getListTiles() { @@ -88,10 +98,17 @@ class _DataStorageNodeViewState extends State { ), body: loading ? const Center( child: CircularProgressIndicator(), + ) : error ? const Center( + child: Column( + children: [ + Icon(Icons.error_outline, size: 100), + SizedBox(height: 10), + Text("Fehler beim Laden der Dateien"), + ], + ) ) : ListView( children: getListTiles(), ), ); } } - From 20cd41a4633c14093990db3935105a09b15673ca Mon Sep 17 00:00:00 2001 From: Alessio Caputo <84250128+alessioC42@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:27:09 +0100 Subject: [PATCH 5/6] feat: datastorage search --- .../client/client_submodules/datastorage.dart | 34 ++++- app/lib/shared/types/dateispeicher_node.dart | 3 +- app/lib/view/data_storage/data_storage.dart | 4 +- app/lib/view/data_storage/file_listtile.dart | 51 +++++++ .../view/data_storage/folder_listtile.dart | 29 ++++ app/lib/view/data_storage/node_view.dart | 23 +-- app/lib/view/data_storage/root_view.dart | 138 ++++++++++++++++++ 7 files changed, 252 insertions(+), 30 deletions(-) create mode 100644 app/lib/view/data_storage/file_listtile.dart create mode 100644 app/lib/view/data_storage/folder_listtile.dart create mode 100644 app/lib/view/data_storage/root_view.dart diff --git a/app/lib/client/client_submodules/datastorage.dart b/app/lib/client/client_submodules/datastorage.dart index 117f4855..aebdddd1 100644 --- a/app/lib/client/client_submodules/datastorage.dart +++ b/app/lib/client/client_submodules/datastorage.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:dio/dio.dart'; import 'package:html/parser.dart'; @@ -13,6 +15,25 @@ class DataStorageParser { dio = dioClient; } + Future searchFiles(String query) async { + final response = await dio.get( + "https://start.schulportal.hessen.de/dateispeicher.php", + queryParameters: { + "q": query, + "a": "searchFiles" + }, + data: { + "q": query, + "a": "searchFiles" + }, + options: Options( + contentType: "application/x-www-form-urlencoded" + ) + ); + final data = jsonDecode(response.data); + return data[0]; + } + Future<(List, List)> getNode(int nodeID) async { try { late final Response response; @@ -41,12 +62,13 @@ class DataStorageParser { var groesse = fields[headers.indexOf("Größe")].text.trim(); var id = int.parse(file.attributes["data-id"]!.trim()); files.add(FileNode( - name, - id, - "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id", - aenderung, - groesse, - hinweis: hinweis)); + name: name, + id: id, + downloadUrl: "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id", + aenderung: aenderung, + groesse: groesse, + hinweis: hinweis, + )); } List folders = []; diff --git a/app/lib/shared/types/dateispeicher_node.dart b/app/lib/shared/types/dateispeicher_node.dart index 2abe8e12..deab1f82 100644 --- a/app/lib/shared/types/dateispeicher_node.dart +++ b/app/lib/shared/types/dateispeicher_node.dart @@ -1,12 +1,13 @@ class FileNode { String name; int id; + int? folderID; String downloadUrl; String groesse; String aenderung; String? hinweis; - FileNode(this.name, this.id, this.downloadUrl, this.aenderung, this.groesse, {this.hinweis}); + FileNode({required this.name, required this.id, required this.downloadUrl, this.aenderung = "", this.groesse = "", this.hinweis, this.folderID}); } class FolderNode { diff --git a/app/lib/view/data_storage/data_storage.dart b/app/lib/view/data_storage/data_storage.dart index 4f2e8c9e..b860a053 100644 --- a/app/lib/view/data_storage/data_storage.dart +++ b/app/lib/view/data_storage/data_storage.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'node_view.dart'; +import 'package:sph_plan/view/data_storage/root_view.dart'; class DataStorageAnsicht extends StatefulWidget { const DataStorageAnsicht({super.key}); @@ -14,7 +14,7 @@ class _DataStorageAnsichtState extends State { @override Widget build(BuildContext context) { - return const DataStorageNodeView(nodeID: 0, title: "Datenspeicher"); + return const DataStorageRootView(); } } diff --git a/app/lib/view/data_storage/file_listtile.dart b/app/lib/view/data_storage/file_listtile.dart new file mode 100644 index 00000000..c16e4c0a --- /dev/null +++ b/app/lib/view/data_storage/file_listtile.dart @@ -0,0 +1,51 @@ +import 'package:file_icon/file_icon.dart'; +import 'package:flutter/material.dart'; + +import '../../shared/launch_file.dart'; +import '../../shared/widgets/marquee.dart'; + +class FileListTile extends ListTile { + final dynamic file; + final BuildContext context; + + const FileListTile({super.key, required this.context, required this.file}); + + @override + Widget build(BuildContext context) { + return ListTile( + title: MarqueeWidget(child: Text(file.name)), + subtitle: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (file.hinweis != null) Expanded(child: MarqueeWidget(child: Text(file.hinweis!),)) else Text(file.groesse), + const SizedBox(width: 5), + Text(file.aenderung), + ], + ), + leading: FileIcon(file.name, ), + onTap: () => launchFile(context, file.downloadUrl, file.name, file.groesse), + ); + } +} + +class SearchFileListTile extends ListTile { + final String name; + final String downloadUrl; + final BuildContext context; + + const SearchFileListTile({ + super.key, + required this.context, + required this.name, + required this.downloadUrl + }); + + @override + Widget build(BuildContext context) { + return ListTile( + title: MarqueeWidget(child: Text(name)), + leading: FileIcon(name, ), + onTap: () => launchFile(context, downloadUrl, name, ""), + ); + } +} \ No newline at end of file diff --git a/app/lib/view/data_storage/folder_listtile.dart b/app/lib/view/data_storage/folder_listtile.dart new file mode 100644 index 00000000..b00e8641 --- /dev/null +++ b/app/lib/view/data_storage/folder_listtile.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:sph_plan/shared/types/dateispeicher_node.dart'; + +import 'node_view.dart'; + + +class FolderListTile extends ListTile { + final FolderNode folder; + final BuildContext context; + + const FolderListTile({super.key, required this.context, required this.folder}); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(folder.name), + subtitle: Text(folder.desc, maxLines: 2, overflow: TextOverflow.ellipsis,), + leading: const Icon(Icons.folder_outlined), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DataStorageNodeView(nodeID: folder.id, title: folder.name), + ), + ); + } + ); + } +} \ No newline at end of file diff --git a/app/lib/view/data_storage/node_view.dart b/app/lib/view/data_storage/node_view.dart index e7c649de..10e4ddc9 100644 --- a/app/lib/view/data_storage/node_view.dart +++ b/app/lib/view/data_storage/node_view.dart @@ -1,12 +1,9 @@ -import 'package:file_icon/file_icon.dart'; import 'package:flutter/material.dart'; -import 'package:sph_plan/shared/widgets/error_view.dart'; -import 'package:sph_plan/shared/widgets/marquee.dart'; import '../../client/client.dart'; import '../../shared/exceptions/client_status_exceptions.dart'; -import '../../shared/launch_file.dart'; import '../../shared/types/dateispeicher_node.dart'; +import 'file_listtile.dart'; class DataStorageNodeView extends StatefulWidget { final int nodeID; @@ -24,11 +21,6 @@ class _DataStorageNodeViewState extends State { late List files; late List folders; - @override - void dispose() { - super.dispose(); - } - @override void initState() { super.initState(); @@ -73,18 +65,7 @@ class _DataStorageNodeViewState extends State { } for (var file in files) { - listTiles.add(ListTile( - title: MarqueeWidget(child: Text(file.name)), - subtitle: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (file.hinweis != null) MarqueeWidget(child: Text( file.hinweis!)) else Text(file.groesse), - Text(file.aenderung), - ], - ), - leading: FileIcon(file.name, ), - onTap: () => launchFile(context, file.downloadUrl, file.name, file.groesse), - )); + listTiles.add(FileListTile(context: context, file: file)); } return listTiles; diff --git a/app/lib/view/data_storage/root_view.dart b/app/lib/view/data_storage/root_view.dart new file mode 100644 index 00000000..2034f457 --- /dev/null +++ b/app/lib/view/data_storage/root_view.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:sph_plan/view/data_storage/folder_listtile.dart'; + +import '../../client/client.dart'; +import '../../shared/exceptions/client_status_exceptions.dart'; +import '../../shared/types/dateispeicher_node.dart'; +import 'file_listtile.dart'; + +class DataStorageRootView extends StatefulWidget { + + const DataStorageRootView({super.key}); + + @override + State createState() => _DataStorageRootViewState(); +} + +class _DataStorageRootViewState extends State { + var loading = true; + var error = false; + late List files; + late List folders; + final SearchController searchController = SearchController(); + + @override + void initState() { + super.initState(); + loadItems(); + } + + void loadItems() async { + try { + var items = await client.dataStorage.getRoot(); + var (fileList, folderList) = items; + files = fileList; + folders = folderList; + + setState(() { + loading = false; + }); + } on LanisException catch (e) { + setState(() { + error = true; + loading = false; + }); + } + } + + List getListTiles() { + var listTiles = []; + + for (var folder in folders) { + listTiles.add(FolderListTile(context: context, folder: folder)); + } + for (var file in files) { + listTiles.add(FileListTile(context: context, file: file)); + } + return listTiles; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Datenspeicher"), + actions: const [ + AsyncSearchAnchor(), + ], + ), + body: loading ? const Center( + child: CircularProgressIndicator(), + ) : error ? const Center( + child: Column( + children: [ + Icon(Icons.error_outline, size: 100), + SizedBox(height: 10), + Text("Fehler beim Laden der Dateien"), + ], + ) + ) : ListView( + children: getListTiles(), + ), + ); + } +} + +class AsyncSearchAnchor extends StatefulWidget { + const AsyncSearchAnchor({super.key}); + + @override + State createState() => _AsyncSearchAnchorState(); +} + +class _AsyncSearchAnchorState extends State { + String? _searchingWithQuery; + late Iterable _lastOptions = []; + + @override + Widget build(BuildContext context) { + return SearchAnchor( + builder: (BuildContext context, SearchController controller) { + return IconButton( + icon: const Icon(Icons.search), + onPressed: () { + controller.openView(); + }, + ); + }, suggestionsBuilder: + (BuildContext context, SearchController controller) async { + _searchingWithQuery = controller.text; + var options = await client.dataStorage.searchFiles(_searchingWithQuery!); + + debugPrint("Search results: ${options.length}"); + + if (_searchingWithQuery != controller.text) { + return _lastOptions; + } + + _lastOptions = List.generate(options.length, (int index) { + final Map item = options[index]; + return SearchFileListTile( + context: context, + name: item["text"], + downloadUrl: "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=${item["id"]}" + ); + }); + + if (_lastOptions.isEmpty) { + _lastOptions = [ + const ListTile( + title: Text("Keine Ergebnisse"), + ) + ]; + } + + return _lastOptions; + }); + } +} From 4f47be7414d838e74ea41191beb9344d962c3440 Mon Sep 17 00:00:00 2001 From: Alessio Caputo <84250128+alessioC42@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:27:48 +0100 Subject: [PATCH 6/6] feat: "dateispeicher" file indication dot --- app/lib/client/client.dart | 49 +++++------ .../client_submodules/substitutions.dart | 1 - app/lib/home_page.dart | 1 - app/lib/shared/launch_file.dart | 3 +- app/lib/view/data_storage/file_listtile.dart | 83 ++++++++++++++++--- app/lib/view/data_storage/node_view.dart | 4 +- app/lib/view/data_storage/root_view.dart | 4 +- app/lib/view/login/auth.dart | 2 - .../view/mein_unterricht/course_overview.dart | 4 +- .../settings/subsettings/clear_cache.dart | 1 - 10 files changed, 103 insertions(+), 49 deletions(-) diff --git a/app/lib/client/client.dart b/app/lib/client/client.dart index 1589a181..69b7bfd5 100644 --- a/app/lib/client/client.dart +++ b/app/lib/client/client.dart @@ -205,7 +205,6 @@ class SPHclient { } on LanisException { rethrow; } catch (e) { - debugPrint(e.toString()); throw LoggedOffOrUnknownException(); } } @@ -364,8 +363,6 @@ class SPHclient { var userDataTableBody = document.querySelector("div.col-md-12 table.table.table-striped tbody"); - //TODO find out how "Zugeordnete Eltern/Erziehungsberechtigte" is used in this scope - if (userDataTableBody != null) { var result = {}; @@ -410,43 +407,47 @@ class SPHclient { await deleteSubfoldersAndFiles(tempDir); } + // This function generates a unique hash for a given source string + String generateUniqueHash(String source) { + var bytes = utf8.encode(source); + var digest = sha256.convert(bytes); + + var shortHash = digest + .toString() + .replaceAll(RegExp(r'[^A-z0-9]'), '') + .substring(0, 12); + + return shortHash; + } + + /// This function checks if a file exists in the temporary directory downloaded by [downloadFile] + Future doesFileExist(String url, String filename) async { + var tempDir = await getTemporaryDirectory(); + String urlHash = generateUniqueHash(url); + String folderPath = "${tempDir.path}/$urlHash"; + String filePath = "$folderPath/$filename"; + + File existingFile = File(filePath); + return existingFile.existsSync(); + } + ///downloads a file from an URL and returns the path of the file. /// ///The file is stored in the temporary directory of the device. ///So calling the same URL twice will result in the same file and one Download. Future downloadFile(String url, String filename) async { - String generateUniqueHash(String source) { - var bytes = utf8.encode(source); - var digest = sha256.convert(bytes); - - var shortHash = digest - .toString() - .replaceAll(RegExp(r'[^A-z0-9]'), '') - .substring(0, 6); - - return shortHash; - } - try { var tempDir = await getTemporaryDirectory(); - - // To ensure unique file names, we store each file in a folder - // with a hashed value of the download URL. - // It is necessary for a teacher to upload files with unique file names. String urlHash = generateUniqueHash(url); - String folderPath = "${tempDir.path}/$urlHash"; String savePath = "$folderPath/$filename"; - // Check if the folder exists, create it if not Directory folder = Directory(folderPath); if (!folder.existsSync()) { folder.createSync(recursive: true); } - // Check if the file already exists - File existingFile = File(savePath); - if (existingFile.existsSync()) { + if (await doesFileExist(url, filename)) { return savePath; } diff --git a/app/lib/client/client_submodules/substitutions.dart b/app/lib/client/client_submodules/substitutions.dart index 40ec44c7..52fe86f3 100644 --- a/app/lib/client/client_submodules/substitutions.dart +++ b/app/lib/client/client_submodules/substitutions.dart @@ -152,7 +152,6 @@ class SubstitutionsParser { try { var dates = await getSubstitutionDates(); - debugPrint(dates.toString()); if (dates.isEmpty) { return getVplanNonAJAX(); diff --git a/app/lib/home_page.dart b/app/lib/home_page.dart index 6605fb98..75ed5f81 100644 --- a/app/lib/home_page.dart +++ b/app/lib/home_page.dart @@ -319,7 +319,6 @@ class _HomePageState extends State { indexNavbarTranslationLayer.add(null); } } - debugPrint(selectedDestinationDrawer.toString()); return NavigationBar( destinations: barDestinations, selectedIndex: indexNavbarTranslationLayer[selectedDestinationDrawer]!, diff --git a/app/lib/shared/launch_file.dart b/app/lib/shared/launch_file.dart index b04e7e7b..977bcdd1 100644 --- a/app/lib/shared/launch_file.dart +++ b/app/lib/shared/launch_file.dart @@ -3,7 +3,7 @@ import 'package:open_file/open_file.dart'; import '../client/client.dart'; -void launchFile(context, url, filename, filesize) { +void launchFile(BuildContext context, String url, String filename, String filesize, Function callback) { showDialog( context: context, barrierDismissible: false, @@ -39,6 +39,7 @@ void launchFile(context, url, filename, filesize) { )); } else { OpenFile.open(filepath); + callback(); // Call the callback function after the file is opened } }); } \ No newline at end of file diff --git a/app/lib/view/data_storage/file_listtile.dart b/app/lib/view/data_storage/file_listtile.dart index c16e4c0a..293133d7 100644 --- a/app/lib/view/data_storage/file_listtile.dart +++ b/app/lib/view/data_storage/file_listtile.dart @@ -1,51 +1,110 @@ import 'package:file_icon/file_icon.dart'; import 'package:flutter/material.dart'; +import '../../client/client.dart'; import '../../shared/launch_file.dart'; import '../../shared/widgets/marquee.dart'; -class FileListTile extends ListTile { +enum FileExists { yes, no, loading } +extension FileExistsExtension on FileExists { + MaterialColor? get color => { + FileExists.yes: Colors.green, + FileExists.no: Colors.red, + FileExists.loading: Colors.grey, + }[this]; +} + + +class FileListTile extends StatefulWidget { final dynamic file; final BuildContext context; const FileListTile({super.key, required this.context, required this.file}); + @override + _FileListTileState createState() => _FileListTileState(); +} + +class _FileListTileState extends State { + var exists = FileExists.loading; + + @override + void initState() { + super.initState(); + updateLocalFileStatus(); + } + + void updateLocalFileStatus() { + client.doesFileExist(widget.file.downloadUrl, widget.file.name).then((value) { + setState(() { + exists = value ? FileExists.yes : FileExists.no; + }); + }); + } + @override Widget build(BuildContext context) { return ListTile( - title: MarqueeWidget(child: Text(file.name)), + title: MarqueeWidget(child: Text(widget.file.name)), subtitle: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (file.hinweis != null) Expanded(child: MarqueeWidget(child: Text(file.hinweis!),)) else Text(file.groesse), + if (widget.file.hinweis != null) Expanded(child: MarqueeWidget(child: Text(widget.file.hinweis!),)) else Text(widget.file.groesse), const SizedBox(width: 5), - Text(file.aenderung), + Text(widget.file.aenderung), ], ), - leading: FileIcon(file.name, ), - onTap: () => launchFile(context, file.downloadUrl, file.name, file.groesse), + leading: Badge( + backgroundColor: exists.color, + child: FileIcon(widget.file.name, ) + ), + onTap: () => launchFile(context, widget.file.downloadUrl, widget.file.name, widget.file.groesse, updateLocalFileStatus), ); } } -class SearchFileListTile extends ListTile { +class SearchFileListTile extends StatefulWidget { final String name; final String downloadUrl; final BuildContext context; const SearchFileListTile({ - super.key, + Key? key, required this.context, required this.name, required this.downloadUrl - }); + }) : super(key: key); + + @override + _SearchFileListTileState createState() => _SearchFileListTileState(); +} + +class _SearchFileListTileState extends State { + var exists = FileExists.loading; + + @override + void initState() { + super.initState(); + updateLocalFileStatus(); + } + + void updateLocalFileStatus() { + client.doesFileExist(widget.downloadUrl, widget.name).then((value) { + setState(() { + exists = value ? FileExists.yes : FileExists.no; + }); + }); + } @override Widget build(BuildContext context) { return ListTile( - title: MarqueeWidget(child: Text(name)), - leading: FileIcon(name, ), - onTap: () => launchFile(context, downloadUrl, name, ""), + title: MarqueeWidget(child: Text(widget.name)), + leading: Badge( + backgroundColor: exists.color, + child: FileIcon(widget.name), + ), + onTap: () => launchFile(context, widget.downloadUrl, widget.name, "", updateLocalFileStatus), ); } } \ No newline at end of file diff --git a/app/lib/view/data_storage/node_view.dart b/app/lib/view/data_storage/node_view.dart index 10e4ddc9..e7c30759 100644 --- a/app/lib/view/data_storage/node_view.dart +++ b/app/lib/view/data_storage/node_view.dart @@ -45,8 +45,8 @@ class _DataStorageNodeViewState extends State { } } - List getListTiles() { - var listTiles = []; + List getListTiles() { + var listTiles = []; for (var folder in folders) { listTiles.add(ListTile( diff --git a/app/lib/view/data_storage/root_view.dart b/app/lib/view/data_storage/root_view.dart index 2034f457..0de4a2de 100644 --- a/app/lib/view/data_storage/root_view.dart +++ b/app/lib/view/data_storage/root_view.dart @@ -109,13 +109,11 @@ class _AsyncSearchAnchorState extends State { _searchingWithQuery = controller.text; var options = await client.dataStorage.searchFiles(_searchingWithQuery!); - debugPrint("Search results: ${options.length}"); - if (_searchingWithQuery != controller.text) { return _lastOptions; } - _lastOptions = List.generate(options.length, (int index) { + _lastOptions = List.generate(options.length, (int index) { final Map item = options[index]; return SearchFileListTile( context: context, diff --git a/app/lib/view/login/auth.dart b/app/lib/view/login/auth.dart index fc97b2b6..074f18c8 100644 --- a/app/lib/view/login/auth.dart +++ b/app/lib/view/login/auth.dart @@ -129,10 +129,8 @@ class LoginFormState extends State { InputDecoration(labelText: "Schule auswählen")), selectedItem: dropDownSelectedItem, onChanged: (value) { - debugPrint("changed!"); dropDownSelectedItem = value; selectedSchoolID = extractNumber(value); - debugPrint(selectedSchoolID); }, items: schoolList, ), diff --git a/app/lib/view/mein_unterricht/course_overview.dart b/app/lib/view/mein_unterricht/course_overview.dart index 1196d2d4..17352df8 100644 --- a/app/lib/view/mein_unterricht/course_overview.dart +++ b/app/lib/view/mein_unterricht/course_overview.dart @@ -7,7 +7,7 @@ import '../../shared/widgets/error_view.dart'; import '../../shared/widgets/format_text.dart'; class CourseOverviewAnsicht extends StatefulWidget { - final String dataFetchURL; // Add the dataFetchURL property + final String dataFetchURL; final String title; const CourseOverviewAnsicht( {super.key, required this.dataFetchURL, required this.title}); @@ -119,7 +119,7 @@ class _CourseOverviewAnsichtState extends State { files.add(ActionChip( label: Text(file["filename"]), onPressed: () => launchFile( - context, file["url"], file["filename"], file["size"] + context, file["url"], file["filename"], file["size"], (){} ), )); }); diff --git a/app/lib/view/settings/subsettings/clear_cache.dart b/app/lib/view/settings/subsettings/clear_cache.dart index 13861f0b..61155a95 100644 --- a/app/lib/view/settings/subsettings/clear_cache.dart +++ b/app/lib/view/settings/subsettings/clear_cache.dart @@ -41,7 +41,6 @@ class _BodyState extends State { } }); } - debugPrint(fileNum.toString()); return {'fileNum': fileNum, 'size': totalSize}; }