From 881372dd13c1f7daab12cdca84260880a2861c59 Mon Sep 17 00:00:00 2001 From: Andy Trand Date: Thu, 9 Dec 2021 23:21:53 +0200 Subject: [PATCH] selection delegate methods, imports cleanup (#515) --- lib/src/utils/color.dart | 2 - lib/src/widgets/controller.dart | 2 + lib/src/widgets/default_styles.dart | 1 - lib/src/widgets/delegate.dart | 3 - lib/src/widgets/editor.dart | 5 +- lib/src/widgets/image.dart | 2 - lib/src/widgets/raw_editor.dart | 5 +- .../raw_editor_state_keyboard_mixin.dart | 9 +- ...editor_state_selection_delegate_mixin.dart | 131 ++++++++++++++++-- ..._editor_state_text_input_client_mixin.dart | 1 - lib/src/widgets/simple_viewer.dart | 1 - lib/src/widgets/text_block.dart | 1 - lib/src/widgets/text_selection.dart | 2 - lib/src/widgets/toolbar/camera_button.dart | 2 - .../widgets/toolbar/clear_format_button.dart | 1 - lib/src/widgets/toolbar/color_button.dart | 1 - lib/src/widgets/toolbar/history_button.dart | 1 - lib/src/widgets/toolbar/image_button.dart | 2 - .../widgets/toolbar/image_video_utils.dart | 1 - lib/src/widgets/toolbar/indent_button.dart | 1 - .../widgets/toolbar/insert_embed_button.dart | 1 - .../widgets/toolbar/link_style_button.dart | 1 - .../toolbar/select_alignment_button.dart | 2 +- .../toolbar/select_header_style_button.dart | 2 +- .../toolbar/toggle_check_list_button.dart | 1 - .../widgets/toolbar/toggle_style_button.dart | 1 - lib/src/widgets/toolbar/video_button.dart | 2 - 27 files changed, 136 insertions(+), 48 deletions(-) diff --git a/lib/src/utils/color.dart b/lib/src/utils/color.dart index 93b6e12b..dc3b5fa5 100644 --- a/lib/src/utils/color.dart +++ b/lib/src/utils/color.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/material.dart'; Color stringToColor(String? s) { diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index d389cc3d..5ef115c8 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -200,6 +200,8 @@ class QuillController extends ChangeNotifier { /// Called in two cases: /// forward == false && textBefore.isEmpty /// forward == true && textAfter.isEmpty + /// Android only + /// see https://github.com/singerdmx/flutter-quill/discussions/514 void handleDelete(int cursorPosition, bool forward) => onDelete?.call(cursorPosition, forward); diff --git a/lib/src/widgets/default_styles.dart b/lib/src/widgets/default_styles.dart index 79d5f55a..af1469c1 100644 --- a/lib/src/widgets/default_styles.dart +++ b/lib/src/widgets/default_styles.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:tuple/tuple.dart'; import 'style_widgets/style_widgets.dart'; diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index fa03c9a6..14adb9a9 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -1,11 +1,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import '../../flutter_quill.dart'; -import '../models/documents/nodes/leaf.dart'; -import 'editor.dart'; import 'text_selection.dart'; typedef EmbedBuilder = Widget Function( diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 7cf198e7..c12c314d 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -7,7 +7,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:string_validator/string_validator.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -51,7 +50,7 @@ abstract class EditorState extends State { TextEditingValue getTextEditingValue(); - void setTextEditingValue(TextEditingValue value); + void setTextEditingValue(TextEditingValue value, SelectionChangedCause cause); RenderEditor? getRenderEditor(); @@ -62,6 +61,8 @@ abstract class EditorState extends State { void hideToolbar(); void requestKeyboard(); + + bool get readOnly; } /// Base interface for editable render objects. diff --git a/lib/src/widgets/image.dart b/lib/src/widgets/image.dart index eb07e8db..3d09bd74 100644 --- a/lib/src/widgets/image.dart +++ b/lib/src/widgets/image.dart @@ -1,6 +1,4 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:photo_view/photo_view.dart'; class ImageTapWrapper extends StatelessWidget { diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index e4e3f312..1a4f3b6a 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -641,7 +641,8 @@ class RawEditorState extends EditorState } @override - void setTextEditingValue(TextEditingValue value) { + void setTextEditingValue( + TextEditingValue value, SelectionChangedCause cause) { if (value.text == textEditingValue.text) { widget.controller.updateSelection(value.selection, ChangeSource.LOCAL); } else { @@ -723,6 +724,8 @@ class RawEditorState extends EditorState @override bool get wantKeepAlive => widget.focusNode.hasFocus; + + bool get readOnly => widget.readOnly; } class _Editor extends MultiChildRenderObjectWidget { diff --git a/lib/src/widgets/raw_editor/raw_editor_state_keyboard_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_keyboard_mixin.dart index 72fd5dc2..8156e6ee 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_keyboard_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_keyboard_mixin.dart @@ -104,7 +104,7 @@ mixin RawEditorStateKeyboardMixin on EditorState { text: selection.textBefore(plainText) + selection.textAfter(plainText), selection: TextSelection.collapsed(offset: selection.start), - )); + ), SelectionChangedCause.keyboard); } return; } @@ -156,12 +156,7 @@ mixin RawEditorStateKeyboardMixin on EditorState { if (size == 0) { widget.controller.handleDelete(cursorPosition, forward); } else { - widget.controller.replaceText( - cursorPosition, - size, - '', - newSelection, - ); + widget.controller.replaceText(cursorPosition, size, '', newSelection); } } diff --git a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart index dcb9809a..8f29c2b7 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart @@ -1,6 +1,8 @@ -import 'dart:math'; +import 'dart:math' as math; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import '../editor.dart'; @@ -14,7 +16,8 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState @override set textEditingValue(TextEditingValue value) { - setTextEditingValue(value); + // deprecated + setTextEditingValue(value, SelectionChangedCause.keyboard); } @override @@ -50,8 +53,8 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState final expandedRect = Rect.fromCenter( center: rect.center, width: rect.width, - height: - max(rect.height, getRenderEditor()!.preferredLineHeight(position)), + height: math.max( + rect.height, getRenderEditor()!.preferredLineHeight(position)), ); additionalOffset = expandedRect.height >= editableSize.height @@ -81,10 +84,8 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState @override void userUpdateTextEditingValue( - TextEditingValue value, - SelectionChangedCause cause, - ) { - setTextEditingValue(value); + TextEditingValue value, SelectionChangedCause cause) { + setTextEditingValue(value, cause); } @override @@ -98,4 +99,118 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState @override bool get selectAllEnabled => widget.toolbarOptions.selectAll; + + void setSelection(TextSelection nextSelection, SelectionChangedCause cause) { + if (nextSelection == textEditingValue.selection) { + return; + } + setTextEditingValue( + textEditingValue.copyWith(selection: nextSelection), + cause, + ); + } + + @override + void copySelection(SelectionChangedCause cause) { + final selection = textEditingValue.selection; + if (selection.isCollapsed || !selection.isValid) { + return; + } + Clipboard.setData( + ClipboardData(text: selection.textInside(textEditingValue.text))); + + if (cause == SelectionChangedCause.toolbar) { + bringIntoView(textEditingValue.selection.extent); + hideToolbar(false); + + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + break; + case TargetPlatform.macOS: + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + // Collapse the selection and hide the toolbar and handles. + userUpdateTextEditingValue( + TextEditingValue( + text: textEditingValue.text, + selection: TextSelection.collapsed(offset: textEditingValue.selection.end), + ), + SelectionChangedCause.toolbar, + ); + break; + } + } + } + + @override + void cutSelection(SelectionChangedCause cause) { + final selection = textEditingValue.selection; + if (readOnly || !selection.isValid || selection.isCollapsed) { + return; + } + final text = textEditingValue.text; + Clipboard.setData(ClipboardData(text: selection.textInside(text))); + setTextEditingValue( + TextEditingValue( + text: selection.textBefore(text) + selection.textAfter(text), + selection: TextSelection.collapsed( + offset: math.min(selection.start, selection.end), + affinity: selection.affinity, + ), + ), + cause, + ); + + if (cause == SelectionChangedCause.toolbar) { + bringIntoView(textEditingValue.selection.extent); + hideToolbar(); + } + } + + @override + Future pasteText(SelectionChangedCause cause) async { + final selection = textEditingValue.selection; + if (readOnly || !selection.isValid) { + return; + } + final text = textEditingValue.text; + // See https://github.com/flutter/flutter/issues/11427 + final data = await Clipboard.getData(Clipboard.kTextPlain); + if (data == null) { + return; + } + setTextEditingValue( + TextEditingValue( + text: + selection.textBefore(text) + data.text! + selection.textAfter(text), + selection: TextSelection.collapsed( + offset: math.min(selection.start, selection.end) + data.text!.length, + affinity: selection.affinity, + ), + ), + cause, + ); + + if (cause == SelectionChangedCause.toolbar) { + bringIntoView(textEditingValue.selection.extent); + hideToolbar(); + } + } + + @override + void selectAll(SelectionChangedCause cause) { + setSelection( + textEditingValue.selection.copyWith( + baseOffset: 0, + extentOffset: textEditingValue.text.length, + ), + cause, + ); + + if (cause == SelectionChangedCause.toolbar) { + bringIntoView(textEditingValue.selection.extent); + } + } } diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index 6e4607f9..37c5ece1 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import '../../models/documents/document.dart'; import '../../utils/diff_delta.dart'; diff --git a/lib/src/widgets/simple_viewer.dart b/lib/src/widgets/simple_viewer.dart index dc68fe2a..86dcd4f3 100644 --- a/lib/src/widgets/simple_viewer.dart +++ b/lib/src/widgets/simple_viewer.dart @@ -4,7 +4,6 @@ import 'dart:io' as io; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:string_validator/string_validator.dart'; import 'package:tuple/tuple.dart'; diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index 40fc351b..245cd332 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:tuple/tuple.dart'; diff --git a/lib/src/widgets/text_selection.dart b/lib/src/widgets/text_selection.dart index 16374789..593a8135 100644 --- a/lib/src/widgets/text_selection.dart +++ b/lib/src/widgets/text_selection.dart @@ -1,11 +1,9 @@ import 'dart:async'; import 'dart:math' as math; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import '../models/documents/nodes/node.dart'; diff --git a/lib/src/widgets/toolbar/camera_button.dart b/lib/src/widgets/toolbar/camera_button.dart index 7686e47c..96776c39 100644 --- a/lib/src/widgets/toolbar/camera_button.dart +++ b/lib/src/widgets/toolbar/camera_button.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; @@ -6,7 +5,6 @@ import '../../models/themes/quill_icon_theme.dart'; import '../controller.dart'; import '../toolbar.dart'; import 'image_video_utils.dart'; -import 'quill_icon_button.dart'; class CameraButton extends StatelessWidget { const CameraButton({ diff --git a/lib/src/widgets/toolbar/clear_format_button.dart b/lib/src/widgets/toolbar/clear_format_button.dart index 7410c637..652b783f 100644 --- a/lib/src/widgets/toolbar/clear_format_button.dart +++ b/lib/src/widgets/toolbar/clear_format_button.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import '../../../flutter_quill.dart'; -import 'quill_icon_button.dart'; class ClearFormatButton extends StatefulWidget { const ClearFormatButton({ diff --git a/lib/src/widgets/toolbar/color_button.dart b/lib/src/widgets/toolbar/color_button.dart index c0d323bf..d6c90731 100644 --- a/lib/src/widgets/toolbar/color_button.dart +++ b/lib/src/widgets/toolbar/color_button.dart @@ -8,7 +8,6 @@ import '../../translations/toolbar.i18n.dart'; import '../../utils/color.dart'; import '../controller.dart'; import '../toolbar.dart'; -import 'quill_icon_button.dart'; /// Controls color styles. /// diff --git a/lib/src/widgets/toolbar/history_button.dart b/lib/src/widgets/toolbar/history_button.dart index f9a8bf93..a245c2e8 100644 --- a/lib/src/widgets/toolbar/history_button.dart +++ b/lib/src/widgets/toolbar/history_button.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import '../../../flutter_quill.dart'; -import 'quill_icon_button.dart'; class HistoryButton extends StatefulWidget { const HistoryButton({ diff --git a/lib/src/widgets/toolbar/image_button.dart b/lib/src/widgets/toolbar/image_button.dart index 09616e7c..2d42c192 100644 --- a/lib/src/widgets/toolbar/image_button.dart +++ b/lib/src/widgets/toolbar/image_button.dart @@ -4,12 +4,10 @@ import 'package:image_picker/image_picker.dart'; import '../../models/documents/nodes/embed.dart'; import '../../models/themes/quill_dialog_theme.dart'; import '../../models/themes/quill_icon_theme.dart'; -import '../../utils/media_pick_setting.dart'; import '../controller.dart'; import '../link_dialog.dart'; import '../toolbar.dart'; import 'image_video_utils.dart'; -import 'quill_icon_button.dart'; class ImageButton extends StatelessWidget { const ImageButton({ diff --git a/lib/src/widgets/toolbar/image_video_utils.dart b/lib/src/widgets/toolbar/image_video_utils.dart index c4591dd4..f2814ec9 100644 --- a/lib/src/widgets/toolbar/image_video_utils.dart +++ b/lib/src/widgets/toolbar/image_video_utils.dart @@ -6,7 +6,6 @@ import 'package:image_picker/image_picker.dart'; import '../../models/documents/nodes/embed.dart'; import '../../translations/toolbar.i18n.dart'; -import '../../utils/media_pick_setting.dart'; import '../controller.dart'; import '../toolbar.dart'; diff --git a/lib/src/widgets/toolbar/indent_button.dart b/lib/src/widgets/toolbar/indent_button.dart index d72718fc..476ebc46 100644 --- a/lib/src/widgets/toolbar/indent_button.dart +++ b/lib/src/widgets/toolbar/indent_button.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import '../../../flutter_quill.dart'; -import 'quill_icon_button.dart'; class IndentButton extends StatefulWidget { const IndentButton({ diff --git a/lib/src/widgets/toolbar/insert_embed_button.dart b/lib/src/widgets/toolbar/insert_embed_button.dart index 91a6822c..45a4d5ba 100644 --- a/lib/src/widgets/toolbar/insert_embed_button.dart +++ b/lib/src/widgets/toolbar/insert_embed_button.dart @@ -4,7 +4,6 @@ import '../../models/documents/nodes/embed.dart'; import '../../models/themes/quill_icon_theme.dart'; import '../controller.dart'; import '../toolbar.dart'; -import 'quill_icon_button.dart'; class InsertEmbedButton extends StatelessWidget { const InsertEmbedButton({ diff --git a/lib/src/widgets/toolbar/link_style_button.dart b/lib/src/widgets/toolbar/link_style_button.dart index 5ec66b29..35fca8a6 100644 --- a/lib/src/widgets/toolbar/link_style_button.dart +++ b/lib/src/widgets/toolbar/link_style_button.dart @@ -7,7 +7,6 @@ import '../../translations/toolbar.i18n.dart'; import '../controller.dart'; import '../link_dialog.dart'; import '../toolbar.dart'; -import 'quill_icon_button.dart'; class LinkStyleButton extends StatefulWidget { const LinkStyleButton({ diff --git a/lib/src/widgets/toolbar/select_alignment_button.dart b/lib/src/widgets/toolbar/select_alignment_button.dart index 98eb90ad..5ded1021 100644 --- a/lib/src/widgets/toolbar/select_alignment_button.dart +++ b/lib/src/widgets/toolbar/select_alignment_button.dart @@ -84,7 +84,7 @@ class _SelectAlignmentButtonState extends State { mainAxisSize: MainAxisSize.min, children: List.generate(buttonCount, (index) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), + padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), child: ConstrainedBox( constraints: BoxConstraints.tightFor( width: widget.iconSize * kIconButtonFactor, diff --git a/lib/src/widgets/toolbar/select_header_style_button.dart b/lib/src/widgets/toolbar/select_header_style_button.dart index bb6eee65..3244b31d 100644 --- a/lib/src/widgets/toolbar/select_header_style_button.dart +++ b/lib/src/widgets/toolbar/select_header_style_button.dart @@ -67,7 +67,7 @@ class _SelectHeaderStyleButtonState extends State { mainAxisSize: MainAxisSize.min, children: List.generate(4, (index) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), + padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), child: ConstrainedBox( constraints: BoxConstraints.tightFor( width: widget.iconSize * kIconButtonFactor, diff --git a/lib/src/widgets/toolbar/toggle_check_list_button.dart b/lib/src/widgets/toolbar/toggle_check_list_button.dart index e631d23a..ed24a84d 100644 --- a/lib/src/widgets/toolbar/toggle_check_list_button.dart +++ b/lib/src/widgets/toolbar/toggle_check_list_button.dart @@ -5,7 +5,6 @@ import '../../models/documents/style.dart'; import '../../models/themes/quill_icon_theme.dart'; import '../controller.dart'; import '../toolbar.dart'; -import 'toggle_style_button.dart'; class ToggleCheckListButton extends StatefulWidget { const ToggleCheckListButton({ diff --git a/lib/src/widgets/toolbar/toggle_style_button.dart b/lib/src/widgets/toolbar/toggle_style_button.dart index caf36879..e4a680fe 100644 --- a/lib/src/widgets/toolbar/toggle_style_button.dart +++ b/lib/src/widgets/toolbar/toggle_style_button.dart @@ -5,7 +5,6 @@ import '../../models/documents/style.dart'; import '../../models/themes/quill_icon_theme.dart'; import '../controller.dart'; import '../toolbar.dart'; -import 'quill_icon_button.dart'; typedef ToggleStyleButtonBuilder = Widget Function( BuildContext context, diff --git a/lib/src/widgets/toolbar/video_button.dart b/lib/src/widgets/toolbar/video_button.dart index db2afdeb..dbda51d3 100644 --- a/lib/src/widgets/toolbar/video_button.dart +++ b/lib/src/widgets/toolbar/video_button.dart @@ -4,12 +4,10 @@ import 'package:image_picker/image_picker.dart'; import '../../models/documents/nodes/embed.dart'; import '../../models/themes/quill_dialog_theme.dart'; import '../../models/themes/quill_icon_theme.dart'; -import '../../utils/media_pick_setting.dart'; import '../controller.dart'; import '../link_dialog.dart'; import '../toolbar.dart'; import 'image_video_utils.dart'; -import 'quill_icon_button.dart'; class VideoButton extends StatelessWidget { const VideoButton({