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

[SuperEditor][mobile] Implement spellchecking support (Resolves #2353) #2378

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

angelosilvestre
Copy link
Collaborator

@angelosilvestre angelosilvestre commented Oct 16, 2024

[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 a SpellCheckerPopoverDelegate (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 on SuperEditor, 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

@matthew-carroll
Copy link
Contributor

@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.

Copy link
Contributor

@matthew-carroll matthew-carroll left a 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.


/// Attaches this controller to a delegate that knows how to
/// show a popover with spelling suggestions.
void attach(SpellCheckerPopoverDelegate delegate) {
Copy link
Contributor

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.
Copy link
Contributor

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 {
Copy link
Contributor

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.

Copy link
Collaborator Author

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].
Copy link
Contributor

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.

Copy link
Collaborator Author

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?

super_editor/lib/src/default_editor/super_editor.dart Outdated Show resolved Hide resolved
@angelosilvestre
Copy link
Collaborator Author

@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.

This PR is meant to review the controller usage only. I should have added that to the description, sorry.

@angelosilvestre
Copy link
Collaborator Author

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.

@matthew-carroll I moved the controller to SuperEditorIosControlsController and SuperEditorAndroidControlsController. Can you take a look to see if that's how we should proceed?

@angelosilvestre angelosilvestre marked this pull request as ready for review October 29, 2024 01:32
@angelosilvestre angelosilvestre changed the title WIP: [SuperEditor][mobile] Implement spellchecking support (Resolves #2353) [SuperEditor][mobile] Implement spellchecking support (Resolves #2353) Oct 29, 2024
widget.contentTapHandler?.addListener(_updateMouseCursorAtLatestOffset);
if (widget.contentTapHandlers != null) {
for (final handler in widget.contentTapHandlers!) {
handler.addListener(_updateMouseCursorAtLatestOffset);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't remember what this callback is for. Are you sure it makes sense to run that callback for every handler?

@@ -62,9 +63,9 @@ class DocumentMouseInteractor extends StatefulWidget {
final Stream<DocumentSelectionChange> selectionChanges;
final ValueListenable<DocumentSelection?> selectionNotifier;

/// Optional handler that responds to taps on content, e.g., opening
/// Optional list of handlers that respond to taps on content, e.g., opening
Copy link
Contributor

Choose a reason for hiding this comment

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

For each Dart Doc related to these handlers, please specify the chain of responsibility behavior. The current Dart Docs here, and elsewhere, don't explain that only a single handler is ever run...


if (widget.contentTapHandlers != null && docPosition != null) {
for (final handler in widget.contentTapHandlers!) {
final result = handler.onTap(docPosition);
Copy link
Contributor

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?

@@ -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
Copy link
Contributor

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?

@@ -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) //
Copy link
Contributor

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?

@@ -311,6 +314,12 @@ class AndroidControlsDocumentLayerState
});
}

void _onSelectionHandlesAllowedChange() {
setState(() {
//
Copy link
Contributor

Choose a reason for hiding this comment

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

Please specify what's being rebuilt and why

@@ -319,6 +328,11 @@ class AndroidControlsDocumentLayerState
return null;
}

if (_controlsController!.areSelectionHandlesAllowed.value == false) {
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 value nullable? It seems strange that areSelectionHandlesAllowed would ever be null...

autofocus: true,
editor: _editor,
appendedStylePhases: [
_spellingAndGrammarPlugin.styler,
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be added automatically as part of the plugin?

@angelosilvestre
Copy link
Collaborator Author

@matthew-carroll This should wait on #2397, because we will have some conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE] - Add spellcheck support for mobile platforms
2 participants