Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native ios context menu #143002

Merged
merged 58 commits into from
May 13, 2024
Merged

Conversation

justinmc
Copy link
Contributor

@justinmc justinmc commented Feb 6, 2024

In order to work around the fact that iOS 15 shows a notification on pressing Flutter's paste button (#103163), this PR allows showing the iOS system context menu in text fields.

Screenshot 2024-02-06 at 11 52 25 AM

It is currently opt-in, which a user would typically do like this (also in example system_context_menu.0.dart):

      contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
        // If supported, show the system context menu.
        if (SystemContextMenu.isSupported(context)) {
          return SystemContextMenu.editableText(
            editableTextState: editableTextState,
          );
        }
        // Otherwise, show the flutter-rendered context menu for the current
        // platform.
        return AdaptiveTextSelectionToolbar.editableText(
          editableTextState: editableTextState,
        );
      },

Requires engine PR flutter/engine#50095.

API changes

SystemContextMenu

A widget that shows the system context menu when built, and removes it when disposed. The main high-level way that most users would use this PR. Only works on later versions of iOS.

SystemContextMenuController

Used under the hood to hide and show a system context menu. There can only be one visible at a time.

MediaQuery.supportsShowingSystemContextMenu

Sent by the iOS embedder to tell the framework whether or not the platform supports showing the system context menu. That way the framework, or Flutter developers, can decide to show a different menu.

flutter/platform ContextMenu.showSystemContextMenu

Sent by the framework to show the menu at a given targetRect, which is the current selection rect.

flutter/platform ContextMenu.hideSystemContextMenu

Sent by the framework to hide the menu. Typically not needed, because the platform will hide the menu when the user taps outside of it and after the user presses a button, but it handles edge cases where the user programmatically rebuilds the context menu, for example.

flutter/platform System.onDismissSystemContextMenu

Sent by the iOS embedder to indicate that the system context menu has been hidden by the system, such as when the user taps outside of the menu. This is useful when there are multiple instances of SystemContextMenu, such as with multiple text fields.

