Skip to content

Conversation

@AliAkrem
Copy link
Owner

@AliAkrem AliAkrem commented Nov 28, 2025

Description

  • allow students to see their academic debts

Types of changes

  • Bug Fix (non-breaking change which fixes an issue)
  • New Feature (non-breaking change which adds functionality)
  • Others (any other types not listed above)

Checklist

Summary by CodeRabbit

  • New Features
    • Academic Debts section added to the dashboard with its own page and navigation.
    • View debts organized by academic year with per-course cards and year summary cards.
    • Shows totals, cached-data indicator, and supports pull-to-refresh/refresh button.
    • Localized UI added for Arabic and English.
    • Client-side caching for faster load and offline resilience.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

Walkthrough

Adds a new Debts feature: data models, repository, caching service, BLoC (events/states), UI pages/widgets, DI registration, router route, dashboard card, auth logout cache-clear, and English/Arabic localization entries.

Changes

Cohort / File(s) Summary
App integration
lib/app.dart, lib/config/routes/app_router.dart
Registered DebtsBloc in app providers and added debts route (builder → DebtsPage) with path constant.
Dependency Injection
lib/core/di/injector.dart
Registered DebtsRepositoryImpl, DebtsCacheService, and factory for DebtsBloc in the injector.
BLoC & State
lib/features/debts/presentation/bloc/debts_bloc.dart, .../debts_event.dart, .../debts_state.dart
Implemented DebtsBloc with LoadDebts (forceRefresh) and ClearDebtsCache events; states: Initial, Loading, Loaded (fromCache flag), Empty, Error; cache-first loading + fallback on error.
Repository
lib/features/debts/data/repositories/debts_repository_impl.dart
Added DebtsRepositoryImpl fetching /infos/dettes/{uuid}, parsing AcademicYearDebt, sorting by idAnneeAcademique.
Caching service
lib/features/debts/data/services/debts_cache_service.dart
Added DebtsCacheService using SharedPreferences: cache/read/clear, last-updated, and staleness check (default 12h).
Data models
lib/features/debts/data/models/academic_year_debt.dart, .../debt_course.dart
Added AcademicYearDebt and DebtCourse models with required fields, fromJson factories and toJson methods.
Presentation (pages & widgets)
lib/features/debts/presentation/pages/debts_page.dart, .../year_summary_card.dart, .../debt_course_card.dart
New DebtsPage (dispatches LoadDebts on init, renders states), YearSummaryCard, and DebtCourseCard UI components with themed/localized content.
Dashboard
lib/features/dashboard/presentation/widgets/dashboard.dart
Added academicDebts grid card (icon assignment_late) navigating to debts route.
Auth integration
lib/features/auth/presentation/bloc/auth_bloc.dart
On logout, dispatches ClearDebtsCache to DebtsBloc after other cache clears.
Localization
lib/l10n/app_en.arb, lib/l10n/app_ar.arb, lib/l10n/app_localizations*.dart
Added debt-related keys and generated localization getters/methods (academicDebts, noDebts, debtsSummary, totalDebts, academicYears, debtCoursesCount, finalGrade, gradeBreakdown, continuousControl) in English and Arabic.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DebtsPage
    participant DebtsBloc
    participant DebtsCacheService
    participant DebtsRepositoryImpl
    participant ApiClient

    User->>DebtsPage: Open DebtsPage
    DebtsPage->>DebtsBloc: Dispatch LoadDebts(forceRefresh: false)
    DebtsBloc->>DebtsBloc: Emit DebtsLoading

    alt Cache fresh and not forceRefresh
        DebtsBloc->>DebtsCacheService: isDataStale()
        DebtsCacheService-->>DebtsBloc: false
        DebtsBloc->>DebtsCacheService: getCachedDebts()
        DebtsCacheService-->>DebtsBloc: cached debts
        DebtsBloc->>DebtsPage: Emit DebtsLoaded(fromCache: true)
    else Fetch from API
        DebtsBloc->>DebtsRepositoryImpl: getStudentDebts()
        DebtsRepositoryImpl->>ApiClient: GET /infos/dettes/{uuid}
        ApiClient-->>DebtsRepositoryImpl: response
        DebtsRepositoryImpl-->>DebtsBloc: List<AcademicYearDebt>
        DebtsBloc->>DebtsCacheService: cacheDebts(debts)
        DebtsCacheService-->>DebtsBloc: cached
        DebtsBloc->>DebtsPage: Emit DebtsLoaded(fromCache: false)
    end

    alt Fetch error
        DebtsBloc->>DebtsCacheService: getCachedDebts()
        DebtsCacheService-->>DebtsBloc: maybe cached debts / null
        alt cached exists
            DebtsBloc->>DebtsPage: Emit DebtsLoaded(fromCache: true)
        else
            DebtsBloc->>DebtsPage: Emit DebtsError(message)
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25–35 minutes

  • Pay extra attention to:
    • debts_bloc.dart — staleness check, state emission order, error fallback logic
    • debts_cache_service.dart — SharedPreferences serialization/deserialization and timestamp handling
    • debts_repository_impl.dart — null-safety for API response and UUID retrieval
    • auth_bloc.dart logout integration — ensure dispatch of ClearDebtsCache is safe during logout
    • Localization pluralization in app_localizations_*.dart

