From 7a791ef21fb63c75bd7c5606199dfcce63dd5c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Mon, 12 Jun 2023 17:17:22 +0200 Subject: [PATCH] Added holidays viewer --- lib/api/holidays/getHolidays.dart | 13 ++ lib/api/holidays/getHolidaysCache.dart | 22 +++ lib/api/holidays/getHolidaysResponse.dart | 38 +++++ lib/api/holidays/getHolidaysResponse.g.dart | 43 ++++++ .../webuntis/queries/getRooms/getRooms.dart | 10 +- lib/main.dart | 2 + lib/model/holidays/holidaysProps.dart | 26 ++++ lib/storage/base/settings.dart | 3 + lib/storage/base/settings.g.dart | 3 + lib/storage/base/settingsProvider.dart | 5 + lib/storage/holidays/holidaysSettings.dart | 14 ++ lib/storage/holidays/holidaysSettings.g.dart | 19 +++ lib/view/pages/holidays/holidays.dart | 140 ++++++++++++++++++ lib/view/pages/more/overhang.dart | 3 +- 14 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 lib/api/holidays/getHolidays.dart create mode 100644 lib/api/holidays/getHolidaysCache.dart create mode 100644 lib/api/holidays/getHolidaysResponse.dart create mode 100644 lib/api/holidays/getHolidaysResponse.g.dart create mode 100644 lib/model/holidays/holidaysProps.dart create mode 100644 lib/storage/holidays/holidaysSettings.dart create mode 100644 lib/storage/holidays/holidaysSettings.g.dart create mode 100644 lib/view/pages/holidays/holidays.dart diff --git a/lib/api/holidays/getHolidays.dart b/lib/api/holidays/getHolidays.dart new file mode 100644 index 0000000..1aff999 --- /dev/null +++ b/lib/api/holidays/getHolidays.dart @@ -0,0 +1,13 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import 'getHolidaysResponse.dart'; + +class GetHolidays { + Future query() async { + String response = (await http.get(Uri.parse("https://ferien-api.de/api/v1/holidays/HE"))).body; + List parsedListJson = jsonDecode(response); + return GetHolidaysResponse(List.from(parsedListJson.map((dynamic i) => GetHolidaysResponseObject.fromJson(i)))); + } +} \ No newline at end of file diff --git a/lib/api/holidays/getHolidaysCache.dart b/lib/api/holidays/getHolidaysCache.dart new file mode 100644 index 0000000..fe7abb7 --- /dev/null +++ b/lib/api/holidays/getHolidaysCache.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; + +import '../requestCache.dart'; +import 'getHolidays.dart'; +import 'getHolidaysResponse.dart'; + +class GetHolidaysCache extends RequestCache { + GetHolidaysCache({onUpdate, renew}) : super(RequestCache.cacheDay, onUpdate, renew: renew) { + start("MarianumMobile", "state-holidays"); + } + + @override + GetHolidaysResponse onLocalData(String json) { + List parsedListJson = jsonDecode(json)['data']; + return GetHolidaysResponse(List.from(parsedListJson.map((dynamic i) => GetHolidaysResponseObject.fromJson(i)))); + } + + @override + Future onLoad() { + return GetHolidays().query(); + } +} \ No newline at end of file diff --git a/lib/api/holidays/getHolidaysResponse.dart b/lib/api/holidays/getHolidaysResponse.dart new file mode 100644 index 0000000..2ce1a95 --- /dev/null +++ b/lib/api/holidays/getHolidaysResponse.dart @@ -0,0 +1,38 @@ + +import 'package:json_annotation/json_annotation.dart'; + +import '../apiResponse.dart'; + +part 'getHolidaysResponse.g.dart'; + +@JsonSerializable(explicitToJson: true) +class GetHolidaysResponse extends ApiResponse { + List data; + + GetHolidaysResponse(this.data); + + factory GetHolidaysResponse.fromJson(Map json) => _$GetHolidaysResponseFromJson(json); + Map toJson() => _$GetHolidaysResponseToJson(this); +} + +@JsonSerializable() +class GetHolidaysResponseObject { + String start; + String end; + int year; + String stateCode; + String name; + String slug; + + GetHolidaysResponseObject({ + required this.start, + required this.end, + required this.year, + required this.stateCode, + required this.name, + required this.slug + }); + + factory GetHolidaysResponseObject.fromJson(Map json) => _$GetHolidaysResponseObjectFromJson(json); + Map toJson() => _$GetHolidaysResponseObjectToJson(this); +} \ No newline at end of file diff --git a/lib/api/holidays/getHolidaysResponse.g.dart b/lib/api/holidays/getHolidaysResponse.g.dart new file mode 100644 index 0000000..2960c49 --- /dev/null +++ b/lib/api/holidays/getHolidaysResponse.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getHolidaysResponse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetHolidaysResponse _$GetHolidaysResponseFromJson(Map json) => + GetHolidaysResponse( + (json['data'] as List) + .map((e) => + GetHolidaysResponseObject.fromJson(e as Map)) + .toList(), + ); + +Map _$GetHolidaysResponseToJson( + GetHolidaysResponse instance) => + { + 'data': instance.data.map((e) => e.toJson()).toList(), + }; + +GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson( + Map json) => + GetHolidaysResponseObject( + start: json['start'] as String, + end: json['end'] as String, + year: json['year'] as int, + stateCode: json['stateCode'] as String, + name: json['name'] as String, + slug: json['slug'] as String, + ); + +Map _$GetHolidaysResponseObjectToJson( + GetHolidaysResponseObject instance) => + { + 'start': instance.start, + 'end': instance.end, + 'year': instance.year, + 'stateCode': instance.stateCode, + 'name': instance.name, + 'slug': instance.slug, + }; diff --git a/lib/api/webuntis/queries/getRooms/getRooms.dart b/lib/api/webuntis/queries/getRooms/getRooms.dart index 4b56f24..698745f 100644 --- a/lib/api/webuntis/queries/getRooms/getRooms.dart +++ b/lib/api/webuntis/queries/getRooms/getRooms.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import '../../webuntisApi.dart'; import 'getRoomsResponse.dart'; @@ -9,7 +10,14 @@ class GetRooms extends WebuntisApi { @override Future run() async { String rawAnswer = await query(this); - return finalize(GetRoomsResponse.fromJson(jsonDecode(rawAnswer))); + try { + return finalize(GetRoomsResponse.fromJson(jsonDecode(rawAnswer))); + } catch(e, trace) { + log(trace.toString()); + log("Failed to parse getRoom data with server response: $rawAnswer"); + } + + throw Exception("Failed to parse getRoom server response"); } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 6f3c23c..5b9bd46 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'model/accountModel.dart'; import 'model/chatList/chatListProps.dart'; import 'model/chatList/chatProps.dart'; import 'model/files/filesProps.dart'; +import 'model/holidays/holidaysProps.dart'; import 'model/message/messageProps.dart'; import 'model/timetable/timetableProps.dart'; import 'storage/base/settingsProvider.dart'; @@ -42,6 +43,7 @@ Future main() async { ChangeNotifierProvider(create: (context) => FilesProps()), ChangeNotifierProvider(create: (context) => MessageProps()), + ChangeNotifierProvider(create: (context) => HolidaysProps()), ], child: const Main(), ) diff --git a/lib/model/holidays/holidaysProps.dart b/lib/model/holidays/holidaysProps.dart new file mode 100644 index 0000000..c46820f --- /dev/null +++ b/lib/model/holidays/holidaysProps.dart @@ -0,0 +1,26 @@ + +import '../../api/apiResponse.dart'; +import '../../api/holidays/getHolidaysCache.dart'; +import '../../api/holidays/getHolidaysResponse.dart'; +import '../dataHolder.dart'; + + +class HolidaysProps extends DataHolder { + GetHolidaysResponse? _getHolidaysResponse; + GetHolidaysResponse get getHolidaysResponse => _getHolidaysResponse!; + + @override + List properties() { + return [_getHolidaysResponse]; + } + + @override + void run() { + GetHolidaysCache( + onUpdate: (GetHolidaysResponse data) => { + _getHolidaysResponse = data, + notifyListeners(), + }, + ); + } +} \ No newline at end of file diff --git a/lib/storage/base/settings.dart b/lib/storage/base/settings.dart index 93f5b0f..45886eb 100644 --- a/lib/storage/base/settings.dart +++ b/lib/storage/base/settings.dart @@ -3,6 +3,7 @@ import 'package:json_annotation/json_annotation.dart'; import '../file/fileSettings.dart'; import '../gradeAverages/gradeAveragesSettings.dart'; +import '../holidays/holidaysSettings.dart'; import '../talk/talkSettings.dart'; import '../timetable/timetableSettings.dart'; @@ -21,6 +22,7 @@ class Settings { TimetableSettings timetableSettings; TalkSettings talkSettings; FileSettings fileSettings; + HolidaysSettings holidaysSettings; Settings({ required this.appTheme, @@ -29,6 +31,7 @@ class Settings { required this.timetableSettings, required this.talkSettings, required this.fileSettings, + required this.holidaysSettings, }); static String _themeToJson(ThemeMode m) => m.name; diff --git a/lib/storage/base/settings.g.dart b/lib/storage/base/settings.g.dart index 235a0fd..1325069 100644 --- a/lib/storage/base/settings.g.dart +++ b/lib/storage/base/settings.g.dart @@ -17,6 +17,8 @@ Settings _$SettingsFromJson(Map json) => Settings( TalkSettings.fromJson(json['talkSettings'] as Map), fileSettings: FileSettings.fromJson(json['fileSettings'] as Map), + holidaysSettings: HolidaysSettings.fromJson( + json['holidaysSettings'] as Map), ); Map _$SettingsToJson(Settings instance) => { @@ -26,4 +28,5 @@ Map _$SettingsToJson(Settings instance) => { 'timetableSettings': instance.timetableSettings.toJson(), 'talkSettings': instance.talkSettings.toJson(), 'fileSettings': instance.fileSettings.toJson(), + 'holidaysSettings': instance.holidaysSettings.toJson(), }; diff --git a/lib/storage/base/settingsProvider.dart b/lib/storage/base/settingsProvider.dart index c347a00..6b40a00 100644 --- a/lib/storage/base/settingsProvider.dart +++ b/lib/storage/base/settingsProvider.dart @@ -5,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../file/fileSettings.dart'; import '../gradeAverages/gradeAveragesSettings.dart'; +import '../holidays/holidaysSettings.dart'; import '../talk/talkSettings.dart'; import '../timetable/timetableSettings.dart'; import 'settings.dart'; @@ -64,6 +65,10 @@ class SettingsProvider extends ChangeNotifier { fileSettings: FileSettings( sortFoldersToTop: true, ), + holidaysSettings: HolidaysSettings( + dismissedDisclaimer: false, + showPastEvents: false, + ), ); } } \ No newline at end of file diff --git a/lib/storage/holidays/holidaysSettings.dart b/lib/storage/holidays/holidaysSettings.dart new file mode 100644 index 0000000..af6912b --- /dev/null +++ b/lib/storage/holidays/holidaysSettings.dart @@ -0,0 +1,14 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'holidaysSettings.g.dart'; + +@JsonSerializable() +class HolidaysSettings { + bool dismissedDisclaimer; + bool showPastEvents; + + HolidaysSettings({required this.dismissedDisclaimer, required this.showPastEvents}); + + factory HolidaysSettings.fromJson(Map json) => _$HolidaysSettingsFromJson(json); + Map toJson() => _$HolidaysSettingsToJson(this); +} \ No newline at end of file diff --git a/lib/storage/holidays/holidaysSettings.g.dart b/lib/storage/holidays/holidaysSettings.g.dart new file mode 100644 index 0000000..f229202 --- /dev/null +++ b/lib/storage/holidays/holidaysSettings.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'holidaysSettings.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +HolidaysSettings _$HolidaysSettingsFromJson(Map json) => + HolidaysSettings( + dismissedDisclaimer: json['dismissedDisclaimer'] as bool, + showPastEvents: json['showPastEvents'] as bool, + ); + +Map _$HolidaysSettingsToJson(HolidaysSettings instance) => + { + 'dismissedDisclaimer': instance.dismissedDisclaimer, + 'showPastEvents': instance.showPastEvents, + }; diff --git a/lib/view/pages/holidays/holidays.dart b/lib/view/pages/holidays/holidays.dart new file mode 100644 index 0000000..602e270 --- /dev/null +++ b/lib/view/pages/holidays/holidays.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:provider/provider.dart'; + +import '../../../api/holidays/getHolidaysResponse.dart'; +import '../../../model/holidays/holidaysProps.dart'; +import '../../../storage/base/settingsProvider.dart'; +import '../../../widget/confirmDialog.dart'; +import '../../../widget/loadingSpinner.dart'; + +class Holidays extends StatefulWidget { + const Holidays({super.key}); + + @override + State createState() => _HolidaysState(); +} + +class _HolidaysState extends State { + late SettingsProvider settings = Provider.of(context, listen: false); + late bool showPastEvents = settings.val().holidaysSettings.showPastEvents; + + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + Provider.of(context, listen: false).run(); + if(!settings.val().holidaysSettings.dismissedDisclaimer) showDisclaimer(); + }); + + super.initState(); + } + + String parseString(String enDate) { + return Jiffy.parse(enDate).format(pattern: "dd.MM.yyyy"); + } + + void showDisclaimer() { + showDialog(context: context, builder: (context) { + return AlertDialog( + title: const Text("Richtigkeit und Bereitstellung der Daten"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text("" + "Sämtliche Datumsangaben sind ohne Gewähr.\n" + "Ich übernehme weder Verantwortung für die Richtigkeit der Daten noch hafte ich für wirtschaftliche Schäden die aus der Verwendung dieser Daten entstehen können.\n\n" + "Die Daten stammen von https://ferien-api.de/"), + const SizedBox(height: 30), + ListTile( + title: const Text("Diese Meldung nicht mehr anzeigen"), + trailing: Checkbox( + value: settings.val().holidaysSettings.dismissedDisclaimer, + onChanged: (value) => settings.val(write: true).holidaysSettings.dismissedDisclaimer = value!, + ), + ) + ], + ), + actions: [ + TextButton(child: const Text("ferien-api.de öffnen"), onPressed: () => ConfirmDialog.openBrowser(context, "https://ferien-api.de/")), + TextButton(child: const Text("Schlißen"), onPressed: () => Navigator.of(context).pop()), + ], + ); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Schulferien in Hessen"), + actions: [ + IconButton( + icon: const Icon(Icons.warning_amber_outlined), + onPressed: () => showDisclaimer(), + ), + PopupMenuButton( + initialValue: settings.val().holidaysSettings.showPastEvents, + icon: const Icon(Icons.manage_history_outlined), + itemBuilder: (context) { + return [true, false].map((e) => PopupMenuItem( + value: e, + enabled: e != showPastEvents, + child: Row( + children: [ + Icon(e ? Icons.history_outlined : Icons.history_toggle_off_outlined, color: Theme.of(context).colorScheme.onSurface), + const SizedBox(width: 15), + Text(e ? "Alle anzeigen" : "Nur zukünftige anzeigen") + ], + ) + )).toList(); + }, + onSelected: (e) { + setState(() { + showPastEvents = e; + settings.val(write: true).holidaysSettings.showPastEvents = e; + }); + }, + ), + ], + ), + body: Consumer(builder: (context, value, child) { + if(value.primaryLoading()) return const LoadingSpinner(); + + List holidays = value.getHolidaysResponse.data; + if(!showPastEvents) holidays = holidays.where((element) => DateTime.parse(element.end).isAfter(DateTime.now())).toList(); + + return ListView.builder( + itemCount: holidays.length, + itemBuilder: (context, index) { + GetHolidaysResponseObject holiday = holidays[index]; + return ListTile( + leading: const Icon(Icons.calendar_month), + title: Text("${parseString(holiday.start)} bis ${parseString(holiday.end)}"), + subtitle: Text("${holiday.name} - (${Jiffy.parse(holiday.start).fromNow()})"), + onTap: () => showDialog(context: context, builder: (context) => SimpleDialog( + title: Text("Ferien in Hessen | ${holiday.year}"), + children: [ + ListTile( + leading: const Icon(Icons.signpost_outlined), + title: Text(holiday.name), + subtitle: Text(holiday.slug), + ), + ListTile( + leading: const Icon(Icons.event), + title: Text("beginnend ${parseString(holiday.start)}"), + ), + ListTile( + leading: const Icon(Icons.event_busy_outlined), + title: Text("endend ${parseString(holiday.end)}"), + ), + ], + )), + trailing: const Icon(Icons.arrow_right), + ); + }, + ); + }, + ) + ); + } +} diff --git a/lib/view/pages/more/overhang.dart b/lib/view/pages/more/overhang.dart index af5d7ed..aa3c0a6 100644 --- a/lib/view/pages/more/overhang.dart +++ b/lib/view/pages/more/overhang.dart @@ -5,6 +5,7 @@ import 'package:persistent_bottom_nav_bar/persistent_tab_view.dart'; import '../../../widget/ListItem.dart'; import '../../settings/settings.dart'; +import '../holidays/holidays.dart'; import 'countdown/countdown.dart'; import 'gradeAverages/gradeAverage.dart'; import 'message/message.dart'; @@ -27,7 +28,7 @@ class Overhang extends StatelessWidget { ListItemNavigator(icon: Icons.newspaper, text: "Marianum Message", target: Message()), ListItemNavigator(icon: Icons.room, text: "Raumplan", target: Roomplan()), ListItemNavigator(icon: Icons.calculate, text: "Notendurschnitts rechner", target: GradeAverage()), - if(!kReleaseMode) ListItemNavigator(icon: Icons.calendar_month, text: "Schulferien", target: Roomplan()), + ListItemNavigator(icon: Icons.calendar_month, text: "Schulferien", target: Holidays()), if(!kReleaseMode) ListItemNavigator(icon: Icons.timer, text: "Countdown", target: Countdown()), ], ),