Skip to content

feat: Kanban board#41

Merged
mcquenji merged 22 commits intomainfrom
lpa-53
Sep 23, 2025
Merged

feat: Kanban board#41
mcquenji merged 22 commits intomainfrom
lpa-53

Conversation

@MasterMarcoHD
Copy link
Contributor

@MasterMarcoHD MasterMarcoHD commented Sep 21, 2025

This pull request introduces support for a Kanban board feature, updates localization for new and existing UI elements, and upgrades the project's Flutter SDK version. The changes also include adjustments to code generation settings and minor code cleanups. Below are the most important updates grouped by theme:

Kanban Board Feature Integration

  • Added Kanban board-related exports in lib/eduplanner.dart to expose Kanban functionality throughout the app.
  • Introduced extensive Kanban-related localization keys and descriptions in both l10n/en.arb and l10n/de.arb, as well as corresponding getters in lib/gen/l10n/app_localizations.dart. This covers labels, settings, and UI text for the Kanban board. [1] [2] [3]

Localization Improvements

  • Added new localization keys for the "Duplicate" button and slot editing features, and updated several existing keys to use "Students" instead of "Size" for clarity in both English and German ARB files and in the localization Dart class. [1] [2] [3] [4] [5] [6] [7] [8] [9]
  • Added a new label for adding course mappings in slot editing to both ARB files and the Dart localization class. [1] [2] [3]

Flutter SDK and Tooling Updates

  • Upgraded the Flutter SDK version from 3.29.0 to 3.35.0 in .fvmrc and updated the VSCode Dart SDK path accordingly. [1] [2]

Code Generation and Build Configuration

  • Updated build.yaml to restrict code generation (json_serializable, source_gen, freezed) to domain model directories for better performance and organization.
  • Removed the synthetic-package field from l10n.yaml for localization configuration.

Code Cleanup and Maintenance

  • Added @Deprecated annotations to all Echidna licensing-related constants in lib/config/echidna.dart to indicate licensing is currently disabled.
  • Minor formatting improvements in the localization Dart class for readability. [1] [2] [3]

These changes collectively add Kanban board support, improve clarity and consistency in localization, and keep the project up-to-date with the latest Flutter tooling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 21, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Introduced a Kanban board with backlog toggle, drag-and-drop between columns, and desktop sidebar entry.
    • Added Kanban settings: auto-move rules and column color toggle.
    • Added “Duplicate” action for slots.
  • Improvements

    • Revamped General Settings with a cleaner, unified layout.
    • Enhanced theming: new TabBar styling; minor color updates.
    • Better animations stability; preserved cursor position in number inputs.
    • Slot Master uses tabbed navigation and time-based grouping.
    • Added course/task quick lookups.
  • Localization

    • New and updated strings (EN/DE) for Kanban and slot labels.
  • Chores

    • Bumped Flutter and dependencies; removed license badge.
  • Tests

    • Added Kanban module test scaffold.

Walkthrough

Adds a Kanban feature (domain, infra, presentation, DI, routing, UI, tests), Kanban user preferences, settings UI, slot improvements (duplication, mappings, tabbed master), numerous generated-model JsonKey annotation updates, Echidna licensing removal, dependency/SDK bumps, and related repository/datasource changes.

Changes

