claude refactor

This commit is contained in:
2026-05-04 13:54:39 +02:00
parent 9973f12733
commit 551c1bf1fa
125 changed files with 4484 additions and 2544 deletions
@@ -0,0 +1,24 @@
import '../../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart';
import '../../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart';
sealed class ArbitraryAppointment {
const ArbitraryAppointment();
T when<T>({
required T Function(GetTimetableResponseObject lesson) webuntis,
required T Function(CustomTimetableEvent event) custom,
}) => switch (this) {
WebuntisAppointment(:final lesson) => webuntis(lesson),
CustomAppointment(:final event) => custom(event),
};
}
class WebuntisAppointment extends ArbitraryAppointment {
final GetTimetableResponseObject lesson;
const WebuntisAppointment(this.lesson);
}
class CustomAppointment extends ArbitraryAppointment {
final CustomTimetableEvent event;
const CustomAppointment(this.event);
}
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'lesson_status.dart';
class LessonColor {
static const Color cancelled = Color(0xff000000);
static const Color irregular = Color(0xff8F19B3);
static const Color teacherChanged = Color(0xFF29639B);
static const Color parseFallback = Color(0xff404040);
static Color forStatus(LessonStatus status, ColorScheme scheme) {
switch (status) {
case LessonStatus.cancelled:
return cancelled;
case LessonStatus.irregular:
return irregular;
case LessonStatus.teacherChanged:
return teacherChanged;
case LessonStatus.past:
case LessonStatus.regular:
return scheme.primary;
case LessonStatus.ongoing:
return Color.from(
alpha: scheme.primary.a,
red: 200 / 255,
green: scheme.primary.g,
blue: scheme.primary.b,
);
}
}
}
@@ -0,0 +1,21 @@
import '../../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart';
enum LessonStatus {
cancelled,
irregular,
teacherChanged,
past,
ongoing,
regular,
}
class LessonStatusClassifier {
static LessonStatus classify(GetTimetableResponseObject lesson, DateTime startTime, DateTime endTime, DateTime now) {
if (lesson.code == 'cancelled') return LessonStatus.cancelled;
if (lesson.code == 'irregular' || (lesson.te.isNotEmpty && lesson.te.first.id == 0)) return LessonStatus.irregular;
if (lesson.te.any((t) => t.orgname != null)) return LessonStatus.teacherChanged;
if (endTime.isBefore(now)) return LessonStatus.past;
if (startTime.isBefore(now) && endTime.isAfter(now)) return LessonStatus.ongoing;
return LessonStatus.regular;
}
}
@@ -0,0 +1,136 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
import '../../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart';
import '../../../../api/webuntis/queries/getRooms/getRoomsResponse.dart';
import '../../../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart';
import '../../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart';
import '../../../../storage/timetable/timetableSettings.dart';
import '../../../../storage/timetable/timetable_name_mode.dart';
import '../custom_events/custom_event_colors.dart';
import 'arbitrary_appointment.dart';
import 'lesson_color.dart';
import 'lesson_status.dart';
import 'webuntis_time.dart';
class TimetableAppointmentFactory {
final List<GetTimetableResponseObject> lessons;
final List<CustomTimetableEvent> customEvents;
final GetRoomsResponse rooms;
final GetSubjectsResponse subjects;
final TimetableSettings settings;
final ColorScheme colorScheme;
final DateTime now;
TimetableAppointmentFactory({
required this.lessons,
required this.customEvents,
required this.rooms,
required this.subjects,
required this.settings,
required this.colorScheme,
required this.now,
});
List<Appointment> build() {
final source = settings.connectDoubleLessons ? _mergeAdjacentLessons(lessons) : lessons;
return [
...source.map(_lessonToAppointment),
...customEvents.map(_customEventToAppointment),
];
}
Appointment _lessonToAppointment(GetTimetableResponseObject lesson) {
try {
final startTime = WebuntisTime.parse(lesson.date, lesson.startTime);
final endTime = WebuntisTime.parse(lesson.date, lesson.endTime);
final status = LessonStatusClassifier.classify(lesson, startTime, endTime, now);
return Appointment(
id: WebuntisAppointment(lesson),
startTime: startTime,
endTime: endTime,
subject: _subjectName(lesson),
location: _locationLabel(lesson),
notes: lesson.activityType,
color: LessonColor.forStatus(status, colorScheme),
);
} catch (_) {
return Appointment(
id: WebuntisAppointment(lesson),
startTime: WebuntisTime.parse(lesson.date, lesson.startTime),
endTime: WebuntisTime.parse(lesson.date, lesson.endTime),
subject: 'Änderung',
notes: lesson.info,
location: 'Unbekannt',
color: LessonColor.parseFallback,
startTimeZone: '',
endTimeZone: '',
);
}
}
Appointment _customEventToAppointment(CustomTimetableEvent event) => Appointment(
id: CustomAppointment(event),
startTime: event.startDate,
endTime: event.endDate,
location: event.description,
subject: event.title,
recurrenceRule: event.rrule,
color: TimetableColors.getColorFromString(event.color ?? TimetableColors.defaultColor.name),
startTimeZone: '',
endTimeZone: '',
);
String _subjectName(GetTimetableResponseObject lesson) {
final subject = subjects.result.firstWhereOrNull((s) => s.id == lesson.su.firstOrNull?.id);
if (subject == null) return 'Unbekannt';
return switch (settings.timetableNameMode) {
TimetableNameMode.name => subject.name,
TimetableNameMode.longName => subject.longName,
TimetableNameMode.alternateName => subject.alternateName,
};
}
String _locationLabel(GetTimetableResponseObject lesson) {
final roomName = rooms.result.firstWhereOrNull((r) => r.id == lesson.ro.firstOrNull?.id)?.name ?? 'Unbekannt';
final teacherName = lesson.te.firstOrNull?.longname ?? 'Unbekannt';
return '$roomName\n$teacherName';
}
// Pure: returns a new list, does not mutate input.
static List<GetTimetableResponseObject> _mergeAdjacentLessons(
List<GetTimetableResponseObject> input, {
Duration maxGap = const Duration(minutes: 5),
}) {
if (input.isEmpty) return const [];
final sorted = [...input]..sort((a, b) =>
WebuntisTime.parse(a.date, a.startTime).compareTo(WebuntisTime.parse(b.date, b.startTime)));
final merged = <GetTimetableResponseObject>[sorted.first];
for (var i = 1; i < sorted.length; i++) {
final previous = merged.last;
final current = sorted[i];
if (_canMerge(previous, current, maxGap)) {
previous.endTime = current.endTime;
} else {
merged.add(current);
}
}
return merged;
}
static bool _canMerge(GetTimetableResponseObject a, GetTimetableResponseObject b, Duration maxGap) {
final aSubject = a.su.firstOrNull?.id;
final bSubject = b.su.firstOrNull?.id;
if (aSubject == null || bSubject == null || aSubject != bSubject) return false;
if (a.ro.firstOrNull?.id != b.ro.firstOrNull?.id) return false;
if (a.te.firstOrNull?.id != b.te.firstOrNull?.id) return false;
if (a.code != b.code) return false;
final gap = WebuntisTime.parse(b.date, b.startTime).difference(WebuntisTime.parse(a.date, a.endTime));
return gap <= maxGap;
}
}
@@ -0,0 +1,14 @@
import 'package:intl/intl.dart';
class WebuntisTime {
static final DateFormat _dateFormat = DateFormat('yyyyMMdd');
static DateTime parse(int date, int time) {
final timeString = time.toString().padLeft(4, '0');
return DateTime.parse('$date ${timeString.substring(0, 2)}:${timeString.substring(2, 4)}');
}
static int formatDate(DateTime date) => int.parse(_dateFormat.format(date));
static String dateKey(DateTime date) => _dateFormat.format(date);
}