Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@hellohuanlin
Copy link
Contributor

@hellohuanlin hellohuanlin commented Nov 4, 2024

This PR shows the context menu based on what's received from the framework via method channel. There are 2 cases:

1. For basic editing actions (e.g. copy/paste):

We perform a DFS search inside suggestedActions, so that we don't have to specify things like keyboard shortcut.

2. For additional actions (e.g. look up, share, search web):

We manually create UICommand because (1) some actions are not provided by suggestedActions (2) these are simply UICommand (rather than UIKeyCommand), so we just need title and action (3) the actions are private APIs. However this would require framework to send the item title.

List which issues are fixed by this PR. You must list at least one issue.

Second milestone for flutter/flutter#103163

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@hellohuanlin
Copy link
Contributor Author

hellohuanlin commented Nov 4, 2024

@justinmc This PR is not complete, but it should be good enough to start the framework side on top of it.

Example usage:

SystemChannels.platform.invokeMethod<void>(
      'ContextMenu.showSystemContextMenu',
      <String, dynamic>{
        'targetRect': <String, dynamic>{
          'x': 100,
          'y': 100,
          'width': 100,
          'height': 100,
        },
        'items': <dynamic>[
          <String, dynamic>{
            'type': 'default',
            'action': 'paste',
          },
          <String, dynamic>{
            'type': 'default',
            'action': 'cut',
          },
          <String, dynamic>{
            'type': 'default',
            'action': 'copy',
          },
          <String, dynamic>{
            'type': 'default',
            'action': 'selectAll',
          },
          <String, dynamic>{
            'type': 'default',
            'title': 'Look Up',
            'action': 'lookUp',
          },
          <String, dynamic>{
            'type': 'default',
            'title': "Search Web",
            'action': 'searchWeb',
          },
          <String, dynamic>{
            'type': 'default',
            'title': "Share",
            'action': 'share',
          },
        ],
      }
    );

Also notice that I added title field for the additional actions. You can decide either add title to all actions for consistency, or just the additional actions.

@hellohuanlin
Copy link
Contributor Author

@justinmc just added implementation for additional items (look up, search web, etc), and can confirm they worked.

@hellohuanlin hellohuanlin requested a review from justinmc November 8, 2024 19:14
@justinmc
Copy link
Contributor

justinmc commented Nov 9, 2024

Cool I'm excited about this! I'll be OOO on Monday but I'll start playing around with this on Tuesday.

@hellohuanlin hellohuanlin changed the title [ios][secure_paste]Show context items based on framework's data [ios][secure_paste]Show context items based on items sent from framework Nov 10, 2024
@justinmc
Copy link
Contributor

Still planning to compile this and try talking to it from the framework by the end of the week.

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

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

Ok I was able to get this to compile and play around with it from the framework, and it works great. I still need to write some real framework code to interact with this and double check that it's what we want, but everything seems good.

One thought: Should there be some error checking to make sure that the items that are being passed are well-formatted? For example, if I pass this, it silently just doesn't show the button:

<String, dynamic>{
  'type': 'default',
  'action': 'share',
},

