import 'package:flutter/material.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_app/Components/check_input_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/number_stepper_field.dart'; import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/section_card.dart'; import '../progression_mode.dart'; /// Les champs propres au parcours, sans son contenant ni sa liste d'étapes. /// /// Le DTO est modifié en place ; `onChanged` prévient l'hôte, à charge pour lui /// de se reconstruire et de déclencher l'enregistrement. class ParcoursFields extends StatelessWidget { const ParcoursFields({ Key? key, required this.path, required this.onChanged, }) : super(key: key); final GuidedPathDTO path; final VoidCallback onChanged; /// Réécrit une liste de traductions en conservant la ressource déjà associée /// à chaque langue : `MultiStringInputContainer` ne manipule que le texte. List _mergeResources( List values, List? previous, ) { return values.map((t) { final prev = (previous ?? []).firstWhere( (e) => e.language == t.language, orElse: () => TranslationAndResourceDTO(), ); return TranslationAndResourceDTO( language: t.language, value: t.value, resourceId: prev.resourceId, resource: prev.resource, ); }).toList(); } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ResourceInputContainer( label: "Image :", color: kPrimaryColor, initialValue: path.imageResourceId, onChanged: (resourceDTO) { path.imageResourceId = resourceDTO.id; onChanged(); }, ), SizedBox(height: kSpace5), Row( children: [ Expanded( child: MultiStringInputContainer( label: "Titre :", modalLabel: "Titre du parcours", initialValue: path.title ?? [], onGetResult: (val) { path.title = val; onChanged(); }, maxLines: 1, isTitle: true, isHTML: true, showPreview: true, ), ), SizedBox(width: kSpace7), Expanded( child: MultiStringInputContainer( label: "Description :", modalLabel: "Description du parcours", initialValue: path.description ?? [], onGetResult: (val) { path.description = val; onChanged(); }, maxLines: 1, isTitle: false, isHTML: true, showPreview: true, ), ), ], ), SizedBox(height: kSpace5), SectionCard( icon: Icons.sports_esports, title: "Ambiance", subtitle: "Visite classique ou jeu", child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Quelle ambiance ?", style: kLabelField), RadioGroup( groupValue: path.isGameMode ?? false, onChanged: (val) { path.isGameMode = val; onChanged(); }, child: Column( children: [ RadioListTile( dense: true, contentPadding: EdgeInsets.zero, activeColor: kPrimaryColor, value: false, title: Text("Visite", style: kTextBody), subtitle: Text( "Parcours de découverte : le visiteur avance à son rythme.", style: kTextHint, ), ), RadioListTile( dense: true, contentPadding: EdgeInsets.zero, activeColor: kPrimaryColor, value: true, title: Text("Jeu", style: kTextBody), subtitle: Text( "Escape game ou chasse au trésor : messages de début et de fin, vocabulaire de jeu sur les étapes.", style: kTextHint, ), ), ], ), ), if (path.isGameMode == true) ...[ SizedBox(height: kSpace3), Row( children: [ Expanded( child: MultiStringInputContainer( label: "Message de début :", modalLabel: "Message d'introduction du jeu", color: kPrimaryColor, initialValue: (path.gameMessageDebut ?? []) .map((t) => TranslationDTO( language: t.language, value: t.value)) .toList(), onGetResult: (val) { path.gameMessageDebut = _mergeResources(val, path.gameMessageDebut); onChanged(); }, maxLines: 2, isTitle: false, isHTML: true, showPreview: true, ), ), SizedBox(width: kSpace7), Expanded( child: MultiStringInputContainer( label: "Message de fin :", modalLabel: "Message de félicitations du jeu", color: kPrimaryColor, initialValue: (path.gameMessageFin ?? []) .map((t) => TranslationDTO( language: t.language, value: t.value)) .toList(), onGetResult: (val) { path.gameMessageFin = _mergeResources(val, path.gameMessageFin); onChanged(); }, maxLines: 2, isTitle: false, isHTML: true, showPreview: true, ), ), ], ), ], ], ), ), SizedBox(height: kSpace5), SectionCard( icon: Icons.settings, title: "Options du parcours", child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Comment le visiteur progresse-t-il ?", style: kLabelField), RadioGroup( groupValue: progressionModeOf(path), onChanged: (val) { applyProgressionMode(path, val!); onChanged(); }, child: Column( children: [ for (final mode in ProgressionMode.values) RadioListTile( dense: true, contentPadding: EdgeInsets.zero, activeColor: kPrimaryColor, value: mode, title: Text(mode.label, style: kTextBody), subtitle: Text(mode.description, style: kTextHint), ), ], ), ), if (progressionModeOf(path) == ProgressionMode.stepByStep) Padding( padding: const EdgeInsets.only(left: kSpace5), child: CheckInputContainer( label: "Masquer les étapes pas encore atteintes", subtitle: "Le visiteur ne découvre les étapes suivantes qu'au fur et à mesure.", isChecked: path.hideNextStepsUntilComplete ?? false, onChanged: (val) { path.hideNextStepsUntilComplete = val; onChanged(); }, ), ), SizedBox(height: kSpace3), NumberStepperField( label: "Durée estimée", value: path.estimatedDurationMinutes, min: 0, max: 600, unit: "min", onChanged: (val) { path.estimatedDurationMinutes = val.toInt(); onChanged(); }, ), ], ), ), ], ); } }