pull/1566/head
Ellet 1 year ago
parent 8daccaeec6
commit 90645f1267
No known key found for this signature in database
GPG Key ID: C488CC70BBCEF0D1
  1. 160
      lib/src/widgets/raw_editor/raw_editor_context_menu_items.dart
  2. 406
      lib/src/widgets/raw_editor/raw_editor_state.dart
  3. 4
      lib/src/widgets/toolbar/simple_toolbar.dart

@ -0,0 +1,160 @@
// import 'package:flutter/widgets.dart' show TextSelectionDelegate;
// class QuillEditorTextSelectionDelegate implements TextSelectionDelegate {
// /// Copy current selection to [Clipboard].
// @override
// void copySelection(SelectionChangedCause cause) {
// controller.copiedImageUrl = null;
// _pastePlainText = controller.getPlainText();
// _pasteStyleAndEmbed = controller.getAllIndividualSelectionStylesAndEmbed();
// final selection = textEditingValue.selection;
// final text = textEditingValue.text;
// if (selection.isCollapsed) {
// return;
// }
// Clipboard.setData(ClipboardData(text: selection.textInside(text)));
// if (cause == SelectionChangedCause.toolbar) {
// bringIntoView(textEditingValue.selection.extent);
// // Collapse the selection and hide the toolbar and handles.
// userUpdateTextEditingValue(
// TextEditingValue(
// text: textEditingValue.text,
// selection:
// TextSelection.collapsed(offset: textEditingValue.selection.end),
// ),
// SelectionChangedCause.toolbar,
// );
// }
// }
// /// Cut current selection to [Clipboard].
// @override
// void cutSelection(SelectionChangedCause cause) {
// controller.copiedImageUrl = null;
// _pastePlainText = controller.getPlainText();
// _pasteStyleAndEmbed = controller.getAllIndividualSelectionStylesAndEmbed();
// if (widget.configurations.readOnly) {
// return;
// }
// final selection = textEditingValue.selection;
// final text = textEditingValue.text;
// if (selection.isCollapsed) {
// return;
// }
// Clipboard.setData(ClipboardData(text: selection.textInside(text)));
// _replaceText(ReplaceTextIntent(textEditingValue, '', selection, cause));
// if (cause == SelectionChangedCause.toolbar) {
// bringIntoView(textEditingValue.selection.extent);
// hideToolbar();
// }
// }
// /// Paste text from [Clipboard].
// @override
// Future<void> pasteText(SelectionChangedCause cause) async {
// if (widget.configurations.readOnly) {
// return;
// }
// if (controller.copiedImageUrl != null) {
// final index = textEditingValue.selection.baseOffset;
// final length = textEditingValue.selection.extentOffset - index;
// final copied = controller.copiedImageUrl!;
// controller.replaceText(
// index,
// length,
// BlockEmbed.image(copied.url),
// null,
// );
// if (copied.styleString.isNotEmpty) {
// controller.formatText(
// getEmbedNode(controller, index + 1).offset,
// 1,
// StyleAttribute(copied.styleString),
// );
// }
// controller.copiedImageUrl = null;
// await Clipboard.setData(
// const ClipboardData(text: ''),
// );
// return;
// }
// final selection = textEditingValue.selection;
// if (!selection.isValid) {
// return;
// }
// // Snapshot the input before using `await`.
// // See https://github.com/flutter/flutter/issues/11427
// final text = await Clipboard.getData(Clipboard.kTextPlain);
// if (text != null) {
// _replaceText(
// ReplaceTextIntent(
// textEditingValue,
// text.text!,
// selection,
// cause,
// ),
// );
// bringIntoView(textEditingValue.selection.extent);
// // Collapse the selection and hide the toolbar and handles.
// userUpdateTextEditingValue(
// TextEditingValue(
// text: textEditingValue.text,
// selection: TextSelection.collapsed(
// offset: textEditingValue.selection.end,
// ),
// ),
// cause,
// );
// return;
// }
// final onImagePaste = widget.configurations.onImagePaste;
// if (onImagePaste != null) {
// final reader = await ClipboardReader.readClipboard();
// if (!reader.canProvide(Formats.png)) {
// return;
// }
// reader.getFile(Formats.png, (value) async {
// final image = value;
// final imageUrl = await onImagePaste(await image.readAll());
// if (imageUrl == null) {
// return;
// }
// controller.replaceText(
// textEditingValue.selection.end,
// 0,
// BlockEmbed.image(imageUrl),
// null,
// );
// });
// }
// }
// /// Select the entire text value.
// @override
// void selectAll(SelectionChangedCause cause) {
// userUpdateTextEditingValue(
// textEditingValue.copyWith(
// selection: TextSelection(
// baseOffset: 0, extentOffset: textEditingValue.text.length),
// ),
// cause,
// );
// if (cause == SelectionChangedCause.toolbar) {
// bringIntoView(textEditingValue.selection.extent);
// }
// }
// }

