Initial implementation of quill in Action Taken
This commit is contained in:
@@ -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!,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user