180 lines
6.0 KiB
Dart
180 lines
6.0 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
|
|
|
import '../../../extensions/dateTime.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 'custom_events/custom_event_edit_dialog.dart';
|
|
import 'custom_events/custom_events_view.dart';
|
|
import 'data/arbitrary_appointment.dart';
|
|
import 'data/timetable_appointment_factory.dart';
|
|
import 'details/appointment_details_dispatcher.dart';
|
|
import 'widgets/appointment_tile.dart';
|
|
import 'widgets/lesson_appointment_source.dart';
|
|
import 'widgets/special_regions_builder.dart';
|
|
import 'widgets/time_region_tile.dart';
|
|
|
|
enum _CalendarAction { addEvent, viewEvents }
|
|
|
|
class Timetable extends StatefulWidget {
|
|
const Timetable({super.key});
|
|
|
|
@override
|
|
State<Timetable> createState() => _TimetableState();
|
|
}
|
|
|
|
class _TimetableState extends State<Timetable> {
|
|
final CalendarController _controller = CalendarController();
|
|
late Timer _highlightTicker;
|
|
|
|
LessonAppointmentSource? _cachedSource;
|
|
int? _lastDataVersion;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller.displayDate = _initialDisplayDate();
|
|
|
|
_highlightTicker = Timer.periodic(const Duration(seconds: 30), (_) {
|
|
if (mounted) setState(() => _cachedSource = null);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_highlightTicker.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
DateTime _initialDisplayDate() => DateTime.now().add(const Duration(days: 2));
|
|
|
|
void _jumpToToday() {
|
|
_controller.displayDate = _initialDisplayDate();
|
|
}
|
|
|
|
void _onAction(_CalendarAction action) {
|
|
switch (action) {
|
|
case _CalendarAction.addEvent:
|
|
showDialog(
|
|
context: context,
|
|
builder: (_) => const CustomEventEditDialog(),
|
|
barrierDismissible: false,
|
|
);
|
|
case _CalendarAction.viewEvents:
|
|
Navigator.of(context).push(MaterialPageRoute(builder: (_) => const CustomEventsView()));
|
|
}
|
|
}
|
|
|
|
LessonAppointmentSource _appointmentSource(TimetableState state) {
|
|
if (_cachedSource != null && _lastDataVersion == state.dataVersion) {
|
|
return _cachedSource!;
|
|
}
|
|
_lastDataVersion = state.dataVersion;
|
|
|
|
final settings = context.read<SettingsCubit>();
|
|
final appointments = TimetableAppointmentFactory(
|
|
lessons: state.getAllKnownLessons().toList(),
|
|
customEvents: state.customEvents?.events ?? const [],
|
|
rooms: state.rooms!,
|
|
subjects: state.subjects!,
|
|
settings: settings.val().timetableSettings,
|
|
colorScheme: Theme.of(context).colorScheme,
|
|
now: DateTime.now(),
|
|
).build();
|
|
|
|
return _cachedSource = LessonAppointmentSource(appointments);
|
|
}
|
|
|
|
@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();
|
|
|
|
return SfCalendar(
|
|
timeZone: 'W. Europe Standard Time',
|
|
view: CalendarView.workWeek,
|
|
dataSource: _appointmentSource(state),
|
|
maxDate: DateTime.now().add(const Duration(days: 7)).nextWeekday(DateTime.saturday),
|
|
minDate: DateTime.now().subtract(const Duration(days: 14)).nextWeekday(DateTime.sunday),
|
|
controller: _controller,
|
|
onViewChanged: (details) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (!mounted) return;
|
|
bloc.changeWeek(details.visibleDates.first, details.visibleDates.last);
|
|
});
|
|
},
|
|
onTap: (tap) {
|
|
if (tap.appointments == null || tap.appointments!.isEmpty) return;
|
|
AppointmentDetailsDispatcher.show(context, bloc, tap.appointments!.first);
|
|
},
|
|
firstDayOfWeek: DateTime.monday,
|
|
specialRegions: SpecialRegionsBuilder(
|
|
holidays: state.schoolHolidays!,
|
|
colorScheme: Theme.of(context).colorScheme,
|
|
disabledColor: Theme.of(context).disabledColor,
|
|
).build(),
|
|
timeSlotViewSettings: const TimeSlotViewSettings(
|
|
startHour: 7.5,
|
|
endHour: 16.5,
|
|
timeInterval: Duration(minutes: 30),
|
|
timeFormat: 'HH:mm',
|
|
dayFormat: 'EE',
|
|
timeIntervalHeight: 40,
|
|
),
|
|
timeRegionBuilder: (_, details) => TimeRegionTile(details: details),
|
|
appointmentBuilder: (_, details) => AppointmentTile(
|
|
details: details,
|
|
crossedOut: _isCrossedOut(details),
|
|
),
|
|
headerHeight: 0,
|
|
selectionDecoration: const BoxDecoration(),
|
|
allowAppointmentResize: false,
|
|
allowDragAndDrop: false,
|
|
allowViewNavigation: false,
|
|
);
|
|
}
|
|
|
|
bool _isCrossedOut(CalendarAppointmentDetails details) {
|
|
final appointment = details.appointments.first;
|
|
final id = appointment.id;
|
|
if (id is WebuntisAppointment) return id.lesson.code == 'cancelled';
|
|
return false;
|
|
}
|
|
}
|