Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

15 — Flutter Implementation Guide (Homework Module)

How to build the Homework feature in the Flutter client on top of 00-shared/11. Forward-looking spec; no client repo exists yet.


1. Folder structure

features/homework/
├── domain/
│   ├── models/
│   │   ├── homework.dart          # id, teacherId, classId, subjectId, title,
│   │   │                          #   description?, attachments[], assignedDate,
│   │   │                          #   dueDate, status (HomeStatus enum)
│   │   ├── homework_submission.dart # id, homeworkId, studentId, attachments[],
│   │   │                          #   remarks?, submittedAt, status (SubStatus enum),
│   │   │                          #   gradedAt?, marks? + derived isLate
│   │   └── homework_file.dart     # id, originalName, mimeType, size (files doc)
│   └── exceptions/homework_exceptions.dart  # AlreadySubmittedException (409)
├── data/
│   ├── dto/
│   │   ├── create_homework_dto.dart
│   │   ├── update_homework_dto.dart
│   │   ├── submit_homework_dto.dart
│   │   └── grade_submission_dto.dart
│   └── repositories/
│       ├── homework_repository.dart
│       └── file_repository.dart   # upload/download (shared with other modules)
└── presentation/
    ├── cubit/
    │   ├── homework_list_cubit.dart
    │   ├── homework_detail_cubit.dart
    │   ├── homework_form_cubit.dart
    │   ├── submit_cubit.dart
    │   ├── submissions_cubit.dart
    │   └── grade_cubit.dart
    ├── pages/
    │   ├── homework_list_page.dart
    │   ├── homework_detail_page.dart
    │   ├── homework_form_page.dart      # create + edit (mode flag)
    │   ├── submit_page.dart
    │   ├── submissions_page.dart
    │   └── grade_sheet_page.dart
    └── widgets/
        ├── homework_card.dart
        ├── submission_row.dart
        ├── late_badge.dart
        ├── submission_status_chip.dart
        ├── grade_feedback_card.dart
        ├── attachment_uploader.dart
        ├── attachment_list.dart
        └── grade_sheet_body.dart

2. Dependencies

flutter_bloc, dio (AppDio — 120 s timeout on uploads, 00-shared/11 §5), go_router, get_it, intl, flutter_markdown (description + remarks rendering, sanitize links — 00-shared/03 E), cached_network_image (avatar only; file previews download-then-render), file_picker/image_picker (pick attachments; verify license — wrap in AttachmentPicker so swappable), path_provider (temp previews). No PDF/office viewer lib in P0 — external/share-sheet fallback (verify later).

3. Cubits

Per 13_State_Management.md: HomeworkListCubit, HomeworkDetailCubit, HomeworkFormCubit, SubmitCubit, SubmissionsCubit, GradeCubit. All pure-Dart, repositories injected; UI never calls dio directly.

4. HomeworkRepository

class HomeworkRepository {
  Future<List<Homework>> byClass(String classId);            // GET /homework/class/:id
  Future<Homework> byId(String id);                          // GET /homework/:id
  Future<Homework> create(CreateHomeworkDto);                // POST /homework
  Future<Homework> update(String id, UpdateHomeworkDto);     // PATCH /homework/:id
  Future<void> remove(String id);                            // DELETE /homework/:id
  Future<HomeworkSubmission> submit(String id, SubmitHomeworkDto); // POST /homework/:id/submit
      // throws AlreadySubmittedException on 409
  Future<List<HomeworkSubmission>> submissions(String id);   // GET /homework/:id/submissions
  Future<HomeworkSubmission> grade(String id, String submissionId,
      GradeSubmissionDto);                                   // PATCH …/grade
}

FileRepository.upload(File, onProgress)HomeworkFile (id) via POST /files/upload (file.upload perm); download(id) → bytes via GET /files/:id/download (file.read). DTO mapping per 00-shared/11 §4; envelope parsing in AppDio error interceptor.

5. Navigation

