diff --git a/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart new file mode 100644 index 0000000..25bb7db --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/add/addCustomTimetableEvent.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; + +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()); + 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..c6b668a --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/customTimetableEvent.dart @@ -0,0 +1,27 @@ +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..7bfc00c --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/remove/removeCustomTimetableEvent.dart @@ -0,0 +1,21 @@ +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..0dd8d94 --- /dev/null +++ b/lib/api/mhsl/customTimetableEvent/update/updateCustomTimetableEvent.dart @@ -0,0 +1,21 @@ +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/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 9c4b086..7f44aaf 100644 --- a/lib/view/pages/timetable/timetable.dart +++ b/lib/view/pages/timetable/timetable.dart @@ -16,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'; @@ -64,6 +66,16 @@ class _TimetableState extends State { controller.displayDate = DateTime.now().add(const Duration(days: 2)); } ), + IconButton( + icon: const Icon(Icons.add_outlined), + onPressed: () { + showDialog( + context: context, + builder: (context) => const CustomTimetableEventEditDialog(), + barrierDismissible: false, + ); + }, + ), ], ), body: Consumer( @@ -221,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, @@ -235,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", @@ -248,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); } @@ -277,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 dd5135f..1b1e965 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -93,6 +93,9 @@ dependencies: flutter_app_badger: ^1.5.0 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: