simplify: dedupe boilerplate and remove dead code across all layers
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user