simplify: dedupe boilerplate and remove dead code across all layers

This commit is contained in:
2026-07-13 22:22:39 +02:00
parent 15791423ea
commit 398b147c76
69 changed files with 345 additions and 651 deletions
@@ -8,6 +8,7 @@ import '../../../../api/mhsl/custom_timetable_event/custom_timetable_event.dart'
import '../../../../storage/timetable_settings.dart';
import 'arbitrary_appointment.dart';
import 'lesson_color.dart';
import 'lesson_merger.dart';
import 'lesson_status.dart';
import 'lesson_type_label.dart';
import 'rrule_with_exceptions.dart';
@@ -33,7 +34,7 @@ class TimetableAppointmentFactory {
List<Appointment> build() {
final source = settings.connectDoubleLessons
? _mergeAdjacentLessons(lessons)
? LessonMerger.merge(lessons)
: lessons;
return [
...source.map(_lessonToAppointment),
@@ -267,68 +268,4 @@ class TimetableAppointmentFactory {
.trim();
return cleaned.isEmpty ? null : cleaned;
}
// Pure: builds a new list, does not mutate inputs. The previous version
// mutated `previous.endTime` in place which caused merged blocks to grow
// further on subsequent rebuilds when the same lesson objects were observed
// again by the next merge pass.
static List<McTimetableEntry> _mergeAdjacentLessons(
List<McTimetableEntry> input, {
Duration maxGap = const Duration(minutes: 5),
}) {
if (input.isEmpty) return const [];
final sorted = [...input]
..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
final merged = <McTimetableEntry>[];
for (final current in sorted) {
if (merged.isNotEmpty && _canMerge(merged.last, current, maxGap)) {
final prev = merged.removeLast();
merged.add(_extendedEnd(prev, current.endTime));
} else {
merged.add(current);
}
}
return merged;
}
static McTimetableEntry _extendedEnd(
McTimetableEntry source,
DateTime newEndTime,
) => McTimetableEntry(
id: source.id,
date: source.date,
startTime: source.startTime,
endTime: newEndTime,
subjects: source.subjects,
teachers: source.teachers,
rooms: source.rooms,
classNames: source.classNames,
lessonType: source.lessonType,
status: source.status,
substitutionText: source.substitutionText,
lessonText: source.lessonText,
infoText: source.infoText,
);
static bool _canMerge(
McTimetableEntry a,
McTimetableEntry b,
Duration maxGap,
) {
if (a.subjects.firstOrNull != b.subjects.firstOrNull) return false;
if (a.rooms.firstOrNull != b.rooms.firstOrNull) return false;
if (a.teachers.firstOrNull?.shortName !=
b.teachers.firstOrNull?.shortName) {
return false;
}
if (a.status != b.status) return false;
// Merge only sequential lessons (b starts at or after a ends, within the
// tolerance). Without the lower bound, identical-metadata lessons that
// overlap in time would silently collapse into one.
final gap = b.startDateTime.difference(a.endDateTime);
return !gap.isNegative && gap <= maxGap;
}
}
@@ -268,12 +268,10 @@ class LessonSheet {
);
}
static String _line(String name, {String? longname, String? extra}) {
static String _line(String name, {String? longname}) {
final parts = <String>[if (name.isNotEmpty) name else '?'];
final ln = (longname ?? '').trim();
if (ln.isNotEmpty && ln != name) parts.add('($ln)');
final ex = (extra ?? '').trim();
if (ex.isNotEmpty) parts.add('· $ex');
return parts.join(' ');
}
@@ -116,12 +116,9 @@ class _OutsideDayColumn extends StatelessWidget {
static String _subtitleFor(Appointment a) {
if (isAllDayLike(a)) return 'Ganztägig';
return '${_hm(a.startTime)}${_hm(a.endTime)}';
return '${a.startTime.formatHm()}${a.endTime.formatHm()}';
}
static String _hm(DateTime t) =>
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}';
@override
Widget build(BuildContext context) {
if (appointments.isEmpty) return const SizedBox.shrink();
@@ -182,9 +179,7 @@ class _OutsideChip extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final allDay = isAllDayLike(appointment);
final timeLabel = allDay
? null
: '${appointment.startTime.hour.toString().padLeft(2, '0')}:${appointment.startTime.minute.toString().padLeft(2, '0')}';
final timeLabel = allDay ? null : appointment.startTime.formatHm();
// Past chips fade further, future/ongoing ones get a more saturated tint
// so the strip no longer reads as one uniform grey block.
@@ -289,14 +289,11 @@ class _DayColumn extends StatelessWidget {
}
static String _overflowSubtitle(Appointment apt) {
final time = '${_formatHm(apt.startTime)}${_formatHm(apt.endTime)}';
final time = '${apt.startTime.formatHm()}${apt.endTime.formatHm()}';
final loc = apt.location?.replaceAll('\n', ' · ');
return loc != null && loc.isNotEmpty ? '$time · $loc' : time;
}
static String _formatHm(DateTime t) =>
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}';
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);