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] Prevent long-press to resolve when swiping to pop (Resolves #1947) #2064

Merged
merged 5 commits into from
Jun 7, 2024

Conversation

angelosilvestre
Copy link
Collaborator

[SuperEditor] Prevent long-press to resolve when swiping to pop. Resolves #1947

On mobile, dragging from left to right starts a swipe-to-pop gesture. However, when doing so we are changing the selection on both mobile platforms, showing the magnifier on iOS and showing the mobile toolbar on Android:

android_swipe_before.webm
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-05-30.at.16.28.24.mp4

The issue is that, even though the horizontal drag recognizer is winning in the gesture arena, the onTapDown is still called, causing the long-press timer to be started.

To resolve that, we need to cancel the timer when we lose on the arena.

I found another issue, where the onCancel isn't being called after the recognizer was defeated in the arena, even if the onTapDown was already fired.

This PR changes the TapSequenceGestureRecognizer to call onTapCancel after we lose on the arena and change the mobile interactors to cancel the long-press timers on that callback.

I wasn't able to write a failing test for Android, I guess this back gesture is handled different for Android, since I'm not seeing any other recognizers on the arena after starting the swipe.

With this PR's implementation:

Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-05-30.at.16.30.05.mp4
android_swipe_after.webm

@@ -198,7 +198,10 @@ class TapSequenceGestureRecognizer extends GestureRecognizer {
_trackers.remove(tracker.pointer);
tracker.entry.resolve(GestureDisposition.rejected);
_freezeTracker(tracker);
if (_firstTap != null || _secondTap != null) {

// Check if we need to cancel even when both _firstTap and _secondTap are null,
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 really understand this comment. Can you try rephrasing it?

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.

@@ -372,7 +377,7 @@ class TapSequenceGestureRecognizer extends GestureRecognizer {
}

void _checkCancel() {
if (_firstTap == null && onTapCancel != null) {
if ((_firstTap == null || _firstTapDownDetails != null) && onTapCancel != null) {
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 add an explanation comment to this line, too?

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.

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 _firstTap == null || _firstTapDownDetails != null be an &&?

Also, given that the tap down happens first, temporarily, it's probably more helpful to the reader to reverse those two. First check the tap down, then check the first tap.

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 should be an ||. If the user tapped down, but not released the tap, _firstTap is null.

Copy link
Contributor

Choose a reason for hiding this comment

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

That still sounds like an && condition: "the user has tapped down AND the user has not released the tap" - isn't that the condition?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Currently, if the user:

  • Taps down
  • Release the tap
  • Taps again
  • Drags horizontally

We will still fire the cancel event. If we change it to && it will no longer be the case. Is this expected?

@@ -198,7 +198,16 @@ class TapSequenceGestureRecognizer extends GestureRecognizer {
_trackers.remove(tracker.pointer);
tracker.entry.resolve(GestureDisposition.rejected);
_freezeTracker(tracker);
if (_firstTap != null || _secondTap != null) {

// Both _firstTap and _secondTap are only set when a pointer up event is received,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is good info, but I'm wondering if we're combining unrelated situations in a single if-statement?

Most of this code was copied and adjusted from Flutter, so don't consider the existing code to be particularly readable. If we can improve the readability, we should.

I notice the first if-statement has 3 OR conditions, which sounds like 3 different things. Then within that if-statement we have another if/else, and the else contains another if. It's a lot of different conditions.

Can you try to refactor this little piece into unique conditions, where each condition explains what it represnts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@matthew-carroll Updated. Can you take a look to see if it's better now?

BTW, we still have some unusual code here. In _reject, there are situations that we call _reset. Then, on _reset, we can nullify _firstTap and _secondTap and recursively call _reject again a couple times.

Copy link
Contributor

Choose a reason for hiding this comment

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

I assume that's stuff copied from Flutter. I had no idea how this thing worked when I adapted it to our needs. Any time we figure out a little bit of it, we should add comments. Also, where possible, if we can simplify an area that we're working on, we should.

@@ -372,7 +377,7 @@ class TapSequenceGestureRecognizer extends GestureRecognizer {
}

void _checkCancel() {
if (_firstTap == null && onTapCancel != null) {
if ((_firstTap == null || _firstTapDownDetails != null) && onTapCancel != null) {
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 _firstTap == null || _firstTapDownDetails != null be an &&?

Also, given that the tap down happens first, temporarily, it's probably more helpful to the reader to reverse those two. First check the tap down, then check the first tap.

super_editor/lib/src/infrastructure/multi_tap_gesture.dart Outdated Show resolved Hide resolved
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.

LGTM

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.

When swiping to pop, Magnifier and Toolbar appears until released.
2 participants