import 'dart:convert'; import 'dart:html' as html; import 'dart:async'; import 'dart:io' as io show Directory, File; import 'package:delta_to_html/delta_to_html.dart'; import 'package:flutter/cupertino.dart' as dom; import 'package:flutter/services.dart'; import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter/material.dart'; import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/quill_delta.dart'; import 'package:flutter_quill/quill_delta.dart' as quill; import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart'; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:frontend/Screens/myTemplates/templateForex.dart' as _editorFocusNode; import 'package:frontend/Screens/myTemplates/templateForex.dart' as _editorScrollController; import 'package:frontend/Screens/myTemplates/templateForex.dart' as _controller; import 'package:html2md/html2md.dart' as html2md; import 'package:http_parser/http_parser.dart'; import 'package:image_picker/image_picker.dart'; import 'package:mime/mime.dart'; import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart'; import 'package:flutter_quill/flutter_quill.dart' as quill; import 'package:html/parser.dart' show parse; import 'package:html/dom.dart' as dom hide Element, Text; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_quill/flutter_quill_internal.dart'; import 'package:flutter_quill/quill_delta.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; import 'package:responsive_builder/responsive_builder.dart'; import '../../config/apiUrl.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../widgets/custom_breadcrumb_navigation.dart'; import '../../widgets/custom_user_travel.dart'; import 'dialog_placeholders.dart'; class TemplateForex extends StatefulWidget { final Map? templateData; const TemplateForex({super.key, required this.templateData}); static TemplateForex fromState(GoRouterState state) { return TemplateForex(templateData: state.extra as Map?); } @override TemplateForexState createState() => TemplateForexState(); } class TemplateForexState extends State { final ApiService apiService = ApiService(); Uint8List? _imageBytes; String? selectedOrglogo; // final QuillController _controller = QuillController.basic(); String? orgId; String? userId; Color layoutColor = Colors.redAccent; Color bodyColor = Colors.white; final Map controllers = {}; List dataHeader = ["subject"]; List placeholders = []; late QuillController _controller = QuillController.basic(); final FocusNode _focusNode = FocusNode(); late int templateId = 0; late String templateName = ""; late List> placeholderList = []; bool isDisable = false; Map focusNodes = {}; Map focusStates = {}; Map get TemplateData { final data = { "org_id": orgId, // "template_id": templateId, // "template_name": controllers["templateName"]?.text, "template_id": templateId, "template_name": templateName, "subject": controllers["subject"]?.text, "body_html": DeltaToHTML.encodeJson( _controller.document.toDelta().toJson(), ), "placeholder": jsonEncode(placeholderList), // "created_by": userId }; print('start 123'); print(jsonEncode(_controller.document.toDelta().toJson())); // print(jsonEncode(_controller.document)); // Only add group_id if it's an edit operation // if (widget.templateData != null && widget.templateData!.containsKey('group_id')) { // data["template_id"] = templateData; // } return data; } @override void initState() { super.initState(); _checkAuthAndLoadData(); // // for (var field in dataHeader) { // controllers[field] = TextEditingController(); // } // // updateData(); // loadinitializeData(); // loadInitialData(); for (var field in dataHeader) { focusNodes["${field}FocusNode"] = FocusNode(); focusStates["${field}Focused"] = false; } for (var key in focusNodes.keys) { _addFocusListener(focusNodes[key]!, (focus) { setState(() { focusStates[key.replaceFirst("FocusNode", "Focused")] = focus; }); }); } } void _checkAuthAndLoadData() async { final String? token = await getToken(); // Your async function to get token if (token == null || token.isEmpty) { // Token doesn't exist → redirect to login context.go( "/", ); // or use: router.go("/") if you're using `GoRouter` directly return; } if (!mounted) return; try { final roleUser = await getRoleUser(); if (roleUser != null && (roleUser == 'Org Admin' || roleUser == 'Travel Admin')) { for (var field in dataHeader) { controllers[field] = TextEditingController(); } updateData(); loadinitializeData(); loadInitialData(); } else { apiService.logout(context); } } catch (e) { print("group : $e"); } } @override // void dispose() { // // controllers.dispose(); // // _editorScrollController.dispose(); // _editorFocusNode.dispose(); // super.dispose(); // } void dispose() { for (var node in focusNodes.values) { node.dispose(); } super.dispose(); } void _addFocusListener(FocusNode node, Function(bool) updateState) { node.addListener(() { setState(() { updateState(node.hasFocus); }); }); } void loadinitializeData() async { orgId = await getOrgId(); userId = await getUserId(); } void loadInitialData() async { String? layoutString = await getLayoutColor(); String? bodyStringColor = await getBodyColor(); setState(() { layoutColor = layoutString != null ? Color(int.parse(layoutString)) : Colors.redAccent; bodyColor = bodyStringColor != null ? Color(int.parse(bodyStringColor)) : Colors.white; }); } String convertQuillDocToHtml(quill.Document doc) { final buffer = StringBuffer(); print("convertQuillDocToHtml"); for (final op in doc.toDelta().toList()) { final insert = op.data; final attrs = op.attributes ?? {}; if (insert is String) { var content = insert; // Handle formatting (bold, italic, etc.) if (attrs.containsKey('bold')) { content = '$content'; } if (attrs.containsKey('italic')) { content = '$content'; } // Wrap each paragraph with

