import 'dart:math' as math; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import '../models/documents/attribute.dart'; import '../models/documents/document.dart'; import '../models/documents/nodes/embeddable.dart'; import '../models/documents/nodes/leaf.dart'; import '../models/documents/style.dart'; import '../models/quill_delta.dart'; import '../models/structs/doc_change.dart'; import '../models/structs/image_url.dart'; import '../models/structs/offset_value.dart'; import '../utils/delta.dart'; typedef ReplaceTextCallback = bool Function(int index, int len, Object? data); typedef DeleteCallback = void Function(int cursorPosition, bool forward); class QuillController extends ChangeNotifier { QuillController({ required Document document, required TextSelection selection, bool keepStyleOnNewLine = false, this.onReplaceText, this.onDelete, this.onSelectionCompleted, this.onSelectionChanged, }) : _document = document, _selection = selection, _keepStyleOnNewLine = keepStyleOnNewLine; factory QuillController.basic() { return QuillController( document: Document(), selection: const TextSelection.collapsed(offset: 0), ); } /// Document managed by this controller. Document _document; Document get document => _document; set document(doc) { _document = doc; // Prevent the selection from _selection = const TextSelection(baseOffset: 0, extentOffset: 0); notifyListeners(); } /// Tells whether to keep or reset the [toggledStyle] /// when user adds a new line. final bool _keepStyleOnNewLine; /// Currently selected text within the [document]. TextSelection get selection => _selection; TextSelection _selection; /// Custom [replaceText] handler /// Return false to ignore the event ReplaceTextCallback? onReplaceText; /// Custom delete handler DeleteCallback? onDelete; void Function()? onSelectionCompleted; void Function(TextSelection textSelection)? onSelectionChanged; /// Store any styles attribute that got toggled by the tap of a button /// and that has not been applied yet. /// It gets reset after each format action within the [document]. Style toggledStyle = Style(); bool ignoreFocusOnTextChange = false; /// Skip requestKeyboard being called in /// RawEditorState#_didChangeTextEditingValue bool skipRequestKeyboard = false; /// True when this [QuillController] instance has been disposed. /// /// A safety mechanism to ensure that listeners don't crash when adding, /// removing or listeners to this instance. bool _isDisposed = false; Stream get changes => document.changes; TextEditingValue get plainTextEditingValue => TextEditingValue( text: document.toPlainText(), selection: selection, ); /// Only attributes applied to all characters within this range are /// included in the result. Style getSelectionStyle() { return document .collectStyle(selection.start, selection.end - selection.start) .mergeAll(toggledStyle); } // Increases or decreases the indent of the current selection by 1. void indentSelection(bool isIncrease) { final indent = getSelectionStyle().attributes[Attribute.indent.key]; if (indent == null) { if (isIncrease) { formatSelection(Attribute.indentL1); } return; } if (indent.value == 1 && !isIncrease) { formatSelection(Attribute.clone(Attribute.indentL1, null)); return; } if (isIncrease) { formatSelection(Attribute.getIndentLevel(indent.value + 1)); return; } formatSelection(Attribute.getIndentLevel(indent.value - 1)); } /// Returns all styles for each node within selection List> getAllIndividualSelectionStyles() { final styles = document.collectAllIndividualStyles( selection.start, selection.end - selection.start); return styles; } /// Returns plain text for each node within selection String getPlainText() { final text = document.getPlainText(selection.start, selection.end - selection.start); return text; } /// Returns all styles for any character within the specified text range. List