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';
6library;
7
8import 'package:flutter/rendering.dart';
9import 'package:flutter/services.dart';
10
11import 'basic.dart';
12import 'editable_text.dart';
13import 'framework.dart';
14import 'media_query.dart';
15import '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.
46class 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
104class _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