import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_quill/flutter_quill.dart' as quill; /// Convert a stored Delta JSON or plain text string to trimmed plain text. /// - Delta JSON (starts with '[') is decoded and converted to plain text /// - Non-JSON strings are returned trimmed as-is /// - null returns empty string String deltaJsonToPlainText(String? stored) { if (stored == null) return ''; final trimmed = stored.trim(); if (trimmed.isEmpty) return ''; if (trimmed.startsWith('[')) { try { final decoded = jsonDecode(trimmed); if (decoded is List) { final doc = quill.Document.fromJson(decoded); return doc.toPlainText().trim(); } } catch (_) {} } return trimmed; } /// Parses a stored Delta-JSON string into a [quill.QuillController], or returns /// an empty one. Callers own the controller and must dispose it. quill.QuillController quillControllerFromDelta(String? deltaJson) { if (deltaJson != null && deltaJson.isNotEmpty) { try { final decoded = jsonDecode(deltaJson); if (decoded is List) { return quill.QuillController( document: quill.Document.fromJson(decoded), selection: const TextSelection.collapsed(offset: 0), ); } } catch (_) {} } return quill.QuillController.basic(); } /// Serializes a controller's document to a Delta-JSON string for storage. String quillToDeltaJson(quill.QuillController controller) => jsonEncode(controller.document.toDelta().toJson()); /// The controller's plain text (trimmed) — for empty checks and previews. String quillPlainText(quill.QuillController controller) => controller.document.toPlainText().trim(); /// Bordered rich-text editor with a minimal toolbar (bold/italic/underline + /// bullet/numbered lists). The caller owns [controller] and disposes it. class QuillFieldEditor extends StatelessWidget { const QuillFieldEditor({ super.key, required this.controller, this.placeholder, this.height = 140, }); final quill.QuillController controller; final String? placeholder; final double height; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Container( height: height, clipBehavior: Clip.antiAlias, decoration: BoxDecoration( border: Border.all(color: cs.outlineVariant), borderRadius: BorderRadius.circular(8), ), child: Column( children: [ SizedBox( height: 40, child: quill.QuillSimpleToolbar( controller: controller, config: const quill.QuillSimpleToolbarConfig( multiRowsDisplay: false, showAlignmentButtons: false, showBackgroundColorButton: false, showCenterAlignment: false, showColorButton: false, showDividers: false, showFontFamily: false, showFontSize: false, showHeaderStyle: false, showIndent: false, showInlineCode: false, showLeftAlignment: false, showLink: false, showQuote: false, showRightAlignment: false, showSearchButton: false, showCodeBlock: false, showDirection: false, showJustifyAlignment: false, showListCheck: false, showSubscript: false, showSuperscript: false, showStrikeThrough: false, showSmallButton: false, showClearFormat: false, showRedo: false, showUndo: false, ), ), ), const Divider(height: 1), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), child: quill.QuillEditor.basic( controller: controller, config: quill.QuillEditorConfig( placeholder: placeholder, expands: true, ), ), ), ), ], ), ); } } /// Renders a stored value read-only. Interprets a Delta-JSON string as rich /// text; falls back to a plain [Text] for legacy plain-string values. class QuillReadOnly extends StatelessWidget { const QuillReadOnly(this.deltaOrText, {super.key, this.style}); final String deltaOrText; final TextStyle? style; @override Widget build(BuildContext context) { final trimmed = deltaOrText.trim(); if (trimmed.startsWith('[')) { try { final decoded = jsonDecode(trimmed); if (decoded is List) { final controller = quill.QuillController( document: quill.Document.fromJson(decoded), selection: const TextSelection.collapsed(offset: 0), ); return IgnorePointer( child: quill.QuillEditor.basic( controller: controller, config: const quill.QuillEditorConfig( showCursor: false, autoFocus: false, ), ), ); } } catch (_) {} } return Text(deltaOrText, style: style); } }