11 KiB
11 KiB
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éspartdanslib/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éclarentpart of openapi.api;sans être inclus → génère 183 des ~200 erreursflutter analyze(mapValueOfType/mapDateTime/classes indéfinies). La plupart sont du code mort (0 référence danslib/), quelques-uns utilisés (level_dto,poi_stat_dto,quiz_stat_dto). → Résultat concret :flutter analyzeest inexploitable (bruit noie les vraies erreurs). Nettoyer les orphelins morts, ajouter la lignepartmanquante 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 demanager_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+ calculcontentWidth/halfWidth/thirdWidthrecopié à l'identique + ligne boutons Annuler/Sauvegarder (15 occurrences). → extraire unFormDialogScaffold({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 8showNewOrUpdate*: 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.) vstranslation_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 duresourceId. Même schéma pourmulti_string_input_container.dartvs..._and_resource_container.dart. → fusionner avec un flagwithResource. - 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 :writeSessionchiffre le password maisreadSessionWeb(chemin actif) faitSession.fromJsonsans 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:93et:186—catch (_) {}sursubscriptionPlanGet/instanceGet, aucun feedback utilisateur.Resources/show_resource_popup.dart:128—resourceApi!.resourceDelete(...)sans try/catch (notif de succès peut s'afficher à tort) ;:139catch (e) {}vide.main.dart:213-221— token expiré →nullsilencieux, redirection/logincommentée (// todo).Helpers/FileHelper.dart:60-64—importConfigurationne gère que le409, toute autre erreur avalée.
_localPathcassé (Helpers/FileHelper.dart:19-23) :var directory; return directory.path;→directorytoujours null →NoSuchMethodErrorsi 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.dartjamais 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).
- 2 fichiers morts avec espaces dans le nom (non conforme, fragile multiplateforme) :
- 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 demulti_select_flutterré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 dansshowNewOrUpdateProgrammeBlock.dart:16-31— incohérence pour un besoin identique. → helperdeepCopyDto<T>unique. - Constants dupliquées / mortes :
statistics_screen.dart:395/398redéfinitColor(0xFF264863)/Color(0xFFC2C9D6)qui dupliquentkPrimaryColor/kSeconddeconstants.dart;constants.dartcontient lui-même du code mort (// TO FILL WIT CORRECT COLOR,kBackgroundColorcommenté, blockTextStyleentièrement commenté). - Signatures de callbacks incohérentes entre dialogs —
showNewOrUpdateGeoPoint.dart:16utiliseFunction getResultnon typé avec un ordre d'arguments différent des autres (showNewOrUpdateEventAgenda.dart:12-17,showNewOrUpdateProgrammeBlock.dart:11-15) ;onSavetantôtFunction(X), tantôtFutureOr<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 → patternclientAPI!.xApi!.method()répété 54 fois dans 18 fichiers, bruit qui masque le vrai point de nullabilité (ManagerAppContext.clientAPI). → passer les façades enfinalnon-nullable.ManagerAppContext(Models/managerContext.dart:7) mixeChangeNotifiermaisnotifyListeners()n'est jamais appelé dessus (le vrai notifier estAppContext) — mixin trompeur/mort. ChampisLoadingporté mais jamais utilisé (loading géré ensetStatelocal ailleurs).- 58
print()de debug oubliés (dump DTO dansapp_configuration_link_screen.dart:268-289,"error auth"danslogin_screen.dart,"Refresh UI"dansmap_config.dart/menu_config.dart/quizz_config.dart...) alors quelog()(dart:developer) n'est utilisé qu'une fois (configuration_detail_screen.dart:385) → incohérence, à unifier derrièrekDebugMode. login_screen.dart:172,211-212: host localhost et credentials de test codés en dur dansinitStatepour 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:255ListView.buildersans key par item ;showNewOrUpdateGuidedPath.dart:297contourne avec une clé de liste globale (stepsRevision++) plutôt que desValueKey(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 lesshowNewOrUpdate*.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.dartcouvrent 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_screenmixeProvider.of+setState+StatefulBuilder). Pas un bug, absence de convention documentée. analysis_options.yamlminimal (seulementflutter_lintspar défaut, aucune règle additionnelle activée) — laisse passer imports inutilisés etprint()sans avertissement.Models/Model.dart:33:return credential!;juste après unif (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 à faireas ManagerAppContextpartout.
Notes complémentaires
- Les fichiers générés
lib/l10n/app_localizations*.dartont été modifiés (Jul 10) après les.arbsources (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 quemanager_api_new/). test/widget_test.dartest cassé (missing_required_argumentsurmanagerAppContext/router/session) — le seul test du projet ne compile pas.Components/vlc_viewer.dart(DartVLC) utilisé alors quedart_vlcest commenté danspubspec.yaml— dépendance ambiguë à vérifier.