import 'package:flutter/material.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart'; import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Models/managerContext.dart' show ManagerAppContext; import 'package:manager_app/Services/ai_translate_service.dart'; import 'package:manager_app/constants.dart'; import 'package:provider/provider.dart'; import 'flag_decoration.dart'; import 'message_notification.dart'; import 'package:manager_app/app_context.dart'; class TranslationInputContainer extends StatefulWidget { TranslationInputContainer({ Key? key, required this.isTitle, required this.values, required this.newValues, required this.onGetResult, required this.maxLines, required this.resourceTypes, }) : super(key: key); bool isTitle; List values; List newValues; Function onGetResult; int maxLines; List? resourceTypes; @override State createState() => _TranslationInputContainerState(); } class _TranslationInputContainerState extends State { late Map _controllers; bool _isEnforcingLimit = false; bool _isTranslating = false; @override void initState() { super.initState(); _controllers = _buildControllers(); } static const _emptyDelta = [{'insert': '\n'}]; List _htmlToDeltaJson(String html) { if (html.trim().isEmpty) return _emptyDelta; final ops = HtmlToDelta().convert(html).toJson(); return ops.isEmpty ? _emptyDelta : ops; } Map _buildControllers() { final map = {}; for (final translation in widget.newValues) { final html = translation.value ?? ''; final controller = QuillController( document: Document.fromJson(_htmlToDeltaJson(html)), selection: const TextSelection.collapsed(offset: 0), ); _setupListener(translation.language!, controller); map[translation.language!] = controller; } return map; } void _setupListener(String lang, QuillController controller) { controller.document.changes.listen((_) { if (!mounted || _isEnforcingLimit) return; final limit = widget.isTitle ? kTitleMaxLength : kDescriptionMaxLength; final plain = controller.document.toPlainText().trimRight(); if (plain.length > limit) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted && !_isEnforcingLimit) { _isEnforcingLimit = true; controller.undo(); _isEnforcingLimit = false; } }); return; } widget.newValues .firstWhere((e) => e.language == lang) .value = _controllerToHtml(controller); }); } String _controllerToHtml(QuillController controller) { final ops = controller.document.toDelta().toJson(); return QuillDeltaToHtmlConverter( List>.from(ops), ).convert(); } Future _translateWithAI() async { final appContext = context.read().getContext() as ManagerAppContext; if (appContext.instanceDTO?.isAssistant != true) return; final source = widget.newValues.firstWhere( (t) => t.value != null && t.value!.trim().isNotEmpty && t.value!.trim() != '


', orElse: () => widget.newValues.first, ); if (source.value == null || source.value!.trim().isEmpty) { showNotification(kError, kWhite, 'Aucun texte source à traduire', context, null); return; } final targetLangs = widget.newValues .where((t) => t.language != source.language) .map((t) => t.language!) .toList(); if (targetLangs.isEmpty) return; setState(() => _isTranslating = true); try { final translations = await AiTranslateService.translate( host: appContext.host!, accessToken: appContext.accessToken!, instanceId: appContext.instanceId!, text: source.value!, sourceLang: source.language!, targetLangs: targetLangs, ); setState(() { for (final translation in widget.newValues) { final lang = translation.language!; if (lang == source.language) continue; final translated = translations[lang]; if (translated == null) continue; translation.value = translated; _controllers[lang]?.dispose(); final controller = QuillController( document: Document.fromJson(_htmlToDeltaJson(translated)), selection: const TextSelection.collapsed(offset: 0), ); _setupListener(lang, controller); _controllers[lang] = controller; } }); showNotification(kSuccess, kWhite, 'Traduction appliquée', context, null); } catch (e) { showNotification(kError, kWhite, 'Erreur lors de la traduction IA', context, null); } finally { setState(() => _isTranslating = false); } } void _applyToAllLanguages() { if (_controllers.isEmpty) return; final firstLang = widget.newValues.first.language!; final html = _controllerToHtml(_controllers[firstLang]!); setState(() { for (final translation in widget.newValues) { translation.value = html; if (translation.language != firstLang) { _controllers[translation.language!]?.dispose(); final controller = QuillController( document: Document.fromJson(_htmlToDeltaJson(html)), selection: const TextSelection.collapsed(offset: 0), ); _setupListener(translation.language!, controller); _controllers[translation.language!] = controller; } } }); showNotification(kSuccess, kWhite, 'Le texte a été appliqué à toutes les langues', context, null); } @override void dispose() { for (final c in _controllers.values) { c.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Flexible( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...widget.newValues.map((t) => _buildLanguageSection(t)), ], ), ), ), if (widget.resourceTypes == null) ...[ const SizedBox(height: 8), Center( child: SizedBox( width: 370, height: 70, child: RoundedButton( text: "Appliquer à toutes les langues", icon: Icons.copy, color: kSecond, press: _applyToAllLanguages, fontSize: 20, ), ), ), Builder(builder: (ctx) { final instance = ctx.watch().getContext()?.instanceDTO; if (instance?.isAssistant != true) return const SizedBox.shrink(); return Center( child: SizedBox( width: 370, height: 70, child: _isTranslating ? const Center(child: CircularProgressIndicator()) : RoundedButton( text: "Traduire via IA", icon: Icons.auto_awesome, color: kPrimaryColor, press: _translateWithAI, fontSize: 20, ), ), ); }), ] ], ); } Widget _buildLanguageSection(TranslationDTO translation) { final lang = translation.language!; if (widget.resourceTypes != null) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLangHeader(lang), const SizedBox(height: 4), SizedBox( width: 250, height: 120, child: ResourceInputContainer( label: "", initialValue: translation.value, inResourceTypes: widget.resourceTypes!, onChanged: (ResourceDTO resource) { translation.value = resource.id; }, ), ), ], ), ); } final controller = _controllers[lang]!; return Padding( padding: const EdgeInsets.only(bottom: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLangHeader(lang), const SizedBox(height: 4), QuillSimpleToolbar( controller: controller, config: _buildToolbarConfig(), ), Container( height: widget.isTitle ? 100 : 200, decoration: BoxDecoration( color: kBackgroundColor, border: Border.all(color: Colors.grey.shade300), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(4), bottomRight: Radius.circular(4), ), ), child: QuillEditor.basic( controller: controller, config: const QuillEditorConfig( scrollable: true, expands: false, padding: EdgeInsets.all(8), autoFocus: false, ), ), ), ], ), ); } Widget _buildLangHeader(String lang) { return Row( children: [ FlagDecoration(language: lang), const SizedBox(width: 8), Text( lang.toUpperCase(), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14), ), ], ); } QuillSimpleToolbarConfig _buildToolbarConfig() { return QuillSimpleToolbarConfig( showBoldButton: true, showItalicButton: true, showColorButton: true, showBackgroundColorButton: true, showListBullets: !widget.isTitle, showListNumbers: !widget.isTitle, showListCheck: false, showClearFormat: true, showUnderLineButton: false, showStrikeThrough: false, showInlineCode: false, showSubscript: false, showSuperscript: false, showSmallButton: false, showLineHeightButton: false, showHeaderStyle: false, showLink: false, showSearchButton: false, showQuote: false, showCodeBlock: false, showIndent: false, showAlignmentButtons: false, showLeftAlignment: false, showCenterAlignment: false, showRightAlignment: false, showJustifyAlignment: false, showDirection: false, showUndo: false, showRedo: false, showClipboardCut: false, showClipboardCopy: false, showClipboardPaste: false, ); } }