if (content.trim().isNotEmpty) { buffer.write('

${content.trim()}

'); } } } return buffer.toString(); } String convertQuillDocToHtml2(quill.Document doc) { final buffer = StringBuffer(); final lines = []; final delta = doc.toDelta(); String applyStyles(String text, Map? attrs) { if (attrs == null) return text; if (attrs.containsKey('bold')) { text = '$text'; } if (attrs.containsKey('italic')) { text = '$text'; } return text; } for (final op in delta.toList()) { final insert = op.data; final attrs = op.attributes; if (insert is String) { final parts = insert.split('\n'); for (int i = 0; i < parts.length; i++) { final part = applyStyles(parts[i], attrs); lines.add(part); if (i < parts.length - 1) { // End of line: wrap accumulated content into

final joined = lines.join(''); if (joined.trim().isNotEmpty) { buffer.writeln('

${joined.trim()}

'); } lines.clear(); } } } } // Add remaining lines final joined = lines.join(''); if (joined.trim().isNotEmpty) { buffer.writeln('

${joined.trim()}

'); } return buffer.toString(); } String extractPlainTextFromHtml(String html) { final regex = RegExp(r'

(.*?)<\/p>', multiLine: true, dotAll: true); final matches = regex.allMatches(html); final buffer = StringBuffer(); for (final match in matches) { final text = match.group(1)?.replaceAll(RegExp(r'<[^>]*>'), '') ?? ''; buffer.writeln(text.trim()); } return buffer.toString(); } String decodeHtmlEntities(String text) { return text .replaceAll(' ', ' ') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll(''', "'"); // add more as needed } // quill.Document convertSimpleHtmlToQuill(String htmlString) { // final delta = quill.Delta(); // final doc = html_parser.parse(htmlString); // final body = doc.body; // // void walk(Node node) { // if (node is Text) { // delta.insert(node.text); // } else if (node is Element) { // switch (node.localName) { // case 'p': // node.nodes.forEach(walk); // delta.insert('\n'); // break; // case 'br': // delta.insert('\n'); // break; // case 'strong': // case 'b': // delta.insert(node.text, {'bold': true}); // break; // case 'em': // case 'i': // delta.insert(node.text, {'italic': true}); // break; // case 'a': // delta.insert(node.text, {'link': node.attributes['href']}); // break; // default: // node.nodes.forEach(walk); // } // } // } // // if (body != null) { // walk(body); // } // // return quill.Document.fromDelta(delta..insert('\n')); // } String formatTemplateName(String input) { return input .split('_') // split by underscore .map( (word) => word.isNotEmpty ? '${word[0].toUpperCase()}${word.substring(1)}' : '', ) .join(' '); } Future updateData() async { // Ensure apiselectedUser is not null before printing if (widget.templateData != null) { print("API Selected User Has Data - ${widget.templateData}"); print( "API Selected User Has - ${widget.templateData?["templateData"]?["subject"]}", ); setState(() async { // ✅ Wrap in setState to update the UI controllers["templateName"]?.text = widget.templateData?["templateData"]?["template_name"] ?? ""; controllers["subject"]?.text = widget.templateData?["templateData"]?["subject"] ?? ""; final bodyHtml = widget.templateData?["templateData"]?["body_html"] ?? ""; print("bodyHtml - $bodyHtml"); String html = widget.templateData?["templateData"]?["body_html"] ?? ""; final htmlToDelta = HtmlToDelta(); // Convert the HTML string to Quill Delta format // This is where the magic happens, but also where complex HTML might be simplified final quill.Delta initialDelta = htmlToDelta.convert(html); // Create a Quill Document from the Delta final quill.Document quillDoc = quill.Document.fromDelta(initialDelta); // Initialize the QuillController with the converted document _controller = quill.QuillController( document: quillDoc, selection: const TextSelection.collapsed( offset: 0, ), // Cursor at the start ); // final plainText = extractPlainTextFromHtml(bodyHtml); // final decodedText = decodeHtmlEntities(plainText); // final quillDoc = quill.Document()..insert(0, decodedText); // _controller = quill.QuillController( // document: quillDoc, // selection: const TextSelection.collapsed(offset: 0), // ); templateName = widget.templateData?["templateData"]?["template_name"] ?? ""; print("Fetched template_name: $templateName"); final rawPlaceholder = widget.templateData?["templateData"]?["placeholder"]; if (rawPlaceholder is String) { // If it's a JSON string, decode it first placeholderList = List>.from( jsonDecode(rawPlaceholder), ); } else if (rawPlaceholder is List) { // If it's already a list (ideal case) placeholderList = List>.from(rawPlaceholder); } print("Extracted placeholders: $placeholders"); print("Fetched placeholders: $placeholderList"); templateId = int.tryParse( widget.templateData?["templateData"]?["template_id"] ?.toString() ?? '0', ) ?? 0; print("Fetched template_id: $templateId"); fetchSignature(); // if (widget.group?["international_policy_id"] != null) { // selectedInternational = // widget.group!["international_policy_id"].toString(); // } }); } else { print("API Selected User Has Data - No data available yet"); } } Future fetchSignature() async { final uri = Uri.parse('$apiUrl/api/getForexSignaturePath'); final token = await getToken(); final response = await http.get( uri, headers: { 'Authorization': 'Bearer $token', 'app-signature': 'ts-traveltool-2025-signature-123456', }, ); if (response.statusCode == 200) { print("ERS - $response"); final json = jsonDecode(response.body); print("ERSjson - $json"); String? rawLogoPath = json['url']?.toString(); if (rawLogoPath != null && rawLogoPath.isNotEmpty) { print("ERSrawLogoPath - $rawLogoPath"); setState(() { selectedOrglogo = rawLogoPath; }); } } else if (response.statusCode == 403) { print("403-FORB"); await apiService.logout(context); return null; // throw Exception('Failed to load users'); } else { print("❌ Failed to fetch signature: ${response.statusCode}"); } } Future handleSubmit() async { Map data = TemplateData; print('TemplateData - $data'); // final String deltaJsonString = TemplateData?["body_html"] ?? "[]"; // // print('deltaJsonString $deltaJsonString'); // // // Decode the JSON string into a list // final List deltaJson = jsonDecode(deltaJsonString); // // print(DeltaToHTML.encodeJson(deltaJson)); // // // Then convert it to a Quill document // final doc = Document.fromJson(List>.from(deltaJson)); // // // Set it to the controller // _controller = QuillController( // document: doc, // selection: const TextSelection.collapsed(offset: 0), // ); // print('doc JSON: ${jsonEncode(doc.toDelta().toJson())}'); // print('doc plain text: ${doc.toPlainText()}'); // print('doc $doc'); setState(() { isDisable = true; updateTemplateData(data); // This triggers UI rebuild with error messages // if (validateData()) { // postGroupData(); // } }); } Future updateTemplateData(policyData) async { final String apiUrldata = '$apiUrl/api/template/update/${templateId}'; final token = await getToken(); // Fetch token if (token == null) { throw Exception('Token not found. Please log in.'); } try { final response = await http.put( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', 'app-signature': 'ts-traveltool-2025-signature-123456', }, body: jsonEncode(policyData), // Convert map to JSON ); if (response.statusCode == 200) { print("policyData submitted successfully!"); print("Response: ${response.body}"); // context.go('/templateList'); context.go('/OrganizationSettings'); setState(() { isDisable = false; }); } else if (response.statusCode == 403) { print("403-FORB"); await apiService.logout(context); return null; // throw Exception('Failed to load users'); } else { print("Failed to submit policyData. Status: ${response.statusCode}"); print("Error: ${response.body}"); setState(() { isDisable = false; }); } } catch (e) { print(" Error submitting policyData: $e"); setState(() { isDisable = false; }); } } Future _pickImage() async { final picker = ImagePicker(); final XFile? pickedFile = await picker.pickImage( source: ImageSource.gallery, ); if (pickedFile != null && kIsWeb) { try { final allowedExtensions = ['jpg', 'jpeg', 'png']; final fileExtension = pickedFile.name.split('.').last.toLowerCase(); if (!allowedExtensions.contains(fileExtension)) { print('❌ Invalid file type. Please select a JPG or PNG image.'); return; } final bytes = await pickedFile.readAsBytes(); print('✅ Image loaded, size: ${bytes.length} bytes'); setState(() { _imageBytes = bytes; }); await uploadSignature(); } catch (e) { print('❌ Error reading image bytes: $e'); } } else { print('⚠️ Image picking canceled or not on web.'); } } Future uploadSignature() async { if (_imageBytes == null) { print('⚠️ No image selected'); return; } final token = await getToken(); // Fetch token if (token == null) { throw Exception('Token not found. Please log in.'); } final uri = Uri.parse('$apiUrl/api/forex_signature_upload'); final request = http.MultipartRequest('POST', uri); // Add auth header request.headers['Authorization'] = 'Bearer $token'; request.headers['app-signature'] = 'ts-traveltool-2025-signature-123456'; // Add the image as multipart with the key "signature" request.files.add( http.MultipartFile.fromBytes( 'signature', // <-- key name _imageBytes!, // <-- image bytes filename: 'signature.png', // <-- filename (can be png/jpg) contentType: MediaType('image', 'png'), ), ); try { final response = await request.send(); final respStr = await response.stream.bytesToString(); if (response.statusCode == 200 || response.statusCode == 201) { print('✅ Upload successful: $respStr'); } else { print('❌ Upload failed (${response.statusCode}): $respStr'); } } catch (e) { print('❌ Error uploading signature: $e'); } } @override Widget build(BuildContext context) { return ResponsiveBuilder( builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( backgroundColor: const Color(0xFFf5f5f5), appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop ? EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.1, // 30% of screen width as horizontal padding vertical: MediaQuery.of(context).size.height * 0, // 5% of screen height as vertical padding ) : EdgeInsets.all(0), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), Expanded( child: buildUserTable( isDesktop, context, bodyColor, layoutColor, ), ), ], ), ), ); }, ); } Widget buildUserTable( bool isDesktop, context, Color? bodyColor, Color layoutColor, ) { return Container( margin: isDesktop ? const EdgeInsets.only(top: 5.0, bottom: 10.0) : null, padding: const EdgeInsets.all(28), decoration: BoxDecoration( color: isDesktop ? Colors.white : Color(0xFFFCFCFC), ), child: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Row( children: [ Container( child: BreadcrumbNavigation( isDesktop: isDesktop, breadcrumbItems: [ BreadcrumbItem( title: 'Org Settings', tooltip: 'Go To Organization Settings', onTap: (context) { context.go("/OrganizationSettings"); }, ), BreadcrumbItem(title: 'Forex'), ], ), ), ], ), SizedBox(height: 10), Text( formatTemplateName(templateName), style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w400, color: Colors.black, ), ), // SizedBox(height: 10), // buildTempalteSubject(isDesktop), // // SizedBox(height: 10), buildTempalteBody(isDesktop), Spacer(), buildActions(isDesktop), ], ), ), ); } Widget buildTempalteSubject(bool isDesktop) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Subject", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( // isFocused: false, isFocused: focusStates["subjectFocused"] ?? false, color: Colors.white, width: isDesktop ? MediaQuery.of(context).size.width * 0.8 : null, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: focusNodes["subjectFocusNode"], controller: controllers["subject"], inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')), ], onChanged: (value) { // _clearError("local_id_num"); }, decoration: InputDecoration( labelText: "Enter the subject", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ); } Widget buildTempalteBody(bool isDesktop) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Text( // "Content", // style: GoogleFonts.poppins( // fontSize: 12, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74), // ), // ), // const SizedBox(height: 10), Container( color: Color(0xFFFFFEF0), width: isDesktop ? MediaQuery.of(context).size.width * 0.8 : null, child: IconTheme( data: IconThemeData(size: 18), // Set icon size here child: Container( color: Color(0xFFFFFEF0), width: isDesktop ? MediaQuery.of(context).size.width * 0.8 : null, child: IconTheme( data: IconThemeData(size: 18), // Set icon size here child: QuillSimpleToolbar(controller: _controller), ), ), // child: QuillSimpleToolbar( // controller: _controller, // config: QuillSimpleToolbarConfig( // embedButtons: FlutterQuillEmbeds.toolbarButtons(), // showClipboardPaste: true, // customButtons: [ // QuillToolbarCustomButtonOptions( // icon: const Icon(Icons.add_alarm_rounded), // onPressed: () { // _controller.document.insert( // _controller.selection.extentOffset, // TimeStampEmbed(DateTime.now().toString()), // ); // // _controller.updateSelection( // TextSelection.collapsed( // offset: _controller.selection.extentOffset + 1, // ), // ChangeSource.local, // ); // }, // ), // ], // buttonOptions: QuillSimpleToolbarButtonOptions( // base: QuillToolbarBaseButtonOptions( // afterButtonPressed: () { // final isDesktop = { // TargetPlatform.linux, // TargetPlatform.windows, // TargetPlatform.macOS, // }.contains(defaultTargetPlatform); // // if (isDesktop) { // // _editorFocusNode.requestFocus(); // // } // }, // ), // linkStyle: QuillToolbarLinkStyleButtonOptions( // validateLink: (link) { // // Treats all links as valid. When launching the URL, // // `https://` is prefixed if the link is incomplete (e.g., `google.com` → `https://google.com`) // // however this happens only within the editor. // return true; // }, // ), // ), // ), // ), ), ), SizedBox(height: 2), Container( child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () async { final selected = await showDialog( context: context, builder: (context) => PlaceholdersModal(placeholders: placeholderList), ); if (selected != null) { print("User selected placeholder: $selected"); // You can now insert into a controller or editor // Ensure the editor is focused FocusScope.of(context).requestFocus(_focusNode); final selection = _controller.selection; final position = selection.baseOffset; // if (position >= 0) { // final intPosition = position.toInt(); // // _controller.document.insert(intPosition, selected); // // _controller.updateSelection( // TextSelection.collapsed( // offset: intPosition + selected.length, // ), // ChangeSource.local, // ); // } } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.grey, // padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( 'Placeholders', style: GoogleFonts.poppins(fontSize: 13, color: Colors.black), ), ), ], ), ), Container( padding: const EdgeInsets.all(16), height: MediaQuery.of(context).size.height * 0.38, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all(color: Color(0xFFD6D5E6), width: 0.5), ), child: QuillEditor( controller: _controller, scrollController: ScrollController(), focusNode: _focusNode, config: QuillEditorConfig( placeholder: 'Start writing your notes...', padding: const EdgeInsets.all(16), embedBuilders: [ ...FlutterQuillEmbeds.editorBuilders( // imageEmbedConfig: QuillEditorImageEmbedConfig( // imageProviderBuilder: (context, imageUrl) { // if (imageUrl.startsWith('data:image')) { // return MemoryImage( // base64Decode(imageUrl.split(',').last), // ); // } // return null; // }, // ), videoEmbedConfig: QuillEditorVideoEmbedConfig( customVideoBuilder: (videoUrl, readOnly) { // To load YouTube videos https://github.com/singerdmx/flutter-quill/releases/tag/v10.8.0 return null; }, ), ), TimeStampEmbedBuilder(), ], ), ), ), SizedBox(height: 2), Container( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( // mainAxisAlignment: MainAxisAlignment.end, children: [ Text( "Upload Signature", style: GoogleFonts.poppins(fontSize: 11.5), ), SizedBox(width: 5), GestureDetector( onTap: _pickImage, child: _imageBytes != null ? ClipOval( child: Image.memory( _imageBytes!, // width: 50, // height: 50, width: 50, // Use responsive width height: 50, fit: BoxFit.cover, ), ) : selectedOrglogo != null ? ClipRect( child: Image.network( selectedOrglogo!, width: 50, // Use responsive width height: 50, // width: 250, // height: 55, fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) { return const CircleAvatar( radius: 20, backgroundColor: Colors.redAccent, child: Icon(Icons.error, size: 10), ); }, ), ) : const CircleAvatar( radius: 20, backgroundColor: Color(0xFFFFFAA0), // backgroundColor: Color(0xFFFFFEF0), child: Icon(Icons.add_a_photo, size: 10), ), ), ], ), SizedBox( // width: 200, child: Text( "* Allow types jpg, jpeg, png", // maxLines: 2, // softWrap: true, style: GoogleFonts.poppins( fontSize: 9, fontWeight: FontWeight.w400, color: Colors.grey, ), ), ), ], ), ), ], ); } Widget buildActions(bool isDesktop) { return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ SizedBox( child: ElevatedButton( onPressed: () { context.go('/OrganizationSettings'); // You can get text from commentController.text Navigator.of(context).pop(); // Close the modal }, style: ElevatedButton.styleFrom( foregroundColor: layoutColor, // backgroundColor: widget.layoutColor, backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( 'Cancel', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, color: layoutColor, ), ), ), ), SizedBox(width: 10), SizedBox( child: ElevatedButton( onPressed: isDisable ? null : () async { setState(() { isDisable = true; }); await handleSubmit(); // You can get text from commentController.text // Navigator.of(context).pop(); // Close the modal }, style: ElevatedButton.styleFrom( backgroundColor: layoutColor, // backgroundColor: widget.layoutColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( 'Save', style: GoogleFonts.poppins(fontSize: 14, color: Colors.white), ), ), ), ], ); } } @override void dispose() { _controller.dispose(); _editorScrollController.dispose(); _editorFocusNode.dispose(); } class TimeStampEmbed extends Embeddable { const TimeStampEmbed(String value) : super(timeStampType, value); static const String timeStampType = 'timeStamp'; static TimeStampEmbed fromDocument(Document document) => TimeStampEmbed(jsonEncode(document.toDelta().toJson())); Document get document => Document.fromJson(jsonDecode(data)); } class TimeStampEmbedBuilder extends EmbedBuilder { @override String get key => 'timeStamp'; @override String toPlainText(Embed node) { return node.value.data; } @override Widget build(BuildContext context, EmbedContext embedContext) { return Row( children: [ const Icon(Icons.access_time_rounded), Text(embedContext.node.value.data as String), ], ); } }