Cohort / File(s) Change Summary
Tooling & build
\.fvmrc, .vscode/settings.json, pubspec.yaml, build.yaml, l10n.yaml
Flutter SDK bumped (3.29.0 → 3.35.0), VSCode SDK path updated, dependency versions changed and echidna_flutter commented out, build.yaml generator generate_for targets added, synthetic-package: false removed.
Localization
l10n/en.arb, l10n/de.arb
Added Kanban strings, global_duplicate, slots_edit_addCourseMapping; updated "Size"→"Students" entries and annotations/placeholders.
Echidna / licensing removal
lib/config/echidna.dart, lib/main.dart, lib/src/app/app.dart, lib/src/app/presentation/widgets/title_bar.dart, lib/src/auth/auth.dart, lib/src/auth/presentation/repositories/echidna_user_repository.dart
Deprecation annotation on Echidna constants, removed/commented Echidna imports/initialization, disabled license-badge UI, and commented out Echidna module/repository wiring.
Public surface / module wiring
lib/eduplanner.dart, lib/src/kanban/kanban.dart, lib/src/app/presentation/widgets/sidebar.dart, lib/src/app/presentation/widgets/sidebar_target.dart
Exported Kanban module; added KanbanModule to routes; added desktop sidebar item /kanban/ gated for students; introduced iconTransformer on SidebarTarget.
Kanban domain models & barrels
lib/src/kanban/domain/models/kanban_board.*, lib/src/kanban/domain/models/models.dart, lib/src/kanban/domain/datasources/kanban_datasource.dart, lib/src/kanban/domain/datasources/datasources.dart, lib/src/kanban/domain/domain.dart
New KanbanBoard model and KanbanColumn enum (Freezed + json), new abstract KanbanDatasource (getBoard, move), and domain barrel exports.
Kanban infra
lib/src/kanban/infra/datasources/local_kanban_datasource.dart, lib/src/kanban/infra/datasources/moodle_kanban_datasource.dart, lib/src/kanban/infra/datasources/datasources.dart, lib/src/kanban/infra/infra.dart, lib/src/kanban/infra/services/services.dart
Implemented LocalKanbanDatasource (in-memory) and MoodleKanbanDatasource (API wiring, move/getBoard), added infra barrels and placeholder services file.
Kanban presentation
lib/src/kanban/presentation/repositories/kanban_repository.dart, lib/src/kanban/presentation/screens/kanban_screen.dart, lib/src/kanban/presentation/widgets/kanban_card.dart, lib/src/kanban/presentation/widgets/kanban_column_widget.dart, lib/src/kanban/presentation/presentation.dart, test/kanban_module_test.dart
New KanbanRepository (load/move/autoMove), KanbanScreen UI, KanbanCard, KanbanColumnWidget, presentation barrels, and test scaffold.
User model & preferences
lib/src/auth/domain/models/user.dart, lib/src/auth/domain/models/user.freezed.dart, lib/src/auth/domain/models/user.g.dart
Added showColumnColors and three autoMove...To Kanban preference fields with converters and updated generated serialization.
Auth / user flows
lib/src/auth/presentation/repositories/user_repository.dart, lib/src/auth/infra/datasources/moodle_user_datasource.dart, lib/src/auth/utils/kanban_column_converter_utils.dart, lib/src/auth/utils/utils.dart
Added setters for Kanban prefs, Sentry span propagation, Moodle user update accepts new keys, added KanbanColumnConverter and exported it.
Settings UI / framework
lib/src/settings/presentation/widgets/generic_settings.dart, lib/src/settings/presentation/widgets/general_settings.dart, lib/src/settings/presentation/widgets/kanban_settings.dart, lib/src/settings/presentation/screens/settings_screen.dart, lib/src/settings/presentation/widgets/widgets.dart
New GenericSettings framework (Icon/Boolean/Enum items), GeneralSettings refactored to use it, new KanbanSettings and inclusion in desktop settings when student-capable.
Slots: UI & dialogs
lib/src/slots/presentation/screens/slot_master_screen.dart, lib/src/slots/presentation/widgets/edit_slot_dialog.dart, lib/src/slots/presentation/widgets/slot_master_widget.dart, lib/src/slots/presentation/widgets/number_spinner.dart, lib/src/slots/presentation/repositories/slot_master_slots_repository.dart
Slot Master tabbed by weekday with TabController; EditSlotDialog gains startUnit, duplicate and supports multiple MappingElements; duplicate action; NumberSpinner preserves selection; repository adds groupByStartUnit().
Moodle repos
lib/src/moodle/presentation/repositories/moodle_courses_repository.dart, lib/src/moodle/presentation/repositories/moodle_tasks_repository.dart
Added getById/getByCmid helpers and extended task filtering to support cmids.
Calendar & serializers
lib/src/calendar/domain/models/*.freezed.dart, lib/src/calendar/domain/models/calendar_plan.g.dart, lib/src/calendar/presentation/repositories/calendar_plan_repository.dart, lib/src/calendar/presentation/widgets/plan_popup.dart
Unified Freezed JsonKey annotations to ignore: true for copyWith/hashCode, some toJson implementations now emit raw nested values (removed per-item toJson), filterDeadlines logic inverted, wrapped PlanPopup with TabBarTheme.
Generated models: JsonKey unification
multiple *.freezed.dart and some *.g.dart (auth, moodle, slots, statistics, notifications, theming, etc.)
Replaced @JsonKey(includeFromJson: false, includeToJson: false) with @JsonKey(ignore: true) for copyWith/hashCode across many generated files; some serializers now omit nested .toJson() calls.
App utilities & theming
lib/src/app/utils/animate_utils.dart, lib/src/theming/infra/services/material_theme_generator_service.dart, lib/src/theming/domain/services/theme_generator_service.dart
AnimateUtils/AnimateX signatures accept keys for per-item keys; assertion message adjusted. Theme generator: CardThemeCardThemeData, added TabBarThemeData, and hid Flutter Theme symbol to avoid clash.
Minor UI tweaks
lib/src/app/presentation/widgets/sidebar.dart, lib/src/app/presentation/widgets/title_bar.dart, lib/src/settings/presentation/widgets/feedback_widget.dart, lib/src/calendar/presentation/widgets/plan_popup.dart
Sidebar surface color usage switched to theme colorScheme.surface; title_bar license badge disabled; feedback dropdown alignment tweaked; PlanPopup wrapped with TabBarTheme.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant UI as KanbanScreen
  participant KR as KanbanRepository
  participant DS as KanbanDatasource
  participant API as ApiService (Moodle)

  U->>UI: Open /kanban/
  UI->>KR: build(trigger)
  KR->>DS: getBoard(token, backlogCandidates)
  alt Moodle datasource
    DS->>API: local_lbplanner_kanban_get_board
    API-->>DS: Board JSON
    DS-->>KR: KanbanBoard
  else Local datasource
    DS-->>KR: KanbanBoard (static)
  end
  KR-->>UI: State with KanbanBoard
  UI-->>U: Render columns (optional backlog)
Loading
sequenceDiagram
  autonumber
  actor U as User (drag)
  participant UI as KanbanColumnWidget
  participant KR as KanbanRepository
  participant DS as KanbanDatasource
  participant A as Analytics
  participant S as Sentry

  U->>UI: Drag task to column
  UI->>KR: move(taskId, to)
  KR->>S: startSpan("kanban.move")
  KR->>KR: Optimistically update board
  KR->>DS: move(token, taskId, to)
  DS-->>KR: ok
  KR->>A: event(move_task)
  KR-->>UI: refreshed state
  KR->>S: finishSpan
Loading
sequenceDiagram
  autonumber
  participant KR as KanbanRepository
  participant UR as UserRepository
  participant TR as MoodleTasksRepository
  participant DS as KanbanDatasource
  participant S as Sentry

  KR->>UR: read auto-move prefs
  KR->>TR: get tasks by status
  KR->>S: startSpan("kanban.automove")
  loop per source column
    KR->>KR: derive target mapping
    KR->>DS: move(token, taskId, to) (batch/parallel)
  end
  KR->>S: finishSpan
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

I hop between columns, todo to done,
With carrots of config and settings spun.
A backlog I hide, then show with a grin—
Drag a task over, and watch it hop in.
New tabs, new themes, a garden re-sown—
Kanban is live; the burrow has grown! 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title “feat: kanban board” concisely and accurately highlights the primary addition of the pull request, namely the Kanban board feature, without extraneous detail, making it clear to reviewers what the main change entails.
Description Check ✅ Passed The description clearly outlines the scope of the changes—including Kanban board integration, localization updates, Flutter SDK upgrade, build configuration tweaks, and code cleanups—and directly corresponds to the modifications in the diff without introducing unrelated information.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch lpa-53

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@MasterMarcoHD MasterMarcoHD changed the title lpa-53 chore: merge lpa-53 Sep 21, 2025
@necodeit
Copy link

necodeit bot commented Sep 21, 2025

Analysis Report for 4978af7

  • Infos: 1
  • Warnings: 0
  • Errors: 0
Click to see the full report
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

1 similar comment
@necodeit
Copy link

necodeit bot commented Sep 21, 2025

Analysis Report for 4978af7

  • Infos: 1
  • Warnings: 0
  • Errors: 0
Click to see the full report
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 74

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (25)
lib/src/theming/domain/services/theme_generator_service.dart (2)

23-26: left/right/top/bottom params are unused—either remove or make them effective.

They currently have no effect and can mislead callers.

Option A (implement side gating):

 SmoothRectangleBorder squircle({
   BorderSide side = BorderSide.none,
   double radius = 10,
   bool topLeft = true,
   bool topRight = true,
   bool bottomLeft = true,
   bool bottomRight = true,
   bool left = true,
   bool right = true,
   bool top = true,
   bool bottom = true,
   BorderAlign borderAlign = BorderAlign.inside,
 }) {
   final _radius = SmoothRadius(
     cornerRadius: radius,
     cornerSmoothing: 0.9,
   );

+  final tl = (topLeft && left && top) ? _radius : SmoothRadius.zero;
+  final tr = (topRight && right && top) ? _radius : SmoothRadius.zero;
+  final bl = (bottomLeft && left && bottom) ? _radius : SmoothRadius.zero;
+  final br = (bottomRight && right && bottom) ? _radius : SmoothRadius.zero;

   return SmoothRectangleBorder(
     side: side,
     borderAlign: borderAlign,
     borderRadius: SmoothBorderRadius.only(
-      topLeft: topLeft ? _radius : SmoothRadius.zero,
-      topRight: topRight ? _radius : SmoothRadius.zero,
-      bottomLeft: bottomLeft ? _radius : SmoothRadius.zero,
-      bottomRight: bottomRight ? _radius : SmoothRadius.zero,
+      topLeft: tl,
+      topRight: tr,
+      bottomLeft: bl,
+      bottomRight: br,
     ),
   );
 }

Option B (simplify API): drop the side booleans if not needed.

Also applies to: 37-42


3-13: Do not hide Flutter's Theme; rename the generic type to TTheme

Hiding Theme prevents calling Theme.of(context) and creates ambiguity — rename the generic type parameter and restore the normal Material import.

-import 'package:flutter/material.dart' hide Theme;
+import 'package:flutter/material.dart';

-/// A service that generates a theme of type [Theme] based on a [ThemeBase].
+/// A service that generates a theme of type [TTheme] based on a [ThemeBase].
-abstract class ThemeGeneratorService<Theme> extends Service {
+abstract class ThemeGeneratorService<TTheme> extends Service {
  @override
  String get name => 'ThemeGenerator';

  /// Generates a theme based on the provided [themeBase].
-  Theme generateTheme(ThemeBase themeBase);
+  TTheme generateTheme(ThemeBase themeBase);
}
lib/src/theming/infra/services/material_theme_generator_service.dart (1)

46-51: Fix type: use CardTheme, not CardThemeData (compile-time error).

ThemeData.cardTheme expects a CardTheme. CardThemeData is not a Flutter SDK type for cards and will fail analysis/compile.

Apply this diff:

-      cardTheme: CardThemeData(
+      cardTheme: CardTheme(
         color: themeBase.primaryColor,
         elevation: 6,
         margin: EdgeInsets.zero,
         shape: squircle(),
       ),
lib/src/calendar/presentation/repositories/calendar_plan_repository.dart (6)

340-351: Date-only vs DateTime comparisons can silently mis-filter. Normalize input dates.

_start/_end are truncated to day, but start/end/betweenStart/betweenEnd aren’t. Passing a DateTime with time-of-day will fail equality/bounds checks unexpectedly.

Apply normalization and use the normalized values in comparisons:

   final now = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day);
+  // Normalize incoming filters to date-only to avoid time-of-day mismatches
+  final DateTime? s = start != null ? DateTime(start.year, start.month, start.day) : null;
+  final DateTime? e = end != null ? DateTime(end.year, end.month, end.day) : null;
+  final DateTime? bs = betweenStart != null ? DateTime(betweenStart.year, betweenStart.month, betweenStart.day) : null;
+  final DateTime? be = betweenEnd != null ? DateTime(betweenEnd.year, betweenEnd.month, betweenEnd.day) : null;
@@
-      if (start != null && _start != start) return false;
-      if (end != null && _end != end) return false;
+      if (s != null && _start != s) return false;
+      if (e != null && _end != e) return false;
@@
-      if (betweenStart != null && _start.isBefore(betweenStart)) return false;
-      if (betweenEnd != null && _end.isAfter(betweenEnd)) return false;
+      if (bs != null && _start.isBefore(bs)) return false;
+      if (be != null && _end.isAfter(be)) return false;

Also applies to: 347-351


365-368: Possible crash: firstWhere throws if the current user isn’t in members.

Use a safe lookup and default to read.

-  PlanMemberAccessType get accessType => state.hasData && _user.state.hasData
-      ? state.requireData.members.firstWhere((member) => member.id == _user.state.requireData.id).accessType
-      : PlanMemberAccessType.read;
+  PlanMemberAccessType get accessType {
+    if (!(state.hasData && _user.state.hasData)) return PlanMemberAccessType.read;
+    final idx = state.requireData.members.indexWhere((m) => m.id == _user.state.requireData.id);
+    return idx != -1 ? state.requireData.members[idx].accessType : PlanMemberAccessType.read;
+  }

370-371: Typo in public API: canModifiy. Add canModify alias and deprecate old.

Keeps backward compatibility while fixing the name.

-  bool get canModifiy => accessType == PlanMemberAccessType.write || accessType == PlanMemberAccessType.owner;
+  @Deprecated('Use canModify')
+  bool get canModifiy => accessType == PlanMemberAccessType.write || accessType == PlanMemberAccessType.owner;
+  bool get canModify => accessType == PlanMemberAccessType.write || accessType == PlanMemberAccessType.owner;

61-61: Log typo.

“chached” → “cached”.

-      log('Cannot load plan: No internet connection. Skipping to preserve chached data.');
+      log('Cannot load plan: No internet connection. Skipping to preserve cached data.');

50-59: Guard for missing lb_planner_api token.

tokens.isEmpty doesn’t guarantee the specific key exists; calling getPlan with null/empty can fail.

-    final tokens = waitForData(_auth);
+    final tokens = waitForData(_auth);
+    final token = tokens[Webservice.lb_planner_api];
@@
-    if (tokens.isEmpty) {
-      log('Cannot load plan: No tokens provided.');
+    if (tokens.isEmpty || token == null || (token is String && token.isEmpty)) {
+      log('Cannot load plan: Missing token for ${Webservice.lb_planner_api}.');
       return;
     }
@@
-      () => _plan.getPlan(tokens[Webservice.lb_planner_api]),
+      () => _plan.getPlan(token as String),

Also applies to: 68-75


118-124: Validate date range when setting a deadline.

Prevent inverted ranges early.

     final transaction = startTransaction('setDeadline');
 
     try {
+      if (end.isBefore(start)) {
+        log('Cannot set deadline: end is before start.');
+        return;
+      }
       final deadline = PlanDeadline(
         id: taskId,
         start: start,
         end: end,
       );
lib/src/slots/presentation/widgets/number_spinner.dart (6)

139-147: Clamping without setState leaves UI out-of-sync

When clamping to min/max you update value but not the field, leading to mismatch. Update UI and move caret to end.

Apply this outside the changed hunk:

if (widget.max != null && number > widget.max!) {
  final clamped = widget.max!;
  setState(() {
    value = clamped;
    final text = clamped.toString();
    controller.value = TextEditingValue(
      text: text,
      selection: TextSelection.collapsed(offset: text.length),
    );
  });
  return;
}

if (widget.min != null && number < widget.min!) {
  final clamped = widget.min!;
  setState(() {
    value = clamped;
    final text = clamped.toString();
    controller.value = TextEditingValue(
      text: text,
      selection: TextSelection.collapsed(offset: text.length),
    );
  });
  return;
}

63-69: Generic default 0 as T can crash for T=double

_value = widget.initialValue ?? widget.min ?? 0 as T; throws when T is double (int→double cast). Initialize by type.

@override
void initState() {
  super.initState();
  if (widget.initialValue != null) {
    _value = widget.initialValue!;
  } else if (widget.min != null) {
    _value = widget.min!;
  } else if (T == double) {
    _value = 0.0 as T;
  } else {
    _value = 0 as T; // int/num
  }
}

51-59: Dispose the controller

Leak risk without disposing TextEditingController.

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

20-25: Doc comments mention a “controller” prop that doesn’t exist

These comments are misleading for API users.

-  /// The controller to use for the spinner. If null, the default behavior is to create a new controller.
+  /// Initial value for the spinner. If null, defaults to `min` or 0.
   final T? initialValue;
 
-  /// The controller to use for the spinner. If null, the default behavior is to create a new controller.
+  /// Callback invoked when the value changes.
   final void Function(T)? onChanged;

110-127: Use the computed enabled consistently

You compute enabled but pass widget.enabled to TextField. Use the same boolean for consistency.

-      enabled: widget.enabled,
+      enabled: enabled,

157-158: Formatter may block decimals/negatives despite T extends num

FilteringTextInputFormatter.digitsOnly forbids - and .; if T can be double or negative int, input becomes impossible.

Options:

  • Provide formatters based on T (allow - and . for double).
  • Document that only non-negative integers are supported here.
lib/src/app/presentation/widgets/title_bar.dart (1)

199-205: Replace large commented blocks with a feature flag or helper.

Long commented sections reduce readability. Gate the license badge behind a constant/config or extract buildLicenseBadge() that returns SizedBox.shrink() when disabled.

Apply this minimal pattern:

+const bool kLicenseBadgeEnabled = false;
 ...
-                  // if (showLicenseBadge) ...
+                  if (kLicenseBadgeEnabled) ...[
+                    Spacing.smallHorizontal(),
+                    /* badge widget */
+                    Spacing.smallHorizontal(),
+                  ],

Also applies to: 234-255, 352-355, 387-409

lib/src/slots/domain/models/reservation.freezed.dart (1)

34-36: Doc comment reads incorrectly for userId.

“The id of this reservation is for.” → likely “The id of the user this reservation is for.”

Please fix in the source model (reservation.dart), not the generated file. For example:

/// The id of the user this reservation is for.
@JsonKey(name: 'userid')
final int userId;
lib/src/moodle/domain/models/moodle_course.freezed.dart (1)

38-41: Grammar nit in docstring.

“Whether the user want's the app…” → “Whether the user wants the app…”

Please update in moodle_course.dart so regenerated docs are correct.

lib/src/moodle/domain/models/moodle_task.freezed.dart (1)

27-30: Grammar nit in docstring.

“within it's parent” → “within its parent”.

Please change in moodle_task.dart (source) so generation reflects it.

l10n/de.arb (2)

552-555: Typo: “Abonament” → “Abonnement”.

User-facing text; fix before release.

Apply:

-    "settings_general_manageSubscription": "Abonament verwalten",
+    "settings_general_manageSubscription": "Abonnement verwalten",

718-726: Grammar: “Desktop geräte” → “Desktopgeräte”.

Compound noun spelling; user-facing.

Apply:

-    "mobile": "Sorry, {appname} ist derzeit nur für Desktop geräte verfügbar. Bitte öffne die App auf einem Desktop-Gerät, um fortzufahren.",
+    "mobile": "Sorry, {appname} ist derzeit nur für Desktopgeräte verfügbar. Bitte öffne die App auf einem Desktop-Gerät, um fortzufahren.",
lib/src/settings/presentation/screens/settings_screen.dart (1)

71-74: Optional: add Kanban settings to mobile for parity.

Consider surfacing KanbanSettings on mobile behind the same capability gate.

Apply after ThemesSettings:

   SizedBox(child: const GeneralSettings().stretch()),
   const ThemesSettings().stretch(),
+  if (capabilities.hasStudent)
+    SizedBox(child: const KanbanSettings().stretch()),
lib/src/auth/presentation/repositories/user_repository.dart (1)

91-115: Optimistic update lacks rollback on failure.

If the server update fails, state remains patched. Capture the previous state and revert on error.

Apply this diff:

   Future<void> _updateUser(User patch, ISentrySpan span) async {
-    log('Updating user to $patch');
+    log('Updating user to $patch');
     if (!state.hasData) {
       log('User is not loaded yet. Aborting.');
       return;
     }
-    data(patch);
+    final prev = state.requireData;
+    data(patch);
     final transaction = span.startChild('updateUser');
     await guard(
       () => _userDatasource.updateUser(
         _auth.state.requireData[Webservice.lb_planner_api],
         patch,
       ),
       onError: (e, s) {
         log('Failed to update user', e, s);
         transaction.internalError(e);
+        // rollback
+        data(prev);
       },
       onData: (user) => log('User updated successfully'),
     );
     await transaction.commit();
   }
