import 'package:flutter/cupertino.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:marianum_mobile/api/webuntis/queries/getHolidays/getHolidaysResponse.dart'; import 'package:marianum_mobile/screen/pages/more/roomplan/roomplan.dart'; import 'package:marianum_mobile/screen/settings/debug/jsonViewer.dart'; import 'package:persistent_bottom_nav_bar/persistent_tab_view.dart'; import 'package:timetable_view/timetable_view.dart'; import '../../../api/webuntis/queries/getHolidays/getHolidays.dart'; import '../../../api/webuntis/queries/getRooms/getRoomsResponse.dart'; import '../../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart'; import '../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; import '../../../data/timetable/timetableProps.dart'; extension DateHelpers on DateTime { bool isToday() { final now = DateTime.now(); return now.day == day && now.month == month && now.year == year; } } class WeekView extends StatefulWidget { final TimetableProps value; const WeekView(this.value, {Key? key}) : super(key: key); @override State createState() => _WeekViewState(); } class _WeekViewState extends State { @override Widget build(BuildContext context) { return TimetableView( laneEventsList: _buildLaneEvents(widget.value), onEventTap: (TableEvent event) { try { GetTimetableResponseObject timetableData = widget.value.getTimetableResponse.result.firstWhere((element) => element.id == event.eventId); GetSubjectsResponseObject subject = widget.value.getSubjectsResponse.result.firstWhere((subject) => subject.id == timetableData.su[0]['id']); GetRoomsResponseObject room = widget.value.getRoomsResponse.result.firstWhere((room) => room.id == timetableData.ro[0]['id']); showModalBottomSheet(context: context, builder: (context) => Column( children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 30), child: Center( child: Column( children: [ Icon(Icons.info, color: event.backgroundColor), const SizedBox(height: 10), Text("${getEventPrefix(timetableData.code)}${subject.alternateName} - (${subject.longName})", style: const TextStyle(fontSize: 30)), Text("${Jiffy(event.startTime).format("HH:mm")} - ${Jiffy(event.endTime).format("HH:mm")}", style: const TextStyle(fontSize: 15)), ], ), ), ), Expanded( child: ListView( children: [ ListTile( leading: const Icon(Icons.notifications_active), title: Text("Status: ${timetableData.code != null ? "Geändert" : "Regulär"}"), ), ListTile( leading: const Icon(Icons.room), title: Text("Raum: ${room.name}"), trailing: IconButton( icon: const Icon(Icons.house_outlined), onPressed: () { PersistentNavBarNavigator.pushNewScreen(context, withNavBar: false, screen: const Roomplan()); }, ), ), ListTile( leading: const Icon(Icons.person), title: Text("Lehrkraft: (${timetableData.te[0]['name']}) ${timetableData.te[0]['longname']}"), trailing: IconButton( icon: const Icon(Icons.textsms_outlined), onPressed: () { showDialog(context: context, builder: (context) => const AlertDialog(content: Text("Not implemented yet"))); }, ), ), 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(", ")}"), ), ListTile( leading: const Icon(Icons.bug_report_outlined), title: const Text("Webuntis Rohdaten zeigen"), onTap: () => JsonViewer.asDialog(context, timetableData.toJson()), ) ], ), ) ], )); } on StateError { return; } }, timetableStyle: CustomTableStyle(context), onEmptySlotTap: (int laneIndex, TableEventTime start, TableEventTime end) => { }, ); } List _buildLaneEvents(TimetableProps data) { List laneEvents = List.empty(growable: true); if(data.primaryLoading()) throw UnimplementedError(); GetTimetableResponse timetable = data.getTimetableResponse; GetRoomsResponse rooms = data.getRoomsResponse; GetSubjectsResponse subjects = data.getSubjectsResponse; GetHolidaysResponse holidays = data.getHolidaysResponse; List dayList = timetable.result.map((e) => e.date).toSet().toList(); dayList.sort((a, b) => a-b); for(int i = 0; i <= data.endDate.difference(data.startDate).inDays; i++) { DateTime currentDay = data.startDate.copyWith().add(Duration(days: i)); // Check Holiday Information GetHolidaysResponseObject? holidayInfo = GetHolidays.find(holidays, time: currentDay); if(holidayInfo != null) { laneEvents.add( LaneEvents( lane: getLane(currentDay), events: List.of([ TableEvent( title: holidayInfo.name, eventId: holidayInfo.id, laneIndex: data.startDate.millisecondsSinceEpoch, startTime: parseTime(0800), endTime: parseTime(1500), padding: const EdgeInsets.all(5), backgroundColor: const Color(0xff3D62B3), location: "\n${holidayInfo.longName}", ) ]), ) ); } } for (var day in dayList) { DateTime currentDay = DateTime.parse("$day"); Lane currentLane = getLane(currentDay); //Every Day List events = List.generate( timetable.result.where((element) => element.date == day).length, (index) { GetTimetableResponseObject tableEvent = timetable.result.where((element) => element.date == day).elementAt(index); try { GetSubjectsResponseObject subject = subjects.result.firstWhere((subject) => subject.id == tableEvent.su[0]['id']); return TableEvent( title: "${getEventPrefix(tableEvent.code)}${subject.alternateName} (${subject.longName})", eventId: tableEvent.id, laneIndex: day, startTime: parseTime(tableEvent.startTime), endTime: parseTime(tableEvent.endTime), padding: const EdgeInsets.all(5), backgroundColor: getEventColor( tableEvent.code ?? "", currentDay.add(Duration(hours: parseTime(tableEvent.startTime).hour, minutes: parseTime(tableEvent.startTime).minute)), currentDay.add(Duration(hours: parseTime(tableEvent.endTime).hour, minutes: parseTime(tableEvent.endTime).minute)), ), location: "\n${rooms.result.firstWhereOrNull((room) => room.id == tableEvent.ro[0]['id'])?.name ?? "?"} - ${tableEvent.te[0]['longname']} (${tableEvent.te[0]['name']})", ); } on Error { return TableEvent(title: "Unbekannt", eventId: index, laneIndex: day, startTime: parseTime(tableEvent.startTime), endTime: parseTime(tableEvent.endTime)); } } ); //Timepointer if(currentDay.isToday()) { events.add(TableEvent( title: "", eventId: 0, laneIndex: day, startTime: formatTime(DateTime.now()), endTime: formatTime(DateTime.now().add(const Duration(minutes: 3))), backgroundColor: Theme.of(context).disabledColor, )); } laneEvents.add( LaneEvents( lane: currentLane, events: events ) ); } return laneEvents; } Lane getLane(DateTime currentDay) { return Lane( backgroundColor: currentDay.isToday() ? Theme.of(context).dividerColor : Colors.white, laneIndex: currentDay.millisecondsSinceEpoch, name: "${Jiffy(currentDay.toString()).format("dd MMM")}\n${Jiffy(currentDay.toString()).format("EE")}", textStyle: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, fontSize: 14 ) ); } TableEventTime parseTime(int input) { String time = input.toString().length < 4 ? "0$input" : input.toString(); return TableEventTime(hour: int.parse(time.substring(0, 2)), minute: int.parse(time.substring(2, 4))); } TableEventTime formatTime(DateTime input) { return TableEventTime(hour: input.hour, minute: input.minute); } String getEventPrefix(String? code) { if(code == "cancelled") return "Entfällt: "; if(code == "irregular") return "Änderung: "; return code ?? ""; } Color getEventColor(String? code, DateTime startTime, DateTime endTime) { if(code == "cancelled") return const Color(0xff8F19B3); if(code == "irregular") return const Color(0xff992B99); if(endTime.isBefore(DateTime.now())) return Colors.grey; if(startTime.isAfter(DateTime.now())) return Theme.of(context).primaryColor; return const Color(0xff99563A); } } class CustomTableStyle extends TimetableStyle { dynamic context; CustomTableStyle(this.context); @override int get startHour => 07; @override int get endHour => 17; @override Color get cornerColor => Theme.of(context).primaryColor; @override Color get timeItemTextColor => Theme.of(context).primaryColor; @override double get timeItemHeight => MediaQuery.of(context).size.width > 1000 ? 60 : 90; @override double get timeItemWidth => 40; @override double get laneHeight => 40; @override double get laneWidth => (MediaQuery.of(context).size.width - timeItemWidth) / 5; }