Lot E : PDF en média d'étape, et QuestionType nommé
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 <noreply@anthropic.com>
This commit is contained in:
parent
ac4927bdef
commit
5066b6c5e3
@ -497,6 +497,18 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
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)
|
if (agendaDTO.isOnlineAgenda == true)
|
||||||
MultiStringInputContainer(
|
MultiStringInputContainer(
|
||||||
label: AppLocalizations.of(context)!.jsonFilesLabel,
|
label: AppLocalizations.of(context)!.jsonFilesLabel,
|
||||||
|
|||||||
@ -642,6 +642,21 @@ class _MapConfigState extends State<MapConfig> {
|
|||||||
isSmall: true),
|
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),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
|||||||
@ -18,6 +18,15 @@ import 'package:manager_app/l10n/app_localizations.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'showNewOrUpdateQuizQuestion.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 = <ResourceType>[
|
||||||
|
...kSliderContentResourceTypes,
|
||||||
|
ResourceType.Pdf,
|
||||||
|
];
|
||||||
|
|
||||||
void showNewOrUpdateGuidedStep(
|
void showNewOrUpdateGuidedStep(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
GuidedStepDTO? step,
|
GuidedStepDTO? step,
|
||||||
@ -248,7 +257,10 @@ void showNewOrUpdateGuidedStep(
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.add_circle_outline, color: kSuccess),
|
icon: Icon(Icons.add_circle_outline, color: kSuccess),
|
||||||
onPressed: () async {
|
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) {
|
if (result != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
result.order = workingStep.contents!.length;
|
result.order = workingStep.contents!.length;
|
||||||
@ -438,7 +450,7 @@ void showNewOrUpdateGuidedStep(
|
|||||||
textStyle: TextStyle(fontSize: 14),
|
textStyle: TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
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: [
|
actions: [
|
||||||
|
|||||||
@ -54,7 +54,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
id: 0,
|
id: 0,
|
||||||
label: _fromTranslationList(workingLabel),
|
label: _fromTranslationList(workingLabel),
|
||||||
responses: workingResponses,
|
responses: workingResponses,
|
||||||
validationQuestionType: QuestionType.number0,
|
validationQuestionType: QuestionType.simple,
|
||||||
order: question?.order ?? 0,
|
order: question?.order ?? 0,
|
||||||
guidedStepId: question?.guidedStepId ?? stepId,
|
guidedStepId: question?.guidedStepId ?? stepId,
|
||||||
);
|
);
|
||||||
@ -163,13 +163,13 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
value: workingQuestion.validationQuestionType,
|
value: workingQuestion.validationQuestionType,
|
||||||
items: [
|
items: [
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: QuestionType.number0,
|
value: QuestionType.simple,
|
||||||
child: Text("Simple (texte attendu)")),
|
child: Text("Simple (texte attendu)")),
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: QuestionType.number1,
|
value: QuestionType.multipleChoice,
|
||||||
child: Text("Choix multiples (QCM)")),
|
child: Text("Choix multiples (QCM)")),
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: QuestionType.number2,
|
value: QuestionType.puzzle,
|
||||||
child: Text("Puzzle")),
|
child: Text("Puzzle")),
|
||||||
],
|
],
|
||||||
onChanged: (val) => setState(() {
|
onChanged: (val) => setState(() {
|
||||||
@ -182,7 +182,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
// Type 0 : Simple texte
|
// Type 0 : Simple texte
|
||||||
// =========================================
|
// =========================================
|
||||||
if (workingQuestion.validationQuestionType ==
|
if (workingQuestion.validationQuestionType ==
|
||||||
QuestionType.number0) ...[
|
QuestionType.simple) ...[
|
||||||
Divider(height: 24),
|
Divider(height: 24),
|
||||||
Text(AppLocalizations.of(context)!.expectedAnswerLabel,
|
Text(AppLocalizations.of(context)!.expectedAnswerLabel,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
@ -245,7 +245,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
// Type 1 : QCM
|
// Type 1 : QCM
|
||||||
// =========================================
|
// =========================================
|
||||||
if (workingQuestion.validationQuestionType ==
|
if (workingQuestion.validationQuestionType ==
|
||||||
QuestionType.number1) ...[
|
QuestionType.multipleChoice) ...[
|
||||||
Divider(height: 24),
|
Divider(height: 24),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@ -349,7 +349,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
// Type 2 : Puzzle
|
// Type 2 : Puzzle
|
||||||
// =========================================
|
// =========================================
|
||||||
if (workingQuestion.validationQuestionType ==
|
if (workingQuestion.validationQuestionType ==
|
||||||
QuestionType.number2) ...[
|
QuestionType.puzzle) ...[
|
||||||
Divider(height: 24),
|
Divider(height: 24),
|
||||||
Text("Configuration du Puzzle",
|
Text("Configuration du Puzzle",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
|||||||
@ -8,7 +8,22 @@ import 'package:manager_app/constants.dart';
|
|||||||
import 'package:manager_app/l10n/app_localizations.dart';
|
import 'package:manager_app/l10n/app_localizations.dart';
|
||||||
import 'package:manager_api_new/api.dart';
|
import 'package:manager_api_new/api.dart';
|
||||||
|
|
||||||
Future<ContentDTO?> 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>[
|
||||||
|
ResourceType.Image,
|
||||||
|
ResourceType.ImageUrl,
|
||||||
|
ResourceType.Video,
|
||||||
|
ResourceType.VideoUrl,
|
||||||
|
ResourceType.Audio,
|
||||||
|
];
|
||||||
|
|
||||||
|
Future<ContentDTO?> showNewOrUpdateContentSlider(
|
||||||
|
ContentDTO? inputContentDTO,
|
||||||
|
AppContext appContext,
|
||||||
|
BuildContext context,
|
||||||
|
bool showTitle,
|
||||||
|
bool showDescription,
|
||||||
|
{List<ResourceType> resourceTypes = kSliderContentResourceTypes}) async {
|
||||||
ContentDTO contentDTO = new ContentDTO();
|
ContentDTO contentDTO = new ContentDTO();
|
||||||
|
|
||||||
if (inputContentDTO != null) {
|
if (inputContentDTO != null) {
|
||||||
@ -64,7 +79,7 @@ Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap
|
|||||||
initialValue: contentDTO.resourceId,
|
initialValue: contentDTO.resourceId,
|
||||||
color: kPrimaryColor,
|
color: kPrimaryColor,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
inResourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio],
|
inResourceTypes: resourceTypes,
|
||||||
onChanged: (ResourceDTO resource) {
|
onChanged: (ResourceDTO resource) {
|
||||||
if(resource.id == null) {
|
if(resource.id == null) {
|
||||||
contentDTO.resourceId = null;
|
contentDTO.resourceId = null;
|
||||||
|
|||||||
@ -684,5 +684,6 @@
|
|||||||
"annotationsLabel": "Annotations",
|
"annotationsLabel": "Annotations",
|
||||||
"discardPathChangesConfirm": "This path has unsaved changes, along with its steps and their questions. Discard these changes?",
|
"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?",
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -684,5 +684,6 @@
|
|||||||
"annotationsLabel": "Annotations",
|
"annotationsLabel": "Annotations",
|
||||||
"discardPathChangesConfirm": "Ce parcours contient des modifications non enregistrées, ainsi que ses étapes et leurs questions. Abandonner ces modifications ?",
|
"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 ?",
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3315,6 +3315,12 @@ abstract class AppLocalizations {
|
|||||||
/// In fr, this message translates to:
|
/// In fr, this message translates to:
|
||||||
/// **'Cette question contient des modifications non enregistrées, ainsi que ses réponses. Abandonner ces modifications ?'**
|
/// **'Cette question contient des modifications non enregistrées, ainsi que ses réponses. Abandonner ces modifications ?'**
|
||||||
String get discardQuestionChangesConfirm;
|
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
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@ -1756,4 +1756,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get discardQuestionChangesConfirm =>
|
String get discardQuestionChangesConfirm =>
|
||||||
'This question has unsaved changes, along with its answers. Discard these changes?';
|
'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.';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1794,4 +1794,8 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get discardQuestionChangesConfirm =>
|
String get discardQuestionChangesConfirm =>
|
||||||
'Cette question contient des modifications non enregistrées, ainsi que ses réponses. Abandonner ces modifications ?';
|
'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.';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1773,4 +1773,8 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get discardQuestionChangesConfirm =>
|
String get discardQuestionChangesConfirm =>
|
||||||
'Deze vraag heeft niet-opgeslagen wijzigingen, samen met de antwoorden. Deze wijzigingen weggooien?';
|
'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.';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -684,5 +684,6 @@
|
|||||||
"annotationsLabel": "Annotaties",
|
"annotationsLabel": "Annotaties",
|
||||||
"discardPathChangesConfirm": "Deze route heeft niet-opgeslagen wijzigingen, samen met de stappen en hun vragen. Deze wijzigingen weggooien?",
|
"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?",
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,14 @@ class QuestionType {
|
|||||||
static const number1 = QuestionType._(1);
|
static const number1 = QuestionType._(1);
|
||||||
static const number2 = QuestionType._(2);
|
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].
|
/// List of all possible values in this [enum][QuestionType].
|
||||||
static const values = <QuestionType>[
|
static const values = <QuestionType>[
|
||||||
number0,
|
number0,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user