Possibly related PRs

  • Feat/student discharge #6 — Modifies the same app integration points (DI providers, router, dashboard card) to register a feature bloc/route; likely relates to integration patterns and conflicts.

Poem

🐰 I hop through code with eager paws,
New debts lined up in tidy rows,
Cache and API dance a tune,
Cards appear beneath the moon,
Two tongues sing — the feature grows! 🎀

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/add academic debts feature' clearly describes the main change: adding a new academic debts feature to the application. It directly maps to the pull request's core objective and is supported by the extensive changes across multiple files implementing this feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Feat/add-debts-feature

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.

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: 0

🧹 Nitpick comments (8)
lib/l10n/app_localizations_ar.dart (1)

532-540: Consider enhanced Arabic plural forms for grammatical accuracy.

Arabic has six plural categories (zero, one, two, few, many, other) according to CLDR. The current implementation only handles one and other, which works but may not be grammatically ideal for all count values (e.g., dual form for 2, or specific forms for 3-10).

If more precise Arabic pluralization is desired, consider:

 String debtCoursesCount(int count) {
   String _temp0 = intl.Intl.pluralLogic(
     count,
     locale: localeName,
     other: 'مقاييس راسبة',
     one: 'مقياس راسب',
+    two: 'مقياسان راسبان',
+    few: 'مقاييس راسبة',
+    many: 'مقياساً راسباً',
   );
   return '$count $_temp0';
 }

This is optional and can be deferred if not critical for the target audience.

lib/features/dashboard/presentation/widgets/dashboard.dart (1)

143-149: New academic debts dashboard card is wired correctly; minor consistency nit

The card correctly uses the new localization key and navigates to the debts route; behavior looks good. The only small nit is consistency: surrounding cards call goNamed with the *Path constants, while this one uses AppRouter.debts. Because both constants are the same string this is safe, but you may want to standardize on either the name or path constant for readability.

lib/config/routes/app_router.dart (1)

6-6: Debts route integration under the dashboard shell looks correct

The new debts route constants and nested GoRoute under dashboardPath are consistent with the existing routing structure and match the dashboard card’s goNamed(AppRouter.debts) usage, so navigation to /dashboard/debts should work as expected. If you ever clean up this file, you might consider enforcing a single convention (always using either the *Path or non‑Path constants for route names) to avoid confusion.

Also applies to: 36-37, 53-54, 135-139

lib/features/debts/presentation/widgets/debt_course_card.dart (1)

1-220: DebtCourseCard is solid; consider locale‑aware course labels

The widget structure and theming look good and should render debts cleanly. Two small improvements you might consider:

  • Right now the title always uses course.mcFr. If the model also exposes mcAr, you could pick between them based on the active locale so Arabic users see Arabic course names.
  • For the main title text you might want to start from theme.textTheme.titleMedium (or similar) and copyWith rather than constructing a fresh TextStyle, to stay aligned with global typography defaults.

These are purely refinements; the current implementation is functionally fine.

lib/features/debts/data/repositories/debts_repository_impl.dart (1)

1-28: Repository logic is fine; exception handling can be simplified

getStudentDebts correctly guards on missing UUID, hits the debts endpoint, and parses the result into AcademicYearDebt objects. Two small cleanups you might consider:

  • The try/catch that just does rethrow is redundant; you can drop the try block entirely unless you plan to map or log errors here.
  • Instead of throwing a generic Exception('UUID not found, please login again') from the data layer, you could define a small domain/ auth‑related exception and let the bloc map that to a localized, user‑friendly message.

Neither change is required for correctness, but they’d make error handling clearer.

lib/features/debts/presentation/pages/debts_page.dart (2)

21-25: Bloc usage is correct; consider standardizing on context.read

Dispatching LoadDebts in initState and from the refresh button is the right behavior. For consistency with the rest of the file you might switch the initState call from BlocProvider.of<DebtsBloc>(context) to context.read<DebtsBloc>() so all dispatches use the same API.

Also applies to: 44-46


48-53: Be mindful of how much error detail you surface to users

_buildErrorState shows both a generic localized “something went wrong” title and the raw message from DebtsError. If that message is derived from exceptions or backend responses, it could include technical details that you might not want in the UI. Consider mapping known failures to user‑friendly localized messages in the bloc and reserving the raw message for logs or debug builds.

Also applies to: 106-125

