fixed lesson merging mutation, improved overlap detection, and implemented priority-based lane assignment with tablet support
This commit is contained in:
@@ -148,7 +148,13 @@ class TimetableAppointmentFactory {
|
||||
return cleaned.isEmpty ? null : cleaned;
|
||||
}
|
||||
|
||||
// Pure: returns a new list, does not mutate input.
|
||||
// Pure: returns a new list of fresh objects, does not mutate input.
|
||||
// (The previous version replaced `previous.endTime` in place, which
|
||||
// mutated the original lesson object passed in via [input]. Across
|
||||
// rebuilds those mutated lessons were observed again by the next merge
|
||||
// pass — extending lessons further or, after the overlap-gap guard was
|
||||
// added to [_canMerge], even causing the second half of a double lesson
|
||||
// to be emitted alongside the already-merged block.)
|
||||
static List<GetTimetableResponseObject> _mergeAdjacentLessons(
|
||||
List<GetTimetableResponseObject> input, {
|
||||
Duration maxGap = const Duration(minutes: 5),
|
||||
@@ -158,19 +164,22 @@ class TimetableAppointmentFactory {
|
||||
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;
|
||||
final merged = <GetTimetableResponseObject>[];
|
||||
for (final current in sorted) {
|
||||
if (merged.isNotEmpty && _canMerge(merged.last, current, maxGap)) {
|
||||
// `merged.last` is always a copy we created below, so mutating its
|
||||
// endTime is safe and keeps the next iteration's gap check correct.
|
||||
merged.last.endTime = current.endTime;
|
||||
} else {
|
||||
merged.add(current);
|
||||
merged.add(_copyLesson(current));
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
static GetTimetableResponseObject _copyLesson(GetTimetableResponseObject l) =>
|
||||
GetTimetableResponseObject.fromJson(l.toJson());
|
||||
|
||||
static bool _canMerge(GetTimetableResponseObject a, GetTimetableResponseObject b, Duration maxGap) {
|
||||
final aSubject = a.su.firstOrNull?.id;
|
||||
final bSubject = b.su.firstOrNull?.id;
|
||||
@@ -179,7 +188,12 @@ class TimetableAppointmentFactory {
|
||||
if (a.te.firstOrNull?.id != b.te.firstOrNull?.id) return false;
|
||||
if (a.code != b.code) 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 — and because the
|
||||
// merge sets `previous.endTime = current.endTime`, an overlapping merge
|
||||
// can even truncate the earlier lesson.
|
||||
final gap = WebuntisTime.parse(b.date, b.startTime).difference(WebuntisTime.parse(a.date, a.endTime));
|
||||
return gap <= maxGap;
|
||||
return !gap.isNegative && gap <= maxGap;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user