From 2664378b176d2a600da7c0f6867baf16032d524f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sun, 27 Sep 2026 17:37:31 +0200 Subject: [PATCH] fixed overwrite on internal copy, DST drift in widget anchors, inline errors without controller and removed dead room lookup --- lib/background/widget_background_task.dart | 14 ++---- lib/share_intent/internal_share_actions.dart | 24 ++++------ .../files/sharing/share_options_sheet.dart | 13 ++---- .../settings/sections/account_section.dart | 40 +++++++--------- lib/view/pages/talk/details/chat_info.dart | 15 ++---- lib/view/pages/talk/widgets/room_actions.dart | 22 ++++----- .../async_actions/async_action_button.dart | 5 +- lib/widget/async_actions/async_mixin.dart | 14 +++--- .../async_actions/async_text_button.dart | 4 +- lib/widget/confirm_dialog.dart | 17 ++++--- lib/widget_data/widget_data_mapper.dart | 46 ++++--------------- lib/widget_data/widget_publisher.dart | 1 - test/widget_data/widget_data_mapper_test.dart | 23 ++++------ 13 files changed, 86 insertions(+), 152 deletions(-) diff --git a/lib/background/widget_background_task.dart b/lib/background/widget_background_task.dart index 550b5ff..4b703e0 100644 --- a/lib/background/widget_background_task.dart +++ b/lib/background/widget_background_task.dart @@ -9,14 +9,13 @@ import '../api/marianumconnect/marianumconnect_endpoint.dart'; import '../api/marianumconnect/queries/timetable_custom_events/timetable_custom_events_get.dart'; import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays.dart'; import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart'; -import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms.dart'; -import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms_response.dart'; import '../api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects.dart'; import '../api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects_response.dart'; import '../api/marianumconnect/queries/timetable_get_timegrid/timetable_get_timegrid.dart'; import '../api/marianumconnect/queries/timetable_get_timegrid/timetable_get_timegrid_response.dart'; import '../api/marianumconnect/queries/timetable_get_week/timetable_get_week.dart'; import '../api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart'; +import '../extensions/date_time.dart'; import '../session/session_manager.dart'; import '../widget_data/widget_data_mapper.dart'; import '../widget_data/widget_publisher.dart'; @@ -151,9 +150,7 @@ Future _refresh() async { // Fetch window matches the week payload's window so the widget can roll // forward into next week's lessons without fresh data. final weekStart = WidgetDataMapper.startOfCalendarWeek(now); - final weekEndExclusive = weekStart.add( - const Duration(days: WidgetDataMapper.weekWindowDays), - ); + final weekEndExclusive = weekStart.addDays(WidgetDataMapper.weekWindowDays); // All six requests are independent — run them concurrently so the total // latency is the slowest request, not the sum (matters for the push path's @@ -161,14 +158,11 @@ Future _refresh() async { // mapper rather than aborting the whole refresh. final timetableFuture = TimetableGetWeek().run( from: weekStart, - until: weekEndExclusive.subtract(const Duration(days: 1)), + until: weekEndExclusive.subtractDays(1), ); final subjectsFuture = _runOrNull( () => TimetableGetSubjects().run(), ); - final roomsFuture = _runOrNull( - () => TimetableGetRooms().run(), - ); final holidaysFuture = _runOrNull( () => TimetableGetHolidays().run(), ); @@ -180,7 +174,6 @@ Future _refresh() async { ); final timetable = await timetableFuture; final subjects = await subjectsFuture; - final rooms = await roomsFuture; final holidays = await holidaysFuture; final timegrid = await timegridFuture; final customEvents = await customEventsFuture; @@ -193,7 +186,6 @@ Future _refresh() async { now: now, lessons: timetable.entries, subjects: subjects, - rooms: rooms, holidays: holidays, timegrid: timegrid, customEvents: customEvents, diff --git a/lib/share_intent/internal_share_actions.dart b/lib/share_intent/internal_share_actions.dart index f9a5a47..f80824d 100644 --- a/lib/share_intent/internal_share_actions.dart +++ b/lib/share_intent/internal_share_actions.dart @@ -7,8 +7,8 @@ import 'remote_file_ref.dart'; /// Server-side WebDAV copy of [source] into [targetFolderPath]. On a 412 /// conflict the user is asked whether to overwrite; on confirmation the call -/// is retried with `overwrite: true`. Returns true when the file ended up at -/// the target, false when the user cancelled. +/// is retried with `overwrite: true` inside the dialog. Returns true when the +/// file ended up at the target, false when the user cancelled. Future copyRemoteFileTo({ required BuildContext context, required RemoteFileRef source, @@ -27,19 +27,11 @@ Future copyRemoteFileTo({ } on DynamiteApiException catch (e) { if (e.statusCode != 412) rethrow; if (!context.mounted) return false; - final overwrite = await showDialog( - context: context, - builder: (ctx) => ConfirmDialog( - title: 'Datei existiert bereits', - content: - '"${source.name}" existiert in /$targetFolderPath. Überschreiben?', - confirmButton: 'Überschreiben', - cancelButton: 'Abbrechen', - onConfirm: () => Navigator.of(ctx).pop(true), - ), - ); - if (overwrite != true) return false; - await webdav.copy(src, dstUri, overwrite: true); - return true; + return ConfirmDialog( + title: 'Datei existiert bereits', + content: '"${source.name}" existiert in /$targetFolderPath. Überschreiben?', + confirmButton: 'Überschreiben', + onConfirmAsync: () => webdav.copy(src, dstUri, overwrite: true), + ).ask(context); } } diff --git a/lib/view/pages/files/sharing/share_options_sheet.dart b/lib/view/pages/files/sharing/share_options_sheet.dart index 81ca9f8..265deb0 100644 --- a/lib/view/pages/files/sharing/share_options_sheet.dart +++ b/lib/view/pages/files/sharing/share_options_sheet.dart @@ -154,20 +154,15 @@ class _ShareOptionsBodyState extends State<_ShareOptionsBody> { _update(const ShareUpdateParams(password: '')); Future _confirmDelete() async { - // Use showDialog directly (not asDialog) so we get the AsyncDialogAction's - // `true` result and can close the options sheet afterwards — popping inside + // Close the options sheet only after the dialog returned — popping inside // onConfirmAsync would target the dialog route, not the sheet. - final dialog = ConfirmDialog( + final deleted = await ConfirmDialog( title: 'Freigabe löschen?', content: 'Die Freigabe wird aufgehoben.', confirmButton: 'Löschen', onConfirmAsync: () => FileSharingApi().remove(_share.id), - ); - final deleted = await showDialog( - context: context, - builder: dialog.build, - ); - if (deleted != true || !mounted) return; + ).ask(context); + if (!deleted || !mounted) return; widget.onChanged(); if (Navigator.of(context).canPop()) Navigator.of(context).pop(); } diff --git a/lib/view/pages/settings/sections/account_section.dart b/lib/view/pages/settings/sections/account_section.dart index 786d69c..b5574ef 100644 --- a/lib/view/pages/settings/sections/account_section.dart +++ b/lib/view/pages/settings/sections/account_section.dart @@ -61,16 +61,11 @@ class _AccountSectionState extends State { if (result == null || !mounted) return; if (result is AvatarRemoveResult) { - var confirmed = false; - await showDialog( - context: context, - builder: (_) => ConfirmDialog( - title: 'Profilbild entfernen', - content: 'Möchtest du dein Profilbild wirklich entfernen?', - confirmButton: 'Entfernen', - onConfirm: () => confirmed = true, - ), - ); + final confirmed = await const ConfirmDialog( + title: 'Profilbild entfernen', + content: 'Möchtest du dein Profilbild wirklich entfernen?', + confirmButton: 'Entfernen', + ).ask(context); if (!confirmed || !mounted) return; } @@ -211,20 +206,17 @@ Future _confirmLogout(BuildContext context) async { // Flip AccountBloc state only after the dialog fully closes: doing it from // inside the sign-out (the previous approach) raced AsyncDialogAction's // pop(true) against the navigator teardown of the account switch. - final confirmed = await showDialog( - context: context, - builder: (dialogContext) => ConfirmDialog( - title: 'Abmelden?', - content: others > 0 - ? 'Möchtest du dich wirklich abmelden? Die App wechselt danach zu ' - 'einem deiner anderen Konten.' - : 'Möchtest du dich wirklich abmelden?', - confirmButton: 'Abmelden', - onConfirmAsync: () async => - nextAccountId = await SessionLifecycle.signOut(), - ), - ); - if (confirmed != true) return; + final confirmed = await ConfirmDialog( + title: 'Abmelden?', + content: others > 0 + ? 'Möchtest du dich wirklich abmelden? Die App wechselt danach zu ' + 'einem deiner anderen Konten.' + : 'Möchtest du dich wirklich abmelden?', + confirmButton: 'Abmelden', + onConfirmAsync: () async => + nextAccountId = await SessionLifecycle.signOut(), + ).ask(context); + if (!confirmed) return; accountBloc.activated(nextAccountId); } diff --git a/lib/view/pages/talk/details/chat_info.dart b/lib/view/pages/talk/details/chat_info.dart index 5df6407..ffe3f4b 100644 --- a/lib/view/pages/talk/details/chat_info.dart +++ b/lib/view/pages/talk/details/chat_info.dart @@ -108,16 +108,11 @@ class _ChatInfoState extends State { if (result == null || !mounted) return; if (result is AvatarRemoveResult) { - var confirmed = false; - await showDialog( - context: context, - builder: (_) => ConfirmDialog( - title: 'Gruppenbild entfernen', - content: 'Möchtest du das Gruppenbild wirklich entfernen?', - confirmButton: 'Entfernen', - onConfirm: () => confirmed = true, - ), - ); + final confirmed = await const ConfirmDialog( + title: 'Gruppenbild entfernen', + content: 'Möchtest du das Gruppenbild wirklich entfernen?', + confirmButton: 'Entfernen', + ).ask(context); if (!confirmed || !mounted) return; } diff --git a/lib/view/pages/talk/widgets/room_actions.dart b/lib/view/pages/talk/widgets/room_actions.dart index 4935d1a..23744c4 100644 --- a/lib/view/pages/talk/widgets/room_actions.dart +++ b/lib/view/pages/talk/widgets/room_actions.dart @@ -32,17 +32,13 @@ class FavoriteToggleTile extends StatelessWidget { /// Resolves to true once the room was left. Future confirmLeaveRoom(BuildContext context, String token) async { final chatList = context.read(); - final left = await showDialog( - context: context, - builder: (_) => ConfirmDialog( - title: 'Talk-Chat verlassen', - content: 'Du benötigst ggf. eine Einladung um erneut beizutreten.', - confirmButton: 'Verlassen', - onConfirmAsync: () async { - await LeaveRoom(token).run(); - unawaited(chatList.refresh()); - }, - ), - ); - return left ?? false; + return ConfirmDialog( + title: 'Talk-Chat verlassen', + content: 'Du benötigst ggf. eine Einladung um erneut beizutreten.', + confirmButton: 'Verlassen', + onConfirmAsync: () async { + await LeaveRoom(token).run(); + unawaited(chatList.refresh()); + }, + ).ask(context); } diff --git a/lib/widget/async_actions/async_action_button.dart b/lib/widget/async_actions/async_action_button.dart index eb1fb4e..8249a73 100644 --- a/lib/widget/async_actions/async_action_button.dart +++ b/lib/widget/async_actions/async_action_button.dart @@ -31,6 +31,7 @@ class AsyncActionButton extends StatelessWidget { errorBuilder: errorBuilder, onError: onError, onSuccess: onSuccess, + showInlineError: showInlineError, builder: (context, busy, handler) { final lead = busy ? AppProgressIndicator.small( @@ -38,13 +39,11 @@ class AsyncActionButton extends StatelessWidget { ) : (icon == null ? null : Icon(icon)); final content = lead == null ? child : _leadingLabel(lead, child); - final button = ElevatedButton( + return ElevatedButton( onPressed: handler, style: style, child: content, ); - if (!showInlineError) return button; - return _InlineErrorWrapper(controller: controller, child: button); }, ); } diff --git a/lib/widget/async_actions/async_mixin.dart b/lib/widget/async_actions/async_mixin.dart index 075db21..18aa20d 100644 --- a/lib/widget/async_actions/async_mixin.dart +++ b/lib/widget/async_actions/async_mixin.dart @@ -6,6 +6,7 @@ class _AsyncMixin extends StatefulWidget { final AsyncErrorBuilder? errorBuilder; final void Function(String message)? onError; final VoidCallback? onSuccess; + final bool showInlineError; final Widget Function(BuildContext context, bool busy, VoidCallback? handler) builder; @@ -16,6 +17,7 @@ class _AsyncMixin extends StatefulWidget { this.errorBuilder, this.onError, this.onSuccess, + this.showInlineError = false, }); @override @@ -73,27 +75,27 @@ class _AsyncMixinState extends State<_AsyncMixin> { @override Widget build(BuildContext context) { final busy = _controller.busy; - return widget.builder( + final child = widget.builder( context, busy, busy || widget.onPressed == null ? null : _trigger, ); + if (!widget.showInlineError) return child; + return _InlineErrorWrapper(controller: _controller, child: child); } } class _InlineErrorWrapper extends StatelessWidget { - final AsyncActionController? controller; + final AsyncActionController controller; final Widget child; const _InlineErrorWrapper({required this.controller, required this.child}); @override Widget build(BuildContext context) { - final c = controller; - if (c == null) return child; return AnimatedBuilder( - animation: c, + animation: controller, builder: (context, _) { - final err = c.error; + final err = controller.error; return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/widget/async_actions/async_text_button.dart b/lib/widget/async_actions/async_text_button.dart index 95a0f95..f2f8acb 100644 --- a/lib/widget/async_actions/async_text_button.dart +++ b/lib/widget/async_actions/async_text_button.dart @@ -27,6 +27,7 @@ class AsyncTextButton extends StatelessWidget { errorBuilder: errorBuilder, onError: onError, onSuccess: onSuccess, + showInlineError: showInlineError, builder: (context, busy, handler) { final content = busy ? _leadingLabel( @@ -37,8 +38,7 @@ class AsyncTextButton extends StatelessWidget { ) : child; final button = TextButton(onPressed: handler, child: content); - if (!showInlineError) return button; - return _InlineErrorWrapper(controller: controller, child: button); + return button; }, ); } diff --git a/lib/widget/confirm_dialog.dart b/lib/widget/confirm_dialog.dart index 3e83421..8ef5265 100644 --- a/lib/widget/confirm_dialog.dart +++ b/lib/widget/confirm_dialog.dart @@ -13,6 +13,9 @@ class ConfirmDialog extends StatelessWidget { /// Label of the cancel button. Set to `null` for a single-button dialog /// (only the confirm action is rendered). final String? cancelButton; + + /// Optional: without [onConfirm]/[onConfirmAsync] the dialog only reports + /// the decision via [ask]. final void Function()? onConfirm; final AsyncActionCallback? onConfirmAsync; final AsyncErrorBuilder? errorBuilder; @@ -27,15 +30,17 @@ class ConfirmDialog extends StatelessWidget { this.onConfirm, this.onConfirmAsync, this.errorBuilder, - }) : assert( - onConfirm != null || onConfirmAsync != null, - 'ConfirmDialog requires either onConfirm or onConfirmAsync', - ); + }); void asDialog(BuildContext context) { showDialog(context: context, builder: build); } + /// Resolves to true once confirmed – with [onConfirmAsync] only after it + /// succeeded. + Future ask(BuildContext context) async => + await showDialog(context: context, builder: build) ?? false; + @override Widget build(BuildContext context) => AlertDialog( icon: icon != null ? Icon(icon) : null, @@ -59,8 +64,8 @@ class ConfirmDialog extends StatelessWidget { TextButton( onPressed: () { Haptics.confirm(); - Navigator.of(context).pop(); - onConfirm!(); + Navigator.of(context).pop(true); + onConfirm?.call(); }, child: Text(confirmButton), ), diff --git a/lib/widget_data/widget_data_mapper.dart b/lib/widget_data/widget_data_mapper.dart index b1ef59c..3b29d6b 100644 --- a/lib/widget_data/widget_data_mapper.dart +++ b/lib/widget_data/widget_data_mapper.dart @@ -2,7 +2,6 @@ import 'dart:developer'; import 'dart:ui'; import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart'; -import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms_response.dart'; import '../api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects_response.dart'; import '../api/marianumconnect/queries/timetable_get_timegrid/timetable_get_timegrid_response.dart'; import '../api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart'; @@ -27,14 +26,14 @@ class WidgetDataMapper { static const _weekend = {DateTime.saturday, DateTime.sunday}; static DateTime resolveDayAnchor(DateTime now) { - var candidate = DateTime(now.year, now.month, now.day); + var candidate = now.dateOnly; final shiftToTomorrow = now.hour >= _dayWidgetCutoffHour || _weekend.contains(now.weekday); if (shiftToTomorrow) { - candidate = candidate.add(const Duration(days: 1)); + candidate = candidate.addDays(1); } while (_weekend.contains(candidate.weekday)) { - candidate = candidate.add(const Duration(days: 1)); + candidate = candidate.addDays(1); } return candidate; } @@ -44,10 +43,8 @@ class WidgetDataMapper { /// Monday of the calendar week containing [reference] — no roll-forward, /// unlike [resolveWeekAnchor]. Start of the week payload's 14-day window. - static DateTime startOfCalendarWeek(DateTime reference) { - final monday = reference.subtract(Duration(days: reference.weekday - 1)); - return DateTime(monday.year, monday.month, monday.day); - } + static DateTime startOfCalendarWeek(DateTime reference) => + reference.mondayOfWeek; /// Days covered by the week payload: current calendar week + the next, so /// native renderers can roll the view forward without fresh data. @@ -58,7 +55,6 @@ class WidgetDataMapper { required DateTime now, required Iterable lessons, required TimetableGetSubjectsResponse? subjects, - required TimetableGetRoomsResponse? rooms, required TimetableGetHolidaysResponse? holidays, TimetableGetTimegridResponse? timegrid, GetCustomTimetableEventResponse? customEvents, @@ -69,7 +65,6 @@ class WidgetDataMapper { now: now, lessons: lessons, subjects: subjects, - rooms: rooms, holidays: holidays, timegrid: timegrid, customEvents: customEvents, @@ -80,7 +75,6 @@ class WidgetDataMapper { now: now, lessons: lessons, subjects: subjects, - rooms: rooms, holidays: holidays, timegrid: timegrid, customEvents: customEvents, @@ -93,7 +87,6 @@ class WidgetDataMapper { required DateTime now, required Iterable lessons, required TimetableGetSubjectsResponse? subjects, - required TimetableGetRoomsResponse? rooms, required TimetableGetHolidaysResponse? holidays, TimetableGetTimegridResponse? timegrid, GetCustomTimetableEventResponse? customEvents, @@ -103,7 +96,7 @@ class WidgetDataMapper { final anchor = resolveDayAnchor(now); final holiday = _findHoliday(anchor, holidays); final dayStart = anchor; - final dayEnd = anchor.add(const Duration(days: 1)); + final dayEnd = anchor.addDays(1); final dayLessons = lessons.where((l) => l.date.isSameDay(anchor)).toList(); final source = connectDoubleLessons ? LessonMerger.merge(dayLessons) @@ -115,7 +108,6 @@ class WidgetDataMapper { source, now, subjects, - rooms, customEvents, dayStart, dayEnd, @@ -131,7 +123,6 @@ class WidgetDataMapper { required DateTime now, required Iterable lessons, required TimetableGetSubjectsResponse? subjects, - required TimetableGetRoomsResponse? rooms, required TimetableGetHolidaysResponse? holidays, TimetableGetTimegridResponse? timegrid, GetCustomTimetableEventResponse? customEvents, @@ -143,7 +134,7 @@ class WidgetDataMapper { // (possibly rolled-forward) week anchor: on Friday evening the payload // must still contain today for renderers that derive day slices. final windowStart = startOfCalendarWeek(now); - final endExclusive = windowStart.add(const Duration(days: weekWindowDays)); + final endExclusive = windowStart.addDays(weekWindowDays); final weekLessons = lessons.where((l) { final dt = l.startDateTime; return !dt.isBefore(windowStart) && dt.isBefore(endExclusive); @@ -170,7 +161,6 @@ class WidgetDataMapper { source, now, subjects, - rooms, customEvents, windowStart, endExclusive, @@ -340,7 +330,6 @@ class WidgetDataMapper { Iterable source, DateTime now, TimetableGetSubjectsResponse? subjects, - TimetableGetRoomsResponse? rooms, GetCustomTimetableEventResponse? customEvents, DateTime start, DateTime endExclusive, { @@ -352,7 +341,6 @@ class WidgetDataMapper { lesson, now, subjects, - rooms, showClassInsteadOfTeacher: showClassInsteadOfTeacher, ), ..._expandCustomEvents(customEvents, start, endExclusive), @@ -363,8 +351,7 @@ class WidgetDataMapper { static WidgetLesson _mapLesson( McTimetableEntry lesson, DateTime now, - TimetableGetSubjectsResponse? subjects, - TimetableGetRoomsResponse? rooms, { + TimetableGetSubjectsResponse? subjects, { required bool showClassInsteadOfTeacher, }) { final start = lesson.startDateTime; @@ -389,15 +376,6 @@ class WidgetDataMapper { .where((s) => s.shortName == subjectShortRaw) .firstOrNull; final subjectLong = subject?.longName; - final roomShort = lesson.rooms.firstOrNull; - var roomName = roomShort; - if (rooms != null && roomShort != null) { - roomName = rooms.result - .where((r) => r.shortName == roomShort) - .firstOrNull - ?.shortName ?? - roomName; - } final teacher = lesson.teachers.firstOrNull; // Lehrerpläne: Klasse in den Teacher-Slot mappen, damit die nativen // Renderer unverändert bleiben. Klassenlose Einträge (Aufsichten) lassen @@ -413,7 +391,7 @@ class WidgetDataMapper { end: end, subjectShort: subjectShort, subjectLong: subjectLong, - room: roomName, + room: lesson.rooms.firstOrNull, teacher: teacherName, originalTeacher: originalTeacher, status: _mapStatus(lessonStatus), @@ -562,11 +540,7 @@ class WidgetDataMapper { final actualEnd = occurrenceStart.add(duration); var segmentStart = occurrenceStart; while (segmentStart.isBefore(actualEnd)) { - final nextMidnight = DateTime( - segmentStart.year, - segmentStart.month, - segmentStart.day, - ).add(const Duration(days: 1)); + final nextMidnight = segmentStart.dateOnly.addDays(1); final segmentEnd = actualEnd.isBefore(nextMidnight) ? actualEnd : nextMidnight.subtract(const Duration(minutes: 1)); diff --git a/lib/widget_data/widget_publisher.dart b/lib/widget_data/widget_publisher.dart index 046cad5..f1515ae 100644 --- a/lib/widget_data/widget_publisher.dart +++ b/lib/widget_data/widget_publisher.dart @@ -85,7 +85,6 @@ class WidgetPublisher { now: now, lessons: state.getAllKnownLessons(), subjects: state.subjects, - rooms: state.rooms, holidays: state.schoolHolidays, timegrid: state.timegrid, customEvents: state.customEvents, diff --git a/test/widget_data/widget_data_mapper_test.dart b/test/widget_data/widget_data_mapper_test.dart index c23d286..575229a 100644 --- a/test/widget_data/widget_data_mapper_test.dart +++ b/test/widget_data/widget_data_mapper_test.dart @@ -97,6 +97,14 @@ void main() { ); expect(anchor, DateTime(2026, 5, 11)); }); + + // Only discriminating when run in a DST zone (e.g. TZ=Europe/Berlin). + test('stays at midnight across the DST fall-back weekend', () { + final anchor = WidgetDataMapper.resolveDayAnchor( + DateTime(2026, 10, 25, 12), + ); + expect(anchor, DateTime(2026, 10, 26)); + }); }); group('resolveWeekAnchor', () { @@ -137,7 +145,6 @@ void main() { now: now, lessons: lessons, subjects: null, - rooms: null, holidays: null, ); expect(data.lessons, hasLength(1)); @@ -174,7 +181,6 @@ void main() { now: now, lessons: lessons, subjects: null, - rooms: null, holidays: null, ); expect(data.lessons.map((l) => l.status).toList(), [ @@ -199,7 +205,6 @@ void main() { now: now, lessons: const [], subjects: null, - rooms: null, holidays: holidays, ); expect(data.isHoliday, isTrue); @@ -231,7 +236,6 @@ void main() { now: now, lessons: lessons, subjects: null, - rooms: null, holidays: null, ); expect(data.lessons.map((l) => l.subjectShort).toList(), [ @@ -278,7 +282,6 @@ void main() { now: now, lessons: lessons, subjects: null, - rooms: null, holidays: null, customEvents: events, ); @@ -315,7 +318,6 @@ void main() { now: now, lessons: lessons, subjects: null, - rooms: null, holidays: null, customEvents: events, ); @@ -336,7 +338,6 @@ void main() { now: DateTime(2026, 5, 5, 10), lessons: const [], subjects: null, - rooms: null, holidays: null, customEvents: events, ); @@ -376,7 +377,6 @@ void main() { now: now, lessons: lessons, subjects: null, - rooms: null, holidays: null, customEvents: events, ); @@ -411,7 +411,6 @@ void main() { now: now, lessons: [lesson], subjects: null, - rooms: null, holidays: null, showClassInsteadOfTeacher: true, ).lessons.single; @@ -458,7 +457,6 @@ void main() { now: now, lessons: [lesson], subjects: subjects, - rooms: null, holidays: null, ).lessons.single; @@ -544,7 +542,6 @@ void main() { now: now, lessons: const [], subjects: null, - rooms: null, holidays: null, customEvents: GetCustomTimetableEventResponse([ _event( @@ -599,7 +596,6 @@ void main() { now: now, lessons: lessons, subjects: null, - rooms: null, holidays: null, ); expect(data.anchorDate, DateTime(2026, 5, 4)); @@ -633,7 +629,6 @@ void main() { now: fridayEvening, lessons: lessons, subjects: null, - rooms: null, holidays: null, ); expect(data.anchorDate, DateTime(2026, 5, 11)); @@ -655,7 +650,6 @@ void main() { now: now, lessons: const [], subjects: null, - rooms: null, holidays: holidays, ); expect(data.days, hasLength(WidgetDataMapper.weekWindowDays)); @@ -687,7 +681,6 @@ void main() { now: now, lessons: const [], subjects: null, - rooms: null, holidays: holidays, ); expect(data.isHoliday, isTrue);