From be17c9a5ab3ba83a1072b3dc357252577ea71a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Fri, 9 Feb 2024 16:39:04 +0100 Subject: [PATCH 1/4] Added timetable custom event dialog --- .../addCustomTimetableEventDialog.dart | 141 ++++++++++++++++++ lib/view/pages/timetable/timetable.dart | 7 + 2 files changed, 148 insertions(+) create mode 100644 lib/view/pages/timetable/addCustomTimetableEventDialog.dart diff --git a/lib/view/pages/timetable/addCustomTimetableEventDialog.dart b/lib/view/pages/timetable/addCustomTimetableEventDialog.dart new file mode 100644 index 0000000..8a55b49 --- /dev/null +++ b/lib/view/pages/timetable/addCustomTimetableEventDialog.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; + +class AddCustomTimetableEventDialog extends StatefulWidget { + const AddCustomTimetableEventDialog({super.key}); + + @override + State createState() => _AddCustomTimetableEventDialogState(); +} + +class _AddCustomTimetableEventDialogState extends State { + DateTime _selectedDate = DateTime.now(); + TimeOfDay _selectedStartTime = TimeOfDay.now(); + TimeOfDay _selectedEndTime = TimeOfDay.now(); + final TextEditingController _eventNameController = TextEditingController(); + bool _isRecurring = false; + String _recurringFrequency = 'Wöchentlich'; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Termin hinzufügen'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: const Text('Startdatum:'), + subtitle: Text('${_selectedDate.day}/${_selectedDate.month}/${_selectedDate.year}'), + onTap: () async { + final DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: _selectedDate, + firstDate: DateTime.now(), + lastDate: DateTime(2101), + ); + if (pickedDate != null && pickedDate != _selectedDate) { + setState(() { + _selectedDate = pickedDate; + }); + } + }, + ), + ListTile( + title: const Text('Startzeit:'), + subtitle: Text('${_selectedStartTime.hour}:${_selectedStartTime.minute}'), + onTap: () async { + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: _selectedStartTime, + ); + if (pickedTime != null && pickedTime != _selectedStartTime) { + setState(() { + _selectedStartTime = pickedTime; + }); + } + }, + ), + ListTile( + title: const Text('Endzeit:'), + subtitle: Text('${_selectedEndTime.hour}:${_selectedEndTime.minute}'), + onTap: () async { + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: _selectedEndTime, + ); + if (pickedTime != null && pickedTime != _selectedEndTime) { + setState(() { + _selectedEndTime = pickedTime; + }); + } + }, + ), + TextField( + controller: _eventNameController, + decoration: const InputDecoration(labelText: 'Terminname'), + ), + CheckboxListTile( + title: const Text('Wiederholend'), + value: _isRecurring, + onChanged: (bool? value) { + setState(() { + _isRecurring = value!; + }); + }, + ), + if (_isRecurring) ...[ + DropdownButtonFormField( + value: _recurringFrequency, + items: ['Wöchentlich', 'Täglich', 'Monatlich'].map((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (String? value) { + setState(() { + _recurringFrequency = value ?? ""; + }); + }, + ), + // Hier könntest du weitere Felder für die Wiederholungseigenschaften hinzufügen + ], + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Abbrechen'), + ), + TextButton( + onPressed: () { + // Hier kannst du die Eingaben verwenden + String startDate = '${_selectedDate.day}/${_selectedDate.month}/${_selectedDate.year}'; + String startTime = '${_selectedStartTime.hour}:${_selectedStartTime.minute}'; + String endTime = '${_selectedEndTime.hour}:${_selectedEndTime.minute}'; + String eventName = _eventNameController.text; + bool isRecurring = _isRecurring; + String recurringFrequency = _recurringFrequency; + + // Hier kannst du die Daten speichern oder weiterverarbeiten + print('Startdatum: $startDate'); + print('Startzeit: $startTime'); + print('Endzeit: $endTime'); + print('Terminname: $eventName'); + print('Wiederholend: $isRecurring'); + if (_isRecurring) { + print('Wiederholungs-Frequenz: $recurringFrequency'); + } + + // Hier könntest du die Termindaten weiterverarbeiten, z.B. speichern und den Dialog schließen + Navigator.of(context).pop(); + }, + child: const Text('Hinzufügen'), + ), + ], + ); + } +} diff --git a/lib/view/pages/timetable/timetable.dart b/lib/view/pages/timetable/timetable.dart index b79ebf4..7cfdbaf 100644 --- a/lib/view/pages/timetable/timetable.dart +++ b/lib/view/pages/timetable/timetable.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:marianum_mobile/extensions/dateTime.dart'; +import 'package:marianum_mobile/view/pages/timetable/addCustomTimetableEventDialog.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; @@ -64,6 +65,12 @@ class _TimetableState extends State { controller.displayDate = DateTime.now().add(const Duration(days: 2)); } ), + IconButton( + icon: const Icon(Icons.add_card_outlined), + onPressed: () { + showDialog(context: context, builder: (context) => const AddCustomTimetableEventDialog()); + }, + ), ], ), body: Consumer( From 3eae5ba10a1abba5c940423ad9330b9d430d8a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Fri, 9 Feb 2024 18:46:05 +0100 Subject: [PATCH 2/4] Added rrule generator for custom events --- .../addCustomTimetableEventDialog.dart | 148 ++++++++++-------- lib/view/pages/timetable/timetable.dart | 8 +- pubspec.yaml | 1 + 3 files changed, 86 insertions(+), 71 deletions(-) diff --git a/lib/view/pages/timetable/addCustomTimetableEventDialog.dart b/lib/view/pages/timetable/addCustomTimetableEventDialog.dart index 8a55b49..5f1da11 100644 --- a/lib/view/pages/timetable/addCustomTimetableEventDialog.dart +++ b/lib/view/pages/timetable/addCustomTimetableEventDialog.dart @@ -1,4 +1,9 @@ + +import 'dart:developer'; + import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:rrule_generator/rrule_generator.dart'; class AddCustomTimetableEventDialog extends StatefulWidget { const AddCustomTimetableEventDialog({super.key}); @@ -8,98 +13,108 @@ class AddCustomTimetableEventDialog extends StatefulWidget { } class _AddCustomTimetableEventDialogState extends State { - DateTime _selectedDate = DateTime.now(); - TimeOfDay _selectedStartTime = TimeOfDay.now(); - TimeOfDay _selectedEndTime = TimeOfDay.now(); - final TextEditingController _eventNameController = TextEditingController(); - bool _isRecurring = false; - String _recurringFrequency = 'Wöchentlich'; + DateTime _date = DateTime.now(); + TimeOfDay _starTime = TimeOfDay.now().replacing(minute: 00); + TimeOfDay _endTime = TimeOfDay.now().replacing(minute: 30); + final TextEditingController _eventName = TextEditingController(); + final TextEditingController _eventDescription = TextEditingController(); + String _recurringRule = ""; @override Widget build(BuildContext context) { return AlertDialog( + insetPadding: const EdgeInsets.all(20), + contentPadding: const EdgeInsets.all(10), title: const Text('Termin hinzufügen'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( - title: const Text('Startdatum:'), - subtitle: Text('${_selectedDate.day}/${_selectedDate.month}/${_selectedDate.year}'), + title: TextField( + controller: _eventName, + autofocus: true, + decoration: const InputDecoration( + labelText: "Terminname", + border: OutlineInputBorder() + ), + ), + ), + ListTile( + title: TextField( + controller: _eventDescription, + maxLines: 2, + minLines: 2, + decoration: const InputDecoration( + labelText: "Beschreibung", + border: OutlineInputBorder() + ), + ), + ), + const Divider(), + ListTile( + title: Text(Jiffy.parseFromDateTime(_date).yMMMd), + subtitle: const Text("Datum"), onTap: () async { final DateTime? pickedDate = await showDatePicker( context: context, - initialDate: _selectedDate, + initialDate: _date, firstDate: DateTime.now(), lastDate: DateTime(2101), ); - if (pickedDate != null && pickedDate != _selectedDate) { + if (pickedDate != null && pickedDate != _date) { setState(() { - _selectedDate = pickedDate; + _date = pickedDate; }); } }, ), ListTile( - title: const Text('Startzeit:'), - subtitle: Text('${_selectedStartTime.hour}:${_selectedStartTime.minute}'), + title: Text(_starTime.format(context).toString()), + subtitle: const Text("Beginnend"), onTap: () async { final TimeOfDay? pickedTime = await showTimePicker( context: context, - initialTime: _selectedStartTime, + initialTime: _starTime, ); - if (pickedTime != null && pickedTime != _selectedStartTime) { - setState(() { - _selectedStartTime = pickedTime; - }); - } - }, - ), - ListTile( - title: const Text('Endzeit:'), - subtitle: Text('${_selectedEndTime.hour}:${_selectedEndTime.minute}'), - onTap: () async { - final TimeOfDay? pickedTime = await showTimePicker( - context: context, - initialTime: _selectedEndTime, - ); - if (pickedTime != null && pickedTime != _selectedEndTime) { - setState(() { - _selectedEndTime = pickedTime; - }); - } - }, - ), - TextField( - controller: _eventNameController, - decoration: const InputDecoration(labelText: 'Terminname'), - ), - CheckboxListTile( - title: const Text('Wiederholend'), - value: _isRecurring, - onChanged: (bool? value) { + if(pickedTime == null) return; setState(() { - _isRecurring = value!; + _starTime = pickedTime; }); }, ), - if (_isRecurring) ...[ - DropdownButtonFormField( - value: _recurringFrequency, - items: ['Wöchentlich', 'Täglich', 'Monatlich'].map((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - onChanged: (String? value) { + ListTile( + title: Text(_endTime.format(context).toString()), + subtitle: const Text("Endend"), + onTap: () async { + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: _endTime, + ); + if (pickedTime != null && pickedTime != _endTime) { setState(() { - _recurringFrequency = value ?? ""; + _endTime = pickedTime; }); - }, + } + }, + ), + const Divider(), + RRuleGenerator( + config: RRuleGeneratorConfig( + headerEnabled: true, + weekdayBackgroundColor: Theme.of(context).colorScheme.secondary, + weekdaySelectedBackgroundColor: Theme.of(context).primaryColor, + weekdayColor: Colors.black, ), - // Hier könntest du weitere Felder für die Wiederholungseigenschaften hinzufügen - ], + initialRRule: _recurringRule, + textDelegate: const GermanRRuleTextDelegate(), + onChange: (String newValue) { + log("Rule: $newValue"); + setState(() { + _recurringRule = newValue; + }); + }, + ) ], ), ), @@ -113,22 +128,17 @@ class _AddCustomTimetableEventDialogState extends State { } ), IconButton( - icon: const Icon(Icons.add_card_outlined), + icon: const Icon(Icons.add_outlined), onPressed: () { - showDialog(context: context, builder: (context) => const AddCustomTimetableEventDialog()); + showDialog( + context: context, + builder: (context) => const AddCustomTimetableEventDialog(), + barrierDismissible: false, + ); }, ), ], diff --git a/pubspec.yaml b/pubspec.yaml index dd5135f..fb41e3d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -93,6 +93,7 @@ dependencies: flutter_app_badger: ^1.5.0 qr_flutter: ^4.1.0 easy_debounce: ^2.0.3 + rrule_generator: ^0.5.6 dev_dependencies: flutter_test: From 22db412e757c5b793cff161a44b27b530f627b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Mon, 12 Feb 2024 01:00:12 +0100 Subject: [PATCH 3/4] Added api for custom timetable events --- .../add/addCustomTimetableEvent.dart | 24 ++ .../add/addCustomTimetableEventParams.dart | 16 ++ .../add/addCustomTimetableEventParams.g.dart | 21 ++ .../customTimetableEvent.dart | 28 +++ .../customTimetableEvent.g.dart | 33 +++ .../get/getCustomTimetableEvent.dart | 23 ++ .../get/getCustomTimetableEventCache.dart | 24 ++ .../get/getCustomTimetableEventParams.dart | 13 + .../get/getCustomTimetableEventParams.g.dart | 19 ++ .../get/getCustomTimetableEventResponse.dart | 16 ++ .../getCustomTimetableEventResponse.g.dart | 21 ++ .../remove/removeCustomTimetableEvent.dart | 22 ++ .../removeCustomTimetableEventParams.dart | 13 + .../removeCustomTimetableEventParams.g.dart | 19 ++ .../update/updateCustomTimetableEvent.dart | 22 ++ .../updateCustomTimetableEventParams.dart | 17 ++ .../updateCustomTimetableEventParams.g.dart | 21 ++ lib/api/mhsl/mhslApi.dart | 5 + lib/api/mhsl/server/feedback/addFeedback.dart | 2 +- .../userIndex/update/updateUserindex.dart | 2 +- .../webuntis/queries/getRooms/getRooms.dart | 2 +- lib/app.dart | 4 +- lib/extensions/dateTime.dart | 10 + lib/extensions/timeOfDay.dart | 15 ++ lib/model/accountData.dart | 4 +- lib/model/timetable/timetableProps.dart | 22 +- .../pages/more/feedback/feedbackDialog.dart | 9 +- lib/view/pages/more/overhang.dart | 4 +- .../addCustomTimetableEventDialog.dart | 151 ------------ .../timetable/appointmenetComponent.dart | 2 +- .../pages/timetable/appointmentDetails.dart | 232 +++++++++++++----- .../pages/timetable/arbitraryAppointment.dart | 23 ++ .../customTimetableEventEditDialog.dart | 199 +++++++++++++++ lib/view/pages/timetable/timetable.dart | 31 ++- lib/widget/infoDialog.dart | 7 + pubspec.yaml | 2 + 36 files changed, 849 insertions(+), 229 deletions(-) create mode 100644 lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart create mode 100644 lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEventParams.dart create mode 100644 lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEventParams.g.dart create mode 100644 lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart create mode 100644 lib/api/mhsl/customTimetableEvent/customTimetableEvent.g.dart create mode 100644 lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEvent.dart create mode 100644 lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventCache.dart create mode 100644 lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventParams.dart create mode 100644 lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventParams.g.dart create mode 100644 lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventResponse.dart create mode 100644 lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventResponse.g.dart create mode 100644 lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart create mode 100644 lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEventParams.dart create mode 100644 lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEventParams.g.dart create mode 100644 lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart create mode 100644 lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEventParams.dart create mode 100644 lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEventParams.g.dart create mode 100644 lib/extensions/timeOfDay.dart delete mode 100644 lib/view/pages/timetable/addCustomTimetableEventDialog.dart create mode 100644 lib/view/pages/timetable/arbitraryAppointment.dart create mode 100644 lib/view/pages/timetable/customTimetableEventEditDialog.dart create mode 100644 lib/widget/infoDialog.dart diff --git a/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart new file mode 100644 index 0000000..00a99f2 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart @@ -0,0 +1,24 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:http/http.dart'; +import 'package:http/http.dart' as http; + +import '../../mhslApi.dart'; +import 'addCustomTimetableEventParams.dart'; + +class AddCustomTimetableEvent extends MhslApi { + AddCustomTimetableEventParams params; + + AddCustomTimetableEvent(this.params) : super('server/timetable/customEvents'); + + @override + void assemble(String raw) {} + + @override + Future? request(Uri uri) { + String body = jsonEncode(params.toJson()); + log(body); + return http.post(uri, body: body); + } +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEventParams.dart b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEventParams.dart new file mode 100644 index 0000000..1ae908f --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEventParams.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; + +import '../customTimetableEvent.dart'; + +part 'addCustomTimetableEventParams.g.dart'; + +@JsonSerializable(explicitToJson: true) +class AddCustomTimetableEventParams { + String user; + CustomTimetableEvent event; + + AddCustomTimetableEventParams(this.user, this.event); + + factory AddCustomTimetableEventParams.fromJson(Map json) => _$AddCustomTimetableEventParamsFromJson(json); + Map toJson() => _$AddCustomTimetableEventParamsToJson(this); +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEventParams.g.dart b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEventParams.g.dart new file mode 100644 index 0000000..1b89749 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEventParams.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'addCustomTimetableEventParams.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AddCustomTimetableEventParams _$AddCustomTimetableEventParamsFromJson( + Map json) => + AddCustomTimetableEventParams( + json['user'] as String, + CustomTimetableEvent.fromJson(json['event'] as Map), + ); + +Map _$AddCustomTimetableEventParamsToJson( + AddCustomTimetableEventParams instance) => + { + 'user': instance.user, + 'event': instance.event.toJson(), + }; diff --git a/lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart new file mode 100644 index 0000000..1fe606b --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart @@ -0,0 +1,28 @@ +import 'package:json_annotation/json_annotation.dart'; + +import '../mhslApi.dart'; + +part 'customTimetableEvent.g.dart'; + +@JsonSerializable() +class CustomTimetableEvent { + String id; + String title; + String description; + @JsonKey(toJson: MhslApi.dateTimeToJson, fromJson: MhslApi.dateTimeFromJson) + DateTime startDate; + @JsonKey(toJson: MhslApi.dateTimeToJson, fromJson: MhslApi.dateTimeFromJson) + DateTime endDate; + String rrule; + @JsonKey(toJson: MhslApi.dateTimeToJson, fromJson: MhslApi.dateTimeFromJson) + DateTime createdAt; + @JsonKey(toJson: MhslApi.dateTimeToJson, fromJson: MhslApi.dateTimeFromJson) + DateTime updatedAt; + + CustomTimetableEvent({required this.id, required this.title, required this.description, required this.startDate, + required this.endDate, required this.rrule, required this.createdAt, required this.updatedAt}); + + + factory CustomTimetableEvent.fromJson(Map json) => _$CustomTimetableEventFromJson(json); + Map toJson() => _$CustomTimetableEventToJson(this); +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/customTimetableEvent.g.dart b/lib/api/mhsl/customTimetableEvent/customTimetableEvent.g.dart new file mode 100644 index 0000000..2d6416a --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/customTimetableEvent.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'customTimetableEvent.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CustomTimetableEvent _$CustomTimetableEventFromJson( + Map json) => + CustomTimetableEvent( + id: json['id'] as String, + title: json['title'] as String, + description: json['description'] as String, + startDate: MhslApi.dateTimeFromJson(json['startDate'] as String), + endDate: MhslApi.dateTimeFromJson(json['endDate'] as String), + rrule: json['rrule'] as String, + createdAt: MhslApi.dateTimeFromJson(json['createdAt'] as String), + updatedAt: MhslApi.dateTimeFromJson(json['updatedAt'] as String), + ); + +Map _$CustomTimetableEventToJson( + CustomTimetableEvent instance) => + { + 'id': instance.id, + 'title': instance.title, + 'description': instance.description, + 'startDate': MhslApi.dateTimeToJson(instance.startDate), + 'endDate': MhslApi.dateTimeToJson(instance.endDate), + 'rrule': instance.rrule, + 'createdAt': MhslApi.dateTimeToJson(instance.createdAt), + 'updatedAt': MhslApi.dateTimeToJson(instance.updatedAt), + }; diff --git a/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEvent.dart new file mode 100644 index 0000000..2c9d3bf --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEvent.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:http/http.dart' as http; + +import '../../mhslApi.dart'; +import 'getCustomTimetableEventParams.dart'; +import 'getCustomTimetableEventResponse.dart'; + +class GetCustomTimetableEvent extends MhslApi { + GetCustomTimetableEventParams params; + GetCustomTimetableEvent(this.params) : super("server/timetable/customEvents?user=${params.user}"); + + @override + GetCustomTimetableEventResponse assemble(String raw) { + return GetCustomTimetableEventResponse.fromJson({"events": jsonDecode(raw)}); + } + + @override + Future? request(Uri uri) { + return http.get(uri); + } +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventCache.dart b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventCache.dart new file mode 100644 index 0000000..fceaba6 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventCache.dart @@ -0,0 +1,24 @@ +import 'dart:convert'; + +import '../../../requestCache.dart'; +import 'getCustomTimetableEvent.dart'; +import 'getCustomTimetableEventParams.dart'; +import 'getCustomTimetableEventResponse.dart'; + +class GetCustomTimetableEventCache extends RequestCache { + GetCustomTimetableEventParams params; + + GetCustomTimetableEventCache(this.params, {onUpdate, renew}) : super(RequestCache.cacheMinute, onUpdate, renew: renew) { + start("MarianumMobile", "customTimetableEvents"); + } + + @override + Future onLoad() { + return GetCustomTimetableEvent(params).run(); + } + + @override + GetCustomTimetableEventResponse onLocalData(String json) { + return GetCustomTimetableEventResponse.fromJson(jsonDecode(json)); + } +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventParams.dart b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventParams.dart new file mode 100644 index 0000000..bf00f78 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventParams.dart @@ -0,0 +1,13 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'getCustomTimetableEventParams.g.dart'; + +@JsonSerializable() +class GetCustomTimetableEventParams { + String user; + + GetCustomTimetableEventParams(this.user); + + factory GetCustomTimetableEventParams.fromJson(Map json) => _$GetCustomTimetableEventParamsFromJson(json); + Map toJson() => _$GetCustomTimetableEventParamsToJson(this); +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventParams.g.dart b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventParams.g.dart new file mode 100644 index 0000000..0909cf3 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventParams.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getCustomTimetableEventParams.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetCustomTimetableEventParams _$GetCustomTimetableEventParamsFromJson( + Map json) => + GetCustomTimetableEventParams( + json['user'] as String, + ); + +Map _$GetCustomTimetableEventParamsToJson( + GetCustomTimetableEventParams instance) => + { + 'user': instance.user, + }; diff --git a/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventResponse.dart b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventResponse.dart new file mode 100644 index 0000000..c923e19 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventResponse.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; + +import '../../../apiResponse.dart'; +import '../customTimetableEvent.dart'; + +part 'getCustomTimetableEventResponse.g.dart'; + +@JsonSerializable() +class GetCustomTimetableEventResponse extends ApiResponse { + List events; + + GetCustomTimetableEventResponse(this.events); + + factory GetCustomTimetableEventResponse.fromJson(Map json) => _$GetCustomTimetableEventResponseFromJson(json); + Map toJson() => _$GetCustomTimetableEventResponseToJson(this); +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventResponse.g.dart b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventResponse.g.dart new file mode 100644 index 0000000..a68bbae --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/get/getCustomTimetableEventResponse.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getCustomTimetableEventResponse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetCustomTimetableEventResponse _$GetCustomTimetableEventResponseFromJson( + Map json) => + GetCustomTimetableEventResponse( + (json['events'] as List) + .map((e) => CustomTimetableEvent.fromJson(e as Map)) + .toList(), + ); + +Map _$GetCustomTimetableEventResponseToJson( + GetCustomTimetableEventResponse instance) => + { + 'events': instance.events, + }; diff --git a/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart new file mode 100644 index 0000000..62557fe --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:http/http.dart' as http; + +import '../../mhslApi.dart'; +import 'removeCustomTimetableEventParams.dart'; + +class RemoveCustomTimetableEvent extends MhslApi { + RemoveCustomTimetableEventParams params; + + RemoveCustomTimetableEvent(this.params) : super('server/timetable/customEvents'); + + @override + void assemble(String raw) {} + + @override + Future? request(Uri uri) { + return http.delete(uri, body: jsonEncode(params.toJson())); + } + +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEventParams.dart b/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEventParams.dart new file mode 100644 index 0000000..1be141a --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEventParams.dart @@ -0,0 +1,13 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'removeCustomTimetableEventParams.g.dart'; + +@JsonSerializable() +class RemoveCustomTimetableEventParams { + String id; + + RemoveCustomTimetableEventParams(this.id); + + factory RemoveCustomTimetableEventParams.fromJson(Map json) => _$RemoveCustomTimetableEventParamsFromJson(json); + Map toJson() => _$RemoveCustomTimetableEventParamsToJson(this); +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEventParams.g.dart b/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEventParams.g.dart new file mode 100644 index 0000000..5300301 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEventParams.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'removeCustomTimetableEventParams.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RemoveCustomTimetableEventParams _$RemoveCustomTimetableEventParamsFromJson( + Map json) => + RemoveCustomTimetableEventParams( + json['id'] as String, + ); + +Map _$RemoveCustomTimetableEventParamsToJson( + RemoveCustomTimetableEventParams instance) => + { + 'id': instance.id, + }; diff --git a/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart new file mode 100644 index 0000000..f657587 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:http/http.dart' as http; + +import '../../mhslApi.dart'; +import 'updateCustomTimetableEventParams.dart'; + +class UpdateCustomTimetableEvent extends MhslApi { + UpdateCustomTimetableEventParams params; + + UpdateCustomTimetableEvent(this.params) : super('server/timetable/customEvents'); + + @override + void assemble(String raw) {} + + @override + Future? request(Uri uri) { + return http.patch(uri, body: jsonEncode(params.toJson())); + } + +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEventParams.dart b/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEventParams.dart new file mode 100644 index 0000000..806d572 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEventParams.dart @@ -0,0 +1,17 @@ + +import 'package:json_annotation/json_annotation.dart'; + +import '../customTimetableEvent.dart'; + +part 'updateCustomTimetableEventParams.g.dart'; + +@JsonSerializable(explicitToJson: true) +class UpdateCustomTimetableEventParams { + String id; + CustomTimetableEvent event; + + UpdateCustomTimetableEventParams(this.id, this.event); + + factory UpdateCustomTimetableEventParams.fromJson(Map json) => _$UpdateCustomTimetableEventParamsFromJson(json); + Map toJson() => _$UpdateCustomTimetableEventParamsToJson(this); +} \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEventParams.g.dart b/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEventParams.g.dart new file mode 100644 index 0000000..14c654c --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEventParams.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'updateCustomTimetableEventParams.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UpdateCustomTimetableEventParams _$UpdateCustomTimetableEventParamsFromJson( + Map json) => + UpdateCustomTimetableEventParams( + json['id'] as String, + CustomTimetableEvent.fromJson(json['event'] as Map), + ); + +Map _$UpdateCustomTimetableEventParamsToJson( + UpdateCustomTimetableEventParams instance) => + { + 'id': instance.id, + 'event': instance.event.toJson(), + }; diff --git a/lib/api/mhsl/mhslApi.dart b/lib/api/mhsl/mhslApi.dart index 422ea1e..4bb7448 100644 --- a/lib/api/mhsl/mhslApi.dart +++ b/lib/api/mhsl/mhslApi.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:jiffy/jiffy.dart'; import '../apiError.dart'; import '../apiRequest.dart'; @@ -27,4 +28,8 @@ abstract class MhslApi extends ApiRequest { return assemble(utf8.decode(data.bodyBytes)); } + + static String dateTimeToJson(DateTime time) => Jiffy.parseFromDateTime(time).format(pattern: "yyyy-MM-dd HH:mm:ss"); + static DateTime dateTimeFromJson(String time) => DateTime.parse(time); + } \ No newline at end of file diff --git a/lib/api/mhsl/server/feedback/addFeedback.dart b/lib/api/mhsl/server/feedback/addFeedback.dart index 679627a..ad1f34a 100644 --- a/lib/api/mhsl/server/feedback/addFeedback.dart +++ b/lib/api/mhsl/server/feedback/addFeedback.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:http/http.dart'; -import 'package:marianum_mobile/api/mhsl/mhslApi.dart'; import 'package:http/http.dart' as http; +import '../../mhslApi.dart'; import 'addFeedbackParams.dart'; diff --git a/lib/api/mhsl/server/userIndex/update/updateUserindex.dart b/lib/api/mhsl/server/userIndex/update/updateUserindex.dart index b41f4c9..dc310cf 100644 --- a/lib/api/mhsl/server/userIndex/update/updateUserindex.dart +++ b/lib/api/mhsl/server/userIndex/update/updateUserindex.dart @@ -28,7 +28,7 @@ class UpdateUserIndex extends MhslApi { UpdateUserIndex( UpdateUserIndexParams( username: AccountData().getUsername(), - user: AccountData().getUserId(), + user: AccountData().getUserSecret(), device: await AccountData().getDeviceId(), appVersion: int.parse((await PackageInfo.fromPlatform()).buildNumber), deviceInfo: jsonEncode((await DeviceInfoPlugin().deviceInfo).data).toString(), diff --git a/lib/api/webuntis/queries/getRooms/getRooms.dart b/lib/api/webuntis/queries/getRooms/getRooms.dart index 698745f..49a8859 100644 --- a/lib/api/webuntis/queries/getRooms/getRooms.dart +++ b/lib/api/webuntis/queries/getRooms/getRooms.dart @@ -17,7 +17,7 @@ class GetRooms extends WebuntisApi { log("Failed to parse getRoom data with server response: $rawAnswer"); } - throw Exception("Failed to parse getRoom server response"); + throw Exception("Failed to parse getRoom server response: $rawAnswer"); } } \ No newline at end of file diff --git a/lib/app.dart b/lib/app.dart index 58dcedd..c25ae56 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -4,7 +4,6 @@ import 'dart:developer'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; -import 'package:marianum_mobile/notification/notificationTasks.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent-tab-view.dart'; import 'package:provider/provider.dart'; import 'package:badges/badges.dart' as badges; @@ -14,7 +13,9 @@ import 'api/mhsl/server/userIndex/update/updateUserindex.dart'; import 'model/breakers/Breaker.dart'; import 'model/breakers/BreakerProps.dart'; import 'model/chatList/chatListProps.dart'; +import 'model/timetable/timetableProps.dart'; import 'notification/notificationController.dart'; +import 'notification/notificationTasks.dart'; import 'notification/notifyUpdater.dart'; import 'storage/base/settingsProvider.dart'; import 'view/pages/files/files.dart'; @@ -41,6 +42,7 @@ class _AppState extends State with WidgetsBindingObserver { if(state == AppLifecycleState.resumed) { NotificationTasks.updateProviders(context); + Provider.of(context, listen: false).run(); } } diff --git a/lib/extensions/dateTime.dart b/lib/extensions/dateTime.dart index f5b85bd..5ae297c 100644 --- a/lib/extensions/dateTime.dart +++ b/lib/extensions/dateTime.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + extension IsSameDay on DateTime { bool isSameDay(DateTime other) { return year == other.year && month == other.month && day == other.day; @@ -6,4 +8,12 @@ extension IsSameDay on DateTime { DateTime nextWeekday(int day) { return add(Duration(days: (day - weekday) % DateTime.daysPerWeek)); } + + DateTime withTime(TimeOfDay time) { + return copyWith(hour: time.hour, minute: time.minute); + } + + TimeOfDay toTimeOfDay() { + return TimeOfDay(hour: hour, minute: minute); + } } \ No newline at end of file diff --git a/lib/extensions/timeOfDay.dart b/lib/extensions/timeOfDay.dart new file mode 100644 index 0000000..cb4d60b --- /dev/null +++ b/lib/extensions/timeOfDay.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +extension TimeOfDayExt on TimeOfDay { + bool isBefore(TimeOfDay other) { + return hour < other.hour && minute < other.minute; + } + + bool isAfter(TimeOfDay other) { + return hour > other.hour && minute > other.minute; + } + + TimeOfDay add({int hours = 0, int minutes = 0}) { + return replacing(hour: hour + hours, minute: minute + minutes); + } +} \ No newline at end of file diff --git a/lib/model/accountData.dart b/lib/model/accountData.dart index 0f6ea35..729487e 100644 --- a/lib/model/accountData.dart +++ b/lib/model/accountData.dart @@ -38,12 +38,12 @@ class AccountData { return _password!; } - String getUserId() { + String getUserSecret() { return sha512.convert(utf8.encode("${AccountData().getUsername()}:${AccountData().getPassword()}")).toString(); } Future getDeviceId() async { - return sha512.convert(utf8.encode("${getUserId()}@${await FirebaseMessaging.instance.getToken()}")).toString(); + return sha512.convert(utf8.encode("${getUserSecret()}@${await FirebaseMessaging.instance.getToken()}")).toString(); } Future setData(String username, String password) async { diff --git a/lib/model/timetable/timetableProps.dart b/lib/model/timetable/timetableProps.dart index 683c940..aa64492 100644 --- a/lib/model/timetable/timetableProps.dart +++ b/lib/model/timetable/timetableProps.dart @@ -1,6 +1,9 @@ import 'package:intl/intl.dart'; import '../../api/apiResponse.dart'; +import '../../api/mhsl/customTimetableEvent/get/getCustomTimetableEventCache.dart'; +import '../../api/mhsl/customTimetableEvent/get/getCustomTimetableEventParams.dart'; +import '../../api/mhsl/customTimetableEvent/get/getCustomTimetableEventResponse.dart'; import '../../api/webuntis/queries/getHolidays/getHolidaysCache.dart'; import '../../api/webuntis/queries/getHolidays/getHolidaysResponse.dart'; import '../../api/webuntis/queries/getRooms/getRoomsCache.dart'; @@ -10,6 +13,7 @@ import '../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart'; import '../../api/webuntis/queries/getTimetable/getTimetableCache.dart'; import '../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; import '../../api/webuntis/webuntisError.dart'; +import '../accountData.dart'; import '../dataHolder.dart'; class TimetableProps extends DataHolder { @@ -30,17 +34,20 @@ class TimetableProps extends DataHolder { GetHolidaysResponse? _getHolidaysResponse; GetHolidaysResponse get getHolidaysResponse => _getHolidaysResponse!; + GetCustomTimetableEventResponse? _getCustomTimetableEventResponse; + GetCustomTimetableEventResponse get getCustomTimetableEventResponse => _getCustomTimetableEventResponse!; + WebuntisError? error; WebuntisError? get getError => error; bool get hasError => error != null; @override List properties() { - return [_getTimetableResponse, _getRoomsResponse, _getSubjectsResponse, _getHolidaysResponse]; + return [_getTimetableResponse, _getRoomsResponse, _getSubjectsResponse, _getHolidaysResponse, _getCustomTimetableEventResponse]; } @override - void run() { + void run({renew}) { GetTimetableCache( startdate: int.parse(DateFormat("yyyyMMdd").format(startDate)), enddate: int.parse(DateFormat("yyyyMMdd").format(endDate)), @@ -78,6 +85,17 @@ class TimetableProps extends DataHolder { // {"jsonrpc":"2.0","id":"ID","result":[]} // """)); + GetCustomTimetableEventCache( + renew: renew, + GetCustomTimetableEventParams( + AccountData().getUserSecret() + ), + onUpdate: (GetCustomTimetableEventResponse data) => { + _getCustomTimetableEventResponse = data, + notifyListeners(), + } + ); + notifyListeners(); } diff --git a/lib/view/pages/more/feedback/feedbackDialog.dart b/lib/view/pages/more/feedback/feedbackDialog.dart index d6d4ceb..01f8d8a 100644 --- a/lib/view/pages/more/feedback/feedbackDialog.dart +++ b/lib/view/pages/more/feedback/feedbackDialog.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:marianum_mobile/api/mhsl/server/feedback/addFeedback.dart'; -import 'package:marianum_mobile/api/mhsl/server/feedback/addFeedbackParams.dart'; -import 'package:marianum_mobile/model/accountData.dart'; import 'package:package_info/package_info.dart'; +import '../../../../api/mhsl/server/feedback/addFeedback.dart'; +import '../../../../api/mhsl/server/feedback/addFeedbackParams.dart'; +import '../../../../model/accountData.dart'; + class FeedbackDialog extends StatefulWidget { const FeedbackDialog({super.key}); @@ -52,7 +53,7 @@ class _FeedbackDialogState extends State { onPressed: () async { AddFeedback( AddFeedbackParams( - user: AccountData().getUserId(), + user: AccountData().getUserSecret(), feedback: _feedbackInput.text, appVersion: int.parse((await PackageInfo.fromPlatform()).buildNumber) ) diff --git a/lib/view/pages/more/overhang.dart b/lib/view/pages/more/overhang.dart index 2922144..edd6ac3 100644 --- a/lib/view/pages/more/overhang.dart +++ b/lib/view/pages/more/overhang.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:marianum_mobile/view/pages/more/feedback/feedbackDialog.dart'; -import 'package:marianum_mobile/widget/centeredLeading.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent-tab-view.dart'; import '../../../widget/ListItem.dart'; +import '../../../widget/centeredLeading.dart'; import '../../settings/settings.dart'; +import 'feedback/feedbackDialog.dart'; import 'gradeAverages/gradeAverage.dart'; import 'holidays/holidays.dart'; import 'message/message.dart'; diff --git a/lib/view/pages/timetable/addCustomTimetableEventDialog.dart b/lib/view/pages/timetable/addCustomTimetableEventDialog.dart deleted file mode 100644 index 5f1da11..0000000 --- a/lib/view/pages/timetable/addCustomTimetableEventDialog.dart +++ /dev/null @@ -1,151 +0,0 @@ - -import 'dart:developer'; - -import 'package:flutter/material.dart'; -import 'package:jiffy/jiffy.dart'; -import 'package:rrule_generator/rrule_generator.dart'; - -class AddCustomTimetableEventDialog extends StatefulWidget { - const AddCustomTimetableEventDialog({super.key}); - - @override - State createState() => _AddCustomTimetableEventDialogState(); -} - -class _AddCustomTimetableEventDialogState extends State { - DateTime _date = DateTime.now(); - TimeOfDay _starTime = TimeOfDay.now().replacing(minute: 00); - TimeOfDay _endTime = TimeOfDay.now().replacing(minute: 30); - final TextEditingController _eventName = TextEditingController(); - final TextEditingController _eventDescription = TextEditingController(); - String _recurringRule = ""; - - @override - Widget build(BuildContext context) { - return AlertDialog( - insetPadding: const EdgeInsets.all(20), - contentPadding: const EdgeInsets.all(10), - title: const Text('Termin hinzufügen'), - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: TextField( - controller: _eventName, - autofocus: true, - decoration: const InputDecoration( - labelText: "Terminname", - border: OutlineInputBorder() - ), - ), - ), - ListTile( - title: TextField( - controller: _eventDescription, - maxLines: 2, - minLines: 2, - decoration: const InputDecoration( - labelText: "Beschreibung", - border: OutlineInputBorder() - ), - ), - ), - const Divider(), - ListTile( - title: Text(Jiffy.parseFromDateTime(_date).yMMMd), - subtitle: const Text("Datum"), - onTap: () async { - final DateTime? pickedDate = await showDatePicker( - context: context, - initialDate: _date, - firstDate: DateTime.now(), - lastDate: DateTime(2101), - ); - if (pickedDate != null && pickedDate != _date) { - setState(() { - _date = pickedDate; - }); - } - }, - ), - ListTile( - title: Text(_starTime.format(context).toString()), - subtitle: const Text("Beginnend"), - onTap: () async { - final TimeOfDay? pickedTime = await showTimePicker( - context: context, - initialTime: _starTime, - ); - if(pickedTime == null) return; - setState(() { - _starTime = pickedTime; - }); - }, - ), - ListTile( - title: Text(_endTime.format(context).toString()), - subtitle: const Text("Endend"), - onTap: () async { - final TimeOfDay? pickedTime = await showTimePicker( - context: context, - initialTime: _endTime, - ); - if (pickedTime != null && pickedTime != _endTime) { - setState(() { - _endTime = pickedTime; - }); - } - }, - ), - const Divider(), - RRuleGenerator( - config: RRuleGeneratorConfig( - headerEnabled: true, - weekdayBackgroundColor: Theme.of(context).colorScheme.secondary, - weekdaySelectedBackgroundColor: Theme.of(context).primaryColor, - weekdayColor: Colors.black, - ), - initialRRule: _recurringRule, - textDelegate: const GermanRRuleTextDelegate(), - onChange: (String newValue) { - log("Rule: $newValue"); - setState(() { - _recurringRule = newValue; - }); - }, - ) - ], - ), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('Abbrechen'), - ), - TextButton( - onPressed: () { - // Hier kannst du die Eingaben verwenden - String startDate = '${_date.day}/${_date.month}/${_date.year}'; - String startTime = '${_starTime.hour}:${_starTime.minute}'; - String endTime = '${_endTime.hour}:${_endTime.minute}'; - String eventName = _eventName.text; - String recurringFrequency = _recurringRule; - - // Hier kannst du die Daten speichern oder weiterverarbeiten - print('Startdatum: $startDate'); - print('Startzeit: $startTime'); - print('Endzeit: $endTime'); - print('Terminname: $eventName'); - - // Hier könntest du die Termindaten weiterverarbeiten, z.B. speichern und den Dialog schließen - Navigator.of(context).pop(); - }, - child: const Text('Hinzufügen'), - ), - ], - ); - } -} diff --git a/lib/view/pages/timetable/appointmenetComponent.dart b/lib/view/pages/timetable/appointmenetComponent.dart index 5f10906..cddb13e 100644 --- a/lib/view/pages/timetable/appointmenetComponent.dart +++ b/lib/view/pages/timetable/appointmenetComponent.dart @@ -53,7 +53,7 @@ class _AppointmentComponentState extends State { FittedBox( fit: BoxFit.fitWidth, child: Text( - meeting.location ?? "-", + (meeting.location == null || meeting.location!.isEmpty ? " " : meeting.location!), maxLines: 3, overflow: TextOverflow.ellipsis, softWrap: true, diff --git a/lib/view/pages/timetable/appointmentDetails.dart b/lib/view/pages/timetable/appointmentDetails.dart index dbfa099..716ff58 100644 --- a/lib/view/pages/timetable/appointmentDetails.dart +++ b/lib/view/pages/timetable/appointmentDetails.dart @@ -4,15 +4,24 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent-tab-view.dart'; +import 'package:provider/provider.dart'; +import 'package:rrule/rrule.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import '../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart'; +import '../../../api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart'; +import '../../../api/mhsl/customTimetableEvent/remove/removeCustomTimetableEventParams.dart'; import '../../../api/webuntis/queries/getRooms/getRoomsResponse.dart'; import '../../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart'; import '../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; import '../../../model/timetable/timetableProps.dart'; +import '../../../widget/centeredLeading.dart'; +import '../../../widget/confirmDialog.dart'; import '../../../widget/debug/debugTile.dart'; import '../../../widget/unimplementedDialog.dart'; import '../more/roomplan/roomplan.dart'; +import 'arbitraryAppointment.dart'; +import 'customTimetableEventEditDialog.dart'; class AppointmentDetails { static String _getEventPrefix(String? code) { @@ -22,8 +31,32 @@ class AppointmentDetails { } static void show(BuildContext context, TimetableProps webuntisData, Appointment appointment) { - GetTimetableResponseObject timetableData = appointment.id as GetTimetableResponseObject; + (appointment.id as ArbitraryAppointment).handlers( + (webuntis) => _webuntis(context, webuntisData, appointment, webuntis), + (customData) => _custom(context, webuntisData, customData) + ); + } + static void _bottomSheet( + BuildContext context, + Widget Function(BuildContext context) header, + SliverChildListDelegate Function(BuildContext context) body + ) { + showStickyFlexibleBottomSheet( + minHeight: 0, + initHeight: 0.4, + maxHeight: 0.7, + anchors: [0, 0.4, 0.7], + isSafeArea: true, + maxHeaderHeight: 100, + + context: context, + headerBuilder: (context, bottomSheetOffset) => header(context), + bodyBuilder: (context, bottomSheetOffset) => body(context) + ); + } + + static void _webuntis(BuildContext context, TimetableProps webuntisData, Appointment appointment, GetTimetableResponseObject timetableData) { GetSubjectsResponseObject subject; GetRoomsResponseObject room; @@ -39,68 +72,155 @@ class AppointmentDetails { room = GetRoomsResponseObject(0, "?", "Unbekannt", true, "?"); } - showStickyFlexibleBottomSheet( - minHeight: 0, - initHeight: 0.4, - maxHeight: 0.7, - anchors: [0, 0.4, 0.7], - isSafeArea: true, - - maxHeaderHeight: 150, - - context: context, - headerBuilder: (context, bottomSheetOffset) => Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text("${_getEventPrefix(timetableData.code)}${subject.alternateName} - (${subject.longName})", style: const TextStyle(fontSize: 25), overflow: TextOverflow.ellipsis), - Text("${Jiffy.parseFromDateTime(appointment.startTime).format(pattern: "HH:mm")} - ${Jiffy.parseFromDateTime(appointment.endTime).format(pattern: "HH:mm")}", style: const TextStyle(fontSize: 15)), - ], - ), - ), - bodyBuilder: (context, bottomSheetOffset) => SliverChildListDelegate( - [ - const Divider(), - ListTile( - leading: const Icon(Icons.notifications_active), - title: Text("Status: ${timetableData.code != null ? "Geändert" : "Regulär"}"), + _bottomSheet( + context, + (context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("${_getEventPrefix(timetableData.code)}${subject.alternateName}", textAlign: TextAlign.center, style: const TextStyle(fontSize: 25), overflow: TextOverflow.ellipsis), + Text(subject.longName), + Text("${Jiffy.parseFromDateTime(appointment.startTime).format(pattern: "HH:mm")} - ${Jiffy.parseFromDateTime(appointment.endTime).format(pattern: "HH:mm")}", style: const TextStyle(fontSize: 15)), + ], ), - ListTile( - leading: const Icon(Icons.room), - title: Text("Raum: ${room.name} (${room.longName})"), - trailing: IconButton( - icon: const Icon(Icons.house_outlined), - onPressed: () { - pushNewScreen(context, withNavBar: false, screen: const Roomplan()); - }, + ); + }, + + (context) { + return SliverChildListDelegate( + [ + const Divider(), + ListTile( + leading: const Icon(Icons.notifications_active), + title: Text("Status: ${timetableData.code != null ? "Geändert" : "Regulär"}"), ), - ), - ListTile( - 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("?"), - trailing: Visibility( - visible: !kReleaseMode, - child: IconButton( - icon: const Icon(Icons.textsms_outlined), + ListTile( + leading: const Icon(Icons.room), + title: Text("Raum: ${room.name} (${room.longName})"), + trailing: IconButton( + icon: const Icon(Icons.house_outlined), onPressed: () { - UnimplementedDialog.show(context); + pushNewScreen(context, withNavBar: false, screen: const Roomplan()); }, ), ), + ListTile( + 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("?"), + trailing: Visibility( + visible: !kReleaseMode, + child: IconButton( + icon: const Icon(Icons.textsms_outlined), + onPressed: () { + UnimplementedDialog.show(context); + }, + ), + ), + ), + ListTile( + leading: const Icon(Icons.abc), + title: Text("Typ: ${timetableData.activityType}"), + ), + ListTile( + leading: const Icon(Icons.people), + title: Text("Klasse(n): ${timetableData.kl.map((e) => e.name).join(", ")}"), + ), + DebugTile(context).jsonData(timetableData.toJson()), + ], + ); + } + ); + } + + static void _custom(BuildContext context, TimetableProps webuntisData, CustomTimetableEvent appointment) { + _bottomSheet( + context, + (context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(appointment.title, style: const TextStyle(fontSize: 25, overflow: TextOverflow.ellipsis)), + Text("${Jiffy.parseFromDateTime(appointment.startDate).format(pattern: "HH:mm")} - ${Jiffy.parseFromDateTime(appointment.endDate).format(pattern: "HH:mm")}", style: const TextStyle(fontSize: 15)), + ], ), - ListTile( - leading: const Icon(Icons.abc), - title: Text("Typ: ${timetableData.activityType}"), - ), - ListTile( - leading: const Icon(Icons.people), - title: Text("Klasse(n): ${timetableData.kl.map((e) => e.name).join(", ")}"), - ), - DebugTile(context).jsonData(timetableData.toJson()), - ], - ), + ); + }, + (context) { + return SliverChildListDelegate( + [ + const Divider(), + Center( + child: Wrap( + children: [ + TextButton.icon( + onPressed: () { + Navigator.of(context).pop(); + showDialog( + context: context, + builder: (context) => CustomTimetableEventEditDialog(existingEvent: appointment), + ); + }, + label: const Text("Bearbeiten"), + icon: const Icon(Icons.edit_outlined), + ), + TextButton.icon( + onPressed: () { + ConfirmDialog( + title: "Termin löschen", + content: "Der ${appointment.rrule.isEmpty ? "Termin" : "Serientermin"} wird unwiederruflich gelöscht.", + confirmButton: "Löschen", + onConfirm: () { + RemoveCustomTimetableEvent( + RemoveCustomTimetableEventParams( + appointment.id + ) + ).run().then((value) { + Navigator.of(context).pop(); + Provider.of(context, listen: false).run(renew: true); + }); + }, + ).asDialog(context); + }, + label: const Text("Löschen"), + icon: const Icon(Icons.delete_outline), + ), + ], + ), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.info_outline), + title: Text(appointment.description.isEmpty ? "Keine Beschreibung" : appointment.description), + ), + ListTile( + leading: const CenteredLeading(Icon(Icons.repeat_outlined)), + title: Text("Serie: ${appointment.rrule.isNotEmpty ? "Wiederholend" : "Einmailg"}"), + 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("..."); + RecurrenceRule rrule = RecurrenceRule.fromString(appointment.rrule); + if(!rrule.canFullyConvertToText) return const Text("Keine genauere Angabe möglich."); + return Text(rrule.toText(l10n: snapshot.data!)); + }, + ) + ), + DebugTile(context).child( + ListTile( + leading: const CenteredLeading(Icon(Icons.rule)), + title: const Text("RRule"), + subtitle: Text(appointment.rrule), + ) + ), + DebugTile(context).jsonData(appointment.toJson()), + ] + ); + } ); } } \ No newline at end of file diff --git a/lib/view/pages/timetable/arbitraryAppointment.dart b/lib/view/pages/timetable/arbitraryAppointment.dart new file mode 100644 index 0000000..62f60a8 --- /dev/null +++ b/lib/view/pages/timetable/arbitraryAppointment.dart @@ -0,0 +1,23 @@ + +import '../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart'; +import '../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; + +class ArbitraryAppointment { + GetTimetableResponseObject? webuntis; + CustomTimetableEvent? custom; + + ArbitraryAppointment({this.webuntis, this.custom}) {} + + bool hasWebuntis() { + return webuntis != null; + } + + bool hasCustom() { + return custom != null; + } + + void handlers(void Function(GetTimetableResponseObject webuntisData) webuntis, void Function(CustomTimetableEvent customData) custom) { + if(hasWebuntis()) webuntis(this.webuntis!); + if(hasCustom()) custom(this.custom!); + } +} \ No newline at end of file diff --git a/lib/view/pages/timetable/customTimetableEventEditDialog.dart b/lib/view/pages/timetable/customTimetableEventEditDialog.dart new file mode 100644 index 0000000..9e36b70 --- /dev/null +++ b/lib/view/pages/timetable/customTimetableEventEditDialog.dart @@ -0,0 +1,199 @@ + +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:marianum_mobile/extensions/dateTime.dart'; +import 'package:provider/provider.dart'; +import 'package:rrule_generator/rrule_generator.dart'; +import 'package:time_range_picker/time_range_picker.dart'; + +import '../../../api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart'; +import '../../../api/mhsl/customTimetableEvent/add/addCustomTimetableEventParams.dart'; +import '../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart'; +import '../../../api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart'; +import '../../../api/mhsl/customTimetableEvent/update/updateCustomTimetableEventParams.dart'; +import '../../../model/accountData.dart'; +import '../../../model/timetable/timetableProps.dart'; +import '../../../widget/infoDialog.dart'; + +class CustomTimetableEventEditDialog extends StatefulWidget { + final CustomTimetableEvent? existingEvent; + const CustomTimetableEventEditDialog({this.existingEvent, super.key}); + + @override + State createState() => _AddCustomTimetableEventDialogState(); +} + +class _AddCustomTimetableEventDialogState extends State { + late DateTime _date = widget.existingEvent?.startDate ?? DateTime.now(); + late TimeOfDay _startTime = widget.existingEvent?.startDate.toTimeOfDay() ?? const TimeOfDay(hour: 08, minute: 00); + 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 bool isEditingExisting = widget.existingEvent != null; + + bool validate() { + if(_eventName.text.isEmpty) return false; + return true; + } + + void fetchTimetable() { + Provider.of(context, listen: false).run(renew: true); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + insetPadding: const EdgeInsets.all(20), + contentPadding: const EdgeInsets.all(10), + title: const Text('Termin hinzufügen'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: TextField( + controller: _eventName, + autofocus: true, + decoration: const InputDecoration( + labelText: "Terminname", + border: OutlineInputBorder() + ), + ), + ), + ListTile( + title: TextField( + controller: _eventDescription, + maxLines: 2, + minLines: 2, + decoration: const InputDecoration( + labelText: "Beschreibung", + border: OutlineInputBorder() + ), + ), + ), + const Divider(), + ListTile( + title: Text(Jiffy.parseFromDateTime(_date).yMMMd), + subtitle: const Text("Datum"), + onTap: () async { + final DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: _date, + firstDate: DateTime.now().subtract(const Duration(days: 30)), + lastDate: DateTime.now().add(const Duration(days: 30)), + ); + if (pickedDate != null && pickedDate != _date) { + setState(() { + _date = pickedDate; + }); + } + }, + ), + ListTile( + title: Text("${_startTime.format(context).toString()} - ${_endTime.format(context).toString()}"), + subtitle: const Text("Zeitraum"), + onTap: () async { + TimeRange timeRange = await showTimeRangePicker( + context: context, + start: _startTime, + end: _endTime, + disabledTime: TimeRange(startTime: const TimeOfDay(hour: 16, minute: 30), endTime: const TimeOfDay(hour: 08, minute: 00)), + disabledColor: Colors.grey, + paintingStyle: PaintingStyle.fill, + interval: const Duration(minutes: 5), + fromText: "Beginnend", + toText: "Endend", + strokeColor: Theme.of(context).colorScheme.secondary, + minDuration: const Duration(minutes: 45), + selectedColor: Theme.of(context).primaryColor, + ticks: 24, + ); + + setState(() { + _startTime = timeRange.startTime; + _endTime = timeRange.endTime; + }); + }, + ), + const Divider(), + RRuleGenerator( + config: RRuleGeneratorConfig( + headerEnabled: true, + weekdayBackgroundColor: Theme.of(context).colorScheme.secondary, + weekdaySelectedBackgroundColor: Theme.of(context).primaryColor, + weekdayColor: Colors.black, + ), + initialRRule: _recurringRule, + textDelegate: const GermanRRuleTextDelegate(), + onChange: (String newValue) { + log("Rule: $newValue"); + setState(() { + _recurringRule = newValue; + }); + }, + ) + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Abbrechen'), + ), + TextButton( + onPressed: () { + if(!validate()) return; + + CustomTimetableEvent editedEvent = CustomTimetableEvent( + id: "", + title: _eventName.text, + description: _eventDescription.text, + startDate: _date.withTime(_startTime), + endDate: _date.withTime(_endTime), + rrule: _recurringRule, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); + + if(!isEditingExisting) { + AddCustomTimetableEvent( + AddCustomTimetableEventParams( + AccountData().getUserSecret(), + editedEvent + ) + ).run().then((value) { + Navigator.of(context).pop(); + fetchTimetable(); + }) + .catchError((error, stack) { + InfoDialog.show(context, error.toString()); + }); + } else { + UpdateCustomTimetableEvent( + UpdateCustomTimetableEventParams( + widget.existingEvent?.id ?? "", + editedEvent + ) + ).run().then((value) { + Navigator.of(context).pop(); + fetchTimetable(); + }) + .catchError((error, stack) { + InfoDialog.show(context, error.toString()); + }); + } + + + }, + child: Text(isEditingExisting ? "Speichern" : "Erstellen"), + ), + ], + ); + } +} diff --git a/lib/view/pages/timetable/timetable.dart b/lib/view/pages/timetable/timetable.dart index 9996475..7f44aaf 100644 --- a/lib/view/pages/timetable/timetable.dart +++ b/lib/view/pages/timetable/timetable.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:marianum_mobile/extensions/dateTime.dart'; -import 'package:marianum_mobile/view/pages/timetable/addCustomTimetableEventDialog.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; @@ -17,6 +16,8 @@ import '../../../widget/loadingSpinner.dart'; import '../../../widget/placeholderView.dart'; import 'appointmenetComponent.dart'; import 'appointmentDetails.dart'; +import 'arbitraryAppointment.dart'; +import 'customTimetableEventEditDialog.dart'; import 'timeRegionComponent.dart'; import 'timetableEvents.dart'; @@ -70,7 +71,7 @@ class _TimetableState extends State { onPressed: () { showDialog( context: context, - builder: (context) => const AddCustomTimetableEventDialog(), + builder: (context) => const CustomTimetableEventEditDialog(), barrierDismissible: false, ); }, @@ -232,7 +233,7 @@ class _TimetableState extends State { DateTime startTime = _parseWebuntisTimestamp(element.date, element.startTime); DateTime endTime = _parseWebuntisTimestamp(element.date, element.endTime); return Appointment( - id: element, + id: ArbitraryAppointment(webuntis: element), startTime: startTime, endTime: endTime, subject: subjects.result.firstWhere((subject) => subject.id == element.su[0].id).name, @@ -246,7 +247,7 @@ class _TimetableState extends State { } catch(e) { DateTime endTime = _parseWebuntisTimestamp(element.date, element.endTime); return Appointment( - id: element, + id: ArbitraryAppointment(webuntis: element), startTime: _parseWebuntisTimestamp(element.date, element.startTime), endTime: endTime, subject: "Änderung", @@ -259,6 +260,20 @@ class _TimetableState extends State { } }).toList(); + appointments.addAll(data.getCustomTimetableEventResponse.events.map((customEvent) { + return Appointment( + id: ArbitraryAppointment(custom: customEvent), + startTime: customEvent.startDate, + endTime: customEvent.endDate, + location: customEvent.description, + subject: customEvent.title, + recurrenceRule: customEvent.rrule, + color: Colors.deepOrange, + startTimeZone: '', + endTimeZone: '', + ); + })); + return TimetableEvents(appointments); } @@ -288,8 +303,10 @@ class _TimetableState extends State { } bool _isCrossedOut(CalendarAppointmentDetails calendarEntry) { - GetTimetableResponseObject webuntisElement = (calendarEntry.appointments.first.id as GetTimetableResponseObject); - - return webuntisElement.code == "cancelled"; + ArbitraryAppointment appointment = calendarEntry.appointments.first.id as ArbitraryAppointment; + if(appointment.hasWebuntis()) { + return appointment.webuntis!.code == "cancelled"; + } + return false; } } diff --git a/lib/widget/infoDialog.dart b/lib/widget/infoDialog.dart new file mode 100644 index 0000000..4a55024 --- /dev/null +++ b/lib/widget/infoDialog.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +class InfoDialog { + static show(BuildContext context, String info) { + showDialog(context: context, builder: (context) => AlertDialog(content: Text(info))); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index fb41e3d..1b1e965 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -94,6 +94,8 @@ dependencies: qr_flutter: ^4.1.0 easy_debounce: ^2.0.3 rrule_generator: ^0.5.6 + rrule: ^0.2.16 + time_range_picker: ^2.2.0 dev_dependencies: flutter_test: From 0f6c75690d48b04ba9c20c7da6ce0f81f22e7a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Mon, 12 Feb 2024 01:08:57 +0100 Subject: [PATCH 4/4] Custom Timetable code style corrections --- .../mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart | 2 -- lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart | 1 - .../customTimetableEvent/remove/removeCustomTimetableEvent.dart | 1 - .../customTimetableEvent/update/updateCustomTimetableEvent.dart | 1 - 4 files changed, 5 deletions(-) diff --git a/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart index 00a99f2..25bb7db 100644 --- a/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart +++ b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:developer'; import 'package:http/http.dart'; import 'package:http/http.dart' as http; @@ -18,7 +17,6 @@ class AddCustomTimetableEvent extends MhslApi { @override Future? request(Uri uri) { String body = jsonEncode(params.toJson()); - log(body); return http.post(uri, body: body); } } \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart index 1fe606b..c6b668a 100644 --- a/lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart +++ b/lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart @@ -22,7 +22,6 @@ class CustomTimetableEvent { CustomTimetableEvent({required this.id, required this.title, required this.description, required this.startDate, required this.endDate, required this.rrule, required this.createdAt, required this.updatedAt}); - factory CustomTimetableEvent.fromJson(Map json) => _$CustomTimetableEventFromJson(json); Map toJson() => _$CustomTimetableEventToJson(this); } \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart index 62557fe..7bfc00c 100644 --- a/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart +++ b/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart @@ -18,5 +18,4 @@ class RemoveCustomTimetableEvent extends MhslApi { Future? request(Uri uri) { return http.delete(uri, body: jsonEncode(params.toJson())); } - } \ No newline at end of file diff --git a/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart index f657587..0dd8d94 100644 --- a/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart +++ b/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart @@ -18,5 +18,4 @@ class UpdateCustomTimetableEvent extends MhslApi { Future? request(Uri uri) { return http.patch(uri, body: jsonEncode(params.toJson())); } - } \ No newline at end of file