import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/number_input_container.dart'; import 'package:manager_app/Components/check_input_container.dart'; // Conversions between TranslationDTO and TranslationAndResourceDTO // (ResponseDTO.label uses TranslationAndResourceDTO, but we use MultiStringInputContainer // which requires TranslationDTO — the resource field is not needed for quiz responses) List _toTranslationList( List? list) => (list ?? []) .map((t) => TranslationDTO(language: t.language, value: t.value)) .toList(); List _fromTranslationList( List list) => list .map((t) => TranslationAndResourceDTO(language: t.language, value: t.value)) .toList(); // Creates an empty ResponseDTO; labels are populated by MultiStringInputContainer ResponseDTO _emptyResponse({bool isGood = false, int order = 0}) => ResponseDTO(label: [], isGood: isGood, order: order); void showNewOrUpdateQuizQuestion( BuildContext context, QuizQuestion? question, String stepId, bool isEscapeMode, Function(QuizQuestion) onSave, ) { // Use JSON cloning for a robust deep copy QuizQuestion? clonedQuestion = question != null ? QuizQuestion.fromJson(jsonDecode(jsonEncode(question))) : null; List workingLabel = _toTranslationList( clonedQuestion != null && clonedQuestion.label.isNotEmpty ? clonedQuestion.label : [TranslationAndResourceDTO(language: 'FR', value: '')]); List workingResponses = clonedQuestion?.responses ?? []; QuizQuestion workingQuestion = clonedQuestion ?? QuizQuestion( id: 0, label: _fromTranslationList(workingLabel), responses: workingResponses, validationQuestionType: QuestionType.number0, order: question?.order ?? 0, guidedStepId: question?.guidedStepId ?? stepId, ); showDialog( context: context, builder: (BuildContext context) { return StatefulBuilder( builder: (context, setState) { final double screenWidth = MediaQuery.of(context).size.width; final double screenHeight = MediaQuery.of(context).size.height; final double dialogWidth = screenWidth * 0.65; final double contentWidth = dialogWidth - 48; final double halfWidth = (contentWidth - 20) / 2; void ensureSimpleResponse() { if (workingQuestion.responses.isEmpty) { workingQuestion.responses .add(_emptyResponse(isGood: true, order: 0)); } workingQuestion.responses[0].isGood = true; } return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( width: dialogWidth, constraints: BoxConstraints(maxHeight: screenHeight * 0.85), padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( question == null ? "Nouvelle Question" : "Modifier la Question", style: TextStyle( color: kPrimaryColor, fontSize: 20, fontWeight: FontWeight.bold), ), SizedBox(height: 16), Flexible( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // --- Intitulé (multi-langue via MultiStringInputContainer) --- MultiStringInputContainer( label: "Question posée :", modalLabel: "Intitulé de la question", initialValue: workingLabel, onGetResult: (val) => setState(() { workingLabel = val; workingQuestion.label = _fromTranslationList(val); }), maxLines: 3, isTitle: false, ), SizedBox(height: 16), // --- Type --- Text("Type de validation :", style: TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: 8), DropdownButton( value: workingQuestion.validationQuestionType, items: [ DropdownMenuItem( value: QuestionType.number0, child: Text("Simple (texte attendu)")), DropdownMenuItem( value: QuestionType.number1, child: Text("Choix multiples (QCM)")), DropdownMenuItem( value: QuestionType.number2, child: Text("Puzzle")), ], onChanged: (val) => setState(() { workingQuestion.validationQuestionType = val; workingQuestion.responses = []; }), ), // ========================================= // Type 0 : Simple texte // ========================================= if (workingQuestion.validationQuestionType == QuestionType.number0) ...[ Divider(height: 24), Text("Réponse attendue :", style: TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: 8), Builder(builder: (_) { ensureSimpleResponse(); final List respLabel = _toTranslationList( workingQuestion.responses[0].label); return MultiStringInputContainer( label: "", modalLabel: "Réponse attendue", initialValue: respLabel, onGetResult: (val) => setState(() { workingQuestion.responses[0].label = _fromTranslationList(val); workingQuestion.responses[0].isGood = true; }), maxLines: 1, isTitle: true, ); }), SizedBox(height: 4), Text( "La validation se fait par comparaison (insensible à la casse).", style: TextStyle( fontSize: 12, fontStyle: FontStyle.italic, color: Colors.grey[600]), ), ], // ========================================= // Type 1 : QCM // ========================================= if (workingQuestion.validationQuestionType == QuestionType.number1) ...[ Divider(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Réponses possibles :", style: TextStyle(fontWeight: FontWeight.bold)), TextButton.icon( icon: Icon(Icons.add_circle_outline, color: kSuccess), label: Text("Ajouter", style: TextStyle(color: kSuccess)), onPressed: () => setState(() => workingQuestion.responses.add( _emptyResponse( order: workingQuestion .responses.length))), ), ], ), if (workingQuestion.responses.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( "Aucune réponse définie. Ajoutez-en au moins une.", style: TextStyle( fontStyle: FontStyle.italic, color: Colors.grey[600]), ), ) else Column( children: List.generate( workingQuestion.responses.length, (i) { final resp = workingQuestion.responses[i]; final List respLabel = _toTranslationList(resp.label); return Card( margin: const EdgeInsets.only(bottom: 8), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Checkbox bonne réponse Tooltip( message: resp.isGood == true ? "Bonne réponse ✓" : "Mauvaise réponse", child: Checkbox( value: resp.isGood ?? false, activeColor: kSuccess, onChanged: (val) => setState( () => resp.isGood = val), ), ), // Traductions (via MultiStringInputContainer) Expanded( child: MultiStringInputContainer( label: "Réponse ${i + 1} :", modalLabel: "Réponse ${i + 1}", initialValue: respLabel, onGetResult: (val) => setState( () => resp.label = _fromTranslationList( val)), maxLines: 1, isTitle: true, ), ), // Supprimer IconButton( icon: Icon(Icons.delete_outline, color: kError, size: 20), onPressed: () => setState(() => workingQuestion.responses .removeAt(i)), ), ], ), ), ); }), ), SizedBox(height: 4), Text( "✓ = bonne réponse. Plusieurs peuvent être correctes.", style: TextStyle( fontSize: 12, fontStyle: FontStyle.italic, color: Colors.grey[600]), ), ], // ========================================= // Type 2 : Puzzle // ========================================= if (workingQuestion.validationQuestionType == QuestionType.number2) ...[ Divider(height: 24), Text("Configuration du Puzzle", style: TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: 12), ResourceInputContainer( label: "Image du puzzle :", initialValue: workingQuestion.puzzleImageId, onChanged: (res) => setState(() { workingQuestion.puzzleImageId = res.id; workingQuestion.puzzleImage = Resource.fromJson(res.toJson()); }), ), SizedBox(height: 12), Row( children: [ SizedBox( width: halfWidth, child: NumberInputContainer( label: "Lignes :", initialValue: workingQuestion.puzzleRows ?? 3, onChanged: (val) => setState(() => workingQuestion.puzzleRows = int.tryParse(val) ?? 3), isSmall: true, ), ), SizedBox(width: 20), SizedBox( width: halfWidth, child: NumberInputContainer( label: "Colonnes :", initialValue: workingQuestion.puzzleCols ?? 3, onChanged: (val) => setState(() => workingQuestion.puzzleCols = int.tryParse(val) ?? 3), isSmall: true, ), ), ], ), SizedBox(height: 8), CheckInputContainer( label: "Puzzle glissant (Sliding) :", isChecked: workingQuestion.isSlidingPuzzle ?? false, onChanged: (val) => setState( () => workingQuestion.isSlidingPuzzle = val), ), ], ], ), ), ), SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ SizedBox( height: 46, child: RoundedButton( text: "Annuler", press: () => Navigator.pop(context), color: kSecond, fontSize: 15, horizontal: 24, ), ), SizedBox(width: 12), SizedBox( height: 46, child: RoundedButton( text: "Sauvegarder", press: () { for (int i = 0; i < workingQuestion.responses.length; i++) { workingQuestion.responses[i].order = i; } onSave(workingQuestion); Navigator.pop(context); }, color: kPrimaryColor, fontSize: 15, horizontal: 24, ), ), ], ), ], ), ), ); }, ); }, ); }