Comment on lines +887 to +889
// DFS algorithm to search a UICommand from the menu tree.
- (UICommand*)searchCommandWithSelector:(SEL)selector
element:(UIMenuElement*)element API_AVAILABLE(ios(16.0)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this searching through the default menu (suggestedActions) trying to find the item given in the call to showSystemContextMenu? Won't all of the ones that we currently support never be nested? Or maybe I just don't understand how suggestedActions is structured.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually all of them are nested at least 1 level deep in my observation - Apple uses UIMenuOptionsDisplayInline to make the items appear on the top level. And since this is not an API contract, we have to do a tree search here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it, thanks for the explanation!

@hellohuanlin
Copy link
Contributor Author

@justinmc Added the check for missing title, for example:

Missing title for context menu item action "share".

@justinmc
Copy link
Contributor

Ok I tried sending a mal-formatted item and indeed I got the error message 👍 . So with this approach, it will log an error but the call to invokeMethod will complete successfully and the menu will be shown minus the malformatted item. Is that the right approach or should invokeMethod fail? With the way it is now, would a Flutter app developer be able to be notified that this error had occurred on a user device?

@hellohuanlin
Copy link
Contributor Author

hellohuanlin commented Nov 18, 2024

Is that the right approach or should invokeMethod fail? With the way it is now, would a Flutter app developer be able to be notified that this error had occurred on a user device?

My understanding is that flutter app developers won't be able to send invalid format from framework to engine, because the menu created using our dart API will be correctly translated into the right format in the framework.

The error message is there just so that the framework contributors (i.e. you and future others) know what's going on during development phase, but the error shouldn't happen to flutter app developers.

@justinmc
Copy link
Contributor

A Flutter app developer can always send whatever crazy json they want over the platform channels if they really want to:

    SystemChannels.platform.invokeMethod<Map<String, dynamic>>(
      'ContextMenu.showSystemContextMenu',
      <String, dynamic>{
        'targetRect': myTargetRect,
        'items': myCrazyItems,
        'something else that doesnt make sense': 42,
    );

But you're right that normally a user would never mess with this low level API, and I'm writing a higher-level API that should make a mal-formatted request impossible at that level.

I guess this is kind of splitting hairs. Either way is fine. In the long term hopefully we will be using FFI with compile time checking of these calls.

@justinmc
Copy link
Contributor

What do you think about changing the name of the "default" type? It's a keyword in Dart so I have to work around it:

enum SystemContextMenuType {
  custom,
  defaultType; // This can't be `default`, so I'm using `defaultType`.

  String get json => switch (this) {
    SystemContextMenuType.defaultType => 'default',
    _ => name,
  };
}

But if we renamed it then it could be simpler:

enum SystemContextMenuType {
  custom,
  builtIn; // Or some better name... `platform`? `platformDefault`?
}

@hellohuanlin
Copy link
Contributor Author

hellohuanlin commented Nov 19, 2024

What do you think about changing the name of the "default" type? It's a keyword in Dart so I have to work around it:

I'm ok with either. How about let's do builtIn? Because with defaultType we would have to rename custom to customType as well to make them consistent.

@hellohuanlin
Copy link
Contributor Author

Updated from default to builtIn

@justinmc
Copy link
Contributor

Thank you! I like builtIn.

@justinmc
Copy link
Contributor

justinmc commented Dec 2, 2024

@hellohuanlin If I pass a title for cut, copy, paste, or select all, it silently ignores the given title and uses the default title ("Cut" etc.). Is that intended/required? I guess it will use the locale of the user's device to get the string in the correct language?

I guess this is just a result of the two different types of actions that you mention in the PR description, but I want to make sure it's clear to app developers.

@hellohuanlin
Copy link
Contributor Author

@hellohuanlin If I pass a title for cut, copy, paste, or select all, it silently ignores the given title and uses the default title ("Cut" etc.). Is that intended/required? I guess it will use the locale of the user's device to get the string in the correct language?

Yes it's intended that I use the title from the system itself, and it is localized.

I can also used the title passed from framework (except for the "paste" item which i cannot change), if you think it's necessary?

I guess this is just a result of the two different types of actions that you mention in the PR description, but I want to make sure it's clear to app developers.

To app developers, do they have to pass in the title? or do they simply pass the ContextMenuButtonType?

@justinmc
Copy link
Contributor

justinmc commented Dec 2, 2024

Thanks for the explanation. I guess let's keep it as-is. I'll enforce on the framework side that no title can be given for cut/copy/paste/selectall, and only the type is needed.

@justinmc
Copy link
Contributor

@hellohuanlin Could we combine the type and action fields, or is there a reason that they're separate? Like we could only have one field that includes all action values plus the value custom (there would be no builtIn value). We would still be able to distinguish everything that we can now (at least as far as the framework side is concerned).

I mention this because I noticed that in the framework I can just infer type from action without needing to store both. This is similar to the existing ContextMenuButtonItemType values.

@hellohuanlin
Copy link
Contributor Author

hellohuanlin commented Dec 10, 2024

@justinmc no specific reason. I can do that if you like?

(deleted internal link)

@hellohuanlin
Copy link
Contributor Author

@justinmc The new design is indeed simpler! i've just updated the PR.

@justinmc
Copy link
Contributor

Thank you! I've updated my framework PR to work with it and it looks good.

@justinmc
Copy link
Contributor

justinmc commented Dec 10, 2024

Are these rules correct for all of the types that we have now?

  • copy, cut, paste, selectAll
    • Must not provide title or onPressed.
  • share, searchWeb, lookUp
    • Must provide title, must not provide onPressed.
  • custom
    • Must provide title and onPressed.

Edit: Updated my understanding of share, searchWeb, lookUp.

@hellohuanlin
Copy link
Contributor Author

hellohuanlin commented Dec 10, 2024

Are these rules correct for all of the types that we have now?

  • copy, cut, paste, selectAll

    • Must not provide title or onPressed.
  • share, searchWeb, lookUp

    • Must provide title, must not provide onPressed.
  • custom

    • Must provide title and onPressed.

Edit: Updated my understanding of share, searchWeb, lookUp.

By "not providing title", do you mean framework not providing title to the engine? or do you mean app developers not providing title to the framework?

If you are talking about framework <-> engine communication, yes you are right.

@justinmc
Copy link
Contributor

Great, thanks. Yes, this was about framework/engine communication. In the SystemContextMenu widget I will populate titles with default localized strings where I can, so the requirements are slightly different.

@justinmc
Copy link
Contributor

justinmc commented Jan 2, 2025

@hellohuanlin Give the repo merge, I think these changes now need to happen in flutter/flutter right? Any idea on the best way to do that? Maybe you could apply the diff to my branch and then push a commit to my branch.

@hellohuanlin
Copy link
Contributor Author

@hellohuanlin Give the repo merge, I think these changes now need to happen in flutter/flutter right? Any idea on the best way to do that? Maybe you could apply the diff to my branch and then push a commit to my branch.

It seems there are a few options:

  1. As you said, push to your branch. Though in this setup I would be approving my own code during PR review, which is a bit strange :)
  2. I push the engine change to the flutter/flutter first, then you rebase on top of it.
  3. Keep working on the current setup, then pick either (1) or (2) when we are done.

I am leaning towards either (2) or (3 then 2). No strong opinion against (1) though. Let me know.

@justinmc
Copy link
Contributor

justinmc commented Jan 3, 2025

Oh right I guess I can still build this branch from my separate local engine repo checkout in order to do local development, so let's go with 3 for now.

For 2, do you mean that you would open a separate PR in flutter/flutter?

@hellohuanlin
Copy link
Contributor Author

Oh right I guess I can still build this branch from my separate local engine repo checkout in order to do local development, so let's go with 3 for now.

Sounds good.

For 2, do you mean that you would open a separate PR in flutter/flutter?

Yep.

@justinmc
Copy link
Contributor

justinmc commented Jan 3, 2025

Could you actually update this branch with main? It seems to be incompatible with flutter/flutter's master branch.

@hellohuanlin
Copy link
Contributor Author

Could you actually update this branch with main? It seems to be incompatible with flutter/flutter's master branch.

I'm not surprised that it's incompatible with the latest master. Do you have an old branch on flutter/flutter? Or if you want I can also put up a PR on flutter/flutter.

@justinmc
Copy link
Contributor

justinmc commented Jan 3, 2025

I think I need you to open a PR on flutter/flutter. I tried updating this branch locally and it still wasn't able to run after rebuilding the engine. My flutter/flutter branch is up to date as of yesterday. Thank you!

@hellohuanlin
Copy link
Contributor Author

I think I need you to open a PR on flutter/flutter. I tried updating this branch locally and it still wasn't able to run after rebuilding the engine. My flutter/flutter branch is up to date as of yesterday. Thank you!

Sounds good.

@justinmc
Copy link
Contributor

justinmc commented Jan 6, 2025

I think this PR can be closed in favor of flutter/flutter#161103.

I was able to get these changes working in the monorepo by merging that branch with my PR flutter/flutter#159013. I had some struggles building the engine in the monorepo but I updated the docs in flutter/flutter#161184 with what I found to work.

@jmagman
Copy link
Member

jmagman commented Jan 7, 2025

I think this PR can be closed in favor of flutter/flutter#161103.

Thank you @justinmc and @hellohuanlin! Closing...

@jmagman jmagman closed this Jan 7, 2025
github-merge-queue bot pushed a commit to flutter/flutter that referenced this pull request Feb 11, 2025
…161103)

This is moved from flutter/engine#56362

*List which issues are fixed by this PR. You must list at least one
issue. An issue is not required if the PR fixes something trivial like a
typo.*

*If you had to change anything in the [flutter/tests] repo, include a
link to the migration guide as per the [breaking change policy].*

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants