Files
Client/lib/view/pages/timetable/timetable.dart
T

149 lines
5.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
import '../../../extensions/date_time.dart';
import '../../../routing/app_routes.dart';
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
import '../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
import '../../../state/app/modules/timetable/bloc/timetable_state.dart';
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../storage/timetable_settings.dart';
import 'custom_events/custom_event_edit_dialog.dart';
import 'data/arbitrary_appointment.dart';
import 'data/lesson_period_schedule.dart';
import 'data/timetable_appointment_factory.dart';
import 'details/appointment_details_dispatcher.dart';
import 'widgets/custom_workweek_calendar.dart';
import 'widgets/special_regions_builder.dart';
enum _CalendarAction { addEvent, viewEvents }
class Timetable extends StatefulWidget {
const Timetable({super.key});
@override
State<Timetable> createState() => _TimetableState();
}
class _TimetableState extends State<Timetable> {
final GlobalKey<CustomWorkWeekCalendarState> _calendarKey = GlobalKey<CustomWorkWeekCalendarState>();
List<Appointment>? _cachedAppointments;
int? _lastDataVersion;
TimetableSettings? _lastTimetableSettings;
DateTime _initialDisplayDate() => DateTime.now().add(const Duration(days: 2));
void _jumpToToday() {
_calendarKey.currentState?.jumpToDate(_initialDisplayDate());
}
void _onAction(_CalendarAction action) {
switch (action) {
case _CalendarAction.addEvent:
showDialog(
context: context,
builder: (_) => const CustomEventEditDialog(),
barrierDismissible: false,
);
case _CalendarAction.viewEvents:
AppRoutes.openCustomEvents(context);
}
}
List<Appointment> _appointments(TimetableState state) {
final timetableSettings = context.watch<SettingsCubit>().val().timetableSettings;
if (_cachedAppointments != null &&
_lastDataVersion == state.dataVersion &&
identical(_lastTimetableSettings, timetableSettings)) {
return _cachedAppointments!;
}
_lastDataVersion = state.dataVersion;
_lastTimetableSettings = timetableSettings;
return _cachedAppointments = TimetableAppointmentFactory(
lessons: state.getAllKnownLessons().toList(),
customEvents: state.customEvents?.events ?? const [],
rooms: state.rooms!,
subjects: state.subjects!,
settings: timetableSettings,
now: DateTime.now(),
).build();
}
bool _isCrossedOut(Appointment appointment) {
final id = appointment.id;
if (id is WebuntisAppointment) return id.lesson.code == 'cancelled';
return false;
}
@override
Widget build(BuildContext context) {
final bloc = context.read<TimetableBloc>();
return Scaffold(
appBar: AppBar(
title: const Text('Stunden & Vertretungsplan'),
actions: [
IconButton(icon: const Icon(Icons.home_outlined), onPressed: _jumpToToday),
PopupMenuButton<_CalendarAction>(
icon: const Icon(Icons.edit_calendar_outlined),
onSelected: _onAction,
itemBuilder: (_) => const [
PopupMenuItem(
value: _CalendarAction.addEvent,
child: ListTile(title: Text('Kalendereintrag hinzufügen'), leading: Icon(Icons.add)),
),
PopupMenuItem(
value: _CalendarAction.viewEvents,
child: ListTile(
title: Text('Kalendereinträge anzeigen'),
leading: Icon(Icons.perm_contact_calendar_outlined),
),
),
],
),
],
),
body: LoadableStateConsumer<TimetableBloc, TimetableState>(
child: (state, _) => _calendar(state, bloc),
),
);
}
Widget _calendar(TimetableState state, TimetableBloc bloc) {
if (!state.hasReferenceData) return const SizedBox.shrink();
final schedule = LessonPeriodSchedule.fromState(state);
final appointments = _appointments(state);
final regions = SpecialRegionsBuilder(
holidays: state.schoolHolidays!,
schedule: schedule,
colorScheme: Theme.of(context).colorScheme,
disabledColor: Theme.of(context).disabledColor,
).build();
return CustomWorkWeekCalendar(
key: _calendarKey,
schedule: schedule,
appointments: appointments,
timeRegions: regions,
initialDate: _initialDisplayDate(),
minDate: DateTime.now().subtract(const Duration(days: 14)).nextWeekday(DateTime.sunday),
maxDate: DateTime.now().add(const Duration(days: 7)).nextWeekday(DateTime.saturday),
onAppointmentTap: (apt) => AppointmentDetailsDispatcher.show(context, bloc, apt),
onWeekChanged: (start, end) => bloc.changeWeek(start, end),
isCrossedOut: _isCrossedOut,
onCreateEvent: _onCreateEventAt,
);
}
void _onCreateEventAt(DateTime start, DateTime end) {
showDialog(
context: context,
builder: (_) => CustomEventEditDialog(initialStart: start, initialEnd: end),
barrierDismissible: false,
);
}
}