Skip to content

[SuperEditor][Android] Fix toolbar being hidden when releasing a long press (Resolves #2481) #2552

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import 'package:super_editor/src/infrastructure/platforms/mobile_documents.dart'
import 'package:super_editor/src/infrastructure/signal_notifier.dart';
import 'package:super_editor/src/infrastructure/sliver_hybrid_stack.dart';
import 'package:super_editor/src/infrastructure/touch_controls.dart';
import 'package:super_keyboard/super_keyboard.dart';

import '../infrastructure/document_gestures.dart';
import '../infrastructure/document_gestures_interaction_overrides.dart';
Expand Down Expand Up @@ -671,6 +672,9 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
}

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

Choose a reason for hiding this comment

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

Can't this be triggered by things like other users typing elsewhere, or theoretically any change that might not be connected to the user doing anything?

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 think it's possible that other users typing will trigger this. At the moment, the document change events don't have a property to determine if the change is caused by an user interaction or by the content being changed by another source.


onNextFrame((_) {
_ensureSelectionExtentIsVisible();
});
Expand Down Expand Up @@ -749,9 +753,9 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt

// Cancel any on-going long-press.
if (_isLongPressInProgress) {
_onLongPressEnd();
_longPressStrategy = null;
_magnifierGlobalOffset.value = null;
_showAndHideEditingControlsAfterTapSelection(didTapOnExistingSelection: false);
return;
}

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

if (didTapOnExistingSelection && _isKeyboardOpen) {
if (didTapOnExistingSelection && SuperKeyboard.instance.state.value == KeyboardState.open) {
// Toggle the toolbar display when the user taps on the collapsed caret,
// or on top of an existing selection.
//
Expand All @@ -1000,16 +1004,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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import 'package:super_editor/src/infrastructure/platforms/platform.dart';
import 'package:super_editor/src/infrastructure/signal_notifier.dart';
import 'package:super_editor/src/infrastructure/sliver_hybrid_stack.dart';
import 'package:super_editor/src/infrastructure/touch_controls.dart';
import 'package:super_keyboard/super_keyboard.dart';

import '../infrastructure/document_gestures.dart';
import '../infrastructure/document_gestures_interaction_overrides.dart';
Expand Down Expand Up @@ -266,7 +267,6 @@ class IosDocumentTouchInteractor extends StatefulWidget {
required this.selection,
this.openKeyboardWhenTappingExistingSelection = true,
required this.openSoftwareKeyboard,
required this.isImeConnected,
required this.scrollController,
required this.dragHandleAutoScroller,
required this.fillViewport,
Expand All @@ -289,10 +289,6 @@ class IosDocumentTouchInteractor 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 @@ -661,7 +657,7 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
selection.extent.nodeId == docPosition.nodeId &&
selection.extent.nodePosition.isEquivalentTo(docPosition.nodePosition);

if (didTapOnExistingSelection && widget.isImeConnected.value) {
if (didTapOnExistingSelection && SuperKeyboard.instance.state.value == KeyboardState.open) {
// Toggle the toolbar display when the user taps on the collapsed caret,
// or on top of an existing selection.
//
Expand Down
1 change: 0 additions & 1 deletion super_editor/lib/src/default_editor/super_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,6 @@ 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 @@ -6,6 +6,7 @@ import 'package:follow_the_leader/follow_the_leader.dart';
import 'package:super_editor/src/infrastructure/platforms/android/selection_handles.dart';
import 'package:super_editor/super_editor.dart';
import 'package:super_editor/super_editor_test.dart';
import 'package:super_keyboard/test/keyboard_simulator.dart';
import 'package:super_text_layout/super_text_layout.dart';

import '../../test_runners.dart';
Expand Down Expand Up @@ -168,6 +169,41 @@ 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();

// The decision about showing the toolbar depends on the keyboard visibility.
// Simulate the keyboard being visible immediately after the IME is connected.
TestSuperKeyboard.install(tester, keyboardAnimationTime: Duration.zero);
addTearDown(() => TestSuperKeyboard.uninstall());

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

// 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 @@ -4,6 +4,7 @@ import 'package:flutter_test_robots/flutter_test_robots.dart';
import 'package:flutter_test_runners/flutter_test_runners.dart';
import 'package:super_editor/super_editor.dart';
import 'package:super_editor/super_editor_test.dart';
import 'package:super_keyboard/test/keyboard_simulator.dart';
import 'package:super_text_layout/super_text_layout.dart';

import '../../test_runners.dart';
Expand Down Expand Up @@ -132,6 +133,41 @@ 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();

// The decision about showing the toolbar depends on the keyboard visibility.
// Simulate the keyboard being visible immediately after the IME is connected.
TestSuperKeyboard.install(tester, keyboardAnimationTime: Duration.zero);
addTearDown(() => TestSuperKeyboard.uninstall());

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

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