diff --git a/lib/screen/pages/files/fileUpload.dart b/lib/screen/pages/files/fileUpload.dart new file mode 100644 index 0000000..84847ec --- /dev/null +++ b/lib/screen/pages/files/fileUpload.dart @@ -0,0 +1,72 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; +import 'package:marianum_mobile/api/marianumcloud/webdav/webdavApi.dart'; + +class FileUpload extends StatefulWidget { + final String localPath; + final String remotePath; + const FileUpload({Key? key, required this.localPath, required this.remotePath}) : super(key: key); + + @override + State createState() => _FileUploadState(); +} + +class _FileUploadState extends State { + CancelableOperation? cancelableOperation; + late File localFile; + + @override + void initState() { + super.initState(); + localFile = File(widget.localPath); + } + + bool isRunning() { + return cancelableOperation != null && !(cancelableOperation!.isCompleted); + } + + bool isComplete() { + return cancelableOperation?.isCompleted ?? false; + } + + void start() async { + cancelableOperation = CancelableOperation.fromFuture( + (await WebdavApi.webdav).upload(localFile.readAsBytesSync(), widget.remotePath).then((value) { + log("Upload done!"); + }), + onCancel: () => log("Upload cancelled"), + ); + + cancelableOperation!.then((e) { + setState(() {}); + }); + setState(() {}); + } + + void stop() { + cancelableOperation?.cancel(); + setState(() {}); + Navigator.of(context).pop(); + } + + + @override + Widget build(BuildContext context) { + List actions = List.empty(growable: true); + if(!isRunning() && !isComplete()) actions.add(TextButton(onPressed: start, child: const Text("Upload starten"))); + if(isRunning()) actions.add(TextButton(onPressed: stop, child: const Text("Upload Abbrechen"))); + if(isComplete()) actions.add(TextButton(onPressed: Navigator.of(context).pop, child: const Text("Fertig"))); + + return AlertDialog( + title: const Text("Hochladen"), + content: Center( + child: isRunning() ? const CircularProgressIndicator() : const Text("Datei hochladen"), + ), + actions: actions, + icon: const Icon(Icons.upload), + ); + } +} diff --git a/lib/screen/pages/files/files.dart b/lib/screen/pages/files/files.dart index fd5c2b6..b0e2a51 100644 --- a/lib/screen/pages/files/files.dart +++ b/lib/screen/pages/files/files.dart @@ -5,9 +5,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:loader_overlay/loader_overlay.dart'; import 'package:marianum_mobile/api/marianumcloud/webdav/queries/listFiles/cacheableFile.dart'; +import 'package:marianum_mobile/screen/pages/files/fileUpload.dart'; import 'package:marianum_mobile/widget/errorView.dart'; -import 'package:nextcloud/nextcloud.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:persistent_bottom_nav_bar/persistent_tab_view.dart'; import '../../../api/marianumcloud/webdav/queries/listFiles/listFilesCache.dart'; import '../../../api/marianumcloud/webdav/queries/listFiles/listFilesResponse.dart'; @@ -77,7 +77,10 @@ class _FilesState extends State { @override void initState() { super.initState(); + _query(); + } + void _query() { ListFilesCache( path: widget.path.isEmpty ? "/" : widget.path.join("/"), onUpdate: (ListFilesResponse d) => { @@ -192,13 +195,19 @@ class _FilesState extends State { child: const Icon(Icons.upload), ), body: data == null ? const Center(child: CircularProgressIndicator()) : data!.files.isEmpty ? const ErrorView(icon: Icons.folder_off_rounded, text: "Der Ordner ist leer") : LoaderOverlay( - child: ListView.builder( - itemCount: files.length, - itemBuilder: (context, index) { - CacheableFile file = files.toList().skip(index).first; - return FileElement(file, widget.path); + child: RefreshIndicator( + onRefresh: () { + _query(); + return Future.delayed(const Duration(seconds: 3)); }, - ), + child: ListView.builder( + itemCount: files.length, + itemBuilder: (context, index) { + CacheableFile file = files.toList().skip(index).first; + return FileElement(file, widget.path); + }, + ), + ) ) ); } @@ -209,26 +218,10 @@ class _FilesState extends State { return; } - SharedPreferences preferences = await SharedPreferences.getInstance(); - WebDavClient client = NextcloudClient("https://cloud.marianum-fulda.de/", username: preferences.getString("username"), password: preferences.getString("password"), loginName: preferences.getString("username")).webdav; - - log("UPLOAD STARTING: $path"); + context.loaderOverlay.show(); File file = File(path); + var remotePath = "${widget.path.join("/")}/${file.path.split(Platform.pathSeparator).last}"; + PersistentNavBarNavigator.pushNewScreen(context, screen: FileUpload(localPath: path, remotePath: remotePath), withNavBar: false); - showDialog( - context: context, - builder: (context) { - return SimpleDialog( - children: [ - Image.memory(file.readAsBytesSync()), - ], - ); - }, - ); - - client.upload(file.readAsBytesSync(), "/${file.path.split(Platform.pathSeparator).last}").then((value) { - log("UPLOADED ${value.statusCode}"); - context.loaderOverlay.hide(); - }); } } diff --git a/lib/screen/pages/timetable/CrossPainter.dart b/lib/screen/pages/timetable/CrossPainter.dart new file mode 100644 index 0000000..c4bb084 --- /dev/null +++ b/lib/screen/pages/timetable/CrossPainter.dart @@ -0,0 +1,17 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class CrossPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.red.withAlpha(200) + ..strokeWidth = 2.0; + + canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), paint); + canvas.drawLine(Offset(size.width, 0), Offset(0, size.height), paint); + } + + @override + bool shouldRepaint(CrossPainter oldDelegate) => false; +} \ No newline at end of file diff --git a/lib/screen/pages/timetable/appointmenetComponent.dart b/lib/screen/pages/timetable/appointmenetComponent.dart index d553934..be22f9f 100644 --- a/lib/screen/pages/timetable/appointmenetComponent.dart +++ b/lib/screen/pages/timetable/appointmenetComponent.dart @@ -1,18 +1,20 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getTimetable/getTimetableResponse.dart'; +import 'package:marianum_mobile/screen/pages/timetable/CrossPainter.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; class AppointmentComponent extends StatefulWidget { final CalendarAppointmentDetails details; - const AppointmentComponent({Key? key, required this.details}) : super(key: key); + final bool crossedOut; + const AppointmentComponent({Key? key, required this.details, this.crossedOut = false}) : super(key: key); @override State createState() => _AppointmentComponentState(); } class _AppointmentComponentState extends State { - @override Widget build(BuildContext context) { final Appointment meeting = widget.details.appointments.first; @@ -20,90 +22,106 @@ class _AppointmentComponentState extends State { double headerHeight = 50; const double footerHeight = 5; final double infoHeight = appointmentHeight - (headerHeight + footerHeight); - if(infoHeight < 0) headerHeight += infoHeight; + if (infoHeight < 0) headerHeight += infoHeight; - return Column( + return Stack( children: [ - Container( - padding: const EdgeInsets.all(3), - height: headerHeight, - alignment: Alignment.topLeft, - decoration: BoxDecoration( - shape: BoxShape.rectangle, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(5), - topRight: Radius.circular(5)), - color: meeting.color, - ), - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FittedBox( - fit: BoxFit.fitWidth, - child: Text( - meeting.subject, - style: const TextStyle( - color: Colors.white, - fontSize: 15, - fontWeight: FontWeight.w500, - ), - maxLines: 1, - softWrap: false, - ), - ), - FittedBox( - fit: BoxFit.fitWidth, - child: Text( - meeting.location ?? "?", - maxLines: 3, - overflow: TextOverflow.ellipsis, - softWrap: true, - style: const TextStyle( - color: Colors.white, - fontSize: 10, - ), - ), - ) - ], - )), - ), - Visibility( - visible: meeting.notes != null && infoHeight > 10, - replacement: Container( - color: meeting.color, - height: infoHeight, - ), - child: Container( - height: infoHeight, - padding: const EdgeInsets.fromLTRB(3, 5, 3, 2), - color: meeting.color.withOpacity(0.8), - alignment: Alignment.topLeft, - child: SingleChildScrollView( + Column( + children: [ + Container( + padding: const EdgeInsets.all(3), + height: headerHeight, + alignment: Alignment.topLeft, + decoration: BoxDecoration( + shape: BoxShape.rectangle, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(5), + topRight: Radius.circular(5), + ), + color: meeting.color, + ), + child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - meeting.notes ?? "", - style: const TextStyle( - color: Colors.white, - fontSize: 10, + FittedBox( + fit: BoxFit.fitWidth, + child: Text( + meeting.subject, + style: const TextStyle( + color: Colors.white, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + softWrap: false, + ), + ), + FittedBox( + fit: BoxFit.fitWidth, + child: Text( + meeting.location ?? "?", + maxLines: 3, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: const TextStyle( + color: Colors.white, + fontSize: 10, + ), ), ) ], - )), - ), + ), + ), + ), + Visibility( + visible: meeting.notes != null && infoHeight > 10, + replacement: Container( + color: meeting.color, + height: infoHeight, + ), + child: Container( + height: infoHeight, + padding: const EdgeInsets.fromLTRB(3, 5, 3, 2), + color: meeting.color.withOpacity(0.8), + alignment: Alignment.topLeft, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + meeting.notes ?? "", + style: const TextStyle( + color: Colors.white, + fontSize: 10, + ), + ) + ], + ), + ), + ), + ), + Container( + height: footerHeight, + decoration: BoxDecoration( + shape: BoxShape.rectangle, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(5), + bottomRight: Radius.circular(5), + ), + color: meeting.color, + ), + ), + ], ), - Container( - height: footerHeight, - decoration: BoxDecoration( - shape: BoxShape.rectangle, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(5), - bottomRight: Radius.circular(5)), - color: meeting.color, + Visibility( + visible: (meeting.id as GetTimetableResponseObject).code == "cancelled", + child: Positioned.fill( + child: CustomPaint( + painter: CrossPainter(), + ), ), ), ], diff --git a/lib/screen/pages/timetable/timetable.dart b/lib/screen/pages/timetable/timetable.dart index ba7faf9..1dcdc5e 100644 --- a/lib/screen/pages/timetable/timetable.dart +++ b/lib/screen/pages/timetable/timetable.dart @@ -115,7 +115,10 @@ class _TimetableState extends State { ), timeRegionBuilder: (BuildContext context, TimeRegionDetails timeRegionDetails) => TimeRegionComponent(details: timeRegionDetails), - appointmentBuilder: (BuildContext context, CalendarAppointmentDetails details) => AppointmentComponent(details: details), + appointmentBuilder: (BuildContext context, CalendarAppointmentDetails details) => AppointmentComponent( + details: details, + crossedOut: value.getTimetableResponse.result.where((element) => element.id == details.appointments.first.id).firstOrNull?.code == "cancelled" + ), headerHeight: 0, selectionDecoration: const BoxDecoration(), diff --git a/pubspec.yaml b/pubspec.yaml index f764e7c..1c420b2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -77,6 +77,7 @@ dependencies: crypto: ^3.0.3 package_info: ^2.0.2 syncfusion_flutter_calendar: ^21.2.4 + async: ^2.11.0 dev_dependencies: flutter_test: