ts-tat/lib/Screens/myTemplates/templateForex.dart
2025-10-29 12:34:19 +05:30

1139 lines
36 KiB
Dart

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<String, dynamic>? templateData;
const TemplateForex({super.key, required this.templateData});
static TemplateForex fromState(GoRouterState state) {
return TemplateForex(templateData: state.extra as Map<String, dynamic>?);
}
@override
TemplateForexState createState() => TemplateForexState();
}
class TemplateForexState extends State<TemplateForex> {
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<String, TextEditingController> controllers = {};
List<String> dataHeader = ["subject"];
List<String> placeholders = [];
late QuillController _controller = QuillController.basic();
final FocusNode _focusNode = FocusNode();
late int templateId = 0;
late String templateName = "";
late List<Map<String, dynamic>> placeholderList = [];
bool isDisable = false;
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
Map<String, dynamic> 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 = '<strong>$content</strong>';
}
if (attrs.containsKey('italic')) {
content = '<em>$content</em>';
}
// Wrap each paragraph with <p>
if (content.trim().isNotEmpty) {
buffer.write('<p>${content.trim()}</p>');
}
}
}
return buffer.toString();
}
String convertQuillDocToHtml2(quill.Document doc) {
final buffer = StringBuffer();
final lines = <String>[];
final delta = doc.toDelta();
String applyStyles(String text, Map<String, dynamic>? attrs) {
if (attrs == null) return text;
if (attrs.containsKey('bold')) {
text = '<strong>$text</strong>';
}
if (attrs.containsKey('italic')) {
text = '<em>$text</em>';
}
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 <p>
final joined = lines.join('');
if (joined.trim().isNotEmpty) {
buffer.writeln('<p>${joined.trim()}</p>');
}
lines.clear();
}
}
}
}
// Add remaining lines
final joined = lines.join('');
if (joined.trim().isNotEmpty) {
buffer.writeln('<p>${joined.trim()}</p>');
}
return buffer.toString();
}
String extractPlainTextFromHtml(String html) {
final regex = RegExp(r'<p>(.*?)<\/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('&nbsp;', ' ')
.replaceAll('&amp;', '&')
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&quot;', '"')
.replaceAll('&#39;', "'"); // 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<void> 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<Map<String, dynamic>>.from(
jsonDecode(rawPlaceholder),
);
} else if (rawPlaceholder is List) {
// If it's already a list (ideal case)
placeholderList = List<Map<String, dynamic>>.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<void> 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<void> handleSubmit() async {
Map<String, dynamic> data = TemplateData;
print('TemplateData - $data');
// final String deltaJsonString = TemplateData?["body_html"] ?? "[]";
//
// print('deltaJsonString $deltaJsonString');
//
// // Decode the JSON string into a list
// final List<dynamic> deltaJson = jsonDecode(deltaJsonString);
//
// print(DeltaToHTML.encodeJson(deltaJson));
//
// // Then convert it to a Quill document
// final doc = Document.fromJson(List<Map<String, dynamic>>.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<void> 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<void> _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<void> 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: Colors.amber,
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),
],
);
}
}