15 — Flutter Implementation Guide (Rooms Module)
- 1. Structure
- 2. Models & enums (exact from schema)
- 3. Repositories
- 4. Cubits (see
13) - 5. Navigation (go_router)
- 6. Theme
- 7. Extensions
- 8. Localization
- 9. Testing
- 10. Performance
- 11. Open items to wire when backend lands
Module extension of 00-shared/11 (Flutter App Architecture). Structure, widgets, cubits, repositories, DTOs, models, navigation, theme, extensions, localization, testing, performance. Forward-looking.
1. Structure
lib/features/rooms/
├── data/
│ ├── dto/
│ │ ├── room_dto.dart # envelope payload → model (fromJson/toJson)
│ │ └── room_create_dto.dart # mirrors CreateRoomDto (create-room.dto.ts:11-38)
│ ├── models/
│ │ └── room.dart # domain model + RoomType enum
│ └── repositories/
│ └── rooms_repository.dart # E1–E5 (12_API_Mapping)
├── domain/
│ └── room_code.dart # pure code-normalization (uppercase/trim) `(proposed)`
└── presentation/
├── cubit/
│ ├── room_list_cubit.dart # PaginatedListMixin<Room>
│ ├── room_detail_cubit.dart
│ └── room_editor_cubit.dart
├── pages/
│ ├── room_list_page.dart # S1
│ ├── room_detail_page.dart # S2
│ └── room_editor_page.dart # S3 (create + edit)
└── widgets/
├── room_card.dart
├── room_type_icon.dart
├── capacity_badge.dart
├── facility_chips.dart
├── room_filter_bar.dart
├── room_type_picker.dart
├── room_code_field.dart
├── facility_input_chips.dart
└── delete_room_dialog.dart
2. Models & enums (exact from schema)
enum RoomType { classroom, lab, library, office, hall, other } // room.schema.ts:7-14
Room mirrors room.schema.ts:16-35 + base.schema.ts: id, tenantId, name, code,
capacity?, type (default classroom), building?, facilities: List<String>,
createdBy?/updatedBy?, isDeleted, deletedAt?, deletedBy?, version, createdAt,
updatedAt. Ids String; dates DateTime (parse ISO). Never send DTOs to widgets
(00-shared/11 §4).
3. Repositories
RoomsRepository (dio via AppDio bearer/refresh/error interceptors — 00-shared/11 §5):
Future<Paginated<Room>> list({int page = 1, int limit = 20}); // E2
Future<Room> getById(String id); // E3
Future<Room> create(RoomCreateDto dto); // E1
Future<Room> update(String id, RoomCreateDto dto); // E4
Future<void> delete(String id); // E5 (void!)
- E5 returns void because the handler is void (
rooms.service.ts:48) — local row removal after 200, never a payload. - Typed exceptions:
ApiException(code, status, fieldDetails, message)from the error interceptor (00-shared/06 §5);DUPLICATE_RESOURCE(409) handled as field error in the editor.
4. Cubits (see 13)
RoomListCubit (PaginatedListMixin + client filters (proposed)), RoomDetailCubit
(404 → NotFound state), RoomEditorCubit (form + dirty + 409-inline). All pure-Dart,
DI via get_it lazy factories (00-shared/11 §2).
5. Navigation (go_router)
GoRoute(path: '/rooms', builder: RoomListPage, guards: [authGuard, permissionGuard('rooms.read')]),
GoRoute(path: '/rooms/new', builder: RoomEditorPage(create), guards: [authGuard, permissionGuard('rooms.create')]),
GoRoute(path: '/rooms/:id', builder: RoomDetailPage, guards: [authGuard, permissionGuard('rooms.read')]),
GoRoute(path: '/rooms/:id/edit', builder: RoomEditorPage(edit), guards: [authGuard, permissionGuard('rooms.update')]),
Permission guards mirror permissions.constants.ts:50-53 (rooms.read/create/update/delete).
Server does not enforce these today (OQ-2 — rooms.controller.ts:19); the client guard
is the only gate until the backend RBAC guard lands; server remains authoritative later
(403 → 403 screen). Master-detail via StatefulShellBranch at ≥ 840 dp (00-shared/05 §3).
Deep links (forward-looking): studylyon://rooms, studylyon://rooms/:id.
6. Theme
Standard AppTheme tokens (00-shared/02, 11); type-tinted icons via
colorScheme.*Container variants (proposed). No literal colors in widgets
(02 §10, 04 §7).
7. Extensions
Reuse shared (00-shared/11 §8): DateTime.toDisplayDate, context.showAppSnackbar, etc.
Module additions: RoomType.displayName, RoomType.iconData (→ RoomTypeIcon),
Room.capacityLabel ("Cap 40" / absent), String.normalizeRoomCode (uppercase + trim —
wraps domain/room_code.dart).
8. Localization
Keys under features/rooms/ namespace in .arb (en + fr + hi smoke): rooms.title,
rooms.list.empty, rooms.list.filter.empty, rooms.detail.notFound, rooms.editor.save,
rooms.editor.code.duplicate, rooms.editor.capacity.min, rooms.delete.confirm.title,
rooms.delete.confirm.typeName, rooms.snackbar.{created,updated,deleted,notFound},
rooms.offline. Server messages rendered via error-code→key map with business-4xx fallback
(00-shared/11 §9, 07 §11).
9. Testing
| Layer | Coverage |
|---|---|
| Unit | room_code normalization vectors ("lab 2" → "LAB 2"); enum mapping; capacityLabel; pagination meta mapping (pagination-query.dto.ts:41-54) |
| Cubit | RoomListCubit pagination + dedupe + filter reset; RoomDetailCubit 404→NotFound; RoomEditorCubit 409-inline + dirty/discard |
| Widget | S1 loading/empty/error + filter chips; S3 code-duplicate hint; S4 typed-confirm disabled state; permission-gated FAB hidden |
| Golden | 3 screens × light/dark × 3 sizes; new components (07 §Golden) |
| Integration | create → list → detail → edit → delete → 404; duplicate-code journey |
| E2E (device cloud) | P0: admin creates room, edits, deletes; read-only role sees list without FAB |
Run: flutter analyze, flutter test, flutter test integration_test (00-shared/11 §12).
10. Performance
ListView.builderfor S1;RepaintBoundaryaround RoomCards;AnimatedSizefor facility expand.- Debounces: search 300 ms, code-duplicate hint 300 ms.
- Caches keyed
sl:{tenant}:rooms:...with TTLs from13 §6;RefreshIndicatorbypasses cache. - Profile against
00-shared/10 §1budgets (list ≤ 300 ms p95, detail ≤ 250 ms).
11. Open items to wire when backend lands
- Server filter/sort/search on
GET /rooms(OQ-3) — drop client-sideRoomFilterBarfiltering, move to query params; addsort/qsupport. - Update-path duplicate check (OQ-1) — remove client-only warning; rely on 409;
map
DUPLICATE_RESOURCEinline on edit. - RBAC guard on controller (OQ-2) — server becomes authoritative; keep client guards as UX, handle real 403.
update-room.dto.ts(OQ-6) — partial PATCH; editor sends only dirty fields.- Capacity bounds (OQ-7) — server
@Min(1); client min mirrors exactly. - In-use delete guard (OQ-4) — delete dialog shows "in use by N timetables/bookings" with server truth; blocked delete → snackbar.
- Bookings module
(planned)— availability section + Book CTA on S2; bookings cubit + endpoints. - QR signage
(forward-looking)—QRCodeCardon S2 + scan route; data already present (room.code,room.schema.ts:21-22). - Utilization analytics
(proposed)— reports surface reading bookings aggregates. - WS
rooms.updated(forward-looking)— cache invalidation on multi-device edits.