Initial implementation of quill in Action Taken

This commit is contained in:
Marc Rejohn Castillano 2026-02-21 16:12:04 +08:00
parent 1478667bbf
commit 2aeb73d5de
3 changed files with 290 additions and 51 deletions

View File

@ -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<TaskDetailScreen>
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<TaskDetailScreen>
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<TaskDetailScreen>
}
}
// 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<TaskDetailScreen>
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<dynamic>;
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<TaskDetailScreen>
_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<TaskDetailScreen>
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!,
),
),
),

View File

@ -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:

View File

@ -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: