59 lines
11 KiB
Markdown
59 lines
11 KiB
Markdown
# Audit manager-app — incohérences & pistes d'amélioration
|
||
|
||
Audit du 2026-07-13. Trois axes analysés : architecture/state management, écrans/composants (duplication), qualité transversale (i18n, constants, dead code, pubspec).
|
||
|
||
## Critique
|
||
|
||
- **Client généré désynchronisé (`manager_api_new/`)** : 153 fichiers modèle mais seulement 139 déclarés `part` dans `lib/api.dart` → **14 fichiers orphelins** (`agenda_event_stat_dto`, `app_configuration_link_dto_application_instance`, `content_dto_resource`, `content_geo_point`, `day_stat_dto`, `game_stat_dto`, `level_dto`, `map_dto_map_provider`, `map_dto_map_type`, `map_dto_map_type_mapbox`, `poi_stat_dto`, `quiz_stat_dto`, `section_stat_dto`, `user`) qui déclarent `part of openapi.api;` sans être inclus → génère **183 des ~200 erreurs `flutter analyze`** (`mapValueOfType`/`mapDateTime`/classes indéfinies). La plupart sont du code mort (0 référence dans `lib/`), quelques-uns utilisés (`level_dto`, `poi_stat_dto`, `quiz_stat_dto`).
|
||
→ Résultat concret : `flutter analyze` est inexploitable (bruit noie les vraies erreurs). Nettoyer les orphelins morts, ajouter la ligne `part` manquante pour ceux utilisés. Cohérent avec la politique "pas de régénération OpenAPI" ([[feedback_manager_api_new_no_regen]]) — les résidus s'accumulent au fil des patches manuels, à surveiller à chaque édition de `manager_api_new/`.
|
||
|
||
## Haute
|
||
|
||
- **Duplication du shell de dialog** — tous les `showNewOrUpdate*.dart` (Parcours, Event, Map, Agenda, Quiz) répètent le même squelette : `Dialog` + `Container` + calcul `contentWidth/halfWidth/thirdWidth` recopié à l'identique + ligne boutons Annuler/Sauvegarder (15 occurrences). → extraire un `FormDialogScaffold({title, child, onSave, onCancel, widthFactor})`. ⚠️ **Mis à jour le 2026-09-04 — le périmètre de cette extraction a changé.** La refonte des écrans Configuration/Section (`DOCS/claude design/refonte-configuration-sections.html` §6) **supprime** les 8 `showNewOrUpdate*` : ils sont absorbés par le panneau d'édition en place de l'éditeur de collection. Extraire un scaffold pour eux serait refactoriser du code à jeter. Le scaffold reste utile, mais pour les dialogues qui **survivent** — traduction, nouvelle section, nouvelle configuration, sélecteur de couleur, fichiers PDF, catégories de carte. Et la coquille de référence existe déjà : `confirmation_dialog.dart`. **Ordre : absorber d'abord, extraire ensuite sur ce qui reste.**
|
||
- **Composants quasi-copiés** — `translation_input_container.dart` (365 l.) vs `translation_input_and_resource_container.dart` (347 l.) : ~80% identiques, mêmes méthodes aux mêmes numéros de ligne, seule différence la gestion du `resourceId`. Même schéma pour `multi_string_input_container.dart` vs `..._and_resource_container.dart`. → fusionner avec un flag `withResource`.
|
||
- **Méthodes/`build()` monolithiques** :
|
||
- `section_detail_screen.dart:455` — `save(bool isTraduction, AppContext)` fait ~440 lignes (455→894), logique métier (appels API, traduction, notifications) mêlée à l'UI.
|
||
- `app_configuration_link_screen.dart:82` — `build()` de ~640 lignes définissant 4 builders locaux (`_generalInfoCard`, `_phoneConfigCard`, `_previewHero`, `_previewCard`, 100-270 lignes chacun) recréés à chaque rebuild.
|
||
→ extraire la logique métier hors de l'UI, promouvoir les builders en méthodes/sous-widgets de la classe.
|
||
- **i18n contournée** — infra l10n complète (fr/nl/en, 373 clés) mais seulement 52 fichiers/120 utilisent `AppLocalizations`. ~100+ chaînes françaises en dur, ex. `main_screen.dart:610/617/626` (`'Application mobile non configurée'` etc.), `statistics_screen.dart:491`, `web_app_screen.dart:129`, `map_geometry_picker.dart:139`.
|
||
|
||
## Moyenne
|
||
|
||
- **Sécurité — session locale** (`Helpers/FileHelper.dart:15-16,32-33`) : clé AES **en dur dans le source** + IV fixe (`IV.fromLength(16)` = zéros) → annule la sécurité d'AES-CBC. Incohérence supplémentaire : `writeSession` chiffre le password mais `readSessionWeb` (chemin actif) fait `Session.fromJson` **sans déchiffrer** — le chiffrement ne sert donc à rien (l'auto-login passe par le token). → retirer le chiffrement mort ou passer à IV aléatoire + clé hors source.
|
||
- **Erreurs API avalées silencieusement** :
|
||
- `main_screen.dart:93` et `:186` — `catch (_) {}` sur `subscriptionPlanGet`/`instanceGet`, aucun feedback utilisateur.
|
||
- `Resources/show_resource_popup.dart:128` — `resourceApi!.resourceDelete(...)` sans try/catch (notif de succès peut s'afficher à tort) ; `:139` `catch (e) {}` vide.
|
||
- `main.dart:213-221` — token expiré → `null` silencieux, redirection `/login` commentée (`// todo`).
|
||
- `Helpers/FileHelper.dart:60-64` — `importConfiguration` ne gère que le `409`, toute autre erreur avalée.
|
||
- **`_localPath` cassé** (`Helpers/FileHelper.dart:19-23`) : `var directory; return directory.path;` → `directory` toujours null → `NoSuchMethodError` si appelé. Mort sur web (branche `!kIsWeb`) mais casserait sur desktop.
|
||
- **Nettoyage fichiers** :
|
||
- 2 fichiers morts avec espaces dans le nom (non conforme, fragile multiplateforme) : `Quizz/new_update_response_quizz - deprecated.dart`, `Quizz/new_update_score_quizz - deprecated.dart`.
|
||
- `lib/api/openApiTest.dart` jamais référencé (surtout du code commenté).
|
||
- `dart analyze` : ~23 imports inutilisés (`auto_size_text`, `dart:convert`, `dart:js_interop`... dans une dizaine de fichiers), dead code (`get_element_for_resource.dart:32/53`, `FileHelper.dart:25` `_localFile`, `policy_screen.dart:19/64`).
|
||
- **TODO visibles à l'écran** (placeholders livrés en prod) : `Components/upload_audio_container.dart:141` `"TODO player affiché ou pas ?"`, `Components/upload_online_resources_container.dart:59` `"LOADING TODO FRAISE"`, `Main/main_screen.dart:629` `"TODO vr"`.
|
||
- **`pubspec.yaml`** : dépendances inutilisées (`drag_and_drop_lists`, `material_segmented_control`, `multiselect_formfield` — doublon de `multi_select_flutter` réellement utilisé —, `tab_container`, `convert`) ; contraintes de version vides (`rxdart:`, `provider:`, `collection: any`).
|
||
- **Deep-clone incohérent** — `jsonDecode(jsonEncode(...))` répété ~13 fois dans les dialogs (`showNewOrUpdateGuidedPath.dart:25`, `showNewOrUpdateEventAgenda.dart:19`, `showNewOrUpdateGeoPoint.dart:20`, `showNewOrUpdateQuizQuestion.dart:41`...), mais fait "à la main" champ par champ dans `showNewOrUpdateProgrammeBlock.dart:16-31` — incohérence pour un besoin identique. → helper `deepCopyDto<T>` unique.
|
||
- **Constants dupliquées / mortes** : `statistics_screen.dart:395/398` redéfinit `Color(0xFF264863)`/`Color(0xFFC2C9D6)` qui dupliquent `kPrimaryColor`/`kSecond` de `constants.dart` ; `constants.dart` contient lui-même du code mort (`// TO FILL WIT CORRECT COLOR`, `kBackgroundColor` commenté, bloc `kTextStyle` entièrement commenté).
|
||
- **Signatures de callbacks incohérentes entre dialogs** — `showNewOrUpdateGeoPoint.dart:16` utilise `Function getResult` non typé avec un ordre d'arguments différent des autres (`showNewOrUpdateEventAgenda.dart:12-17`, `showNewOrUpdateProgrammeBlock.dart:11-15`) ; `onSave` tantôt `Function(X)`, tantôt `FutureOr<void> Function(X)`.
|
||
|
||
## Basse
|
||
|
||
- **`client.dart`** : 18 façades API (`SectionApi?`, `ResourceApi?`...) typées nullable alors qu'assignées inconditionnellement dans le constructeur, jamais renullifiées → pattern `clientAPI!.xApi!.method()` répété 54 fois dans 18 fichiers, bruit qui masque le vrai point de nullabilité (`ManagerAppContext.clientAPI`). → passer les façades en `final` non-nullable.
|
||
- **`ManagerAppContext`** (`Models/managerContext.dart:7`) mixe `ChangeNotifier` mais `notifyListeners()` n'est jamais appelé dessus (le vrai notifier est `AppContext`) — mixin trompeur/mort. Champ `isLoading` porté mais jamais utilisé (loading géré en `setState` local ailleurs).
|
||
- **58 `print()`** de debug oubliés (dump DTO dans `app_configuration_link_screen.dart:268-289`, `"error auth"` dans `login_screen.dart`, `"Refresh UI"` dans `map_config.dart`/`menu_config.dart`/`quizz_config.dart`...) alors que `log()` (`dart:developer`) n'est utilisé qu'une fois (`configuration_detail_screen.dart:385`) → incohérence, à unifier derrière `kDebugMode`.
|
||
- **`login_screen.dart:172,211-212`** : host localhost et credentials de test codés en dur dans `initState` pour le cas debug local.
|
||
- **`configuration_detail_screen.dart:371/380`** : `configurationExport(config.id!)` appelé deux fois (résultat de l'appel ligne 371 ignoré).
|
||
- **Listes sans keys stables** : `showNewOrUpdateProgrammeBlock.dart:255` `ListView.builder` sans key par item ; `showNewOrUpdateGuidedPath.dart:297` contourne avec une clé de liste globale (`stepsRevision++`) plutôt que des `ValueKey(step.id)` par item.
|
||
- **Nommage non conforme aux conventions Dart** (camelCase/PascalCase au lieu de snake_case) : fichiers (`managerContext.dart`, `FileHelper.dart`, `PDFHelper.dart`, `dropDown_input_container.dart`, `dropDown_input_container_categories.dart`, `dropDown_configuration.dart`, `listView_card_*.dart` ×4, `new_update_pdfFile.dart`, `PDF_config.dart`, tous les `showNewOrUpdate*.dart`, `openApiTest.dart`, `section_reorderList.dart`) et dossiers (`Screens/`, `Components/`, `Models/`, `Helpers/`, `Services/`).
|
||
- **Composants de sélection qui se recouvrent** : `single_choice_input_container.dart`, `single_select_container.dart`, `dropDown_input_container.dart`, `segmented_enum_input_container.dart` couvrent tous "choisir une valeur dans une liste" — candidats à consolidation.
|
||
- **Incohérence StatefulWidget vs Provider** : 45 écrans en `StatefulWidget`, 37 en Provider/`context.watch`, souvent mélangés dans le même écran (ex. `app_configuration_link_screen` mixe `Provider.of` + `setState` + `StatefulBuilder`). Pas un bug, absence de convention documentée.
|
||
- **`analysis_options.yaml`** minimal (seulement `flutter_lints` par défaut, aucune règle additionnelle activée) — laisse passer imports inutilisés et `print()` sans avertissement.
|
||
- **`Models/Model.dart:33`** : `return credential!;` juste après un `if (credential != null)` qui peut ne pas être pris → crash potentiel si null.
|
||
- **`app_context.dart:10`** : `getContext()` sans type de retour explicite, forçant les appelants à faire `as ManagerAppContext` partout.
|
||
|
||
## Notes complémentaires
|
||
|
||
- Les fichiers générés `lib/l10n/app_localizations*.dart` ont été modifiés (Jul 10) après les `.arb` sources (Jul 9) — édition manuelle de fichiers générés l10n, à surveiller (risque d'écrasement à la prochaine génération, même logique de risque que `manager_api_new/`).
|
||
- `test/widget_test.dart` est cassé (`missing_required_argument` sur `managerAppContext`/`router`/`session`) — le seul test du projet ne compile pas.
|
||
- `Components/vlc_viewer.dart` (`DartVLC`) utilisé alors que `dart_vlc` est commenté dans `pubspec.yaml` — dépendance ambiguë à vérifier.
|