@ -10,11 +10,12 @@ import 'package:flutter/rendering.dart' show RenderAbstractViewport;
import 'package:flutter/scheduler.dart' show SchedulerBinding;
import 'package:flutter/services.dart'
show
LogicalKeyboardKey,
RawKeyDownEvent,
HardwareKeyboard,
Clipboard,
ClipboardData,
HardwareKeyboard,
LogicalKeyboardKey,
RawKeyDownEvent,
SystemChannels,
TextInputControl;
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'
show KeyboardVisibilityController;
@ -119,10 +120,186 @@ class QuillRawEditorState extends EditorState
/// platform's default selection menu for [QuillRawEditor].
///
/// Copied from [EditableTextState].
// List<ContextMenuButtonItem> get contextMenuButtonItems {
// return EditableText.getEditableButtonItems(
// clipboardStatus: _clipboardStatus.value,
// onLiveTextInput: null,
// onCopy: copyEnabled
// ? () => copySelection(SelectionChangedCause.toolbar)
// : null,
// onCut:
// cutEnabled ? () => cutSelection(SelectionChangedCause.toolbar) : null,
// onPaste:
// pasteEnabled ? () => pasteText(SelectionChangedCause.toolbar) : null,
// onSelectAll: selectAllEnabled
// ? () => selectAll(SelectionChangedCause.toolbar)
// : null,
// onLookUp: null,
// onSearchWeb: null,
// onShare: null,
// );
// }
/// Copy current selection to [Clipboard].
@override
void copySelection(SelectionChangedCause cause) {
controller.copiedImageUrl = null;
_pastePlainText = controller.getPlainText();
_pasteStyleAndEmbed = controller.getAllIndividualSelectionStylesAndEmbed();
final selection = textEditingValue.selection;
final text = textEditingValue.text;
if (selection.isCollapsed) {
return;
}
Clipboard.setData(ClipboardData(text: selection.textInside(text)));
if (cause == SelectionChangedCause.toolbar) {
bringIntoView(textEditingValue.selection.extent);
// Collapse the selection and hide the toolbar and handles.
userUpdateTextEditingValue(
TextEditingValue(
text: textEditingValue.text,
selection:
TextSelection.collapsed(offset: textEditingValue.selection.end),
),
SelectionChangedCause.toolbar,
);
}
}
/// Cut current selection to [Clipboard].
@override
void cutSelection(SelectionChangedCause cause) {
controller.copiedImageUrl = null;
_pastePlainText = controller.getPlainText();
_pasteStyleAndEmbed = controller.getAllIndividualSelectionStylesAndEmbed();
if (widget.configurations.readOnly) {
return;
}
final selection = textEditingValue.selection;
final text = textEditingValue.text;
if (selection.isCollapsed) {
return;
}
Clipboard.setData(ClipboardData(text: selection.textInside(text)));
_replaceText(ReplaceTextIntent(textEditingValue, '', selection, cause));
if (cause == SelectionChangedCause.toolbar) {
bringIntoView(textEditingValue.selection.extent);
hideToolbar();
}
}
/// Paste text from [Clipboard].
@override
Future<void> pasteText(SelectionChangedCause cause) async {
if (widget.configurations.readOnly) {
return;
}
if (controller.copiedImageUrl != null) {
final index = textEditingValue.selection.baseOffset;
final length = textEditingValue.selection.extentOffset - index;
final copied = controller.copiedImageUrl!;
controller.replaceText(
index,
length,
BlockEmbed.image(copied.url),
null,
);
if (copied.styleString.isNotEmpty) {
controller.formatText(
getEmbedNode(controller, index + 1).offset,
1,
StyleAttribute(copied.styleString),
);
}
controller.copiedImageUrl = null;
await Clipboard.setData(
const ClipboardData(text: ''),
);
return;
}
final selection = textEditingValue.selection;
if (!selection.isValid) {
return;
}
// Snapshot the input before using `await`.
// See https://github.com/flutter/flutter/issues/11427
final text = await Clipboard.getData(Clipboard.kTextPlain);
if (text != null) {
_replaceText(
ReplaceTextIntent(
textEditingValue,
text.text!,
selection,
cause,
),
);
bringIntoView(textEditingValue.selection.extent);
// Collapse the selection and hide the toolbar and handles.
userUpdateTextEditingValue(
TextEditingValue(
text: textEditingValue.text,
selection: TextSelection.collapsed(
offset: textEditingValue.selection.end,
),
),
cause,
);
return;
}
final onImagePaste = widget.configurations.onImagePaste;
if (onImagePaste != null) {
final reader = await ClipboardReader.readClipboard();
if (!reader.canProvide(Formats.png)) {
return;
}
reader.getFile(Formats.png, (value) async {
final image = value;
final imageUrl = await onImagePaste(await image.readAll());
if (imageUrl == null) {
return;
}
controller.replaceText(
textEditingValue.selection.end,
0,
BlockEmbed.image(imageUrl),
null,
);
});
}
}
/// Select the entire text value.
@override
void selectAll(SelectionChangedCause cause) {
userUpdateTextEditingValue(
textEditingValue.copyWith(
selection: TextSelection(
baseOffset: 0, extentOffset: textEditingValue.text.length),
),
cause,
);
if (cause == SelectionChangedCause.toolbar) {
bringIntoView(textEditingValue.selection.extent);
}
}
List<ContextMenuButtonItem> get contextMenuButtonItems {
return EditableText.getEditableButtonItems(
clipboardStatus: _clipboardStatus.value,
onLiveTextInput: null,
onCopy: copyEnabled
? () => copySelection(SelectionChangedCause.toolbar)
: null,
@ -133,12 +310,70 @@ class QuillRawEditorState extends EditorState
onSelectAll: selectAllEnabled
? () => selectAll(SelectionChangedCause.toolbar)
: null,
onLookUp: null,
onSearchWeb: null,
onShare: null,
onLookUp: lookUpEnabled
? () => lookUpSelection(SelectionChangedCause.toolbar)
: null,
onSearchWeb: searchWebEnabled
? () => searchWebForSelection(SelectionChangedCause.toolbar)
: null,
onShare: shareEnabled
? () => shareSelection(SelectionChangedCause.toolbar)
: null,
onLiveTextInput: liveTextInputEnabled ? () {} : null,
);
}
/// Look up the current selection,
/// as in the "Look Up" edit menu button on iOS.
///
/// Currently this is only implemented for iOS.
///
/// Throws an error if the selection is empty or collapsed.
Future<void> lookUpSelection(SelectionChangedCause cause) async {
final text = textEditingValue.selection.textInside(textEditingValue.text);
if (text.isEmpty) {
return;
}
await SystemChannels.platform.invokeMethod(
'LookUp.invoke',
text,
);
}
/// Launch a web search on the current selection,
/// as in the "Search Web" edit menu button on iOS.
///
/// Currently this is only implemented for iOS.
///
/// When 'obscureText' is true or the selection is empty,
/// this function will not do anything
Future<void> searchWebForSelection(SelectionChangedCause cause) async {
final text = textEditingValue.selection.textInside(textEditingValue.text);
if (text.isNotEmpty) {
await SystemChannels.platform.invokeMethod(
'SearchWeb.invoke',
text,
);
}
}
/// Launch the share interface for the current selection,
/// as in the "Share" edit menu button on iOS.
///
/// Currently this is only implemented for iOS.
///
/// When 'obscureText' is true or the selection is empty,
/// this function will not do anything
Future<void> shareSelection(SelectionChangedCause cause) async {
final text = textEditingValue.selection.textInside(textEditingValue.text);
if (text.isNotEmpty) {
await SystemChannels.platform.invokeMethod(
'Share.invoke',
text,
);
}
}
/// Returns the anchor points for the default context menu.
///
/// Copied from [EditableTextState].
@ -1262,163 +1497,6 @@ class QuillRawEditorState extends EditorState
);
}
/// Copy current selection to [Clipboard].
@override
void copySelection(SelectionChangedCause cause) {
controller.copiedImageUrl = null;
_pastePlainText = controller.getPlainText();
_pasteStyleAndEmbed = controller.getAllIndividualSelectionStylesAndEmbed();
final selection = textEditingValue.selection;
final text = textEditingValue.text;
if (selection.isCollapsed) {
return;
}
Clipboard.setData(ClipboardData(text: selection.textInside(text)));
if (cause == SelectionChangedCause.toolbar) {
bringIntoView(textEditingValue.selection.extent);
// Collapse the selection and hide the toolbar and handles.
userUpdateTextEditingValue(
TextEditingValue(
text: textEditingValue.text,
selection:
TextSelection.collapsed(offset: textEditingValue.selection.end),
),
SelectionChangedCause.toolbar,
);
}
}
/// Cut current selection to [Clipboard].
@override
void cutSelection(SelectionChangedCause cause) {
controller.copiedImageUrl = null;
_pastePlainText = controller.getPlainText();
_pasteStyleAndEmbed = controller.getAllIndividualSelectionStylesAndEmbed();
if (widget.configurations.readOnly) {
return;
}
final selection = textEditingValue.selection;
final text = textEditingValue.text;
if (selection.isCollapsed) {
return;
}
Clipboard.setData(ClipboardData(text: selection.textInside(text)));
_replaceText(ReplaceTextIntent(textEditingValue, '', selection, cause));
if (cause == SelectionChangedCause.toolbar) {
bringIntoView(textEditingValue.selection.extent);
hideToolbar();
}
}
/// Paste text from [Clipboard].
@override
Future<void> pasteText(SelectionChangedCause cause) async {
if (widget.configurations.readOnly) {
return;
}
if (controller.copiedImageUrl != null) {
final index = textEditingValue.selection.baseOffset;
final length = textEditingValue.selection.extentOffset - index;
final copied = controller.copiedImageUrl!;
controller.replaceText(
index,
length,
BlockEmbed.image(copied.url),
null,
);
if (copied.styleString.isNotEmpty) {
controller.formatText(
getEmbedNode(controller, index + 1).offset,
1,
StyleAttribute(copied.styleString),
);
}
controller.copiedImageUrl = null;
await Clipboard.setData(
const ClipboardData(text: ''),
);
return;
}
final selection = textEditingValue.selection;
if (!selection.isValid) {
return;
}
// Snapshot the input before using `await`.
// See https://github.com/flutter/flutter/issues/11427
final text = await Clipboard.getData(Clipboard.kTextPlain);
if (text != null) {
_replaceText(
ReplaceTextIntent(
textEditingValue,
text.text!,
selection,
cause,
),
);
bringIntoView(textEditingValue.selection.extent);
// Collapse the selection and hide the toolbar and handles.
userUpdateTextEditingValue(
TextEditingValue(
text: textEditingValue.text,
selection: TextSelection.collapsed(
offset: textEditingValue.selection.end,
),
),
cause,
);
return;
}
final onImagePaste = widget.configurations.onImagePaste;
if (onImagePaste != null) {
final reader = await ClipboardReader.readClipboard();
if (!reader.canProvide(Formats.png)) {
return;
}
reader.getFile(Formats.png, (value) async {
final image = value;
final imageUrl = await onImagePaste(await image.readAll());
if (imageUrl == null) {
return;
}
controller.replaceText(
textEditingValue.selection.end,
0,
BlockEmbed.image(imageUrl),
null,
);
});
}
}
/// Select the entire text value.
@override
void selectAll(SelectionChangedCause cause) {
userUpdateTextEditingValue(
textEditingValue.copyWith(
selection: TextSelection(
baseOffset: 0, extentOffset: textEditingValue.text.length),
),
cause,
);
if (cause == SelectionChangedCause.toolbar) {
bringIntoView(textEditingValue.selection.extent);
}
}
@override
bool get wantKeepAlive => widget.configurations.focusNode.hasFocus;

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import '../../../flutter_quill.dart';
import '../../../translations.dart';
import '../../models/config/toolbar/toolbar_configurations.dart';
import '../../models/documents/attribute.dart';
import '../utils/provider.dart';
@ -49,7 +51,7 @@ class QuillSimpleToolbar extends StatelessWidget
configurations.showLink || configurations.showSearchButton
];
List<Widget> childrenBuilder(context) {
List<Widget> childrenBuilder(BuildContext context) {
final toolbarConfigurations =
context.requireQuillSimpleToolbarConfigurations;

Loading…
Cancel
Save