1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'package:flutter/material.dart'; |
6 | library; |
7 | |
8 | import 'package:flutter/rendering.dart'; |
9 | import 'package:flutter/services.dart'; |
10 | |
11 | import 'basic.dart'; |
12 | import 'editable_text.dart'; |
13 | import 'framework.dart'; |
14 | import 'media_query.dart'; |
15 | import 'text_selection_toolbar_anchors.dart'; |
16 | |
17 | /// Displays the system context menu on top of the Flutter view. |
18 | /// |
19 | /// Currently, only supports iOS 16.0 and above and displays nothing on other |
20 | /// platforms. |
21 | /// |
22 | /// The context menu is the menu that appears, for example, when doing text |
23 | /// selection. Flutter typically draws this menu itself, but this class deals |
24 | /// with the platform-rendered context menu instead. |
25 | /// |
26 | /// There can only be one system context menu visible at a time. Building this |
27 | /// widget when the system context menu is already visible will hide the old one |
28 | /// and display this one. A system context menu that is hidden is informed via |
29 | /// [onSystemHide]. |
30 | /// |
31 | /// To check if the current device supports showing the system context menu, |
32 | /// call [isSupported]. |
33 | /// |
34 | /// {@tool dartpad} |
35 | /// This example shows how to create a [TextField] that uses the system context |
36 | /// menu where supported and does not show a system notification when the user |
37 | /// presses the "Paste" button. |
38 | /// |
39 | /// ** See code in examples/api/lib/widgets/system_context_menu/system_context_menu.0.dart ** |
40 | /// {@end-tool} |
41 | /// |
42 | /// See also: |
43 | /// |
44 | /// * [SystemContextMenuController], which directly controls the hiding and |
45 | /// showing of the system context menu. |
46 | class SystemContextMenu extends StatefulWidget { |
47 | /// Creates an instance of [SystemContextMenu] that points to the given |
48 | /// [anchor]. |
49 | const SystemContextMenu._({ |
50 | super.key, |
51 | required this.anchor, |
52 | this.onSystemHide, |
53 | }); |
54 | |
55 | /// Creates an instance of [SystemContextMenu] for the field indicated by the |
56 | /// given [EditableTextState]. |
57 | factory SystemContextMenu.editableText({ |
58 | Key? key, |
59 | required EditableTextState editableTextState, |
60 | }) { |
61 | final ( |
62 | startGlyphHeight: double startGlyphHeight, |
63 | endGlyphHeight: double endGlyphHeight, |
64 | ) = editableTextState.getGlyphHeights(); |
65 | return SystemContextMenu._( |
66 | key: key, |
67 | anchor: TextSelectionToolbarAnchors.getSelectionRect( |
68 | editableTextState.renderEditable, |
69 | startGlyphHeight, |
70 | endGlyphHeight, |
71 | editableTextState.renderEditable.getEndpointsForSelection( |
72 | editableTextState.textEditingValue.selection, |
73 | ), |
74 | ), |
75 | onSystemHide: () { |
76 | editableTextState.hideToolbar(); |
77 | }, |
78 | ); |
79 | } |
80 | |
81 | /// The [Rect] that the context menu should point to. |
82 | final Rect anchor; |
83 | |
84 | /// Called when the system hides this context menu. |
85 | /// |
86 | /// For example, tapping outside of the context menu typically causes the |
87 | /// system to hide the menu. |
88 | /// |
89 | /// This is not called when showing a new system context menu causes another |
90 | /// to be hidden. |
91 | final VoidCallback? onSystemHide; |
92 | |
93 | /// Whether the current device supports showing the system context menu. |
94 | /// |
95 | /// Currently, this is only supported on newer versions of iOS. |
96 | static bool isSupported(BuildContext context) { |
97 | return MediaQuery.maybeSupportsShowingSystemContextMenu(context) ?? false; |
98 | } |
99 | |
100 | @override |
101 | State<SystemContextMenu> createState() => _SystemContextMenuState(); |
102 | } |
103 | |
104 | class _SystemContextMenuState extends State<SystemContextMenu> { |
105 | late final SystemContextMenuController _systemContextMenuController; |
106 | |
107 | @override |
108 | void initState() { |
109 | super.initState(); |
110 | _systemContextMenuController = SystemContextMenuController( |
111 | onSystemHide: widget.onSystemHide, |
112 | ); |
113 | _systemContextMenuController.show(widget.anchor); |
114 | } |
115 | |
116 | @override |
117 | void didUpdateWidget(SystemContextMenu oldWidget) { |
118 | super.didUpdateWidget(oldWidget); |
119 | if (widget.anchor != oldWidget.anchor) { |
120 | _systemContextMenuController.show(widget.anchor); |
121 | } |
122 | } |
123 | |
124 | @override |
125 | void dispose() { |
126 | _systemContextMenuController.dispose(); |
127 | super.dispose(); |
128 | } |
129 | |
130 | @override |
131 | Widget build(BuildContext context) { |
132 | assert(SystemContextMenu.isSupported(context)); |
133 | return const SizedBox.shrink(); |
134 | } |
135 | } |
136 | |