lib/src/settings/presentation/widgets/general_settings.dart (1)

20-35: Remove unused state and stubs (dead code).

checkingUpdates/clearingCache/deletingProfile and their methods aren’t used in UI.

Apply this diff:

-class _GeneralSettingsState extends State<GeneralSettings> {
-  bool checkingUpdates = false;
-  bool clearingCache = false;
-  bool deletingProfile = false;
-
-  Future<void> checkUpdates() async {
-    if (checkingUpdates) {
-      return;
-    }
-    checkingUpdates = true;
-    //checkUpdates
-    checkingUpdates = false;
-  }
-
-  Future<void> clearCache() async {
-    if (clearingCache) {
-      return;
-    }
-    clearingCache = true;
-    //clearCache
-    clearingCache = false;
-  }
-
-  Future<void> deleteProfile() async {
-    if (deletingProfile) {
-      return;
-    }
-    final user = context.read<UserRepository>();
-    deletingProfile = true;
-    await user.deleteUser();
-    deletingProfile = false;
-  }
+class _GeneralSettingsState extends State<GeneralSettings> {

Also applies to: 37-47, 49-61

lib/src/moodle/presentation/repositories/moodle_tasks_repository.dart (1)

44-60: Docs drift: include courseIds/taskIds/cmids in the filter docs.

The signature supports more filters than the bullets list mentions.

Example doc tweak:

  • Add bullets for courseIds, taskIds, and cmids.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d1daf6 and 4978af7.

⛔ Files ignored due to path filters (4)
  • lib/gen/l10n/app_localizations.dart is excluded by !**/gen/**
  • lib/gen/l10n/app_localizations_de.dart is excluded by !**/gen/**
  • lib/gen/l10n/app_localizations_en.dart is excluded by !**/gen/**
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (85)
  • .fvmrc (1 hunks)
  • .vscode/settings.json (1 hunks)
  • build.yaml (1 hunks)
  • l10n.yaml (0 hunks)
  • l10n/de.arb (5 hunks)
  • l10n/en.arb (5 hunks)
  • lib/config/echidna.dart (1 hunks)
  • lib/eduplanner.dart (1 hunks)
  • lib/main.dart (1 hunks)
  • lib/src/app/app.dart (2 hunks)
  • lib/src/app/presentation/widgets/sidebar.dart (3 hunks)
  • lib/src/app/presentation/widgets/sidebar_target.dart (3 hunks)
  • lib/src/app/presentation/widgets/title_bar.dart (4 hunks)
  • lib/src/app/utils/animate_utils.dart (3 hunks)
  • lib/src/auth/auth.dart (2 hunks)
  • lib/src/auth/domain/models/token.freezed.dart (3 hunks)
  • lib/src/auth/domain/models/user.dart (1 hunks)
  • lib/src/auth/domain/models/user.freezed.dart (12 hunks)
  • lib/src/auth/domain/models/user.g.dart (2 hunks)
  • lib/src/auth/infra/datasources/moodle_user_datasource.dart (1 hunks)
  • lib/src/auth/presentation/repositories/echidna_user_repository.dart (1 hunks)
  • lib/src/auth/presentation/repositories/user_repository.dart (8 hunks)
  • lib/src/auth/utils/kanban_column_converter_utils.dart (1 hunks)
  • lib/src/auth/utils/utils.dart (1 hunks)
  • lib/src/calendar/domain/models/calendar_plan.freezed.dart (4 hunks)
  • lib/src/calendar/domain/models/calendar_plan.g.dart (1 hunks)
  • lib/src/calendar/domain/models/plan_deadline.freezed.dart (3 hunks)
  • lib/src/calendar/domain/models/plan_invite.freezed.dart (3 hunks)
  • lib/src/calendar/domain/models/plan_member.freezed.dart (3 hunks)
  • lib/src/calendar/presentation/repositories/calendar_plan_repository.dart (1 hunks)
  • lib/src/calendar/presentation/widgets/plan_popup.dart (1 hunks)
  • lib/src/kanban/domain/datasources/datasources.dart (1 hunks)
  • lib/src/kanban/domain/datasources/kanban_datasource.dart (1 hunks)
  • lib/src/kanban/domain/domain.dart (1 hunks)
  • lib/src/kanban/domain/models/kanban_board.dart (1 hunks)
  • lib/src/kanban/domain/models/kanban_board.freezed.dart (1 hunks)
  • lib/src/kanban/domain/models/kanban_board.g.dart (1 hunks)
  • lib/src/kanban/domain/models/models.dart (1 hunks)
  • lib/src/kanban/domain/services/services.dart (1 hunks)
  • lib/src/kanban/infra/datasources/datasources.dart (1 hunks)
  • lib/src/kanban/infra/datasources/local_kanban_datasource.dart (1 hunks)
  • lib/src/kanban/infra/datasources/moodle_kanban_datasource.dart (1 hunks)
  • lib/src/kanban/infra/infra.dart (1 hunks)
  • lib/src/kanban/infra/services/services.dart (1 hunks)
  • lib/src/kanban/kanban.dart (1 hunks)
  • lib/src/kanban/presentation/guards/guards.dart (1 hunks)
  • lib/src/kanban/presentation/presentation.dart (1 hunks)
  • lib/src/kanban/presentation/repositories/kanban_repository.dart (1 hunks)
  • lib/src/kanban/presentation/repositories/repositories.dart (1 hunks)
  • lib/src/kanban/presentation/screens/kanban_screen.dart (1 hunks)
  • lib/src/kanban/presentation/screens/screens.dart (1 hunks)
  • lib/src/kanban/presentation/widgets/kanban_card.dart (1 hunks)
  • lib/src/kanban/presentation/widgets/kanban_column_widget.dart (1 hunks)
  • lib/src/kanban/presentation/widgets/widgets.dart (1 hunks)
  • lib/src/kanban/utils/utils.dart (1 hunks)
  • lib/src/moodle/domain/models/moodle_course.freezed.dart (3 hunks)
  • lib/src/moodle/domain/models/moodle_task.freezed.dart (3 hunks)
  • lib/src/moodle/presentation/repositories/moodle_courses_repository.dart (2 hunks)
  • lib/src/moodle/presentation/repositories/moodle_tasks_repository.dart (6 hunks)
  • lib/src/notifications/domain/models/notification.freezed.dart (3 hunks)
  • lib/src/settings/presentation/screens/settings_screen.dart (1 hunks)
  • lib/src/settings/presentation/widgets/feedback_widget.dart (0 hunks)
  • lib/src/settings/presentation/widgets/general_settings.dart (3 hunks)
  • lib/src/settings/presentation/widgets/generic_settings.dart (1 hunks)
  • lib/src/settings/presentation/widgets/kanban_settings.dart (1 hunks)
  • lib/src/settings/presentation/widgets/widgets.dart (1 hunks)
  • lib/src/slots/domain/models/course_to_slot.freezed.dart (3 hunks)
  • lib/src/slots/domain/models/reservation.freezed.dart (3 hunks)
  • lib/src/slots/domain/models/slot.freezed.dart (4 hunks)
  • lib/src/slots/domain/models/slot.g.dart (1 hunks)
  • lib/src/slots/presentation/repositories/slot_master_slots_repository.dart (1 hunks)
  • lib/src/slots/presentation/screens/slot_master_screen.dart (3 hunks)
  • lib/src/slots/presentation/widgets/edit_slot_dialog.dart (11 hunks)
  • lib/src/slots/presentation/widgets/number_spinner.dart (4 hunks)
  • lib/src/slots/presentation/widgets/slot_master_widget.dart (2 hunks)
  • lib/src/statistics/domain/models/chart_value.freezed.dart (3 hunks)
  • lib/src/statistics/domain/models/status_aggregate.freezed.dart (3 hunks)
  • lib/src/statistics/domain/models/task_aggregate.freezed.dart (3 hunks)
  • lib/src/statistics/domain/models/task_aggregate.g.dart (1 hunks)
  • lib/src/statistics/domain/models/type_aggregate.freezed.dart (3 hunks)
  • lib/src/theming/domain/models/theme_base.freezed.dart (3 hunks)
  • lib/src/theming/domain/services/theme_generator_service.dart (1 hunks)
  • lib/src/theming/infra/services/material_theme_generator_service.dart (2 hunks)
  • pubspec.yaml (5 hunks)
  • test/kanban_module_test.dart (1 hunks)
💤 Files with no reviewable changes (2)
  • lib/src/settings/presentation/widgets/feedback_widget.dart
  • l10n.yaml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Testing 🧪
  • GitHub Check: Analysis 🔍
  • GitHub Check: Testing 🧪
  • GitHub Check: Analysis 🔍
🔇 Additional comments (82)
lib/src/moodle/presentation/repositories/moodle_courses_repository.dart (2)

1-1: Import for firstWhereOrNull is correct.

Required to use the iterable extension; no issues spotted.


134-145: Approve: callers handle null; no unsafe usages found.

Only call site is lib/src/kanban/presentation/widgets/kanban_card.dart — it checks course == null and uses course ?? MoodleCourse.skeleton(), with no ! or direct member access after getById.

lib/src/theming/infra/services/material_theme_generator_service.dart (1)

72-81: Keep TabBarThemeData and splashBorderRadius — original suggestion is incorrect.

ThemeData.tabBarTheme is TabBarThemeData (post component-theme normalization) and TabBarThemeData includes splashBorderRadius. Do NOT change TabBarThemeData → TabBarTheme or remove splashBorderRadius. For ripple tuning use overlayColor (MaterialStateProperty/WidgetStateProperty) or splashFactory (InteractiveInkFeatureFactory/NoSplash) instead.

Locations: lib/src/theming/infra/services/material_theme_generator_service.dart (tabBarTheme ~line 72); see also lib/src/calendar/presentation/widgets/plan_popup.dart (uses TabBarTheme(data: const TabBarThemeData())).

Likely an incorrect or invalid review comment.

lib/src/calendar/presentation/repositories/calendar_plan_repository.dart (1)

346-346: Resolve — taskIds is an include filter and call sites use it that way.

Call sites pass ID sets to include (kanban_card.dart:29; course_overview_screen.dart:72; calendar/plan_cell.dart:45; dashboard/presentation/widgets/todays_tasks.dart:19,61). No breaking semantic change detected. Optional: add the doc line clarifying inclusive behavior.

lib/src/slots/presentation/widgets/edit_slot_dialog.dart (13)

14-15: LGTM! Constructor signature update handles new parameters correctly.

The addition of startUnit and duplicate parameters with proper default values and assertion logic is well implemented.


20-22: LGTM! New public fields are properly documented.

The new startUnit and duplicate fields are well-documented and follow the existing code style.

Also applies to: 26-28


49-49: LGTM! State management evolution from simple list to structured elements.

The introduction of courseMappings as a list of MappingElement objects provides better encapsulation and management of course mapping state.


57-57: LGTM! Edit mode logic correctly handles duplication.

The logic properly distinguishes between editing and duplicating modes by checking both the slot existence and the duplicate flag.


63-63: LGTM! Proper fallback for start unit initialization.

The initialization correctly uses the slot's start unit first, then falls back to the new startUnit parameter.


69-70: LGTM! Proper initialization of course mappings with fallback.

The code correctly transforms existing mappings into MappingElement objects and ensures at least one mapping exists.


88-89: LGTM! Enhanced validation using structured mapping elements.

The validation logic correctly uses the new MappingElement structure and ensures all mappings are submittable.


106-109: LGTM! Proper reconstruction of mappings before submission.

The code correctly clears the mappings list and rebuilds it from the current courseMappings state, ensuring consistency.


135-136: LGTM! Duplication logic creates new slot with proper ID.

The duplication path correctly creates a new slot by setting ID to -1, which is appropriate for new slot creation.


197-208: LGTM! Mapping creation logic handles both new and existing mappings.

The method properly distinguishes between creating new mappings (no ID) and updating existing ones based on the mappingId presence.


332-362: Enhanced room autocomplete with hover effects looks great.

The implementation of hover effects and improved styling for the autocomplete options significantly enhances the user experience.


456-456: Icon choice improvement for supervisor removal.

Using Icons.remove_circle_outline_rounded instead of a delete icon provides better semantic meaning for removing supervisors from the list.


472-568: Excellent dynamic course mappings UI implementation.

The transformation from static dropdowns to a dynamic list of mapping elements with individual controls is well-executed. The UI provides:

  • Per-element course and vintage selection
  • Individual delete buttons for each mapping
  • Add button to append new mappings
  • Proper state management for each element
.fvmrc (1)

2-2: LGTM! Flutter version upgrade looks good.

Flutter 3.35 has landed and brings performance boost and stateful hot reload on the web, making this a worthwhile upgrade. The version update aligns with the VS Code settings change.

lib/src/kanban/presentation/repositories/repositories.dart (1)

1-1: LGTM! Clean repository barrel export.

The export statement properly re-exports the Kanban repository for use by the presentation layer and higher-level modules.

lib/src/kanban/domain/datasources/datasources.dart (1)

1-1: LGTM! Clean datasource barrel export.

The export statement properly re-exports the Kanban datasource interface for use by the domain layer and dependent modules.

.vscode/settings.json (1)

5-5: LGTM! VS Code Flutter SDK path updated correctly.

The Flutter SDK path has been updated to match the version bump from 3.29.0 to 3.35.0, which aligns with the .fvmrc change.

lib/src/kanban/presentation/guards/guards.dart (1)

1-1: No action required — empty Kanban guards file is harmless.

lib/src/kanban/presentation/guards/guards.dart is an intentionally empty placeholder (exported by kanban/presentation.dart) and there are no Kanban-specific guards referenced by the app routes (routes use Auth/Capability/HasCourses guards from auth/moodle). An empty export file will not cause import errors; optional: add a one-line comment like // intentionally empty to make intent explicit.

lib/src/theming/domain/models/theme_base.freezed.dart (1)

61-63: Freezed JSON ignore updates look good.

@JsonKey(ignore: true) on copyWith aligns with current Freezed conventions and avoids polluting (de)serialization.

Also applies to: 443-448, 525-527

lib/src/statistics/domain/models/type_aggregate.freezed.dart (1)

39-41: Consistent @JsonKey(ignore: true) application.

Matches the project‑wide pattern; safe for (de)serialization.

Also applies to: 204-214, 256-259

lib/src/auth/auth.dart (1)

18-19: Echidna removal — no active runtime references found; only commented/config remnants

rg output: only commented usages and config constants remain:

  • lib/main.dart:106 — commented initializeEchidnaApi(...)
  • lib/src/app/presentation/widgets/title_bar.dart:199, 352 — commented LicenseRepository watchers
  • lib/src/auth/auth.dart:18, 30 — EchidnaModule / initializeLicenseRepo commented
  • lib/src/auth/presentation/repositories/echidna_user_repository.dart:11, 15 — commented class
  • lib/config/echidna.dart:6, 10, 14 — kEchidnaClientKey / kEchidnaClientID / kEchidnaHost
  • CHANGELOG.md:80 — changelog entry

Conclusion: no live references found — safe to drop the module. Remove the commented lines and unused config constants if you want a full cleanup.

lib/src/slots/presentation/widgets/slot_master_widget.dart (1)

55-61: EditSlotDialog: confirm duplication semantics

Constructor exposes a duplicate named parameter (duplicate = false). Verify that when duplicate == true the dialog:

  • creates a new Slot instance (clears/resets the ID) and does not mutate the original,
  • adjusts/validates start/end times as required,
  • uses the create/save flow (not the update path).

File: lib/src/slots/presentation/widgets/edit_slot_dialog.dart

pubspec.yaml (2)

24-27: Confirm echidna_flutter removal side effects.

  • Residual occurrences found: lib/config/echidna.dart; lib/src/auth/presentation/repositories/echidna_user_repository.dart (import/commented echidna code); lib/src/auth/presentation/repositories/repositories.dart (exports echidna_user_repository.dart).
  • Action: either remove/clean these files/exports (and update callers) or restore echidna_flutter in pubspec — otherwise CI may fail.

13-13: Verify breaking changes for awesome_extensions, flutter_sticky_header, skeletonizer

rg output shows direct usages of withValues(), Skeletonizer, and SliverStickyHeader — review package changelogs, run widget/unit/integration tests, and update callsites if APIs changed.

Representative callsites:

  • withValues(): lib/main.dart:360; lib/src/slots/presentation/widgets/slot_widget.dart:108,112,233; lib/src/app/utils/animate_utils.dart:103–104
  • Skeletonizer: lib/src/kanban/presentation/widgets/kanban_column_widget.dart:100; lib/src/kanban/presentation/widgets/kanban_card.dart:57; lib/src/notifications/presentation/widgets/notification.dart:155,215,241,255,274
  • SliverStickyHeader: lib/src/slots/presentation/screens/slot_reservation_screen.dart:101,114; lib/src/calendar/presentation/screens/tasks_overview_screen.dart:77

Run full test suite and a quick manual smoke of Kanban, Slots, Calendar, and Notifications screens; address any failing callsites or API changes.

build.yaml (1)

7-12: Remove or widen generate_for for source_gen:combining_builder and freezed, or verify no annotations outside the path

Constraining these builders to lib/src/*/domain/models/** can skip files annotated with @freezed/@unfreezed. File: build.yaml (lines 7–12). Verify locally:

rg -n -S '@freezed|@unfreezed' lib -g '!**/*.freezed.dart' -g '!**/*.g.dart' | rg -v '^lib/src/[^/]+/domain/models/'

No output = safe; any matches require removing or expanding the generate_for entries.

lib/src/app/presentation/widgets/sidebar_target.dart (2)

103-110: Transformer wrapping on desktop path: OK.

Correctly preserves active state styling while allowing injection.


120-127: Transformer wrapping on mobile path: OK.

Matches desktop behavior; consistent API use.

lib/src/statistics/domain/models/status_aggregate.freezed.dart (1)

36-38: Approve — file is generated by Freezed/build_runner; no action required.
Found "GENERATED CODE - DO NOT MODIFY BY HAND" and part of 'status_aggregate.dart' in the .freezed.dart, @freezed in the source, and freezed_annotation/json_serializable in pubspec; @jsonkey(ignore: true) on copyWith/hashCode is expected.

lib/src/slots/presentation/screens/slot_master_screen.dart (1)

28-29: TabController has no animationDuration parameter — compile error.

Remove the argument; control animation via animateTo when needed.

-    _tabController = TabController(length: Weekday.values.length, vsync: this, animationDuration: 500.ms);
+    _tabController = TabController(length: Weekday.values.length, vsync: this);

Likely an incorrect or invalid review comment.

lib/src/kanban/infra/datasources/local_kanban_datasource.dart (1)

34-47: I’ve requested the exact enum definition to confirm whether the enum value is named inprogress or inProgress. Once we have that, I’ll update my comment accordingly.

lib/src/statistics/domain/models/chart_value.freezed.dart (2)

31-33: Migration to JsonKey(ignore: true) for copyWith — looks good

Switching from includeFromJson/includeToJson to ignore: true is consistent with current json_serializable guidance and keeps copyWith out of JSON. No functional changes to the model surface.

Also applies to: 178-183, 210-213


1-214: Generator/tooling alignment verified — no legacy annotations found.

rg scan results: no @jsonkey(includeFromJson/includeToJson) in .freezed.dart; no "copyWith" or "hashCode" keys emitted in .g.dart. pubspec shows freezed_annotation ^2.4.4, json_annotation ^4.9.0, build_runner ^2.4.11; build targets generate_for: lib/src//domain/models/* (covers lib/src/statistics/domain/models).

lib/src/statistics/domain/models/task_aggregate.freezed.dart (2)

30-33: Uniform ignore on copyWith/hashCode — approved

Applying @jsonkey(ignore: true) to copyWith and hashCode matches the rest of the migration and won’t affect (de)serialization of status/type.

Also applies to: 166-175, 202-205


1-206: Sanity‑check: JSON footprint unchanged

Verified lib/src/statistics/domain/models/task_aggregate.g.dart — fromJson reads 'status' and 'type'; toJson emits 'status' and 'type'; no 'copyWith'/'hashCode' serialized keys present.

lib/src/calendar/domain/models/plan_member.freezed.dart (2)

32-35: PlanMember: ignore copyWith/hashCode — OK

Annotations are consistent; existing JsonKey name mappings (userid/accesstype) remain intact.

Also applies to: 153-162, 192-195


1-196: Verify generated files are source-of-truth

lib/src/calendar/domain/models/plan_member.dart declares @freezed and has the part directives (plan_member.freezed.dart / plan_member.g.dart); generated file exists. Ensure CI runs build_runner to regenerate artifacts (e.g. flutter pub run build_runner build --delete-conflicting-outputs) so generated files remain in sync across environments.

lib/src/slots/domain/models/slot.freezed.dart (2)

58-60: Slot: ignore annotations for copyWith/hashCode — approved

No changes to field semantics. DeepCollectionEquality remains for list fields; unmodifiable views are preserved.

Also applies to: 342-356, 357-362, 433-435


1-437: Guard against accidental JSON key changes and list default regressions

Automated verification failed in the sandbox (/dev/fd/63); manual check required.

  • Check lib/src/slots/domain/models/slot.g.dart: toJson must contain exactly these keys — "id", "startunit", "duration", "weekday", "room", "size", "fullness", "forcuruser", "filters" — and must not serialize copyWith or hashCode.
  • Confirm generated defaults for supervisors and mappings remain const [] (slot.freezed.dart shows defaults) and that fromJson/toJson preserve empty-list behavior.
  • If any serialization keys or defaults changed, revert or add a serialization-compatibility test.
lib/src/notifications/domain/models/notification.freezed.dart (2)

54-57: Notification: consistent @jsonkey(ignore: true) application — good

Converters and JsonKey name mappings remain unchanged; only ignore semantics updated.

Also applies to: 276-286, 349-352


1-353: Converters present and exported via eduplanner barrel

UnixTimestampConverter and BoolConverter are defined in lib/src/app/utils/json_serializable_utils.dart and re-exported via lib/src/app/utils/utils.dart -> lib/src/app/app.dart -> lib/eduplanner.dart; lib/src/notifications/domain/models/notification.dart imports package:eduplanner/eduplanner.dart so the converters are available for code generation.

lib/src/calendar/domain/models/plan_deadline.freezed.dart (2)

38-41: PlanDeadline: ignore copyWith/hashCode — approved

No behavior change; JSON keys (moduleid/deadlinestart/deadlineend) preserved.

Also applies to: 183-192, 233-236


1-237: Missing generated .g.dart — cannot verify toJson unchanged

lib/**/plan_deadline*.g.dart not present; cannot confirm whether toJson keys/structure changed. Regenerate and commit the generated .g.dart (or paste the produced toJson output) so I can re-check.

lib/src/slots/domain/models/reservation.freezed.dart (1)

43-45: LGTM: copyWith/hashCode excluded from JSON.

Using @jsonkey(ignore: true) here is correct and keeps the JSON surface stable.

Also applies to: 220-229, 277-279

lib/src/moodle/domain/models/moodle_course.freezed.dart (1)

43-45: LGTM: JSON ignore alignment.

Consistently marking copyWith/hashCode with @jsonkey(ignore: true) is appropriate.

Also applies to: 220-229, 276-279

lib/src/moodle/domain/models/moodle_task.freezed.dart (1)

50-52: LGTM: JSON ignore standardization.

copyWith/hashCode hidden from JSON is correct and consistent.

Also applies to: 271-280, 340-342

lib/src/auth/domain/models/user.freezed.dart (2)

550-571: LGTM: equality/hashCode/copyWith updated.

New fields included in ==/hashCode and copyWith ignored in JSON. Looks correct.

Also applies to: 572-576


69-91: Kanban user prefs: defaults OK; BoolConverterNullable not applied — verify handling

  • showColumnColors: generated fromJson uses json['showcolumncolors'] as bool? ?? true (lib/src/auth/domain/models/user.g.dart) — missing/null → default true. Note: the field is annotated with @BoolConverterNullable(), but the generated code does not call the converter, so integer values (0/1) from the backend would not be converted and will cause a cast error.
  • KanbanColumnConverter: deserialize uses KanbanColumn.values.asNameMap()[data] and the generated helper only calls the converter when JSON != null, so unknown/invalid strings → null (safe). (lib/src/auth/utils/kanban_column_converter_utils.dart + lib/src/auth/domain/models/user.g.dart)

Action: if the backend can return 0/1 for booleans, either ensure it returns true/false or adjust the JSON converter/annotations so json_serializable actually applies the BoolConverterNullable on these fields.

lib/src/kanban/presentation/screens/screens.dart (1)

1-1: Barrel export LGTM.

Publicly exposing KanbanScreen via the screens barrel is correct.

lib/src/kanban/domain/models/models.dart (1)

1-1: Models barrel export LGTM.

Exposing KanbanBoard through the domain models barrel is appropriate.

lib/src/settings/presentation/screens/settings_screen.dart (1)

32-35: Desktop: gated KanbanSettings integration LGTM.

Conditional Expanded with stagger matches existing layout patterns.

lib/src/settings/presentation/widgets/widgets.dart (1)

4-4: Barrel export LGTM.

Exporting KanbanSettings through the widgets barrel aligns with usage in SettingsScreen.

lib/src/kanban/presentation/widgets/widgets.dart (1)

1-2: Kanban widgets barrel exports LGTM.

Exposes KanbanCard and KanbanColumnWidget as intended.

lib/src/auth/utils/utils.dart (1)

1-1: Layering: remove Kanban-specific export from auth utils

lib/src/auth/utils/utils.dart currently exports a Kanban-only converter (export 'kanban_column_converter_utils.dart';) — this couples auth → kanban. Move the converter to a neutral/shared serializers module, or keep it inside the Kanban feature and have auth store the raw value to avoid cross-feature coupling and potential cycles.

lib/eduplanner.dart (1)

6-6: Public API surface expanded with Kanban module — transitive exports enumerated

Transitive exports (reachable via lib/src/kanban/kanban.dart):

  • lib/src/kanban/kanban.dart -> domain/domain.dart, presentation/presentation.dart, utils/utils.dart
  • lib/src/kanban/domain/domain.dart -> datasources/datasources.dart, models/models.dart, services/services.dart
    • lib/src/kanban/domain/datasources/datasources.dart -> kanban_datasource.dart
    • lib/src/kanban/domain/models/models.dart -> kanban_board.dart
  • lib/src/kanban/presentation/presentation.dart -> guards/guards.dart, repositories/repositories.dart, screens/screens.dart, widgets/widgets.dart
    • lib/src/kanban/presentation/repositories/repositories.dart -> kanban_repository.dart
    • lib/src/kanban/presentation/screens/screens.dart -> kanban_screen.dart
    • lib/src/kanban/presentation/widgets/widgets.dart -> kanban_card.dart, kanban_column_widget.dart
  • lib/src/kanban/utils/utils.dart (no further exports listed)

Note: lib/src/kanban/infra/* files (infra.dart -> datasources/services; infra/datasources -> local_kanban_datasource.dart, moodle_kanban_datasource.dart) exist but are not re-exported by kanban.dart.

Confirm whether domain/datasources (kanban_datasource.dart) and domain/services should be part of the public API; if they are internal, stop re-exporting them from domain/domain.dart.

lib/src/kanban/infra/infra.dart (1)

1-2: Barrel looks good.

Exports are minimal and clear.

lib/src/kanban/domain/domain.dart (1)

1-3: Domain barrel is fine.

No issues spotted.

lib/src/kanban/infra/datasources/datasources.dart (1)

1-2: Infra datasources barrel is fine.

Keeps imports tidy.

lib/src/app/app.dart (1)

34-35: Module import OK.

KanbanModule added to imports aligns with other modules.

lib/src/auth/infra/datasources/moodle_user_datasource.dart (1)

91-103: Filter nulls and use a const Set for O(1) membership.

Replace the inline list/allocation and avoid sending null fields.

File: lib/src/auth/infra/datasources/moodle_user_datasource.dart — change:

-          user.toJson().entries.where(
-                (e) => const <String>[
-                  'theme',
-                  'colorblindness',
-                  'displaytaskcount',
-                  'ekenabled',
-                  'showcolumncolors',
-                  'automoveoverduetasks',
-                  'automovesubmittedtasks',
-                  'automovecompletedtasks',
-                ].contains(e.key),
-              ),
+          user.toJson().entries.where(
+            (e) => _kUserMutableKeys.contains(e.key) && e.value != null,
+          ),

Add near the top of the file (outside the function):

// Allowed mutable user fields for update payloads.
const _kUserMutableKeys = <String>{
  'theme',
  'colorblindness',
  'displaytaskcount',
  'ekenabled',
  'showcolumncolors',
  'automoveoverduetasks',
  'automovesubmittedtasks',
  'automovecompletedtasks',
};

Confirm the Moodle webservice (local_lbplanner_user_update_user) accepts these keys in your target deployment to avoid 400s.

lib/src/kanban/presentation/presentation.dart (1)

1-4: Barrel exports look good.

No issues spotted; exports align with the new presentation surface.

lib/src/app/presentation/widgets/sidebar.dart (1)

19-19: Theme surface change LGTM.

Switch to context.theme.colorScheme.surface is consistent with M3 theming.

Also applies to: 92-92

lib/src/settings/presentation/widgets/kanban_settings.dart (2)

31-40: Confirm EnumSettingsItem<T?> supports null values.

You pass values: [null, ...] and value may be null. Ensure the widget handles null equality/selection and doesn’t assert on non‑null generics.


60-64: Default for showColumnColors should match repository/domain default.

You default to true when settings is null. Verify this aligns with the persisted/default value to avoid UI desync on first load.

lib/src/kanban/kanban.dart (1)

33-35: Route LGTM.

Child route mounts KanbanScreen at module root as expected.

lib/src/kanban/domain/datasources/kanban_datasource.dart (1)

5-8: Interface design LGTM.

Clear naming and documentation; extends existing infra patterns.

lib/src/slots/domain/models/course_to_slot.freezed.dart (1)

38-41: LGTM: standardized on @jsonkey(ignore: true) for copyWith/hashCode.

Consistent with other models; avoids leaking internals into JSON.

Also applies to: 195-204, 243-246

lib/src/auth/domain/models/token.freezed.dart (1)

30-31: LGTM: JSON-ignore annotations align with Freezed conventions.
No behavior change; serialization surface is correct.

Also applies to: 140-149, 175-177

lib/src/auth/presentation/repositories/echidna_user_repository.dart (1)

1-31: Delete or gate the commented-out EchidnaUserRepository.

Commented-out code at lib/src/auth/presentation/repositories/echidna_user_repository.dart adds maintenance noise; remove it (Git history preserves) or gate it behind a compile-time feature flag that compiles cleanly when disabled.

Verification — previous ripgrep run returned "No files were searched" (search was inconclusive). Run one of the commands below locally and paste the output to confirm no remaining references:

# fast, searches tracked files
git grep -n -E '\bEchidnaUserRepository\b|echidna_' || true

# recursive search (includes untracked/hidden files); avoids type filters that can skip files
rg -n --hidden -S -C2 '\bEchidnaUserRepository\b|echidna_' -g 'lib/**' -g '!**/*.g.dart' -g '!**/*.freezed.dart' || true

If both commands produce no matches, delete the file or convert it to a feature-gated implementation; if matches exist, update or remove callers before deleting.

lib/config/echidna.dart (1)

5-18: Deprecations OK — remove leftover commented usages & export.

  • Search found no active runtime uses of the kEchidna* constants; only commented references and an exported (but commented) repository remain:
    • lib/config/echidna.dart — deprecated consts (definitions).
    • lib/main.dart — commented initializeEchidnaApi(... kEchidnaHost, kEchidnaClientKey, kEchidnaClientID) (line ~106).
    • lib/src/auth/auth.dart — commented // EchidnaModule() (line ~18) and // ..initializeLicenseRepo(EchidnaUserRepository.new); (line ~30).
    • lib/src/auth/presentation/repositories/repositories.dart — export 'echidna_user_repository.dart' (line ~2).
    • lib/src/auth/presentation/repositories/echidna_user_repository.dart — file present but its echidna code is commented out.
  • Action: remove the commented initialization and the export (or delete/cleanup the echidna_user_repository file) to prevent accidental runtime usage — these constants default to ''/0 if used.
lib/src/auth/domain/models/user.dart (1)

51-62: Drop redundant @default(null) on nullable fields and fix a doc typo.

  • Remove the unnecessary @default(null) from the nullable Kanban fields; fix capitalization/punctuation and change the overdue-field doc to "overdue tasks".
-    /// Whether to show column colors in the kanban view.
-    @Default(true) @JsonKey(name: 'showcolumncolors') @BoolConverterNullable() bool showColumnColors,
+    /// Whether to show column colors in the Kanban view.
+    @Default(true) @JsonKey(name: 'showcolumncolors') @BoolConverterNullable() bool showColumnColors,

-    /// The column to auto-move completed tasks to
-    @Default(null) @JsonKey(name: 'automovecompletedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveCompletedTasksTo,
+    /// The column to auto-move completed tasks to.
+    @JsonKey(name: 'automovecompletedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveCompletedTasksTo,

-    /// The column to auto-move submitted tasks to
-    @Default(null) @JsonKey(name: 'automovesubmittedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveSubmittedTasksTo,
+    /// The column to auto-move submitted tasks to.
+    @JsonKey(name: 'automovesubmittedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveSubmittedTasksTo,

-    /// The column to auto-move in-progress tasks to
-    @Default(null) @JsonKey(name: 'automoveoverduetasks') @KanbanColumnConverter() KanbanColumn? autoMoveOverdueTasksTo,
+    /// The column to auto-move overdue tasks to.
+    @JsonKey(name: 'automoveoverduetasks') @KanbanColumnConverter() KanbanColumn? autoMoveOverdueTasksTo,

Confirmed: BoolConverterNullable.fromJson returns null for json == null (lib/src/app/utils/json_serializable_utils.dart), and the generated mapping in lib/src/auth/domain/models/user.g.dart uses json['showcolumncolors'] as bool? ?? true — so missing/null JSON will not be coerced to false and the default true is preserved.

l10n/en.arb (1)

749-814: LGTM: Kanban strings and placeholders look consistent.

Keys match usage (due/planned placeholders, screen/setting labels). No structural issues spotted.

lib/src/auth/domain/models/user.g.dart (1)

22-35: LGTM: New fields serialize/deserialize correctly.

Defaults and the helper _$JsonConverterFromJson handle nulls properly; converter usage is consistent.

Also applies to: 52-59, 62-67

lib/src/calendar/domain/models/calendar_plan.freezed.dart (1)

37-39: LGTM: JSON ignore annotations align with Freezed conventions.

Ignoring copyWith/hashCode for JSON is correct and non-breaking.

Also applies to: 209-223, 261-263

lib/src/moodle/presentation/repositories/moodle_tasks_repository.dart (2)

92-105: LGTM: Added getters for cmid/id are safe and efficient.

Use of firstWhereOrNull with a hasData guard is correct.


130-170: Filter: cmid check is fine; consider null cmid behavior explicitly.

If tasks may have cmid == null, the current condition excludes them when cmids is provided. Confirm that’s desired.

lib/src/kanban/domain/models/kanban_board.freezed.dart (1)

139-151: LGTM: model and JSON mapping (inprogress key) align with usage.

lib/src/calendar/domain/models/plan_invite.freezed.dart (1)

46-48: Approve — JsonKey(ignore: true) on copyWith/hashCode; verify codegen locally.
Matches Freezed/JsonSerializable patterns; ignoring copyWith/hashCode is correct. Sandbox build failed ('dart: command not found'); run locally: dart run build_runner build -d and confirm no generated-file drift. Also applies to lines 243–249 and 305–307.

lib/src/settings/presentation/widgets/generic_settings.dart (1)

166-173: BooleanSettingsItem: make onChanged non-nullable and safely map Checkbox's nullable callback.
Change the field to final ValueChanged<bool> onChanged;, keep super(onPressed: () => onChanged(!value));, and in buildSuffix use Checkbox(value: value, onChanged: (v) => onChanged(v ?? value)) (leave Switch as-is).
File: lib/src/settings/presentation/widgets/generic_settings.dart — constructor & buildSuffix (~lines 166–191).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

@mcquenji mcquenji changed the title chore: merge lpa-53 feat: kanban board Sep 22, 2025
@necodeit
Copy link

necodeit bot commented Sep 22, 2025

Analysis Report for 4978af7

  • Infos: 1
  • Warnings: 0
  • Errors: 0
Click to see the full report
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

1 similar comment
@necodeit
Copy link

necodeit bot commented Sep 22, 2025

Analysis Report for 4978af7

  • Infos: 1
  • Warnings: 0
  • Errors: 0
Click to see the full report
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

mcquenji and others added 2 commits September 23, 2025 09:11
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@necodeit
Copy link

necodeit bot commented Sep 23, 2025

Analysis Report for aa52fba

  • Infos: 1
  • Warnings: 0
  • Errors: 0
Click to see the full report
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

@necodeit
Copy link

necodeit bot commented Sep 23, 2025

Analysis Report for 63beecc

  • Infos: 1
  • Warnings: 0
  • Errors: 0
Click to see the full report
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

mcquenji and others added 2 commits September 23, 2025 09:15
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@necodeit
Copy link

necodeit bot commented Sep 23, 2025

Analysis Report for d35c340

  • Infos: 2
  • Warnings: 0
  • Errors: 7
Click to see the full report
info • Missing type annotation on a public API • lib/src/app/utils/animate_utils.dart:27:15 • type_annotate_public_apis
error • Using a colon as the separator before a default value is no longer supported • lib/src/app/utils/animate_utils.dart:27:18 • obsolete_colon_for_default_value
error • The default value of an optional parameter must be constant • lib/src/app/utils/animate_utils.dart:27:20 • non_constant_default_value
error • Undefined name 'keyPrefix' • lib/src/app/utils/animate_utils.dart:27:20 • undefined_identifier
error • Undefined name 'keyPrefix' • lib/src/app/utils/animate_utils.dart:27:51 • undefined_identifier
error • Undefined name 'i' • lib/src/app/utils/animate_utils.dart:27:62 • undefined_identifier
error • Undefined name 'keyPrefex' • lib/src/app/utils/animate_utils.dart:61:20 • undefined_identifier
error • Undefined name 'keyPrefex' • lib/src/app/utils/animate_utils.dart:61:51 • undefined_identifier
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

@necodeit
Copy link

necodeit bot commented Sep 23, 2025

Analysis Report for 93bf70c

  • Infos: 2
  • Warnings: 0
  • Errors: 7
Click to see the full report
info • Missing type annotation on a public API • lib/src/app/utils/animate_utils.dart:27:15 • type_annotate_public_apis
error • Using a colon as the separator before a default value is no longer supported • lib/src/app/utils/animate_utils.dart:27:18 • obsolete_colon_for_default_value
error • The default value of an optional parameter must be constant • lib/src/app/utils/animate_utils.dart:27:20 • non_constant_default_value
error • Undefined name 'keyPrefix' • lib/src/app/utils/animate_utils.dart:27:20 • undefined_identifier
error • Undefined name 'keyPrefix' • lib/src/app/utils/animate_utils.dart:27:51 • undefined_identifier
error • Undefined name 'i' • lib/src/app/utils/animate_utils.dart:27:62 • undefined_identifier
error • Undefined name 'keyPrefex' • lib/src/app/utils/animate_utils.dart:61:20 • undefined_identifier
error • Undefined name 'keyPrefex' • lib/src/app/utils/animate_utils.dart:61:51 • undefined_identifier
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

@necodeit
Copy link

necodeit bot commented Sep 23, 2025

Analysis Report for 156d704

  • Infos: 2
  • Warnings: 0
  • Errors: 7
Click to see the full report
info • Missing type annotation on a public API • lib/src/app/utils/animate_utils.dart:27:15 • type_annotate_public_apis
error • Using a colon as the separator before a default value is no longer supported • lib/src/app/utils/animate_utils.dart:27:18 • obsolete_colon_for_default_value
error • The default value of an optional parameter must be constant • lib/src/app/utils/animate_utils.dart:27:20 • non_constant_default_value
error • Undefined name 'keyPrefix' • lib/src/app/utils/animate_utils.dart:27:20 • undefined_identifier
error • Undefined name 'keyPrefix' • lib/src/app/utils/animate_utils.dart:27:51 • undefined_identifier
error • Undefined name 'i' • lib/src/app/utils/animate_utils.dart:27:62 • undefined_identifier
error • Undefined name 'keyPrefex' • lib/src/app/utils/animate_utils.dart:61:20 • undefined_identifier
error • Undefined name 'keyPrefex' • lib/src/app/utils/animate_utils.dart:61:51 • undefined_identifier
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@necodeit
Copy link

necodeit bot commented Sep 23, 2025

Analysis Report for 010bf19

  • Infos: 2
  • Warnings: 0
  • Errors: 7
Click to see the full report
info • Missing type annotation on a public API • lib/src/app/utils/animate_utils.dart:27:15 • type_annotate_public_apis
error • Using a colon as the separator before a default value is no longer supported • lib/src/app/utils/animate_utils.dart:27:18 • obsolete_colon_for_default_value
error • The default value of an optional parameter must be constant • lib/src/app/utils/animate_utils.dart:27:20 • non_constant_default_value
error • Undefined name 'keyPrefix' • lib/src/app/utils/animate_utils.dart:27:20 • undefined_identifier
error • Undefined name 'keyPrefix' • lib/src/app/utils/animate_utils.dart:27:51 • undefined_identifier
error • Undefined name 'i' • lib/src/app/utils/animate_utils.dart:27:62 • undefined_identifier
error • Undefined name 'keyPrefex' • lib/src/app/utils/animate_utils.dart:61:20 • undefined_identifier
error • Undefined name 'keyPrefex' • lib/src/app/utils/animate_utils.dart:61:51 • undefined_identifier
info • Missing a required trailing comma • lib/src/slots/presentation/widgets/edit_slot_dialog.dart:569:17 • require_trailing_commas

View annotated files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/src/auth/presentation/repositories/user_repository.dart (1)

91-115: Optimistic update lacks rollback and ignores canonical server response.

  • No rollback on error → UI may get stuck in an invalid state.
  • onData only logs; it should sync state with the server-returned user.

Apply this diff to revert on failure and adopt the canonical user on success:

   /// Optimistically updates the user with [patch].
   Future<void> _updateUser(User patch, ISentrySpan span) async {
     log('Updating user to $patch');

     if (!state.hasData) {
       log('User is not loaded yet. Aborting.');
       return;
     }

-    data(patch);
+    final prev = state.requireData;
+    data(patch);

     final transaction = span.startChild('updateUser');
     await guard(
       () => _userDatasource.updateUser(
         _auth.state.requireData[Webservice.lb_planner_api],
         patch,
       ),
       onError: (e, s) {
         log('Failed to update user', e, s);
+        // revert optimistic state
+        data(prev);
         transaction.internalError(e);
       },
-      onData: (user) => log('User updated successfully'),
+      onData: (user) {
+        log('User updated successfully');
+        // sync with canonical server state
+        data(user);
+      },
     );
     await transaction.commit();
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4978af7 and 010bf19.

📒 Files selected for processing (4)
  • lib/src/app/app.dart (2 hunks)
  • lib/src/app/utils/animate_utils.dart (3 hunks)
  • lib/src/auth/presentation/repositories/user_repository.dart (8 hunks)
  • lib/src/slots/presentation/widgets/slot_master_widget.dart (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analysis 🔍
  • GitHub Check: Testing 🧪
🔇 Additional comments (17)
lib/src/slots/presentation/widgets/slot_master_widget.dart (2)

55-61: Duplicate flow wiring — LGTM.

Opening EditSlotDialog with duplicate: true is correct.


173-181: Disable Delete and rebuild on isDeleting; current gating won’t update.

  • Delete button still enabled; please gate it like Duplicate.
  • isDeleting is mutated without setState, so the Duplicate button’s null onPressed won’t take effect at runtime.

Apply this diff for the buttons:

                 TextButton(
-                  onPressed: deleteSlot,
+                  onPressed: isDeleting ? null : deleteSlot,
                   style: TextButton.styleFrom(foregroundColor: context.theme.colorScheme.error),
                   child: Text(context.t.global_delete),
                 ),
                 TextButton(
                   onPressed: isDeleting ? null : duplicateSlot,
                   child: Text(context.t.global_duplicate),
                 ),

Then update deleteSlot to trigger rebuilds:

Future<void> deleteSlot() async {
  if (isDeleting) return;

  final slots = context.read<SlotMasterSlotsRepository>();
  if (!slots.state.hasData) return;

  final confirmed = await showConfirmationDialog(
    context,
    title: context.t.slots_slotmaster_deleteSlot_title(
      widget.slot.room,
      widget.slot.startUnit.humanReadable(),
      widget.slot.endUnit.humanReadable(),
    ),
    message: context.t.slots_slotmaster_deleteSlot_message,
  );
  if (!confirmed || !mounted) return;

  setState(() => isDeleting = true);
  try {
    await slots.deleteSlot(widget.slot.id);
  } finally {
    if (mounted) setState(() => isDeleting = false);
  }
}

Optional: also gate Edit while deleting for consistency.

lib/src/app/utils/animate_utils.dart (3)

29-29: LGTM: assertion message matches the condition.


60-62: Fix typo: keyPrefex → keyPrefix.

Undefined identifier keyPrefex breaks compilation. Use the declared keyPrefix.

-            .animate(
-              key: keyPrefex != null ? ValueKey('$keyPrefex-$i') : null,
-            )
+            .animate(
+              key: keyPrefix != null ? ValueKey('$keyPrefix-$i') : null,
+            )

91-94: Avoid ambiguity with Widget.key: rename parameter to animKey.

Reduces confusion at call sites; safe rename (no known callers passing key).

-    Key? key,
+    Key? animKey,
-    return animate(key: key).scale(duration: duration, delay: stagger?.add() ?? delay, curve: Curves.easeOutCubic);
+    return animate(key: animKey)
+        .scale(duration: duration, delay: stagger?.add() ?? delay, curve: Curves.easeOutCubic);
lib/src/app/app.dart (3)

73-78: LGTM: Kanban route configured consistently.

Route uses customTransition: defaultTransition and mirrors guard order semantics. Thanks for addressing the earlier nitpick.


79-83: Remove commented-out FeatureGuard; if needed, gate via a runtime flag.

Commented code tends to drift. Also, the constant references Calendar; use a Kanban-specific ID when you enable it.

Apply to remove the commented line:

-              // FeatureGuard([kCalendarPlanFeatureID], redirectTo: '/settings/'),

Example pattern if you intend to feature‑gate:

guards: [
  if (kFeatures.kanbanEnabled)
    FeatureGuard([kKanbanFeatureID], redirectTo: '/settings/'),
  CapabilityGuard({UserCapability.student}, redirectTo: '/slots/'),
  AuthGuard(redirectTo: '/auth/'),
],

90-90: Avoid committing commented-out guards.

Either remove it or gate via a runtime flag as shown above for consistency.

-              // FeatureGuard([kCalendarPlanFeatureID], redirectTo: '/settings/'),
lib/src/auth/presentation/repositories/user_repository.dart (9)

4-4: Consolidated import and Sentry integration — LGTM.

Unified eduplanner.dart import and explicit Sentry dependency look good.

Also applies to: 8-8


72-76: Identify payload now serializes enums as strings — LGTM.

Using .name (and null-aware) prevents PostHog JSON serialization issues.


129-137: Await _updateUser to preserve Sentry span/transaction ordering.

Returning the future lets finally commit the parent before the child span finishes.

Apply this diff:

-      return _updateUser(
+      await _updateUser(
         state.requireData.copyWith(themeName: theme),
         transaction,
       );

186-197: Good: awaited update and event capture.

This path now guards state presence and awaits the update.


206-240: Guards + awaited update + logging — LGTM.

Null-checks and telemetry wiring are consistent.


245-276: New setter (showColumnColors) — implementation looks correct.

Early guard, defaulting to true, awaited update, and telemetry are consistent.


331-355: Same issues as above: guard missing and "null" string in analytics.

Apply this diff:

   /// Sets [User.autoMoveOverdueTasksTo] to [column] for the current user.
   Future<void> setAutoMoveOverdueTasksTo(KanbanColumn? column) async {
+    if (!state.hasData) {
+      log('Cannot set auto move overdue tasks: No user loaded.');
+      return;
+    }
     final patch = state.requireData.copyWith(
       autoMoveOverdueTasksTo: column,
     );

     final transaction = startTransaction('setAutoMoveOverdueTasksTo');

     log('Setting auto move overdue tasks to ${column?.name}');

     try {
       await _updateUser(
         patch,
         transaction,
       );

-      await captureEvent('auto_move_overdue_tasks', properties: {'column': '${column?.name}'});
+      await captureEvent('auto_move_overdue_tasks', properties: {'column': column?.name});

       log('Set auto move overdue tasks to ${column?.name}');

306-328: Same issues as above: guard missing and "null" string in analytics.

Apply this diff:

   /// Sets [User.autoMoveSubmittedTasksTo] to [column] for the current user.
   Future<void> setAutoMoveSubmittedTasksTo(KanbanColumn? column) async {
+    if (!state.hasData) {
+      log('Cannot set auto move submitted tasks: No user loaded.');
+      return;
+    }
     final patch = state.requireData.copyWith(
       autoMoveSubmittedTasksTo: column,
     );

     log('Setting auto move submitted tasks to ${column?.name}');

     final transaction = startTransaction('setAutoMoveSubmittedTasksTo');

     try {
       await _updateUser(
         patch,
         transaction,
       );

-      await captureEvent('auto_move_submitted_tasks', properties: {'column': '${column?.name}'});
+      await captureEvent('auto_move_submitted_tasks', properties: {'column': column?.name});

279-303: Add user-loaded guard and avoid sending "null" string to analytics.

  • state.requireData can throw if user not loaded.
  • '${column?.name}' serializes null as the literal "null".

Apply this diff:

   /// Sets [User.autoMoveCompletedTasksTo] to [column] for the current user.
   Future<void> setAutoMoveCompletedTasksTo(KanbanColumn? column) async {
+    if (!state.hasData) {
+      log('Cannot set auto move completed tasks: No user loaded.');
+      return;
+    }
     final patch = state.requireData.copyWith(
       autoMoveCompletedTasksTo: column,
     );

     final transaction = startTransaction('setAutoMoveCompletedTasksTo');

     log('Setting auto move completed tasks to ${column?.name}');

     try {
       await _updateUser(
         patch,
         transaction,
       );

-      await captureEvent('auto_move_completed_tasks', properties: {'column': '${column?.name}'});
+      await captureEvent('auto_move_completed_tasks', properties: {'column': column?.name});

       log('Set auto move completed tasks to ${column?.name}');

@necodeit
Copy link

necodeit bot commented Sep 23, 2025

Analysis Report for b3ab979

  • Infos: 0
  • Warnings: 0
  • Errors: 0
Click to see the full report
No issues found! (ran in 30.0s)

View annotated files

@MasterMarcoHD MasterMarcoHD changed the title feat: kanban board feat: Kanban board Sep 23, 2025
@necodeit
Copy link

necodeit bot commented Sep 23, 2025

Analysis Report for b3ab979

  • Infos: 0
  • Warnings: 0
  • Errors: 0
Click to see the full report
No issues found! (ran in 30.9s)

View annotated files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
lib/src/slots/presentation/widgets/edit_slot_dialog.dart (2)

81-90: Simplify boolean check

No need for “== true”.

-      courseMappings.isNotEmpty &&
-      courseMappings.every((element) => element.isSubmitable() == true);
+      courseMappings.isNotEmpty &&
+      courseMappings.every((e) => e.isSubmitable());

54-79: Dispose controllers and FocusNode (and per‑element controllers)

Missing dispose causes leaks. Dispose TextEditingControllers, FocusNode, and MappingElement controllers.

Add this method to the State class:

@override
void dispose() {
  supervisorController.dispose();
  roomController.dispose();
  roomFocusNode.dispose();
  for (final e in courseMappings) {
    e.courseController.dispose();
    e.vintageController.dispose();
  }
  super.dispose();
}
lib/src/slots/presentation/widgets/slot_master_widget.dart (1)

44-52: Use setState for isDeleting to update UI

Without setState, buttons might not disable/enable during deletion.

-    isDeleting = true;
+    setState(() => isDeleting = true);
     try {
       await slots.deleteSlot(widget.slot.id);
     } catch (e) {
       rethrow;
     } finally {
-      isDeleting = false;
+      if (mounted) {
+        setState(() => isDeleting = false);
+      }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 010bf19 and b3ab979.

⛔ Files ignored due to path filters (3)
  • lib/gen/l10n/app_localizations.dart is excluded by !**/gen/**
  • lib/gen/l10n/app_localizations_de.dart is excluded by !**/gen/**
  • lib/gen/l10n/app_localizations_en.dart is excluded by !**/gen/**
📒 Files selected for processing (11)
  • l10n/de.arb (5 hunks)
  • l10n/en.arb (5 hunks)
  • lib/src/app/utils/animate_utils.dart (3 hunks)
  • lib/src/auth/domain/models/user.dart (1 hunks)
  • lib/src/auth/domain/models/user.freezed.dart (12 hunks)
  • lib/src/kanban/kanban.dart (1 hunks)
  • lib/src/settings/presentation/widgets/kanban_settings.dart (1 hunks)
  • lib/src/slots/presentation/repositories/slot_master_slots_repository.dart (1 hunks)
  • lib/src/slots/presentation/screens/slot_master_screen.dart (3 hunks)
  • lib/src/slots/presentation/widgets/edit_slot_dialog.dart (12 hunks)
  • lib/src/slots/presentation/widgets/slot_master_widget.dart (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analysis 🔍
  • GitHub Check: Testing 🧪
🔇 Additional comments (29)
lib/src/app/utils/animate_utils.dart (4)

25-25: LGTM: keyPrefix parameter addition

The addition of the optional keyPrefix parameter allows callers to provide stable keys for animated widgets, which is good for animation identity management.


27-27: Assertion message correctly updated

The assertion message has been properly corrected from "positive" to "non-negative" to match the condition limit >= 0.


58-60: Index-based keys remain unstable for reordering

While the typo fix is good, the fundamental issue remains: using index i in ValueKey('$keyPrefix-$i') makes widget identity unstable when list order changes, causing animation recycling on reorders/filtering.

Consider the suggested approach from the previous review to add a key factory parameter:

-    String? keyPrefix,
+    String? keyPrefix,
+    Key? Function(int index, Widget widget)? keyOf,

And use it in the animation:

-            .animate(
-              key: keyPrefix != null ? ValueKey('$keyPrefix-$i') : null,
-            )
+            .animate(
+              key: keyOf?.call(i, this[i]) ??
+                  (this[i].key ?? (keyPrefix != null ? ValueKey('$keyPrefix-$i') : null)),
+            )

89-91: Parameter naming: key vs animKey

The key parameter name in AnimateX.show could be confused with Widget.key at call sites, as mentioned in the previous review.

Consider renaming to animKey for clarity:

-    Key? key,
+    Key? animKey,
-    return animate(key: key).scale(duration: duration, delay: stagger?.add() ?? delay, curve: Curves.easeOutCubic);
+    return animate(key: animKey).scale(duration: duration, delay: stagger?.add() ?? delay, curve: Curves.easeOutCubic);
lib/src/slots/presentation/repositories/slot_master_slots_repository.dart (1)

349-361: LGTM: Efficient O(n) grouping with groupListsBy.

Addresses the earlier O(n²) concern; implementation is clean and correct.

lib/src/kanban/kanban.dart (5)

25-25: LGTM: TitleBuilder is localized.

Good switch to context.t.kanban_title; addresses prior feedback.


24-24: Ensure auth tokens never reach logs/traces.

KanbanDatasource takes a token; verify no logs/prints include it and redact if necessary.

Run:

#!/bin/bash
set -euo pipefail

# 1) Find Kanban datasources and token parameters
rg -nP --type=dart -C2 '(abstract\s+class\s+KanbanDatasource|class\s+\w+KanbanDatasource)\b'
rg -nP --type=dart -C2 '\b(token|authToken|authorization)\b' lib | sed -n '1,200p'

# 2) Look for potential logging of tokens
rg -nP --type=dart -C2 '(log\(|logger\.(debug|info|warn|error|trace)\(|print\()' lib \
  | rg -nP -C1 '\b(token|Authorization)\b' || true

8-10: Public export surface looks good.

Domain/presentation/utils only; infra remains internal.


24-26: Prefer a resilient composite datasource (Moodle primary + Local fallback).

Bind LocalKanbanDatasource and expose KanbanDatasource as a composite that falls back on network errors/timeouts. Improves offline/resilience without touching callers.

Apply this diff:

-      ..add<KanbanDatasource>(MoodleKanbanDatasource.new)
+      ..add<LocalKanbanDatasource>(LocalKanbanDatasource.new)
+      ..add<MoodleKanbanDatasource>(MoodleKanbanDatasource.new)
+      ..add<KanbanDatasource>(() => CompositeKanbanDatasource(
+            i.get<MoodleKanbanDatasource>(),
+            i.get<LocalKanbanDatasource>(),
+          ))

If not present yet, add a composite in infra (skeleton):

class CompositeKanbanDatasource implements KanbanDatasource {
  final MoodleKanbanDatasource primary;
  final LocalKanbanDatasource fallback;
  CompositeKanbanDatasource(this.primary, this.fallback);

  // For each method:
  // try { return await primary.method(...); }
  // on TimeoutException catch (_) { return fallback.method(...); }
  // on SocketException catch (_) { return fallback.method(...); }
  // (optionally cache writes for later sync)
}

32-35: No change required — KanbanModule is mounted under '/kanban/'

lib/src/app/app.dart registers ModuleRoute('/kanban/', module: KanbanModule()), so r.child('/', ...) inside KanbanModule maps to the /kanban root and won't clash with the app root.

lib/src/settings/presentation/widgets/kanban_settings.dart (5)

42-44: Good: settings title is localized.

Thanks for switching to a localized key.


13-15: Verify availability of context.watch().

Ensure provider extensions are available via imports/re-exports (e.g., provider.dart) from eduplanner.dart, otherwise import provider explicitly.


16-29: Centralize KanbanColumn → label mapping (duplication).

This switch appears in multiple widgets. Extract to a shared i18n helper and reuse.

Apply this change here (remove the local helper):

-    String translateColumn(KanbanColumn? column) {
-      switch (column) {
-        case KanbanColumn.backlog:
-          return context.t.kanban_screen_backlog;
-        case KanbanColumn.todo:
-          return context.t.kanban_screen_toDo;
-        case KanbanColumn.inprogress:
-          return context.t.kanban_screen_inProgress;
-        case KanbanColumn.done:
-          return context.t.kanban_screen_done;
-        case null:
-          return context.t.kanban_settings_disabled;
-      }
-    }

Then use the centralized function in itemBuilder (see next comment).

Helper (new file suggestion):

// lib/src/common/i18n/kanban_translations.dart
import 'package:eduplanner/eduplanner.dart';
import 'package:flutter/widgets.dart';

String translateKanbanColumn(BuildContext context, KanbanColumn? column) {
  switch (column) {
    case KanbanColumn.backlog:
      return context.t.kanban_screen_backlog;
    case KanbanColumn.todo:
      return context.t.kanban_screen_toDo;
    case KanbanColumn.inprogress:
      return context.t.kanban_screen_inProgress;
    case KanbanColumn.done:
      return context.t.kanban_screen_done;
    case null:
      return context.t.kanban_settings_disabled;
  }
}

36-37: Use the shared translator helper in itemBuilder.

-        itemBuilder: (context, value) => translateColumn(value),
+        itemBuilder: (context, value) => translateKanbanColumn(context, value),

38-39: Avoid fixed dropdown width; prevents truncation in longer locales.

Let it size intrinsically.

-        dropDownWidth: 125,
+        // Let the dropdown size intrinsically to avoid truncation in i18n.
lib/src/auth/domain/models/user.dart (1)

51-53: Field addition LGTM: showColumnColors with sane default.

Annotation and default align with existing boolean fields.

lib/src/auth/domain/models/user.freezed.dart (1)

478-501: Generated surface for Kanban prefs looks correct.

Fields, keys, converters, and defaults are wired through; no manual edits needed.

lib/src/slots/presentation/widgets/edit_slot_dialog.dart (2)

294-306: Weekday dropdown icon consistency resolved

Leading and entry icons now both use FontAwesome5Solid.calendar_check.


584-614: Static ID counter persists across dialogs — optional change

Static counter grows app‑wide; consider per‑dialog IDs or UniqueKey/UUID.

lib/src/slots/presentation/widgets/slot_master_widget.dart (1)

173-184: Action buttons disable during delete — good

Delete, Duplicate, and Edit correctly disable when deleting.

lib/src/slots/presentation/screens/slot_master_screen.dart (3)

33-38: Good lifecycle management

Both TabController and searchController are disposed.


119-127: Sort key nullability — verify room non-null

If Slot.room can be null, sortedBy((s) => s.room) may throw. If it’s non-null in the model, ignore.


69-77: Consider wrapping TabBar in AppBar for consistent theming/elevation

Scaffold.appBar usually expects an AppBar; using AppBar(bottom: TabBar(...)) restores standard theming and affordances.

-        appBar: TabBar(
-          controller: _tabController,
-          tabs: [
-            for (final weekday in Weekday.values)
-              Tab(
-                text: weekday.translate(context),
-              ),
-          ],
-        ),
+        appBar: AppBar(
+          bottom: TabBar(
+            controller: _tabController,
+            tabs: [
+              for (final weekday in Weekday.values)
+                Tab(text: weekday.translate(context)),
+            ],
+          ),
+        ),
l10n/en.arb (3)

51-54: New key: global_duplicate

Consistent with new Duplicate button usage.


604-612: “Size” → “Students” alignment

Updated labels match UI changes.

Also applies to: 660-663


749-818: Kanban keys added — placeholders and descriptions look correct

Keys for titles, columns, settings, and card metadata are well-formed.

l10n/de.arb (3)

51-54: Neuer Schlüssel: global_duplicate

Stimmt mit der neuen Duplizieren-Schaltfläche überein.


602-610: „Größe“ → „Schüler“ angepasst

Entspricht den UI-Änderungen.

Also applies to: 658-661


741-810: Kanban-Übersetzungen — korrekt und konsistent

Backlog „ausblenden/einblenden“ und „Spaltenfarben“ sind idiomatisch.

Comment on lines +54 to +62
/// The column to auto-move completed tasks to
@Default(null) @JsonKey(name: 'automovecompletedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveCompletedTasksTo,

/// The column to auto-move submitted tasks to
@Default(null) @JsonKey(name: 'automovesubmittedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveSubmittedTasksTo,

/// The column to auto-move overdue tasks to
@Default(null) @JsonKey(name: 'automoveoverduetasks') @KanbanColumnConverter() KanbanColumn? autoMoveOverdueTasksTo,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Drop redundant @default(null) on nullable fields.

For KanbanColumn? properties, null is the implicit default; removing @default(null) reduces noise.

-    /// The column to auto-move completed tasks to
-    @Default(null) @JsonKey(name: 'automovecompletedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveCompletedTasksTo,
+    /// The column to auto-move completed tasks to
+    @JsonKey(name: 'automovecompletedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveCompletedTasksTo,

-    /// The column to auto-move submitted tasks to
-    @Default(null) @JsonKey(name: 'automovesubmittedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveSubmittedTasksTo,
+    /// The column to auto-move submitted tasks to
+    @JsonKey(name: 'automovesubmittedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveSubmittedTasksTo,

-    /// The column to auto-move overdue tasks to
-    @Default(null) @JsonKey(name: 'automoveoverduetasks') @KanbanColumnConverter() KanbanColumn? autoMoveOverdueTasksTo,
+    /// The column to auto-move overdue tasks to
+    @JsonKey(name: 'automoveoverduetasks') @KanbanColumnConverter() KanbanColumn? autoMoveOverdueTasksTo,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// The column to auto-move completed tasks to
@Default(null) @JsonKey(name: 'automovecompletedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveCompletedTasksTo,
/// The column to auto-move submitted tasks to
@Default(null) @JsonKey(name: 'automovesubmittedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveSubmittedTasksTo,
/// The column to auto-move overdue tasks to
@Default(null) @JsonKey(name: 'automoveoverduetasks') @KanbanColumnConverter() KanbanColumn? autoMoveOverdueTasksTo,
/// The column to auto-move completed tasks to
@JsonKey(name: 'automovecompletedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveCompletedTasksTo,
/// The column to auto-move submitted tasks to
@JsonKey(name: 'automovesubmittedtasks') @KanbanColumnConverter() KanbanColumn? autoMoveSubmittedTasksTo,
/// The column to auto-move overdue tasks to
@JsonKey(name: 'automoveoverduetasks') @KanbanColumnConverter() KanbanColumn? autoMoveOverdueTasksTo,
🤖 Prompt for AI Agents
In lib/src/auth/domain/models/user.dart around lines 54 to 62, the nullable
KanbanColumn? fields use redundant @Default(null) annotations; remove the
@Default(null) from autoMoveCompletedTasksTo, autoMoveSubmittedTasksTo, and
autoMoveOverdueTasksTo so their implicit null default is used, leaving the
@JsonKey and @KanbanColumnConverter() annotations intact.

);
}

/// Groups all slots by their weekday and startunit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Nit: Doc typo/consistency.

Use “startUnit” (or “start unit”) for consistency with the model field.

-  /// Groups all slots by their weekday and startunit.
+  /// Groups all slots by weekday and startUnit.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Groups all slots by their weekday and startunit.
/// Groups all slots by weekday and startUnit.
🤖 Prompt for AI Agents
In lib/src/slots/presentation/repositories/slot_master_slots_repository.dart
around line 349, the doc comment uses “startunit” which is inconsistent with the
model field naming; update the comment to use “startUnit” (or “start unit” if
you prefer spaced words) so it matches the model field and maintain consistency
across the codebase.

Comment on lines +63 to 65
start = widget.slot?.startUnit ?? widget.startUnit;
end = widget.slot?.endUnit;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Auto-select end when startUnit is provided

Pre-populate end to start + 1 for better UX when creating/duplicating.

-    start = widget.slot?.startUnit ?? widget.startUnit;
-    end = widget.slot?.endUnit;
+    start = widget.slot?.startUnit ?? widget.startUnit;
+    end = widget.slot?.endUnit;
+    if (end == null && start != null) {
+      end = start! + 1;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
start = widget.slot?.startUnit ?? widget.startUnit;
end = widget.slot?.endUnit;
start = widget.slot?.startUnit ?? widget.startUnit;
end = widget.slot?.endUnit;
if (end == null && start != null) {
end = start! + 1;
}
🤖 Prompt for AI Agents
In lib/src/slots/presentation/widgets/edit_slot_dialog.dart around lines 63 to
65, the end unit is not initialized when a startUnit is passed in; update
initialization so that end is set to the slot's endUnit if present, otherwise if
widget.startUnit is non-null set end to widget.startUnit + 1 (ensuring you
handle nullable ints), and only leave end null when neither value exists. Ensure
you preserve existing behavior when slot provides endUnit and handle null-safety
appropriately.

Comment on lines 133 to 139
if (editing) {
await repo.updateSlot(slot);
} else if (widget.duplicate) {
await repo.createSlot(slot.copyWith(id: -1));
} else {
await repo.createSlot(slot);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Duplication creates mappings that still reference the original slot and reuse mapping IDs

When duplicating, you’re creating mappings with the original slotId and possibly reusing existing mapping IDs. This can update the original mappings or be rejected downstream. For duplication/new creation, mappings should use slotId = -1 and omit mappingId.

Apply this diff (paired with the next comment’s change) so duplication/new creation never reuses mapping IDs and never references the old slot:

-    if (editing) {
+    if (editing) {
       await repo.updateSlot(slot);
-    } else if (widget.duplicate) {
-      await repo.createSlot(slot.copyWith(id: -1));
+    } else if (widget.duplicate) {
+      await repo.createSlot(slot.copyWith(id: -1));
     } else {
       await repo.createSlot(slot);
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In lib/src/slots/presentation/widgets/edit_slot_dialog.dart around lines 133 to
139, the duplication branch currently calls createSlot with the slot (or
slot.copyWith(id: -1)) but leaves mapping objects referencing the original
slotId and retaining mapping IDs; update the code that prepares the Slot for
creation (both duplicate and new cases) to deep-copy its mappings so each
mapping has slotId set to -1 and mappingId omitted/null (or removed) before
passing to repo.createSlot, ensuring new mappings are treated as new records and
do not reference or overwrite the original slot's mappings.

Comment on lines +197 to 208
void addMapping(MappingElement element) {
final CourseToSlot mapping;
final slotId = widget.slot?.id ?? -1;

void setCourse(MoodleCourse? course) {
setState(() {
this.course = course;
});
}
if (element.mappingId == null) {
mapping = CourseToSlot.noId(courseId: element.courseId!, slotId: slotId, vintage: element.vintage!);
} else {
mapping = CourseToSlot(id: element.mappingId!, courseId: element.courseId!, slotId: slotId, vintage: element.vintage!);
}

void setVintage(Vintage? vintage) {
setState(() {
this.vintage = vintage;
});
mappings.add(mapping);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix mapping construction for create/duplicate flows

Ensure slotId = -1 for new/duplicate and never reuse mappingId unless editing. This prevents cross-linking to the original slot and accidental updates.

Apply:

   void addMapping(MappingElement element) {
-    final CourseToSlot mapping;
-    final slotId = widget.slot?.id ?? -1;
-
-    if (element.mappingId == null) {
-      mapping = CourseToSlot.noId(courseId: element.courseId!, slotId: slotId, vintage: element.vintage!);
-    } else {
-      mapping = CourseToSlot(id: element.mappingId!, courseId: element.courseId!, slotId: slotId, vintage: element.vintage!);
-    }
-
-    mappings.add(mapping);
+    final CourseToSlot mapping;
+    // Only retain the existing slotId when editing. For create/duplicate, use -1.
+    final slotId = editing ? widget.slot!.id : -1;
+
+    // Only reuse mappingId when editing; for create/duplicate always create a new mapping.
+    if (editing && element.mappingId != null) {
+      mapping = CourseToSlot(
+        id: element.mappingId!,
+        courseId: element.courseId!,
+        slotId: slotId,
+        vintage: element.vintage!,
+      );
+    } else {
+      mapping = CourseToSlot.noId(
+        courseId: element.courseId!,
+        slotId: slotId,
+        vintage: element.vintage!,
+      );
+    }
+
+    mappings.add(mapping);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void addMapping(MappingElement element) {
final CourseToSlot mapping;
final slotId = widget.slot?.id ?? -1;
void setCourse(MoodleCourse? course) {
setState(() {
this.course = course;
});
}
if (element.mappingId == null) {
mapping = CourseToSlot.noId(courseId: element.courseId!, slotId: slotId, vintage: element.vintage!);
} else {
mapping = CourseToSlot(id: element.mappingId!, courseId: element.courseId!, slotId: slotId, vintage: element.vintage!);
}
void setVintage(Vintage? vintage) {
setState(() {
this.vintage = vintage;
});
mappings.add(mapping);
}
void addMapping(MappingElement element) {
final CourseToSlot mapping;
// Only retain the existing slotId when editing. For create/duplicate, use -1.
final slotId = editing ? widget.slot!.id : -1;
// Only reuse mappingId when editing; for create/duplicate always create a new mapping.
if (editing && element.mappingId != null) {
mapping = CourseToSlot(
id: element.mappingId!,
courseId: element.courseId!,
slotId: slotId,
vintage: element.vintage!,
);
} else {
mapping = CourseToSlot.noId(
courseId: element.courseId!,
slotId: slotId,
vintage: element.vintage!,
);
}
mappings.add(mapping);
}
🤖 Prompt for AI Agents
In lib/src/slots/presentation/widgets/edit_slot_dialog.dart around lines 197 to
208, the mapping construction currently risks reusing the original
slotId/mappingId for create/duplicate flows; ensure that for new or duplicate
flows you always set slotId to -1 and do not reuse mappingId (leave it null) so
a new mapping is created, and only preserve mappingId when the user is actively
editing an existing mapping. Modify the logic to detect create/duplicate context
and force slotId = -1 and mappingId = null in those cases, otherwise construct
CourseToSlot with the existing id when genuinely editing.

Comment on lines +332 to +336
height: (options.length * 50.0).clamp(0, 200),
child: Card(
elevation: 8,
color: context.theme.scaffoldBackgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final option in options)
MenuItemButton(
child: Text(option),
onPressed: () => onSelected(option),
),
],
child: ListView.builder(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Clamp returns num; SizedBox.height expects double

This won’t type-check in Dart 3 without a cast or double bounds.

Use toDouble():

-                          height: (options.length * 50.0).clamp(0, 200),
+                          height: (options.length * 50.0).clamp(0, 200).toDouble(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
height: (options.length * 50.0).clamp(0, 200),
child: Card(
elevation: 8,
color: context.theme.scaffoldBackgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final option in options)
MenuItemButton(
child: Text(option),
onPressed: () => onSelected(option),
),
],
child: ListView.builder(
height: (options.length * 50.0).clamp(0, 200).toDouble(),
child: Card(
elevation: 8,
color: context.theme.scaffoldBackgroundColor,
child: ListView.builder(
🤖 Prompt for AI Agents
In lib/src/slots/presentation/widgets/edit_slot_dialog.dart around lines 332 to
336, the expression (options.length * 50.0).clamp(0, 200) yields a num but
SizedBox.height expects a double; change the clamp call to produce a double
(e.g. call .toDouble() on the result or supply double bounds like clamp(0.0,
200.0)) so the height expression is a double and the code type-checks in Dart 3.

@mcquenji mcquenji merged commit 7fbb608 into main Sep 23, 2025
4 of 7 checks passed
@mcquenji mcquenji deleted the lpa-53 branch September 23, 2025 15:30
@coderabbitai coderabbitai bot mentioned this pull request Oct 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants