From 5066b6c5e3592c10fff0f7f1172cf3bc66e557e3 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Tue, 11 Aug 2026 15:31:07 +0200 Subject: [PATCH] =?UTF-8?q?Lot=20E=20:=20PDF=20en=20m=C3=A9dia=20d'=C3=A9t?= =?UTF-8?q?ape,=20et=20QuestionType=20nomm=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PDF en média d'étape — la liste de types du sélecteur de médias devient un paramètre (`kSliderContentResourceTypes` par défaut) et l'étape de parcours passe `kGuidedStepResourceTypes`, soit les types du slider plus Pdf. QuestionType — le client généré nomme ses valeurs number0/1/2, ce qui poussait le front à comparer des entiers bruts (`?.value == 2 ? 'Puzzle'`). Alias nommés d'après l'enum serveur ajoutés à la main dans question_type.dart (simple / multipleChoice / puzzle), `values` inchangé : ce sont les mêmes trois valeurs. Les 7 usages number* du dialogue de question sont migrés avec. Fournisseur de carte (W1) — décision : visitapp-web reste sur Leaflet. Le champ ne peut pas être masqué pour le web, il est porté par la section et une même configuration est servie au mobile comme au web ; il est donc conservé et documenté dans l'interface comme réglage mobile/tablette. flutter build web ✅, analyse du dossier sans erreur ni warning. Co-Authored-By: Claude Opus 5 --- .../SubSection/Agenda/agenda_config.dart | 12 ++++++++++++ .../Section/SubSection/Map/map_config.dart | 15 +++++++++++++++ .../Parcours/showNewOrUpdateGuidedStep.dart | 16 ++++++++++++++-- .../Parcours/showNewOrUpdateQuizQuestion.dart | 14 +++++++------- .../Slider/new_update_image_slider.dart | 19 +++++++++++++++++-- lib/l10n/app_en.arb | 3 ++- lib/l10n/app_fr.arb | 3 ++- lib/l10n/app_localizations.dart | 6 ++++++ lib/l10n/app_localizations_en.dart | 4 ++++ lib/l10n/app_localizations_fr.dart | 4 ++++ lib/l10n/app_localizations_nl.dart | 4 ++++ lib/l10n/app_nl.arb | 3 ++- manager_api_new/lib/model/question_type.dart | 8 ++++++++ 13 files changed, 97 insertions(+), 14 deletions(-) diff --git a/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart b/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart index 77f79b4..0a9f7a8 100644 --- a/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart @@ -497,6 +497,18 @@ class _AgendaConfigState extends State { }); }, ), + if (agendaDTO.agendaMapProvider != null) + // Voir map_config.dart : réglage mobile/tablette, le web garde Leaflet. + Padding( + padding: const EdgeInsets.only(top: 4, bottom: 8), + child: Text( + AppLocalizations.of(context)!.mapProviderMobileOnlyNote, + style: TextStyle( + fontSize: 12, + fontStyle: FontStyle.italic, + color: Colors.grey[600]), + ), + ), if (agendaDTO.isOnlineAgenda == true) MultiStringInputContainer( label: AppLocalizations.of(context)!.jsonFilesLabel, diff --git a/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart b/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart index 2a9d366..0a92673 100644 --- a/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart @@ -642,6 +642,21 @@ class _MapConfigState extends State { isSmall: true), ], ), + const SizedBox(height: 8), + // Décision du 2026-08-11 (lot E, W1) : visitapp-web reste sur Leaflet. + // Le champ ne peut pas être masqué pour le web — il est porté par la + // section, et une même configuration est servie au mobile comme au web. + // On le dit donc au client au lieu de le laisser croire le contraire. + Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.mapProviderMobileOnlyNote, + style: TextStyle( + fontSize: 12, + fontStyle: FontStyle.italic, + color: Colors.grey[600]), + ), + ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart index 0edd4b4..573d79d 100644 --- a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart @@ -18,6 +18,15 @@ import 'package:manager_app/l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'showNewOrUpdateQuizQuestion.dart'; +/// Médias acceptés sur une étape de parcours. Le PDF s'ajoute aux types du +/// slider : une étape peut porter un plan, une partition, un fac-similé. +/// Le rendu visiteur correspondant vit dans `CachedCustomResource` +/// (mymuseum-visitapp) et `ResourceViewer.tsx` (visitapp-web). +const kGuidedStepResourceTypes = [ + ...kSliderContentResourceTypes, + ResourceType.Pdf, +]; + void showNewOrUpdateGuidedStep( BuildContext context, GuidedStepDTO? step, @@ -248,7 +257,10 @@ void showNewOrUpdateGuidedStep( IconButton( icon: Icon(Icons.add_circle_outline, color: kSuccess), onPressed: () async { - final result = await showNewOrUpdateContentSlider(null, appCtx, context, true, false); + final result = await showNewOrUpdateContentSlider( + null, appCtx, context, true, false, + resourceTypes: kGuidedStepResourceTypes, + ); if (result != null) { setState(() { result.order = workingStep.contents!.length; @@ -438,7 +450,7 @@ void showNewOrUpdateGuidedStep( textStyle: TextStyle(fontSize: 14), ), subtitle: Text( - "Type: ${question.validationQuestionType?.value == 2 ? 'Puzzle' : question.validationQuestionType?.value == 1 ? 'QCM' : 'Texte'}"), + "Type: ${question.validationQuestionType == QuestionType.puzzle ? 'Puzzle' : question.validationQuestionType == QuestionType.multipleChoice ? 'QCM' : 'Texte'}"), ); }, actions: [ diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart index 7d3bec8..1365eb3 100644 --- a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart @@ -54,7 +54,7 @@ void showNewOrUpdateQuizQuestion( id: 0, label: _fromTranslationList(workingLabel), responses: workingResponses, - validationQuestionType: QuestionType.number0, + validationQuestionType: QuestionType.simple, order: question?.order ?? 0, guidedStepId: question?.guidedStepId ?? stepId, ); @@ -163,13 +163,13 @@ void showNewOrUpdateQuizQuestion( value: workingQuestion.validationQuestionType, items: [ DropdownMenuItem( - value: QuestionType.number0, + value: QuestionType.simple, child: Text("Simple (texte attendu)")), DropdownMenuItem( - value: QuestionType.number1, + value: QuestionType.multipleChoice, child: Text("Choix multiples (QCM)")), DropdownMenuItem( - value: QuestionType.number2, + value: QuestionType.puzzle, child: Text("Puzzle")), ], onChanged: (val) => setState(() { @@ -182,7 +182,7 @@ void showNewOrUpdateQuizQuestion( // Type 0 : Simple texte // ========================================= if (workingQuestion.validationQuestionType == - QuestionType.number0) ...[ + QuestionType.simple) ...[ Divider(height: 24), Text(AppLocalizations.of(context)!.expectedAnswerLabel, style: TextStyle(fontWeight: FontWeight.bold)), @@ -245,7 +245,7 @@ void showNewOrUpdateQuizQuestion( // Type 1 : QCM // ========================================= if (workingQuestion.validationQuestionType == - QuestionType.number1) ...[ + QuestionType.multipleChoice) ...[ Divider(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -349,7 +349,7 @@ void showNewOrUpdateQuizQuestion( // Type 2 : Puzzle // ========================================= if (workingQuestion.validationQuestionType == - QuestionType.number2) ...[ + QuestionType.puzzle) ...[ Divider(height: 24), Text("Configuration du Puzzle", style: TextStyle(fontWeight: FontWeight.bold)), diff --git a/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart b/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart index 32c3f89..c51da38 100644 --- a/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart +++ b/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart @@ -8,7 +8,22 @@ import 'package:manager_app/constants.dart'; import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; -Future showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, AppContext appContext, BuildContext context, bool showTitle, bool showDescription) async { +/// Types de ressource proposés par défaut pour un contenu de slider. +const kSliderContentResourceTypes = [ + ResourceType.Image, + ResourceType.ImageUrl, + ResourceType.Video, + ResourceType.VideoUrl, + ResourceType.Audio, +]; + +Future showNewOrUpdateContentSlider( + ContentDTO? inputContentDTO, + AppContext appContext, + BuildContext context, + bool showTitle, + bool showDescription, + {List resourceTypes = kSliderContentResourceTypes}) async { ContentDTO contentDTO = new ContentDTO(); if (inputContentDTO != null) { @@ -64,7 +79,7 @@ Future showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap initialValue: contentDTO.resourceId, color: kPrimaryColor, fontSize: 20, - inResourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio], + inResourceTypes: resourceTypes, onChanged: (ResourceDTO resource) { if(resource.id == null) { contentDTO.resourceId = null; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2a9e993..d24a172 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -684,5 +684,6 @@ "annotationsLabel": "Annotations", "discardPathChangesConfirm": "This path has unsaved changes, along with its steps and their questions. Discard these changes?", "discardStepChangesConfirm": "This step has unsaved changes, along with its questions. Discard these changes?", - "discardQuestionChangesConfirm": "This question has unsaved changes, along with its answers. Discard these changes?" + "discardQuestionChangesConfirm": "This question has unsaved changes, along with its answers. Discard these changes?", + "mapProviderMobileOnlyNote": "The map provider and type apply to the mobile and tablet apps. The web app always renders its own map." } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index fa6b9e4..337c170 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -684,5 +684,6 @@ "annotationsLabel": "Annotations", "discardPathChangesConfirm": "Ce parcours contient des modifications non enregistrées, ainsi que ses étapes et leurs questions. Abandonner ces modifications ?", "discardStepChangesConfirm": "Cette étape contient des modifications non enregistrées, ainsi que ses questions. Abandonner ces modifications ?", - "discardQuestionChangesConfirm": "Cette question contient des modifications non enregistrées, ainsi que ses réponses. Abandonner ces modifications ?" + "discardQuestionChangesConfirm": "Cette question contient des modifications non enregistrées, ainsi que ses réponses. Abandonner ces modifications ?", + "mapProviderMobileOnlyNote": "Le fournisseur et le type de carte s'appliquent aux applications mobile et tablette. L'application web affiche toujours sa propre carte." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index d702094..15844fd 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3315,6 +3315,12 @@ abstract class AppLocalizations { /// In fr, this message translates to: /// **'Cette question contient des modifications non enregistrées, ainsi que ses réponses. Abandonner ces modifications ?'** String get discardQuestionChangesConfirm; + + /// No description provided for @mapProviderMobileOnlyNote. + /// + /// In fr, this message translates to: + /// **'Le fournisseur et le type de carte s\'appliquent aux applications mobile et tablette. L\'application web affiche toujours sa propre carte.'** + String get mapProviderMobileOnlyNote; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9439e2e..4149ec5 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1756,4 +1756,8 @@ class AppLocalizationsEn extends AppLocalizations { @override String get discardQuestionChangesConfirm => 'This question has unsaved changes, along with its answers. Discard these changes?'; + + @override + String get mapProviderMobileOnlyNote => + 'The map provider and type apply to the mobile and tablet apps. The web app always renders its own map.'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index dbc9813..4c0c6b9 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1794,4 +1794,8 @@ class AppLocalizationsFr extends AppLocalizations { @override String get discardQuestionChangesConfirm => 'Cette question contient des modifications non enregistrées, ainsi que ses réponses. Abandonner ces modifications ?'; + + @override + String get mapProviderMobileOnlyNote => + 'Le fournisseur et le type de carte s\'appliquent aux applications mobile et tablette. L\'application web affiche toujours sa propre carte.'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index a7d10b1..47e65b1 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1773,4 +1773,8 @@ class AppLocalizationsNl extends AppLocalizations { @override String get discardQuestionChangesConfirm => 'Deze vraag heeft niet-opgeslagen wijzigingen, samen met de antwoorden. Deze wijzigingen weggooien?'; + + @override + String get mapProviderMobileOnlyNote => + 'De kaartprovider en het kaarttype gelden voor de mobiele en tablet-apps. De web-app toont altijd zijn eigen kaart.'; } diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 0f4098e..98094be 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -684,5 +684,6 @@ "annotationsLabel": "Annotaties", "discardPathChangesConfirm": "Deze route heeft niet-opgeslagen wijzigingen, samen met de stappen en hun vragen. Deze wijzigingen weggooien?", "discardStepChangesConfirm": "Deze stap heeft niet-opgeslagen wijzigingen, samen met de vragen. Deze wijzigingen weggooien?", - "discardQuestionChangesConfirm": "Deze vraag heeft niet-opgeslagen wijzigingen, samen met de antwoorden. Deze wijzigingen weggooien?" + "discardQuestionChangesConfirm": "Deze vraag heeft niet-opgeslagen wijzigingen, samen met de antwoorden. Deze wijzigingen weggooien?", + "mapProviderMobileOnlyNote": "De kaartprovider en het kaarttype gelden voor de mobiele en tablet-apps. De web-app toont altijd zijn eigen kaart." } diff --git a/manager_api_new/lib/model/question_type.dart b/manager_api_new/lib/model/question_type.dart index d931d30..36dea84 100644 --- a/manager_api_new/lib/model/question_type.dart +++ b/manager_api_new/lib/model/question_type.dart @@ -27,6 +27,14 @@ class QuestionType { static const number1 = QuestionType._(1); static const number2 = QuestionType._(2); + // Ajouts manuels : le générateur nomme les valeurs number0/1/2, ce qui poussait + // le front à comparer des entiers bruts. Ces alias portent les noms de l'enum + // serveur (`QuizQuestion.QuestionType` : Simple, MultipleChoice, Puzzle). + // `values` n'est pas modifié — ce sont les mêmes trois valeurs, pas de nouvelles. + static const simple = number0; + static const multipleChoice = number1; + static const puzzle = number2; + /// List of all possible values in this [enum][QuestionType]. static const values = [ number0,