15 — Flutter Implementation Guide (Homework Module)
- 1. Folder structure
- 2. Dependencies
- 3. Cubits
- 4. HomeworkRepository
- 5. Navigation
- 6. Upload progress UI (
AppAttachmentUploader) - 7. Markdown description rendering
- 8. Late badge derivation (timezone-safe)
- 9. Theme
- 10. Localization keys
- 11. Testing
- 12. Performance
- 13. Proposals flagged to the team
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)
dioonSendProgress→UploadState.uploading(progress)→LinearProgressIndicatordeterminate; throttle widget rebuilds (updates ≤ 60/s viaValueNotifier<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_markdownwrapped (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 withDateTime.parse(...).toLocal()viaAppDateTimeutil,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:
SubmitCubit409→duplicate;GradeCubitregrade;lateBadgetimezone (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.builderfor lists;RepaintBoundaryaround markdown/PDF previews.- Grade sheet:
Future.waithomework+submissions on open (single skeleton). - Upload progress via
ValueNotifier, not setState. constconstructors; cache student profile classId.
13. Proposals flagged to the team
- When server adds
GET /homework(PLAN.md:67 student endpoint), drop the per-card submissions hack (OQ-6). - When
homework.*permissions land, wire route/FAB/menu gating to real perms (OQ-9). - When
NotificationTypeenum accepts homework events (OQ-8), enable notification deep-link navigationstudylyon://homework/:id. - When parent view lands (
IMPLEMENTATION_PLAN.md:227), add read-only parent mode. - Analytics wiring waits shared
AnalyticsService(00-shared/10 §8).