lib/features/debts/presentation/bloc/debts_bloc.dart (1)

8-69: Robust cache‑first load flow; consider emitting a state on cache clear

The load handler nicely prioritizes fresh cache, falls back to remote, and uses cached data as a safety net on errors—good resilience and separation of concerns.

Optionally, you could have _onClearCache also emit something like DebtsEmpty or DebtsInitial so any debts screen listening to the bloc immediately reflects that the cache was cleared (unless you intentionally want this event to be side‑effect only).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af267c2 and 7d6e2b2.

📒 Files selected for processing (20)
  • lib/app.dart (2 hunks)
  • lib/config/routes/app_router.dart (4 hunks)
  • lib/core/di/injector.dart (3 hunks)
  • lib/features/auth/presentation/bloc/auth_bloc.dart (2 hunks)
  • lib/features/dashboard/presentation/widgets/dashboard.dart (1 hunks)
  • lib/features/debts/data/models/academic_year_debt.dart (1 hunks)
  • lib/features/debts/data/models/debt_course.dart (1 hunks)
  • lib/features/debts/data/repositories/debts_repository_impl.dart (1 hunks)
  • lib/features/debts/data/services/debts_cache_service.dart (1 hunks)
  • lib/features/debts/presentation/bloc/debts_bloc.dart (1 hunks)
  • lib/features/debts/presentation/bloc/debts_event.dart (1 hunks)
  • lib/features/debts/presentation/bloc/debts_state.dart (1 hunks)
  • lib/features/debts/presentation/pages/debts_page.dart (1 hunks)
  • lib/features/debts/presentation/widgets/debt_course_card.dart (1 hunks)
  • lib/features/debts/presentation/widgets/year_summary_card.dart (1 hunks)
  • lib/l10n/app_ar.arb (1 hunks)
  • lib/l10n/app_en.arb (1 hunks)
  • lib/l10n/app_localizations.dart (1 hunks)
  • lib/l10n/app_localizations_ar.dart (1 hunks)
  • lib/l10n/app_localizations_en.dart (1 hunks)
🔇 Additional comments (15)
lib/l10n/app_localizations.dart (1)

1041-1101: LGTM! Localization entries for debts feature are well-structured.

The new abstract getters and the debtCoursesCount method with pluralization support follow the established patterns in the file. Documentation comments are properly formatted.

lib/l10n/app_localizations_en.dart (1)

516-555: LGTM! English translations correctly implemented.

The debtCoursesCount method properly uses Intl.pluralLogic with the correct locale and plural forms. All translations follow the established pattern.

lib/l10n/app_en.arb (1)

673-720: LGTM! ARB entries properly defined with correct ICU message format.

The pluralization syntax {count, plural, =1{course} other{courses}} follows ICU MessageFormat correctly. Placeholder metadata is complete with type and example values.

lib/features/auth/presentation/bloc/auth_bloc.dart (2)

5-6: LGTM! Imports added for debts feature integration.


116-120: LGTM! Debts cache clearing follows established pattern.

The cache clearing for DebtsBloc on logout is consistent with other bloc cache operations in the method. Error handling properly catches exceptions and logs them without interrupting the logout flow.

lib/app.dart (1)

11-11: DebtsBloc registration matches existing DI pattern

Adding DebtsBloc to the MultiBlocProvider via injector<DebtsBloc>() is consistent with the other feature blocs and should make the debts feature available wherever needed without side effects.

Also applies to: 38-38

lib/core/di/injector.dart (1)

28-30: Debts feature DI wiring is consistent with the rest of the app

The new registrations for DebtsRepositoryImpl, DebtsCacheService, and DebtsBloc follow the same singleton/factory pattern used by other features and respect dependency order (ApiClient → repositories → caches → blocs). This should integrate cleanly without lifecycle surprises.

Also applies to: 35-35, 61-63, 69-69, 120-122

lib/features/debts/presentation/bloc/debts_event.dart (1)

1-21: DebtsEvent hierarchy is minimal and idiomatic

The event definitions (base DebtsEvent, LoadDebts with forceRefresh, and ClearDebtsCache) follow the usual Equatable/Bloc patterns and give you the flexibility to distinguish cache‑busting loads without overcomplicating the API.

lib/features/debts/presentation/pages/debts_page.dart (1)

131-238: Debts view composition and caching indicator look good

The debts view correctly:

  • Computes totalDebts from year.dette.length.
  • Shows a clear cached‑data indicator when state.fromCache is true.
  • Summarizes counts and then renders per‑year sections via YearSummaryCard and DebtCourseCard.

This structure should scale fine for typical datasets and keeps the UI logic nicely contained in helpers.

lib/features/debts/data/models/debt_course.dart (1)

1-62: DebtCourse DTO and JSON mapping look solid

Fields, constructor, and fromJson/toJson are symmetric and strongly typed; this is a clean, predictable model for the debts API.

