diff --git a/lib/screens/tasks/task_detail_screen.dart b/lib/screens/tasks/task_detail_screen.dart index 17355d1a..0ae627be 100644 --- a/lib/screens/tasks/task_detail_screen.dart +++ b/lib/screens/tasks/task_detail_screen.dart @@ -10,6 +10,7 @@ import '../../models/ticket_message.dart'; import '../../providers/notifications_provider.dart'; import 'dart:async'; import 'dart:convert'; +import 'package:flutter_quill/flutter_quill.dart' as quill; import '../../providers/supabase_provider.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import '../../providers/profile_provider.dart'; @@ -52,8 +53,8 @@ class _TaskDetailScreenState extends ConsumerState final _requestedController = TextEditingController(); final _notedController = TextEditingController(); final _receivedController = TextEditingController(); - // Plain text controller for Action taken (fallback from rich editor) - TextEditingController? _actionController; + // Rich text editor for Action taken + quill.QuillController? _actionController; Timer? _actionDebounce; Timer? _requestedDebounce; Timer? _notedDebounce; @@ -99,7 +100,7 @@ class _TaskDetailScreenState extends ConsumerState CurvedAnimation(parent: _saveAnimController, curve: Curves.easeInOut), ); // create an empty action controller by default; will seed per-task later - _actionController = TextEditingController(); + _actionController = quill.QuillController.basic(); } @override @@ -138,26 +139,6 @@ class _TaskDetailScreenState extends ConsumerState } } - // Convert stored Quill delta JSON (or other simple structures) to plain text. - String _deltaJsonToPlainText(dynamic json) { - try { - if (json is String) return json; - if (json is List) { - final buffer = StringBuffer(); - for (final item in json) { - if (item is Map && item.containsKey('insert')) { - final ins = item['insert']; - if (ins is String) buffer.write(ins); - } - } - return buffer.toString(); - } - } catch (_) { - // ignore and fallthrough - } - return ''; - } - @override Widget build(BuildContext context) { final tasksAsync = ref.watch(tasksProvider); @@ -228,18 +209,23 @@ class _TaskDetailScreenState extends ConsumerState try { _actionDebounce?.cancel(); _actionController?.dispose(); - _actionController = TextEditingController(); if (task.actionTaken != null && task.actionTaken!.isNotEmpty) { try { - final decoded = jsonDecode(task.actionTaken!); - final plain = _deltaJsonToPlainText(decoded); - _actionController!.text = plain; + final docJson = + jsonDecode(task.actionTaken!) as List; + final doc = quill.Document.fromJson(docJson); + _actionController = quill.QuillController( + document: doc, + selection: const TextSelection.collapsed(offset: 0), + ); } catch (_) { - _actionController!.text = task.actionTaken!; + _actionController = quill.QuillController.basic(); } + } else { + _actionController = quill.QuillController.basic(); } } catch (_) { - _actionController = TextEditingController(); + _actionController = quill.QuillController.basic(); } // Attach auto-save listener for action taken (debounced) @@ -248,15 +234,19 @@ class _TaskDetailScreenState extends ConsumerState _actionDebounce = Timer( const Duration(milliseconds: 700), () async { - final plain = _actionController?.text.trim() ?? ''; + final plain = + _actionController?.document.toPlainText().trim() ?? ''; setState(() { _actionSaving = true; _actionSaved = false; }); try { + final deltaJson = jsonEncode( + _actionController?.document.toDelta().toJson(), + ); await ref .read(tasksControllerProvider) - .updateTask(taskId: task.id, actionTaken: plain); + .updateTask(taskId: task.id, actionTaken: deltaJson); setState(() { _actionSaved = plain.isNotEmpty; }); @@ -1363,19 +1353,86 @@ class _TaskDetailScreenState extends ConsumerState children: [ Column( children: [ - // Plain multiline editor for Action taken + Row( + children: [ + IconButton( + tooltip: 'Bold', + icon: const Icon( + Icons.format_bold, + ), + onPressed: () => + _actionController + ?.formatSelection( + quill + .Attribute + .bold, + ), + ), + IconButton( + tooltip: 'Italic', + icon: const Icon( + Icons.format_italic, + ), + onPressed: () => + _actionController + ?.formatSelection( + quill + .Attribute + .italic, + ), + ), + IconButton( + tooltip: 'Underline', + icon: const Icon( + Icons.format_underlined, + ), + onPressed: () => + _actionController + ?.formatSelection( + quill + .Attribute + .underline, + ), + ), + IconButton( + tooltip: 'Bullet list', + icon: const Icon( + Icons + .format_list_bulleted, + ), + onPressed: () => + _actionController + ?.formatSelection( + quill + .Attribute + .ul, + ), + ), + IconButton( + tooltip: 'Numbered list', + icon: const Icon( + Icons + .format_list_numbered, + ), + onPressed: () => + _actionController + ?.formatSelection( + quill + .Attribute + .ol, + ), + ), + ], + ), const SizedBox(height: 6), Expanded( - child: TextFormField( - controller: - _actionController, - readOnly: !canUpdateStatus, - maxLines: null, - expands: true, - decoration: - const InputDecoration.collapsed( - hintText: - 'Describe the action taken...', + child: MouseRegion( + cursor: + SystemMouseCursors.text, + child: + quill.QuillEditor.basic( + controller: + _actionController!, ), ), ), diff --git a/pubspec.lock b/pubspec.lock index afe6f455..6d2fcaee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -133,7 +133,15 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted version: "1.4.0" @@ -185,6 +193,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" crypto: dependency: transitive description: @@ -193,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" dart_earcut: dependency: transitive description: @@ -217,6 +241,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + dart_quill_delta: + dependency: transitive + description: + name: dart_quill_delta + sha256: bddb0b2948bd5b5a328f1651764486d162c59a8ccffd4c63e8b2c5e44be1dac4 + url: "https://pub.dev" + source: hosted + version: "10.8.3" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" ed25519_edwards: dependency: transitive description: @@ -245,10 +285,26 @@ packages: dependency: transitive description: name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.1.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" fixnum: dependency: transitive description: @@ -262,6 +318,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_colorpicker: + dependency: transitive + description: + name: flutter_colorpicker + sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter_dotenv: dependency: "direct main" description: @@ -302,6 +366,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + flutter_keyboard_visibility_temp_fork: + dependency: transitive + description: + name: flutter_keyboard_visibility_temp_fork + sha256: e3d02900640fbc1129245540db16944a0898b8be81694f4bf04b6c985bed9048 + url: "https://pub.dev" + source: hosted + version: "0.1.5" flutter_keyboard_visibility_web: dependency: transitive description: @@ -334,6 +406,11 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_map: dependency: "direct main" description: @@ -342,6 +419,22 @@ packages: url: "https://pub.dev" source: hosted version: "8.2.2" + flutter_quill: + dependency: "direct main" + description: + name: flutter_quill + sha256: b96bb8525afdeaaea52f5d02f525e05cc34acd176467ab6d6f35d434cf14fde2 + url: "https://pub.dev" + source: hosted + version: "11.5.0" + flutter_quill_delta_from_html: + dependency: transitive + description: + name: flutter_quill_delta_from_html + sha256: "0eb801ea8dd498cadc057507af5da794d4c9599ce58b2569cb3d4bb53ba8bed2" + url: "https://pub.dev" + source: hosted + version: "1.5.3" flutter_riverpod: dependency: "direct main" description: @@ -480,6 +573,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: transitive description: @@ -592,22 +693,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" + url: "https://pub.dev" + source: hosted + version: "7.3.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: @@ -776,6 +885,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + quill_native_bridge: + dependency: transitive + description: + name: quill_native_bridge + sha256: "76a16512e398e84216f3f659f7cb18a89ec1e141ea908e954652b4ce6cf15b18" + url: "https://pub.dev" + source: hosted + version: "11.1.0" + quill_native_bridge_android: + dependency: transitive + description: + name: quill_native_bridge_android + sha256: b75c7e6ede362a7007f545118e756b1f19053994144ec9eda932ce5e54a57569 + url: "https://pub.dev" + source: hosted + version: "0.0.1+2" + quill_native_bridge_ios: + dependency: transitive + description: + name: quill_native_bridge_ios + sha256: d23de3cd7724d482fe2b514617f8eedc8f296e120fb297368917ac3b59d8099f + url: "https://pub.dev" + source: hosted + version: "0.0.1" + quill_native_bridge_macos: + dependency: transitive + description: + name: quill_native_bridge_macos + sha256: "1c0631bd1e2eee765a8b06017c5286a4e829778f4585736e048eb67c97af8a77" + url: "https://pub.dev" + source: hosted + version: "0.0.1" + quill_native_bridge_platform_interface: + dependency: transitive + description: + name: quill_native_bridge_platform_interface + sha256: "8264a2bdb8a294c31377a27b46c0f8717fa9f968cf113f7dc52d332ed9c84526" + url: "https://pub.dev" + source: hosted + version: "0.0.2+1" + quill_native_bridge_web: + dependency: transitive + description: + name: quill_native_bridge_web + sha256: "7c723f6824b0250d7f33e8b6c23f2f8eb0103fe48ee7ebf47ab6786b64d5c05d" + url: "https://pub.dev" + source: hosted + version: "0.0.2" + quill_native_bridge_windows: + dependency: transitive + description: + name: quill_native_bridge_windows + sha256: "3f96ced19e3206ddf4f6f7dde3eb16bdd05e10294964009ea3a806d995aa7caa" + url: "https://pub.dev" + source: hosted + version: "0.0.2" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" realtime_client: dependency: transitive description: @@ -953,10 +1126,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.9" timezone: dependency: "direct main" description: @@ -1093,6 +1266,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" wkt_parser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9a5d915b..cfe6c044 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: flutter_map: ^8.2.2 latlong2: ^0.9.0 flutter_typeahead: ^4.1.0 + flutter_quill: ^11.5.0 dev_dependencies: flutter_test: