-
Notifications
You must be signed in to change notification settings - Fork 249
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
[SuperEditor][mobile] Implement spellchecking support (Resolves #2353) #2378
base: main
Are you sure you want to change the base?
Conversation
@angelosilvestre - Is this the PR you wanted me to review? If so, there should still be a description explaining what you did and how you did it - that's what directs the PR review. Also, whenever you want a review, please tag me in the PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a partial review. Before reviewing the rest, I think we need to align on why this spell check controller was injected in the places that it was. When we talked about this online, I mentioned treating this spell check toolbar the same way we treat the mobile editing toolbars, but as far as I can tell, that's not what this PR is currently doing.
super_editor/lib/src/default_editor/document_gestures_touch_android.dart
Outdated
Show resolved
Hide resolved
super_editor/lib/src/default_editor/document_gestures_touch_android.dart
Outdated
Show resolved
Hide resolved
|
||
/// Attaches this controller to a delegate that knows how to | ||
/// show a popover with spelling suggestions. | ||
void attach(SpellCheckerPopoverDelegate delegate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we use an attach/detach approach for the existing mobile editing toolbar, too?
@@ -0,0 +1,35 @@ | |||
import 'package:super_editor/src/core/document_selection.dart'; | |||
|
|||
/// Shows/hides a popover with spelling suggestions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a lot more information in this Dart Doc. Imagine reading this as someone who needs to use this - there's no guiding information about where/when/how.
} | ||
} | ||
|
||
abstract class SpellCheckerPopoverDelegate { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing Dart Doc. This should have a lot of info about who is supposed to implement this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some docs, can you take a look to see what else should be detailed?
} | ||
|
||
abstract class SpellCheckerPopoverDelegate { | ||
/// Shows spelling suggestions for the word at [wordRange]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the document changes? What if the user's selection changes? Please fully describe expected behaviors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some docs, can you take a look to see what else should be detailed?
This PR is meant to review the controller usage only. I should have added that to the description, sorry. |
@matthew-carroll I moved the controller to |
super_editor/lib/src/default_editor/document_gestures_touch_android.dart
Outdated
Show resolved
Hide resolved
super_editor/lib/src/default_editor/document_gestures_mouse.dart
Outdated
Show resolved
Hide resolved
|
||
if (widget.contentTapHandlers != null && docPosition != null) { | ||
for (final handler in widget.contentTapHandlers!) { | ||
final result = handler.onTap(docPosition); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that this was moved up above the long tap and scrolling handler. I'm not sure that's correct. Can you explain why they belong up here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated after the scrolling rework and the #2397. I guess it should be correct now.
@@ -34,6 +35,15 @@ class SpellingAndGrammarStyler extends SingleColumnLayoutStylePhase { | |||
markDirty(); | |||
} | |||
|
|||
/// Whether or not we should to override the default selection color with [selectionHighlightColor]. | |||
/// | |||
/// On mobile platforms, when the suggestions popover is opened, the selected text uses a different |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is a bit confusing. Does this line mean that this property will be true
on mobile?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a transitive state, while the suggestions popover is open this will be true
, otherwise it will be false
.
@@ -847,7 +861,13 @@ class SuperEditorState extends State<SuperEditor> { | |||
getDocumentLayout: () => editContext.documentLayout, | |||
selectionChanges: editContext.composer.selectionChanges, | |||
selectionNotifier: editContext.composer.selectionNotifier, | |||
contentTapHandler: _contentTapDelegate, | |||
contentTapHandlers: [ | |||
if (_contentTapDelegate != null) // |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The term "delegate" probably needs to be replaced with "handler" because typically there's only one delegate. There might be many handlers.
Given that SuperEditor can take a list of handlers, is there a reason that plugins only get one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The term "delegate" probably needs to be replaced with "handler" because typically there's only one delegate. There might be many handlers.
Updated on a previous PR.
super_editor/lib/src/infrastructure/platforms/android/android_document_controls.dart
Outdated
Show resolved
Hide resolved
@@ -319,6 +328,11 @@ class AndroidControlsDocumentLayerState | |||
return null; | |||
} | |||
|
|||
if (_controlsController!.areSelectionHandlesAllowed.value == false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this value nullable? It seems strange that areSelectionHandlesAllowed
would ever be null
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
super_editor_spellcheck/example/lib/main_super_editor_spellcheck.dart
Outdated
Show resolved
Hide resolved
@matthew-carroll This should wait on #2397, because we will have some conflicts. |
ec6830d
to
329b24e
Compare
@matthew-carroll I also added the Android and iOS platforms to the spellcheck demo app. |
super_editor/lib/src/default_editor/spelling_and_grammar/spelling_and_grammar_styler.dart
Outdated
Show resolved
Hide resolved
@@ -59,13 +69,25 @@ class SpellingAndGrammarStyler extends SingleColumnLayoutStylePhase { | |||
markDirty(); | |||
} | |||
|
|||
/// Temporarily use the [selectionHighlightColor] to override the default selection color. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please describe this in a way that informs the caller how to use this. When/why should this be called? What's the impact of calling it?
Please don't ever use the word "temporarily" unless it's followed by a description of how much time passes before it goes back to what it was before. This particular call doesn't appear to have any connection to time, and in fact it looks like it might not be temporary at all if the user never calls useDefaultSelectionColor()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
markDirty(); | ||
} | ||
|
||
/// Restore the default selection color. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does it mean to "restore" it? Please formulate Dart Docs so that they describe observable results, and/or describe code contract obligations. Otherwise, the caller has no context to understand when/where/why they'd want to "restore the default the selection color"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
@@ -1156,6 +1171,13 @@ abstract class SuperEditorPlugin { | |||
|
|||
/// Additional overlay [SuperEditorLayerBuilder]s that will be added to a given [SuperEditor]. | |||
List<SuperEditorLayerBuilder> get documentOverlayBuilders => []; | |||
|
|||
/// Optional handler that responds to taps on content, e.g., opening |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"handler that responds" -> "handlers that respond" - also this Dart Doc is missing any mention of whether this is a chain of responsibility, and if so, what that means in terms of priority. Any property that has a list of handlers needs to specify details about order of execution and/or whether one of them can prevent events flowing to the rest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
super_editor_spellcheck/lib/src/super_editor/spelling_error_suggestion_overlay.dart
Outdated
Show resolved
Hide resolved
super_editor_spellcheck/lib/src/super_editor/spelling_error_suggestion_overlay.dart
Outdated
Show resolved
Hide resolved
@@ -513,3 +669,273 @@ class _DesktopSpellingSuggestionToolbarState extends State<DesktopSpellingSugges | |||
} | |||
} | |||
} | |||
|
|||
/// A spelling suggestion toolbar, designed for the Android platform, | |||
/// which displays a vertical list alternative spellings for a given mis-spelled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know what "a vertical list alternative spellings" is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
super_editor_spellcheck/lib/src/super_editor/spelling_error_suggestion_overlay.dart
Outdated
Show resolved
Hide resolved
super_editor_spellcheck/lib/src/super_editor/spelling_error_suggestion_overlay.dart
Outdated
Show resolved
Hide resolved
8970f99
to
5b8e79e
Compare
On our call we discovered that behavior in iOS 17 is different than iOS 16. On iOS 17:
We looked at Android but found inconsistent behaviors between Keep, Gmail, SMS, etc. Gmail and Calendar do the following:
We should ensure that these behaviors work by default with this plugin. |
@matthew-carroll I updated the behavior for iOS: Simulator.Screen.Recording.-.iPhone.15.Pro.-.2025-01-10.at.20.01.17.mp4 |
[SuperEditor][mobile] Implement spellchecking support. Resolves #2353
Our current spellchecker plugin only works on macOS. This PR adds support for spellchecking on Android and iOS on the spellchecker plugin.
Currently, the desktop implementation is showing the suggestions toolbar whenever the currently selection sits within a misspelled word with available suggestions. However, this wouldn't work for mobile because, on mobile platforms, the suggestions toolbar is displayed when tapping on a misspelled word, but not when dragging the handle over a misspelled word. This creates an implementation problem, because a gesture (tap up) should trigger the suggestions popover and we don't have an extension point for plugins to do that.
To solve this issue, this PR adds the ability for plugins to install a delegate for tap handlers. The plugin now has the opportunity to handle the tap the way it wants, preventing our gesture system for handling them.
The tap delegate shows/hides the suggestions popover by interacting with a
SpellCheckerPopoverController
, a class that exposes the spellchecker popover functionality, which uses aSpellCheckerPopoverDelegate
(usually a document layer) to effectively display the popover toolbar.This PR also add an option to temporarily disable selection handles. This is needed because, when tapping on a misspelled word, we want to select the whole word, but prevent the selection handles from being displayed.
On mobile platforms, the selection color is different when the suggestions popover is visible. Currently, we don't have an option to override the selection color. On our style pipeline, the selection styler is always last, so even if we use a custom style phase, the selection styler always resets the selection color.
To solve that, this PR exposes an
appendedStylePhases
property onSuperEditor
, so we can add style phases that are positioned after the selection styler, adding the ability to override the selection color.This PR adds default implementations for iOS-style and Android-style suggestions popovers.
Screen.Recording.2024-10-28.at.21.27.40.mov
android_spellcheck.webm