lib/features/debts/data/models/academic_year_debt.dart (1)

1-35: AcademicYearDebt mapping is consistent and readable

The model cleanly represents the API shape, and the nested dette list mapping via DebtCourse.fromJson/toJson is straightforward and symmetric.

lib/features/debts/presentation/bloc/debts_state.dart (1)

1-34: Well-structured DebtsState hierarchy

The state set (initial/loading/loaded/empty/error) is clear, and Equatable props are correctly overridden so Bloc comparisons will behave as expected.

lib/features/debts/presentation/widgets/year_summary_card.dart (1)

5-93: YearSummaryCard is cleanly composed and localization-aware

The card layout, theming usage, and localized texts (year wrapper + pluralized debt count) are well put together and should be easy to reuse across the debts UI.

lib/l10n/app_ar.arb (1)

692-737: Debts-related Arabic localizations look consistent and well-scoped

The new debts strings (titles, empty-state description, summary labels, and pluralized debtCoursesCount) integrate cleanly with the existing ARB structure and match the fields used in the new UI.

lib/features/debts/data/services/debts_cache_service.dart (1)

12-23: Rewritten review comment should be removed — SharedPreferences API does not support the suggested fix

The suggestion to capture boolean returns from setString() and remove() is based on outdated API behavior. In shared_preferences v2.5.3 (the version in pubspec.yaml), these methods return Future<void>, not Future<bool>. The current implementation correctly handles errors via exception handling, which is the appropriate pattern for this API. The code as-is is correct and follows the intended design of the shared_preferences package.

Likely an incorrect or invalid review comment.

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: 2

🧹 Nitpick comments (1)
lib/features/debts/data/repositories/debts_repository_impl.dart (1)

11-28: Remove the try-catch block or add meaningful error handling.

The try-catch block that only rethrows adds no value. Either remove it entirely or add meaningful error handling (e.g., logging, exception transformation, or adding context).

🔎 Proposed fix
   Future<List<AcademicYearDebt>> getStudentDebts() async {
-    try {
-      final uuid = await _apiClient.getUuid();
-      if (uuid == null) {
-        throw Exception('UUID not found, please login again');
-      }
-      final response = await _apiClient.get('/infos/dettes/$uuid');
-      if (response.data == null) {
-        return [];
-      }
-      final List<dynamic> debtsJson = response.data;
-      final debts = debtsJson
-          .map((debtJson) => AcademicYearDebt.fromJson(debtJson))
-          .toList();
-      debts.sort((a, b) => b.idAnneeAcademique.compareTo(a.idAnneeAcademique));
-      return debts;
-    } catch (e) {
-      rethrow;
-    }
+    final uuid = await _apiClient.getUuid();
+    if (uuid == null) {
+      throw Exception('UUID not found, please login again');
+    }
+    final response = await _apiClient.get('/infos/dettes/$uuid');
+    if (response.data == null) {
+      return [];
+    }
+    final List<dynamic> debtsJson = response.data;
+    final debts = debtsJson
+        .map((debtJson) => AcademicYearDebt.fromJson(debtJson))
+        .toList();
+    debts.sort((a, b) => b.idAnneeAcademique.compareTo(a.idAnneeAcademique));
+    return debts;
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d6e2b2 and a30332b.

📒 Files selected for processing (1)
  • lib/features/debts/data/repositories/debts_repository_impl.dart (1 hunks)

Comment on lines +7 to +8
DebtsRepositoryImpl({ApiClient? apiClient})
: _apiClient = apiClient ?? ApiClient();
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 | 🟠 Major

Remove the fallback ApiClient() instantiation.

Creating a new ApiClient() when none is provided defeats dependency injection and makes the class difficult to test. According to the DI configuration, apiClient is always injected when registering this repository. Make the parameter required to enforce proper DI usage.

🔎 Proposed fix
-  DebtsRepositoryImpl({ApiClient? apiClient})
-    : _apiClient = apiClient ?? ApiClient();
+  DebtsRepositoryImpl({required ApiClient apiClient})
+    : _apiClient = apiClient;
📝 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
DebtsRepositoryImpl({ApiClient? apiClient})
: _apiClient = apiClient ?? ApiClient();
DebtsRepositoryImpl({required ApiClient apiClient})
: _apiClient = apiClient;
🤖 Prompt for AI Agents
In lib/features/debts/data/repositories/debts_repository_impl.dart around lines
7-8, the constructor currently falls back to creating a new ApiClient() when
none is passed, which breaks DI and testing; change the constructor to require
an ApiClient parameter (remove the default null and the fallback instantiation)
and assign that required ApiClient to _apiClient so the repository always
receives the injected dependency.

@AliAkrem AliAkrem merged commit 4be1ac1 into master Dec 20, 2025
1 check passed
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