@justinmc justinmc self-assigned this Feb 6, 2024
@github-actions github-actions bot added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Feb 6, 2024
// TODO(justinmc): Flag it off by default.
// TODO(justinmc): Don't forget CupertinoTextField. Anything else like TextFormField?
if (defaultTargetPlatform == TargetPlatform.iOS &&
(MediaQuery.maybeSupportsShowingSystemContextMenu(context) ?? false)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also check if it's for text field vs non-text field widget?

@auto-submit auto-submit bot removed the revert Autorevert PR (with "Reason for revert:" comment) label May 13, 2024
@cbracken
Copy link
Member

cbracken commented May 13, 2024

Reason for revert: unresolved docs links. See failure here: https://ci.chromium.org/ui/p/flutter/builders/prod/Linux%20docs_test/16540/overview

dartdoc:stdout: Generating docs for package flutter...
dartdoc:stderr:   error: unresolved doc reference [TextInput.showSystemContextMenu]
dartdoc:stderr:     from widgets.MediaQueryData.supportsShowingSystemContextMenu: (file:///b/s/w/ir/x/w/flutter/packages/flutter/lib/src/widgets/media_query.dart:579:14)
dartdoc:stderr:     in documentation inherited from widgets.MediaQueryData.supportsShowingSystemContextMenu: (file:///b/s/w/ir/x/w/flutter/packages/flutter/lib/src/widgets/media_query.dart:579:14)
dartdoc:stderr:   error: unresolved doc reference [showSystemContextMenu]
dartdoc:stderr:     from services.SystemContextMenuController.hide: (file:///b/s/w/ir/x/w/flutter/packages/flutter/lib/src/services/text_input.dart:2554:16)
dartdoc:stderr:   error: unresolved doc reference [hideSystemContextMenu]
dartdoc:stderr:     from services.SystemContextMenuController.show: (file:///b/s/w/ir/x/w/flutter/packages/flutter/lib/src/services/text_input.dart:2509:16)

@cbracken cbracken added the revert Autorevert PR (with "Reason for revert:" comment) label May 13, 2024
auto-submit bot pushed a commit that referenced this pull request May 13, 2024
@auto-submit auto-submit bot removed the revert Autorevert PR (with "Reason for revert:" comment) label May 13, 2024
auto-submit bot added a commit that referenced this pull request May 13, 2024
Reverts: #143002
Initiated by: cbracken
Reason for reverting: unresolved docs links. See failure here: https://ci.chromium.org/ui/p/flutter/builders/prod/Linux%20docs_test/16540/overview

```
dartdoc:stdout: Generating docs for package flutter...
dartdoc:stderr:   error: unresolved doc reference [TextInput.showSystemContextMenu]
dartdoc:stderr:     from widgets.MediaQueryData.supportsShowingSystemContextMenu: (file:///b/s/w/ir/x/w/flutter/packages/flutt
Original PR Author: justinmc

Reviewed By: {Renzo-Olivares, hellohuanlin}

This change reverts the following previous change:
In order to work around the fact that iOS 15 shows a notification on pressing Flutter's paste button (#103163), this PR allows showing the iOS system context menu in text fields.

<img width="385" alt="Screenshot 2024-02-06 at 11 52 25 AM" src="https://github.com/flutter/flutter/assets/389558/d82e18ee-b8a3-4082-9225-cf47fa7f3674">

It is currently opt-in, which a user would typically do like this (also in example system_context_menu.0.dart):

```dart
      contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
        // If supported, show the system context menu.
        if (SystemContextMenu.isSupported(context)) {
          return SystemContextMenu.editableText(
            editableTextState: editableTextState,
          );
        }
        // Otherwise, show the flutter-rendered context menu for the current
        // platform.
        return AdaptiveTextSelectionToolbar.editableText(
          editableTextState: editableTextState,
        );
      },
```

Requires engine PR flutter/engine#50095.

## API changes

### SystemContextMenu
A widget that shows the system context menu when built, and removes it when disposed. The main high-level way that most users would use this PR. Only works on later versions of iOS.

### SystemContextMenuController
Used under the hood to hide and show a system context menu. There can only be one visible at a time.

### MediaQuery.supportsShowingSystemContextMenu
Sent by the iOS embedder to tell the framework whether or not the platform supports showing the system context menu. That way the framework, or Flutter developers, can decide to show a different menu.

### `flutter/platform ContextMenu.showSystemContextMenu`
Sent by the framework to show the menu at a given `targetRect`, which is the current selection rect.

### `flutter/platform ContextMenu.hideSystemContextMenu`
Sent by the framework to hide the menu. Typically not needed, because the platform will hide the menu when the user taps outside of it and after the user presses a button, but it handles edge cases where the user programmatically rebuilds the context menu, for example.

### `flutter/platform System.onDismissSystemContextMenu`
Sent by the iOS embedder to indicate that the system context menu has been hidden by the system, such as when the user taps outside of the menu.  This is useful when there are multiple instances of SystemContextMenu, such as with multiple text fields.
justinmc added a commit that referenced this pull request May 13, 2024
@justinmc
Copy link
Contributor Author

Whoops, how did that not get caught in presubmit? Well I'll create a new PR and fix those.

justinmc added a commit that referenced this pull request May 13, 2024
It's now possible to use the native-rendered text selection context menu on iOS. This sacrifices customizability in exchange for avoiding showing a notification when the user presses "Paste". It's off by default, but to enable, see the example system_context_menu.0.dart.
auto-submit bot pushed a commit that referenced this pull request May 13, 2024
auto-submit bot added a commit that referenced this pull request May 13, 2024
Reverts: #148238
Initiated by: zanderso
Reason for reverting: Failures in post submit https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8748025189669617649/+/u/run_test.dart_for_web_canvaskit_tests_shard_and_subshard_3/stdout
Original PR Author: justinmc

Reviewed By: {hellohuanlin}

This change reverts the following previous change:
Reland of #143002, which was reverted in #148237 due to unresolved docs references. Not sure why those weren't caught in presubmit.

```
dartdoc:stdout: Generating docs for package flutter...
dartdoc:stderr:   error: unresolved doc reference [TextInput.showSystemContextMenu]
dartdoc:stderr:     from widgets.MediaQueryData.supportsShowingSystemContextMenu: (file:///b/s/w/ir/x/w/flutter/packages/flutter/lib/src/widgets/media_query.dart:579:14)
dartdoc:stderr:     in documentation inherited from widgets.MediaQueryData.supportsShowingSystemContextMenu: (file:///b/s/w/ir/x/w/flutter/packages/flutter/lib/src/widgets/media_query.dart:579:14)
dartdoc:stderr:   error: unresolved doc reference [showSystemContextMenu]
dartdoc:stderr:     from services.SystemContextMenuController.hide: (file:///b/s/w/ir/x/w/flutter/packages/flutter/lib/src/services/text_input.dart:2554:16)
dartdoc:stderr:   error: unresolved doc reference [hideSystemContextMenu]
dartdoc:stderr:     from services.SystemContextMenuController.show: (file:///b/s/w/ir/x/w/flutter/packages/flutter/lib/src/services/text_input.dart:2509:16)
```
justinmc added a commit that referenced this pull request May 13, 2024
auto-submit bot pushed a commit that referenced this pull request May 15, 2024
Reverting #148238 which was relanded in #148237 which was originally from #143002.

This time post-submit I saw a failure of a wasm version of a test added by this PR.
hello-coder-xu added a commit to hello-coder-xu/flutter that referenced this pull request May 16, 2024
* master: (2580 commits)
  plugin_ffi template comment fix (flutter#148378)
  Roll Flutter Engine from 942d7c35de75 to 9e17588b330c (2 revisions) (flutter#148455)
  Reland fix TextField helper top padding on M3 (flutter#146754)
  Removing duplicate assert on `VisualDensity` constructor (flutter#148281)
  Roll Flutter Engine from 65ac4bf96ed7 to 942d7c35de75 (1 revision) (flutter#148450)
  Roll Flutter Engine from c11d64be5102 to 65ac4bf96ed7 (3 revisions) (flutter#148448)
  Roll Flutter Engine from f6195e9d4b4b to c11d64be5102 (1 revision) (flutter#148445)
  Roll Flutter Engine from cd150986ae63 to f6195e9d4b4b (2 revisions) (flutter#148441)
  Fix leaky tests. (flutter#148434)
  Roll Flutter Engine from 41b86b59f0ab to cd150986ae63 (2 revisions) (flutter#148430)
  Roll Flutter Engine from bf1c6da0dd31 to 41b86b59f0ab (15 revisions) (flutter#148428)
  Update _handlePushRouteInformation to Future<bool>  to indicate whether any of the observer has handled the route or not (flutter#147901)
  Fix memory leaks in `_PopupMenuRoute` (flutter#148373)
  Add `clipBehavior` to `DrawerThemeData` (flutter#148061)
  Reland Native ios context menu (flutter#143002) (flutter#148238) (flutter#148265)
  Roll Packages from fd714bd7d516 to 87a02e393be0 (8 revisions) (flutter#148419)
  Stop running module_test_ios in devicelab and x64 Macs (flutter#148264)
  Roll Flutter Engine from d35a1a603c80 to bf1c6da0dd31 (1 revision) (flutter#148369)
  Roll Flutter Engine from 55c62ff82c7e to d35a1a603c80 (4 revisions) (flutter#148367)
  Roll Flutter Engine from a1d930a3a84d to 55c62ff82c7e (3 revisions) (flutter#148365)
  ...
TecHaxter pushed a commit to TecHaxter/flutter_packages that referenced this pull request May 22, 2024
…#6723)

flutter/flutter@1dfb46e...1255435

2024-05-13 jmccandless@google.com Native ios context menu (flutter/flutter#143002)
2024-05-13 82763757+NobodyForNothing@users.noreply.github.com test sliver fill remaining examples (flutter/flutter#148041)
2024-05-13 engine-flutter-autoroll@skia.org Roll Flutter Engine from fab7a7417888 to 0050bf9a8094 (1 revision) (flutter/flutter#148211)
2024-05-13 engine-flutter-autoroll@skia.org Roll Flutter Engine from e568e5db9391 to fab7a7417888 (1 revision) (flutter/flutter#148207)
2024-05-13 engine-flutter-autoroll@skia.org Roll Flutter Engine from 57e74a1b3860 to e568e5db9391 (2 revisions) (flutter/flutter#148204)
2024-05-13 engine-flutter-autoroll@skia.org Roll Flutter Engine from 34fd28e8c7bd to 57e74a1b3860 (1 revision) (flutter/flutter#148196)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
@matthew-carroll
Copy link
Contributor

@justinmc is there a reason that the new widget for displaying the toolbar only works with Flutter's built-in text field implementation? It has a private constructor and the factory constructor requires a EditableTextState.

@matthew-carroll
Copy link
Contributor

@justinmc I'm also curious, is a Rect truly the fundamental required geometry? Is that what iOS demands? Or does it really want an Offset and Rect was used in the API because that happens to fit better with Flutter's existing text field implementation?

I ask that because in SuperTextField, while we expose the text layout, most of our toolbar work is in terms of a predetermined focal point.

@matthew-carroll
Copy link
Contributor

@justinmc I'm continuing integration with this and I have another question. This widget, and surrounding classes, are all given platform-agnostic names. For example: SystemContextMenu instead of IOSSystemContextMenu.

Do you have meaningful evidence that a singular API will be able to work across all platforms? As far as I can tell this work has only attempted to integrate with iOS APIs. I'm wondering if there's a good chance that over time this API (especially once you add the ability to customize the buttons) will only work for iOS due to platform API differences. If so, at that point Flutter will be stuck with an iOS-specific class called SystemContextMenu, which will then be joined by an AndroidSystemContextMenu and Flutter will forever have to explain that SystemContextMenu is only for iOS because reasons. What are your thoughts on that?

@hellohuanlin
Copy link
Contributor

hellohuanlin commented May 29, 2024

@matthew-carroll

I'm also curious, is a Rect truly the fundamental required geometry? Is that what iOS demands?

Assuming you are talking about the targetRect which is the rect of the selected text. Yes it's what iOS demands. iOS make use of this rect to determine where to show the menu.

Or does it really want an Offset and Rect was used in the API because that happens to fit better with Flutter's existing text field implementation?

There's an alternative iOS API that takes the offset and the arrow direction, but it won't work because there's no way to tell the size of the menu (the size can vary based on accessibility settings). So we can't tell the offset (should it be
the top or bottom of text field?) or arrow direction (pointing up or down?).

Do you have meaningful evidence that a singular API will be able to work across all platforms? As far as I can tell this work has only attempted to integrate with iOS APIs. I'm wondering if there's a good chance that over time this API (especially once you add the ability to customize the buttons) will only work for iOS due to platform API differences.

Currently we don't see a need to implement this on Android. However, we did try to make the API platform agnostic, if such need arise in the future.

The current milestone requires developers to adopt this API manually, since it lacks of feature parity (e.g. look up, search web, custom actions). In the next milestone we want to enable it by default, so that developers get system menu on iOS 16+, and Flutter menu on other platforms.

@matthew-carroll
Copy link
Contributor

Currently we don't see a need to implement this on Android. However, we did try to make the API platform agnostic, if such need arise in the future.

Ok, as I mentioned in my previous comment, I would expect that this is more likely to end up in a bad place than a good place. If you and @justinmc haven't inspected these APIs on Android/Windows/Linux, then there's no indication that the API you've created here would work at all with any other platform. I think if Flutter is going to bring in little pieces of specific platforms, it's much safer to name them after that platform, which is what it really represents anyway. Then, after all platforms are accumulated, you can create a lowest common denominator on top, if desired.

@hellohuanlin
Copy link
Contributor

@matthew-carroll Note that once milestone 2 is done, developers should get secure paste out of box. I am not expecting them to use these types directly. I believe they are only exposed so that we can share code between Cupertino and Material.

I am not an expert on flutter/dart tho, so I will ask for @justinmc's opinion.

@matthew-carroll
Copy link
Contributor

I am not expecting them to use these types directly.

Please do NOT make that assumption. This is a long-lived blind spot for Flutter framework developers. @justinmc I'd really appreciate it if you would socialize this problem. Assume that EVERYTHING will be used externally. The reason I'm commenting on this is precisely because I'm adding the use of these APIs to SuperTextField and SuperEditor, neither of which uses any of Flutter's built-in RenderEditable system. Cupertino and Material do not define the universe. They're just two arbitrary UI themes.

@justinmc
Copy link
Contributor Author

EditableTextState requirement

The reason why the constructor requires EditableTextState is because currently the system context menu won't work without an active text input connection, so I'm trying to lock it down for the time being to avoid people using it in situations where the native menu won't work. I'd like to generalize it in the future so that it can work with arbitrary buttons that may not have to do with any text input connection.

Maybe instead of taking an EditableTextState it should take an interface that EditableTextState implements? What do you think?

SystemContextMenu naming

I think I'm probably in agreement about the SystemContextMenu name now that I'm facing a similar situation in trying to support Android's Scribe and iOS's Scribble... I think I'm going to try to expose the native APIs to the framework more or less verbatim, and then build any abstraction on top of that. I'll see how it goes over there and then see if anything can be done for SystemContextMenu going forward.

@matthew-carroll you've probably seen SystemContextMenuController, will that work for your case? There is an assert about needing a text input connection, but it's meant to be more generic.

@matthew-carroll
Copy link
Contributor

I think I'm going to try to expose the native APIs to the framework more or less verbatim, and then build any abstraction on top of that.

I'm glad you've finally joined me in that view! 😅

There is an assert about needing a text input connection, but it's meant to be more generic.

That's probably fine for us. Though, if iOS technically makes it possible to show and orient a toolbar without an active IME connection, then I don't know why Flutter would want to add that restriction.

@hellohuanlin
Copy link
Contributor

I'm glad you've finally joined me in that view! 😅

I am happy with this too, as long as both of you agree on a long term vision. I suspect Android may introduce a similar restriction and we should have a good plan for that. Ideally most developers shouldn't have to deal with things like which platform they are working on, and menu's arrow direction.

That's probably fine for us. Though, if iOS technically makes it possible to show and orient a toolbar without an active IME connection, then I don't know why Flutter would want to add that restriction.

It is non-trivial effort to support showing iOS edit menu for non-text field in the engine. We would need a UIView similar to FlutterTextInput to be served as a UIEditMenuInteractionDelegate.

@justinmc
Copy link
Contributor Author

justinmc commented Jun 5, 2024

That's probably fine for us. Though, if iOS technically makes it possible to show and orient a toolbar without an active IME connection, then I don't know why Flutter would want to add that restriction.

It is non-trivial effort to support showing iOS edit menu for non-text field in the engine. We would need a UIView similar to FlutterTextInput to be served as a UIEditMenuInteractionDelegate.

+1, if/when we add support for that we should remove that assert.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: tests "flutter test", flutter_test, or one of our tests a: text input Entering text in a text field or keyboard related problems d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants