Skip to content

Commit

Permalink
[SuperEditor][Android] Fix toolbar being hidden when releasing a long…
Browse files Browse the repository at this point in the history
… press (Resolves #2481)
  • Loading branch information
angelosilvestre committed Jan 26, 2025
1 parent c3106bb commit 6d70bca
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 16 deletions.
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) {
// 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();

// 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
// 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.
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();

// 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

0 comments on commit 6d70bca

Please sign in to comment.