Skip to content

Commit 9f3e8c2

Browse files
committed
feat(iOS): use system context menu in iOS(for secure paste)
1 parent cf7f8d6 commit 9f3e8c2

File tree

3 files changed

+89
-10
lines changed

3 files changed

+89
-10
lines changed

lib/src/editor/config/editor_config.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class QuillEditorConfig {
8686
this.readOnlyMouseCursor = SystemMouseCursors.text,
8787
this.onPerformAction,
8888
@experimental this.customLeadingBlockBuilder,
89+
this.useSystemContextMenuItems = false,
8990
});
9091

9192
@experimental
@@ -471,6 +472,11 @@ class QuillEditorConfig {
471472
/// Called when a text input action is performed.
472473
final void Function(TextInputAction action)? onPerformAction;
473474

475+
/// Show native context menu items on iOS.
476+
/// To use the default context menu items on iOS, set this to true.
477+
/// Use system context menu can unlock the secure paste feature for iOS
478+
final bool useSystemContextMenuItems;
479+
474480
// IMPORTANT For project authors: The copyWith()
475481
// should be manually updated each time we add or remove a property
476482

@@ -531,6 +537,7 @@ class QuillEditorConfig {
531537
void Function()? onScribbleActivated,
532538
EdgeInsets? scribbleAreaInsets,
533539
void Function(TextInputAction action)? onPerformAction,
540+
bool? useSystemContextMenuItems,
534541
}) {
535542
return QuillEditorConfig(
536543
customLeadingBlockBuilder:
@@ -600,6 +607,7 @@ class QuillEditorConfig {
600607
onScribbleActivated: onScribbleActivated ?? this.onScribbleActivated,
601608
scribbleAreaInsets: scribbleAreaInsets ?? this.scribbleAreaInsets,
602609
onPerformAction: onPerformAction ?? this.onPerformAction,
610+
useSystemContextMenuItems: useSystemContextMenuItems ?? this.useSystemContextMenuItems,
603611
);
604612
}
605613
}

lib/src/editor/editor.dart

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import '../document/nodes/leaf.dart';
1818
import 'config/editor_config.dart';
1919
import 'embed/embed_editor_builder.dart';
2020
import 'raw_editor/config/raw_editor_config.dart';
21+
import 'raw_editor/quill_system_context_menu.dart';
2122
import 'raw_editor/raw_editor.dart';
2223
import 'widgets/box.dart';
2324
import 'widgets/cursor.dart';
@@ -223,6 +224,29 @@ class QuillEditorState extends State<QuillEditor>
223224
});
224225
}
225226

