@@ -17,6 +17,7 @@ import 'package:flutter/foundation.dart';
1717import 'package:vector_math/vector_math_64.dart' show Matrix4;
1818
1919import 'autofill.dart' ;
20+ import 'binding.dart' ;
2021import 'clipboard.dart' show Clipboard;
2122import 'keyboard_inserted_content.dart' ;
2223import 'message_codec.dart' ;
@@ -1808,7 +1809,7 @@ class TextInput {
18081809
18091810 Future <dynamic > _handleTextInputInvocation (MethodCall methodCall) async {
18101811 final String method = methodCall.method;
1811- switch (methodCall. method) {
1812+ switch (method) {
18121813 case 'TextInputClient.focusElement' :
18131814 final List <dynamic > args = methodCall.arguments as List <dynamic >;
18141815 _scribbleClients[args[0 ]]? .onScribbleFocus (Offset ((args[1 ] as num ).toDouble (), (args[2 ] as num ).toDouble ()));
@@ -2403,3 +2404,178 @@ class _PlatformTextInputControl with TextInputControl {
24032404 );
24042405 }
24052406}
2407+
2408+ /// Allows access to the system context menu.
2409+ ///
2410+ /// The context menu is the menu that appears, for example, when doing text
2411+ /// selection. Flutter typically draws this menu itself, but this class deals
2412+ /// with the platform-rendered context menu.
2413+ ///
2414+ /// Only one instance can be visible at a time. Calling [show] while the system
2415+ /// context menu is already visible will hide it and show it again at the new
2416+ /// [Rect] . An instance that is hidden is informed via [onSystemHide] .
2417+ ///
2418+ /// Currently this system context menu is bound to text input. The buttons that
2419+ /// are shown and the actions they perform are dependent on the currently
2420+ /// active [TextInputConnection] . Using this without an active
2421+ /// [TextInputConnection] is a noop.
2422+ ///
2423+ /// Call [dispose] when no longer needed.
2424+ ///
2425+ /// See also:
2426+ ///
2427+ /// * [ContextMenuController] , which controls Flutter-drawn context menus.
2428+ /// * [SystemContextMenu] , which wraps this functionality in a widget.
2429+ /// * [MediaQuery.maybeSupportsShowingSystemContextMenu] , which indicates
2430+ /// whether the system context menu is supported.
2431+ class SystemContextMenuController with SystemContextMenuClient {
2432+ /// Creates an instance of [SystemContextMenuController] .
2433+ ///
2434+ /// Not shown until [show] is called.
2435+ SystemContextMenuController ({
2436+ this .onSystemHide,
2437+ }) {
2438+ ServicesBinding .registerSystemContextMenuClient (this );
2439+ }
2440+
2441+ /// Called when the system has hidden the context menu.
2442+ ///
2443+ /// For example, tapping outside of the context menu typically causes the
2444+ /// system to hide it directly. Flutter is made aware that the context menu is
2445+ /// no longer visible through this callback.
2446+ ///
2447+ /// This is not called when [show] ing a new system context menu causes another
2448+ /// to be hidden.
2449+ final VoidCallback ? onSystemHide;
2450+
2451+ static const MethodChannel _channel = SystemChannels .platform;
2452+
2453+ static SystemContextMenuController ? _lastShown;
2454+
2455+ /// The target [Rect] that was last given to [show] .
2456+ ///
2457+ /// Null if [show] has not been called.
2458+ Rect ? _lastTargetRect;
2459+
2460+ /// True when the instance most recently [show] n has been hidden by the
2461+ /// system.
2462+ bool _hiddenBySystem = false ;
2463+
2464+ bool get _isVisible => this == _lastShown && ! _hiddenBySystem;
2465+
2466+ /// After calling [dispose] , this instance can no longer be used.
2467+ bool _isDisposed = false ;
2468+
2469+ // Begin SystemContextMenuClient.
2470+
2471+ @override
2472+ void handleSystemHide () {
2473+ assert (! _isDisposed);
2474+ // If this instance wasn't being shown, then it wasn't the instance that was
2475+ // hidden.
2476+ if (! _isVisible) {
2477+ return ;
2478+ }
2479+ if (_lastShown == this ) {
2480+ _lastShown = null ;
2481+ }
2482+ _hiddenBySystem = true ;
2483+ onSystemHide? .call ();
2484+ }
2485+
2486+ // End SystemContextMenuClient.
2487+
2488+ /// Shows the system context menu anchored on the given [Rect] .
2489+ ///
2490+ /// The [Rect] represents what the context menu is pointing to. For example,
2491+ /// for some text selection, this would be the selection [Rect] .
2492+ ///
2493+ /// There can only be one system context menu visible at a time. Calling this
2494+ /// while another system context menu is already visible will remove the old
2495+ /// menu before showing the new menu.
2496+ ///
2497+ /// Currently this system context menu is bound to text input. The buttons
2498+ /// that are shown and the actions they perform are dependent on the
2499+ /// currently active [TextInputConnection] . Using this without an active
2500+ /// [TextInputConnection] will be a noop.
2501+ ///
2502+ /// This is only supported on iOS 16.0 and later.
2503+ ///
2504+ /// See also:
2505+ ///
2506+ /// * [hideSystemContextMenu] , which hides the menu shown by this method.
2507+ /// * [MediaQuery.supportsShowingSystemContextMenu] , which indicates whether
2508+ /// this method is supported on the current platform.
2509+ Future <void > show (Rect targetRect) {
2510+ assert (! _isDisposed);
2511+ assert (
2512+ TextInput ._instance._currentConnection != null ,
2513+ 'Currently, the system context menu can only be shown for an active text input connection' ,
2514+ );
2515+
2516+ // Don't show the same thing that's already being shown.
2517+ if (_lastShown != null && _lastShown! ._isVisible && _lastShown! ._lastTargetRect == targetRect) {
2518+ return Future <void >.value ();
2519+ }
2520+
2521+ assert (
2522+ _lastShown == null || _lastShown == this || ! _lastShown! ._isVisible,
2523+ 'Attempted to show while another instance was still visible.' ,
2524+ );
2525+
2526+ _lastTargetRect = targetRect;
2527+ _lastShown = this ;
2528+ _hiddenBySystem = false ;
2529+ return _channel.invokeMethod <Map <String , dynamic >>(
2530+ 'ContextMenu.showSystemContextMenu' ,
2531+ < String , dynamic > {
2532+ 'targetRect' : < String , double > {
2533+ 'x' : targetRect.left,
2534+ 'y' : targetRect.top,
2535+ 'width' : targetRect.width,
2536+ 'height' : targetRect.height,
2537+ },
2538+ },
2539+ );
2540+ }
2541+
2542+ /// Hides this system context menu.
2543+ ///
2544+ /// If this hasn't been shown, or if another instance has hidden this menu,
2545+ /// does nothing.
2546+ ///
2547+ /// Currently this is only supported on iOS 16.0 and later.
2548+ ///
2549+ /// See also:
2550+ ///
2551+ /// * [showSystemContextMenu] , which shows the menu hidden by this method.
2552+ /// * [MediaQuery.supportsShowingSystemContextMenu] , which indicates whether
2553+ /// the system context menu is supported on the current platform.
2554+ Future <void > hide () async {
2555+ assert (! _isDisposed);
2556+ // This check prevents a given instance from accidentally hiding some other
2557+ // instance, since only one can be visible at a time.
2558+ if (this != _lastShown) {
2559+ return ;
2560+ }
2561+ _lastShown = null ;
2562+ // This may be called unnecessarily in the case where the user has already
2563+ // hidden the menu (for example by tapping the screen).
2564+ return _channel.invokeMethod <void >(
2565+ 'ContextMenu.hideSystemContextMenu' ,
2566+ );
2567+ }
2568+
2569+ @override
2570+ String toString () {
2571+ return 'SystemContextMenuController(onSystemHide=$onSystemHide , _hiddenBySystem=$_hiddenBySystem , _isVisible=$_isVisible , _isDiposed=$_isDisposed )' ;
2572+ }
2573+
2574+ /// Used to release resources when this instance will never be used again.
2575+ void dispose () {
2576+ assert (! _isDisposed);
2577+ hide ();
2578+ ServicesBinding .unregisterSystemContextMenuClient (this );
2579+ _isDisposed = true ;
2580+ }
2581+ }
0 commit comments