import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; import '../../../api/webuntis/queries/getHolidays/getHolidaysResponse.dart'; import '../../../api/webuntis/queries/getRooms/getRoomsResponse.dart'; import '../../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart'; import '../../../model/timetable/timetableProps.dart'; import '../../../storage/base/settingsProvider.dart'; import '../../../widget/errorView.dart'; import 'appointmenetComponent.dart'; import 'appointmentDetails.dart'; import 'timeRegionComponent.dart'; import 'timetableEvents.dart'; class Timetable extends StatefulWidget { const Timetable({Key? key}) : super(key: key); @override State createState() => _TimetableState(); } class _TimetableState extends State { CalendarController controller = CalendarController(); double elementScale = 40; double baseElementScale = 40; late final SettingsProvider settings; @override void initState() { settings = Provider.of(context, listen: false); elementScale = baseElementScale = settings.val().timetableSettings.zoom; WidgetsBinding.instance.addPostFrameCallback((timeStamp) { Provider.of(context, listen: false).run(); }); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Stunden & Vertretungsplan"), actions: [ IconButton( icon: const Icon(Icons.today), onPressed: () { // controller.displayDate = DateTime.now().jumpToNextWeekDay(DateTime.monday); // controller.displayDate = DateTime.now().add(Duration(days: 2)); controller.displayDate = DateTime.now(); } ), ], ), body: Consumer( builder: (context, value, child) { if(value.primaryLoading()) return const Placeholder(); GetHolidaysResponse holidays = value.getHolidaysResponse; if(value.hasError) { return ErrorView( icon: Icons.calendar_month, text: "Webuntis error: ${value.error.toString()}", ); } return GestureDetector( onScaleStart: (details) => baseElementScale = elementScale, onScaleUpdate: (details) { setState(() { elementScale = (baseElementScale * details.scale).clamp(40, 80); }); }, onScaleEnd: (details) { settings.val(write: true).timetableSettings.zoom = elementScale; }, child: SfCalendar( view: CalendarView.workWeek, dataSource: _buildTableEvents(value), controller: controller, onViewChanged: (ViewChangedDetails details) { log(details.visibleDates.toString()); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { Provider.of(context, listen: false).updateWeek(details.visibleDates.first, details.visibleDates.last); }); }, onTap: (calendarTapDetails) { if(calendarTapDetails.appointments == null) return; Appointment tapped = calendarTapDetails.appointments!.first; AppointmentDetails.show(context, value, tapped); log(tapped.id.toString()); }, firstDayOfWeek: DateTime.monday, specialRegions: _buildSpecialTimeRegions(holidays), timeSlotViewSettings: TimeSlotViewSettings( startHour: 07.5, endHour: 16.5, timeInterval: const Duration(minutes: 30), timeFormat: "HH:mm", dayFormat: "EE", timeIntervalHeight: elementScale, ), timeRegionBuilder: (BuildContext context, TimeRegionDetails timeRegionDetails) => TimeRegionComponent(details: timeRegionDetails), appointmentBuilder: (BuildContext context, CalendarAppointmentDetails details) => AppointmentComponent( details: details, crossedOut: value.getTimetableResponse.result.where((element) => element.id == details.appointments.first.id).firstOrNull?.code == "cancelled" ), headerHeight: 0, selectionDecoration: const BoxDecoration(), allowAppointmentResize: false, allowDragAndDrop: false, allowViewNavigation: false, ), ); }, ), ); } List _buildSpecialTimeRegions(GetHolidaysResponse holidays, ) { DateTime lastMonday = DateTime.now().subtract(Duration(days: DateTime.now().weekday - 1)); DateTime firstBreak = lastMonday.copyWith(hour: 10, minute: 15); DateTime secondBreak = lastMonday.copyWith(hour: 13, minute: 50); DateTime beforeSchool = lastMonday.copyWith(hour: 7, minute: 30); return [ ...holidays.result.map((e) { return TimeRegion( startTime: _parseWebuntisTimestamp(e.startDate, 755), endTime: _parseWebuntisTimestamp(e.startDate, 1630), text: 'holiday:${e.name}', color: Theme.of(context).disabledColor.withAlpha(50), iconData: Icons.holiday_village_outlined ); }), TimeRegion( startTime: firstBreak, endTime: firstBreak.add(const Duration(minutes: 20)), recurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=5', text: 'centerIcon', color: Theme.of(context).primaryColor.withAlpha(50), iconData: Icons.restaurant ), TimeRegion( startTime: secondBreak, endTime: secondBreak.add(const Duration(minutes: 15)), recurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=5', text: 'centerIcon', color: Theme.of(context).primaryColor.withAlpha(50), iconData: Icons.restaurant ), TimeRegion( startTime: beforeSchool, endTime: beforeSchool.add(const Duration(minutes: 25)), recurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=5', color: Theme.of(context).disabledColor.withAlpha(50), text: "centerIcon", ), ]; } TimetableEvents _buildTableEvents(TimetableProps data) { List appointments = data.getTimetableResponse.result.map((element) { GetRoomsResponse rooms = data.getRoomsResponse; GetSubjectsResponse subjects = data.getSubjectsResponse; try { DateTime startTime = _parseWebuntisTimestamp(element.date, element.startTime); DateTime endTime = _parseWebuntisTimestamp(element.date, element.endTime); return Appointment( id: element, startTime: startTime, endTime: endTime, subject: subjects.result.firstWhere((subject) => subject.id == element.su[0]['id']).alternateName, location: "" "${rooms.result.firstWhere((room) => room.id == element.ro[0]['id']).name}" "\n" "${element.te.first['longname']}", notes: element.activityType, color: _getEventColor(element.code, startTime, endTime), ); } on Error catch(e) { log(e.toString()); return Appointment( startTime: _parseWebuntisTimestamp(element.date, element.startTime), endTime: _parseWebuntisTimestamp(element.date, element.endTime), subject: "ERROR", notes: element.info, location: 'LOCATION', color: Theme.of(context).primaryColor, startTimeZone: '', endTimeZone: '', ); } }).toList(); return TimetableEvents(appointments); } DateTime _parseWebuntisTimestamp(int date, int time) { String timeString = time.toString().padLeft(4, '0'); return DateTime.parse('$date ${timeString.substring(0, 2)}:${timeString.substring(2, 4)}'); } Color _getEventColor(String? code, DateTime startTime, DateTime endTime) { int opacity = endTime.isBefore(DateTime.now()) ? 100 : 255; if(code == "cancelled") return const Color(0xff000000).withAlpha(opacity); if(code == "irregular") return const Color(0xff8F19B3).withAlpha(opacity); if(endTime.isBefore(DateTime.now())) return Theme.of(context).primaryColor.withAlpha(opacity); if(endTime.isAfter(DateTime.now()) && startTime.isBefore(DateTime.now())) return Theme.of(context).primaryColor.withRed(100); return Theme.of(context).primaryColor; } }