go_router routes (under the /homework shell branch, 00-shared/05 §2): /homework (list), /homework/new, /homework/:id, /homework/:id/edit, /homework/:id/submit, /homework/:id/submissions, /homework/:id/submissions/:submissionId. Guards: authGuard; role-gated route meta canCreate/canGrade — hidden at navigation level (FAB, menu) and enforced by permissionGuard('homework.create') style checks once server perms land (OQ-9). Deep link: studylyon://homework/:id → detail (root Uri table in 00-shared/11 §6).

6. Upload progress UI (AppAttachmentUploader)

  • dio onSendProgressUploadState.uploading(progress)LinearProgressIndicator determinate; throttle widget rebuilds (updates ≤ 60/s via ValueNotifier<double>).
  • States per file: idle → uploading → uploaded(fileId) | failed(reason); retry re-POSTs (no resume — 00-shared/12 B7).
  • Gate: Submit/Assign disabled while uploading > 0 || failed > 0 (failed shows inline error + Retry, never silent).
  • a11y: Semantics(liveRegion: true, label: "Uploading {name}, {percent}%").
  • 25 MB client cap; error copy "File too large (max 25 MB)" (server has no limit — OQ-4/01; update when server adds one).
  • Offline mid-upload → file keeps failed(network); on reconnect auto-retry (proposed).

7. Markdown description rendering

  • AppMarkdownViewer(data: homework.description)flutter_markdown wrapped (00-shared/03 E): sanitize links (allow http/https only), selectable: true, compact style for remarks, full style for description; empty → hide block.
  • Server stores plain text (description: String?, homework.schema.ts:21-22) — no backend rendering; client-side only.

8. Late badge derivation (timezone-safe)

  • isLate(DateTime dueDate, DateTime submittedAt) compared in tenant timezone (from org config; default device). Compute at render time, never cache the boolean in the model (due date is server UTC ISO — parse with DateTime.parse(...).toLocal() via AppDateTime util, 00-shared/11 §8).
  • Server has no late flag (OQ-1) — badge is derived UI; when the server adds isLate/overdue scheduler, prefer server value.

9. Theme

AppTheme.light()/dark() unchanged; module adds no tokens — all surfaces use role colors (11_Design_System_Mapping.md). AppSpacing/AppRadius/AppMotion constants only.

10. Localization keys

homework.list.title, homework.list.empty, homework.create.title, homework.submit.title, homework.submit.already_submitted, homework.grade.title, homework.grade.regrade_warning, homework.late.badge ("Late · {d}d"), homework.status.* (active/closed/submitted/graded), homework.attachment.* (uploading/too_large/retry), error-code fallbacks per 00-shared/07 §11.

11. Testing

  • Unit: SubmitCubit 409→duplicate; GradeCubit regrade; lateBadge timezone (UTC boundary cases); DTO↔model mappers; upload state machine.
  • Widget: list loading/error/empty; detail role variants (student/teacher); submit duplicate banner; grade sheet error keeps values; uploader states.
  • Golden: HomeworkCard, SubmissionRow, LateBadge, AttachmentUploader, GradeSheetBody (light/dark × 3 sizes).
  • Integration: teacher create → student submit → grade → feedback; duplicate submit.
  • E2E (P0): full loop with real upload on device cloud (14_QA_Checklist.md).

12. Performance

  • ListView.builder for lists; RepaintBoundary around markdown/PDF previews.
  • Grade sheet: Future.wait homework+submissions on open (single skeleton).
  • Upload progress via ValueNotifier, not setState.
  • const constructors; cache student profile classId.

13. Proposals flagged to the team

  1. When server adds GET /homework (PLAN.md:67 student endpoint), drop the per-card submissions hack (OQ-6).
  2. When homework.* permissions land, wire route/FAB/menu gating to real perms (OQ-9).
  3. When NotificationType enum accepts homework events (OQ-8), enable notification deep-link navigation studylyon://homework/:id.
  4. When parent view lands (IMPLEMENTATION_PLAN.md:227), add read-only parent mode.
  5. Analytics wiring waits shared AnalyticsService (00-shared/10 §8).