custom login implementation, period-based timetable layout with overlap handling, enhanced error dialogs, and unified bottom sheets
This commit is contained in:
@@ -6,3 +6,11 @@ const double kCalendarViewHeaderHeight = 60;
|
||||
/// Minimum pixels per hour. Below this, the grid scrolls vertically rather
|
||||
/// than compressing further.
|
||||
const double kCalendarMinPxPerHour = 56;
|
||||
|
||||
/// Minimum height of a lesson block in the period-based layout. The grid
|
||||
/// scrolls vertically once lessons would otherwise be smaller than this.
|
||||
const double kLessonBlockMinHeight = 50;
|
||||
|
||||
/// Fixed height of a break block in the period-based layout. Independent of
|
||||
/// the actual break duration; breaks are rendered as a compact indicator.
|
||||
const double kBreakBlockHeight = 28;
|
||||
|
||||
@@ -72,8 +72,8 @@ class TimetableAppointmentFactory {
|
||||
id: CustomAppointment(event),
|
||||
startTime: event.startDate,
|
||||
endTime: event.endDate,
|
||||
location: event.description,
|
||||
subject: event.title,
|
||||
location: _collapseWhitespace(event.description),
|
||||
subject: _collapseWhitespace(event.title) ?? event.title,
|
||||
recurrenceRule: event.rrule,
|
||||
color: TimetableColors.getColorFromString(event.color ?? TimetableColors.defaultColor.name),
|
||||
startTimeZone: '',
|
||||
@@ -83,19 +83,38 @@ class TimetableAppointmentFactory {
|
||||
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) {
|
||||
final name = switch (settings.timetableNameMode) {
|
||||
TimetableNameMode.name => subject.name,
|
||||
TimetableNameMode.longName => subject.longName,
|
||||
TimetableNameMode.alternateName => subject.alternateName,
|
||||
};
|
||||
return _collapseWhitespace(name) ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
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';
|
||||
final roomName = _collapseWhitespace(
|
||||
rooms.result.firstWhereOrNull((r) => r.id == lesson.ro.firstOrNull?.id)?.name) ??
|
||||
'Unbekannt';
|
||||
final teacherName = _collapseWhitespace(lesson.te.firstOrNull?.longname) ?? 'Unbekannt';
|
||||
return '$roomName\n$teacherName';
|
||||
}
|
||||
|
||||
/// Collapses any line-break or whitespace run to a single space and trims.
|
||||
/// Returns null when input is null or fully whitespace. Webuntis sometimes
|
||||
/// returns multi-line room names like "A30\n4" — this normalizes those so
|
||||
/// the tile renders the room on a single line.
|
||||
static String? _collapseWhitespace(String? s) {
|
||||
if (s == null) return null;
|
||||
final cleaned = s
|
||||
.replaceAll('\r\n', ' ')
|
||||
.replaceAll('\n', ' ')
|
||||
.replaceAll('\r', ' ')
|
||||
.replaceAll('\t', ' ')
|
||||
.replaceAll(RegExp(r'\s+'), ' ')
|
||||
.trim();
|
||||
return cleaned.isEmpty ? null : cleaned;
|
||||
}
|
||||
|
||||
// Pure: returns a new list, does not mutate input.
|
||||
static List<GetTimetableResponseObject> _mergeAdjacentLessons(
|
||||
List<GetTimetableResponseObject> input, {
|
||||
|
||||
Reference in New Issue
Block a user