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][Android] Fix toolbar being hidden when releasing a long press (Resolves #2481) #2552

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[SuperEditor][Android] Fix toolbar being hidden when releasing a long…
… press (Resolves #2481)
  • Loading branch information
angelosilvestre committed Jan 26, 2025
commit 6d70bcaf3e72cf3aa68ad9f1ed67a6e838ad07b2
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ class AndroidDocumentTouchInteractor extends StatefulWidget {
required this.selection,
this.openKeyboardWhenTappingExistingSelection = true,
required this.openSoftwareKeyboard,
required this.isImeConnected,
required this.scrollController,
required this.fillViewport,
this.contentTapHandlers,
Expand All @@ -452,6 +453,10 @@ class AndroidDocumentTouchInteractor extends StatefulWidget {
/// A callback that should open the software keyboard when invoked.
final VoidCallback openSoftwareKeyboard;

/// A [ValueListenable] that should notify this widget when the IME connects
/// and disconnects.
final ValueListenable<bool> isImeConnected;

/// Optional list of handlers that respond to taps on content, e.g., opening
/// a link when the user taps on text with a link attribution.
///
Expand Down Expand Up @@ -671,6 +676,9 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
}

void _onDocumentChange(_) {
// The user might start typing when the toolbar is visible. Hide it.
_controlsController!.hideToolbar();

onNextFrame((_) {
_ensureSelectionExtentIsVisible();
});
Expand Down Expand Up @@ -751,7 +759,7 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
if (_isLongPressInProgress) {
_longPressStrategy = null;
_magnifierGlobalOffset.value = null;
_showAndHideEditingControlsAfterTapSelection(didTapOnExistingSelection: false);
_onLongPressEnd();
return;
}

Expand Down Expand Up @@ -985,7 +993,7 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
..hideMagnifier()
..blinkCaret();

if (didTapOnExistingSelection && _isKeyboardOpen) {
if (didTapOnExistingSelection && widget.isImeConnected.value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This was "is keyboard open" now it's "is IME connected". These aren't the same thing. Which one do we actually want here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We actually want to know if the keyboard is open, but the method this is using isn't reliable. It will always report false if there is an ancestor Scaffold that resizes to avoid the bottom insets.

We already did this change on iOS.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you use super_keyboard to get this info?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated. O also modified to do the same for iOS.

// Toggle the toolbar display when the user taps on the collapsed caret,
// or on top of an existing selection.
//
Expand All @@ -1000,16 +1008,6 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
}
}

/// Returns `true` if we *think* the software keyboard is currently open, or
/// `false` otherwise.
///
/// We say "think" because Flutter doesn't report this info to us. Instead, we
/// inspect the bottom insets on the window, and we assume any insets greater than
/// zero means a keyboard is visible.
bool get _isKeyboardOpen {
return MediaQuery.viewInsetsOf(context).bottom > 0;
}

void _onPanStart(DragStartDetails details) {
// Stop waiting for a long-press to start, if a long press isn't already in-progress.
_tapDownLongPressTimer?.cancel();
Expand Down Expand Up @@ -1168,7 +1166,7 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
}

void _onLongPressEnd() {
_longPressStrategy!.onLongPressEnd();
_longPressStrategy?.onLongPressEnd();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why change to ?? How do we get here without a long press strategy?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was calling _onLongPressEnd at the wrong time. Fixed it.


// Cancel any on-going long-press.
_longPressStrategy = null;
Expand All @@ -1178,10 +1176,12 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt

_controlsController!.hideMagnifier();
if (!widget.selection.value!.isCollapsed) {
_controlsController!
..showExpandedHandles()
..showToolbar();
_controlsController!.showExpandedHandles();
}

// Show the toolbar even the selection is collapsed, because the user might
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm confused about the timing of the user interactions that we're solving with this PR. I thought the PR described the toolbar disappearing after it was already visible. But this code seems to be making the toolbar appear. What's the actual bug here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It seems we don't need this code. Just need the _onLongPressEnd to be called.

// be long-pressing on an empty paragraph or on a whitespace.
_controlsController!.showToolbar();
}

void _onCaretDragEnd() {
Expand Down
1 change: 1 addition & 0 deletions super_editor/lib/src/default_editor/super_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,7 @@ class SuperEditorState extends State<SuperEditor> {
selection: editContext.composer.selectionNotifier,
openKeyboardWhenTappingExistingSelection: widget.selectionPolicies.openKeyboardWhenTappingExistingSelection,
openSoftwareKeyboard: _openSoftwareKeyboard,
isImeConnected: _isImeConnected,
contentTapHandlers: [
..._contentTapHandlers ?? [],
for (final plugin in widget.plugins) //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,36 @@ void main() {
expect(SuperEditorInspector.isMobileMagnifierVisible(), isFalse);
});

testWidgetsOnAndroid("shows toolbar when long pressing on an empty paragraph and hides it after typing",
(tester) async {
await tester //
.createDocument()
.withSingleEmptyParagraph()
.pump();

// Ensure the toolbar is not visible.
expect(SuperEditorInspector.isMobileToolbarVisible(), isFalse);

// Long press to show the toolbar.
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 long press open the magnifier?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

On Android, it shouldn't. The magnifier is displayed when dragging a handle.

final gesture = await tester.longPressDownInParagraph('1', 0);

// Ensure the toolbar is visible.
expect(SuperEditorInspector.isMobileToolbarVisible(), isTrue);

// Release the finger.
await gesture.up();
await tester.pumpAndSettle();

// Ensure the toolbar is still visible.
expect(SuperEditorInspector.isMobileToolbarVisible(), isTrue);

// Type a character to hide the toolbar.
await tester.typeImeText('a');

// Ensure the toolbar is not visible.
expect(SuperEditorInspector.isMobileToolbarVisible(), isFalse);
});

testWidgetsOnAndroid("shows magnifier when dragging expanded handle", (tester) async {
await _pumpSingleParagraphApp(tester);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,36 @@ void main() {
expect(SuperEditorInspector.isMobileMagnifierVisible(), isFalse);
});

testWidgetsOnIos("shows toolbar when long pressing on an empty paragraph and hides it after typing",
(tester) async {
await tester //
.createDocument()
.withSingleEmptyParagraph()
.pump();

// Ensure the toolbar is not visible.
expect(SuperEditorInspector.isMobileToolbarVisible(), isFalse);

// Long press, this shouldn't show the toolbar.
final gesture = await tester.longPressDownInParagraph('1', 0);

// Ensure the toolbar is not visible yet.
expect(SuperEditorInspector.isMobileToolbarVisible(), isFalse);

// Release the finger.
await gesture.up();
await tester.pumpAndSettle();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to settle?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It seems pump is enough.


// Ensure the toolbar is visible.
expect(SuperEditorInspector.isMobileToolbarVisible(), isTrue);

// Type a character to hide the toolbar.
await tester.typeImeText('a');

// Ensure the toolbar is not visible.
expect(SuperEditorInspector.isMobileToolbarVisible(), isFalse);
});

testWidgetsOnIos("does not show toolbar upon first tap", (tester) async {
await tester //
.createDocument()
Expand Down
Loading