227+
/// 构建上下文菜单构建器,简化嵌套逻辑
228+
QuillEditorContextMenuBuilder? _buildContextMenuBuilder(bool showSelectionToolbar) {
229+
if (!showSelectionToolbar) {
230+
return null;
231+
}
232+
233+
if (config.useSystemContextMenuItems) {
234+
return (context, state) {
235+
if (QuillSystemContextMenu.isSupported(context)) {
236+
return QuillSystemContextMenu.quillEditor(
237+
quillEditorState: state,
238+
);
239+
}
240+
return (config.contextMenuBuilder ??
241+
QuillRawEditorConfig.defaultContextMenuBuilder)(
242+
context, state);
243+
};
244+
}
245+
246+
return config.contextMenuBuilder ??
247+
QuillRawEditorConfig.defaultContextMenuBuilder;
248+
}
249+
226250
@override
227251
Widget build(BuildContext context) {
228252
final theme = Theme.of(context);
@@ -280,10 +304,7 @@ class QuillEditorState extends State<QuillEditor>
280304
disableClipboard: config.disableClipboard,
281305
placeholder: config.placeholder,
282306
onLaunchUrl: config.onLaunchUrl,
283-
contextMenuBuilder: showSelectionToolbar
284-
? (config.contextMenuBuilder ??
285-
QuillRawEditorConfig.defaultContextMenuBuilder)
286-
: null,
307+
contextMenuBuilder: _buildContextMenuBuilder(showSelectionToolbar),
287308
showSelectionHandles: isMobile,
288309
showCursor: config.showCursor ?? true,
289310
cursorStyle: CursorStyle(

lib/src/editor/raw_editor/system_context_menu.dart renamed to lib/src/editor/raw_editor/quill_system_context_menu.dart

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ library;
88
import 'package:flutter/material.dart';
99
import 'package:flutter/services.dart';
1010

11+
import 'raw_editor_state.dart';
12+
1113
/// Displays the system context menu on top of the Flutter view.
1214
///
1315
/// Currently, only supports iOS 16.0 and above and displays nothing on other
@@ -45,10 +47,10 @@ import 'package:flutter/services.dart';
4547
///
4648
/// * [SystemContextMenuController], which directly controls the hiding and
4749
/// showing of the system context menu.
48-
class SystemContextMenu extends StatefulWidget {
50+
class QuillSystemContextMenu extends StatefulWidget {
4951
/// Creates an instance of [SystemContextMenu] that points to the given
5052
/// [anchor].
51-
const SystemContextMenu._({
53+
const QuillSystemContextMenu._({
5254
super.key,
5355
required this.anchor,
5456
required this.items,
@@ -57,15 +59,15 @@ class SystemContextMenu extends StatefulWidget {
5759

5860
/// Creates an instance of [SystemContextMenu] for the field indicated by the
5961
/// given [EditableTextState].
60-
factory SystemContextMenu.editableText({
62+
factory QuillSystemContextMenu.editableText({
6163
Key? key,
6264
required EditableTextState editableTextState,
6365
List<IOSSystemContextMenuItem>? items,
6466
}) {
6567
final (startGlyphHeight: double startGlyphHeight, endGlyphHeight: double endGlyphHeight) =
6668
editableTextState.getGlyphHeights();
6769

68-
return SystemContextMenu._(
70+
return QuillSystemContextMenu._(
6971
key: key,
7072
anchor: TextSelectionToolbarAnchors.getSelectionRect(
7173
editableTextState.renderEditable,
@@ -80,6 +82,42 @@ class SystemContextMenu extends StatefulWidget {
8082
);
8183
}
8284

85+
86+
/// Creates an instance of [QuillSystemContextMenu] for the field indicated by the
87+
/// given [QuillRawEditorState].
88+
factory QuillSystemContextMenu.quillEditor({
89+
Key? key,
90+
required QuillRawEditorState quillEditorState,
91+
List<IOSSystemContextMenuItem>? items,
92+
}) {
93+
final selection = quillEditorState.textEditingValue.selection;
94+
final points = quillEditorState.renderEditor.getEndpointsForSelection(selection);
95+
96+
// Calculate glyph heights manually since _getGlyphHeights is private
97+
double startGlyphHeight, endGlyphHeight;
98+
if (selection.isValid && !selection.isCollapsed) {
99+
final startCharacterRect = quillEditorState.renderEditor.getLocalRectForCaret(selection.base);
100+
final endCharacterRect = quillEditorState.renderEditor.getLocalRectForCaret(selection.extent);
101+
startGlyphHeight = startCharacterRect.height;
102+
endGlyphHeight = endCharacterRect.height;
103+
} else {
104+
startGlyphHeight = quillEditorState.renderEditor.preferredLineHeight(selection.base);
105+
endGlyphHeight = startGlyphHeight;
106+
}
107+
108+
return QuillSystemContextMenu._(
109+
key: key,
110+
anchor: TextSelectionToolbarAnchors.getSelectionRect(
111+
quillEditorState.renderEditor,
112+
startGlyphHeight,
113+
endGlyphHeight,
114+
points,
115+
),
116+
items: items ?? getDefaultItemsForQuill(quillEditorState),
117+
onSystemHide: quillEditorState.hideToolbar,
118+
);
119+
}
120+
83121
/// The [Rect] that the context menu should point to.
84122
final Rect anchor;
85123

@@ -130,11 +168,23 @@ class SystemContextMenu extends StatefulWidget {
130168
];
131169
}
132170

171+
/// Returns the default context menu items for the given [QuillRawEditorState].
172+
static List<IOSSystemContextMenuItem> getDefaultItemsForQuill(QuillRawEditorState quillEditorState) {
173+
return <IOSSystemContextMenuItem>[
174+
if (quillEditorState.copyEnabled) const IOSSystemContextMenuItemCopy(),
175+
if (quillEditorState.cutEnabled) const IOSSystemContextMenuItemCut(),
176+
if (quillEditorState.pasteEnabled) const IOSSystemContextMenuItemPaste(),
177+
if (quillEditorState.selectAllEnabled) const IOSSystemContextMenuItemSelectAll(),
178+
if (quillEditorState.lookUpEnabled) const IOSSystemContextMenuItemLookUp(),
179+
if (quillEditorState.searchWebEnabled) const IOSSystemContextMenuItemSearchWeb(),
180+
];
181+
}
182+
133183
@override
134-
State<SystemContextMenu> createState() => _SystemContextMenuState();
184+
State<QuillSystemContextMenu> createState() => _SystemContextMenuState();
135185
}
136186

137-
class _SystemContextMenuState extends State<SystemContextMenu> {
187+
class _SystemContextMenuState extends State<QuillSystemContextMenu> {
138188
late final SystemContextMenuController _systemContextMenuController;
139189

140190
@override

0 commit comments

Comments
 (0)