Added holidays viewer

This commit is contained in:
Elias Müller 2023-06-12 17:17:22 +02:00
parent 38c0cfba9c
commit 7a791ef21f
14 changed files with 339 additions and 2 deletions

@ -0,0 +1,13 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'getHolidaysResponse.dart';
class GetHolidays {
Future<GetHolidaysResponse> query() async {
String response = (await http.get(Uri.parse("https://ferien-api.de/api/v1/holidays/HE"))).body;
List<dynamic> parsedListJson = jsonDecode(response);
return GetHolidaysResponse(List<GetHolidaysResponseObject>.from(parsedListJson.map<GetHolidaysResponseObject>((dynamic i) => GetHolidaysResponseObject.fromJson(i))));
}
}

@ -0,0 +1,22 @@
import 'dart:convert';
import '../requestCache.dart';
import 'getHolidays.dart';
import 'getHolidaysResponse.dart';
class GetHolidaysCache extends RequestCache<GetHolidaysResponse> {
GetHolidaysCache({onUpdate, renew}) : super(RequestCache.cacheDay, onUpdate, renew: renew) {
start("MarianumMobile", "state-holidays");
}
@override
GetHolidaysResponse onLocalData(String json) {
List<dynamic> parsedListJson = jsonDecode(json)['data'];
return GetHolidaysResponse(List<GetHolidaysResponseObject>.from(parsedListJson.map<GetHolidaysResponseObject>((dynamic i) => GetHolidaysResponseObject.fromJson(i))));
}
@override
Future<GetHolidaysResponse> onLoad() {
return GetHolidays().query();
}
}

@ -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<GetHolidaysResponseObject> data;
GetHolidaysResponse(this.data);
factory GetHolidaysResponse.fromJson(Map<String, dynamic> json) => _$GetHolidaysResponseFromJson(json);
Map<String, dynamic> 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<String, dynamic> json) => _$GetHolidaysResponseObjectFromJson(json);
Map<String, dynamic> toJson() => _$GetHolidaysResponseObjectToJson(this);
}

@ -0,0 +1,43 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getHolidaysResponse.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
GetHolidaysResponse _$GetHolidaysResponseFromJson(Map<String, dynamic> json) =>
GetHolidaysResponse(
(json['data'] as List<dynamic>)
.map((e) =>
GetHolidaysResponseObject.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$GetHolidaysResponseToJson(
GetHolidaysResponse instance) =>
<String, dynamic>{
'data': instance.data.map((e) => e.toJson()).toList(),
};
GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson(
Map<String, dynamic> 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<String, dynamic> _$GetHolidaysResponseObjectToJson(
GetHolidaysResponseObject instance) =>
<String, dynamic>{
'start': instance.start,
'end': instance.end,
'year': instance.year,
'stateCode': instance.stateCode,
'name': instance.name,
'slug': instance.slug,
};

@ -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<GetRoomsResponse> 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");
}
}

@ -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<void> main() async {
ChangeNotifierProvider(create: (context) => FilesProps()),
ChangeNotifierProvider(create: (context) => MessageProps()),
ChangeNotifierProvider(create: (context) => HolidaysProps()),
],
child: const Main(),
)

@ -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<ApiResponse?> properties() {
return [_getHolidaysResponse];
}
@override
void run() {
GetHolidaysCache(
onUpdate: (GetHolidaysResponse data) => {
_getHolidaysResponse = data,
notifyListeners(),
},
);
}
}

@ -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;

@ -17,6 +17,8 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings(
TalkSettings.fromJson(json['talkSettings'] as Map<String, dynamic>),
fileSettings:
FileSettings.fromJson(json['fileSettings'] as Map<String, dynamic>),
holidaysSettings: HolidaysSettings.fromJson(
json['holidaysSettings'] as Map<String, dynamic>),
);
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
@ -26,4 +28,5 @@ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
'timetableSettings': instance.timetableSettings.toJson(),
'talkSettings': instance.talkSettings.toJson(),
'fileSettings': instance.fileSettings.toJson(),
'holidaysSettings': instance.holidaysSettings.toJson(),
};

@ -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,
),
);
}
}

@ -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<String, dynamic> json) => _$HolidaysSettingsFromJson(json);
Map<String, dynamic> toJson() => _$HolidaysSettingsToJson(this);
}

@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'holidaysSettings.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
HolidaysSettings _$HolidaysSettingsFromJson(Map<String, dynamic> json) =>
HolidaysSettings(
dismissedDisclaimer: json['dismissedDisclaimer'] as bool,
showPastEvents: json['showPastEvents'] as bool,
);
Map<String, dynamic> _$HolidaysSettingsToJson(HolidaysSettings instance) =>
<String, dynamic>{
'dismissedDisclaimer': instance.dismissedDisclaimer,
'showPastEvents': instance.showPastEvents,
};

@ -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<Holidays> createState() => _HolidaysState();
}
class _HolidaysState extends State<Holidays> {
late SettingsProvider settings = Provider.of<SettingsProvider>(context, listen: false);
late bool showPastEvents = settings.val().holidaysSettings.showPastEvents;
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Provider.of<HolidaysProps>(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<bool>(
initialValue: settings.val().holidaysSettings.showPastEvents,
icon: const Icon(Icons.manage_history_outlined),
itemBuilder: (context) {
return [true, false].map((e) => PopupMenuItem<bool>(
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<HolidaysProps>(builder: (context, value, child) {
if(value.primaryLoading()) return const LoadingSpinner();
List<GetHolidaysResponseObject> 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),
);
},
);
},
)
);
}
}

@ -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()),
],
),