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

Added an option for ClipboardPaste button to disable ClipboardMonitor #2427

Open
wants to merge 23 commits into
base: master
Choose a base branch
from

Conversation

Maksim-Nikolaev
Copy link
Contributor

@Maksim-Nikolaev Maksim-Nikolaev commented Jan 5, 2025

Description

According to the issue #2421 ClipboardMonitor was causing lag if "paste" button was enabled in QuillSimpleToolbar.

That was because of the periodic 1 second check for Clipboard content. In case with large strings or images in clipboard, it would lag out the application and slow down everything drastically.

class ClipboardMonitor {
  bool _canPaste = false;
  bool get canPaste => _canPaste;
  Timer? _timer;

  void monitorClipboard(bool add, void Function() listener) {
    if (kIsWeb) return;
    if (add) {
      _timer = Timer.periodic(
          const Duration(seconds: 1), (timer) => _update(listener));
    } else {
      _timer?.cancel();
    }
  }

  Future<void> _update(void Function() listener) async {
    final clipboardService = ClipboardServiceProvider.instance;
    if (await clipboardService.hasClipboardContent) {
      _canPaste = true;
      listener();
    }
  }
}

This is not a breaking change, however it will change the behavior of availability for "paste" button for QuillSimpleToolbar since it always be enabled (except of readonly mode).

Related Issues

Type of Change

  • 🛠️ Bug fix: Resolves an issue without changing current behavior.

@EchoEllet
Copy link
Collaborator

EchoEllet commented Jan 5, 2025

Thanks for your contribution.

This is not a breaking change

A breaking change is not only the kind that requires users to update their code to compile, but to also keep the current state without any behavior breaking.

Changing the default of a value is also a breaking change.

What would happen if the user pressed the paste button and there was nothing to paste?

I suggest providing the option to override the canPaste state instead, so they don't need to depend on ClipboardMonitor.

I'm with the idea of disabling something that's causing issues. However, users should have the option to revert to the old behavior in case they still want ClipboardMonitor with a signle boolean parameter.

@Maksim-Nikolaev

This comment was marked as resolved.

@Maksim-Nikolaev
Copy link
Contributor Author

Please check the most recent commit.
I added bool? enableClipboardPaste in lib\src\toolbar\config\simple_toolbar_config.dart for QuillSimpleToolbarConfig

This is passed directly to ClipboardButton with .paste action

As for the ClipboardButton itself and monitor:

  • enableClipboardPaste flag can be null or bool:
  /// If null, uses in-built [ClipboardMonitor]
  /// If true, paste button is enabled (if true & not readonly)
  /// If false, paste button is disabled
  final bool? enableClipboardPaste;
  • Updated ClipboardMonitor periodic timer from 1 second to 5 seconds
  • Update ClipboardMonitor to not overlap with previous task of itself (in case of big clipboard content)
  • Changed getter for "currentStateValue" to prioritize the enableClipboardPaste flag:
        return !controller.readOnly &&
            (kIsWeb || (widget.enableClipboardPaste ?? _monitor.canPaste));

@EchoEllet
Copy link
Collaborator

Updated ClipboardMonitor periodic timer from 1 second to 5 seconds

This is still a breaking change.
Could you minimize the changes to only override the can paste state (whether the button is enabled)?

I added bool? enableClipboardPaste in lib\src\toolbar\config\simple_toolbar_config.dart for QuillSimpleToolbarConfig

This option should be only in the button config, not the toolbar config.

@Maksim-Nikolaev

This comment was marked as resolved.

@Maksim-Nikolaev
Copy link
Contributor Author

This option should be only in the button config, not the toolbar config.

I agree that it would be the best way to do it. Unfortunately I couldn't find the proper place to add a new value, could you elaborate here?

  final QuillToolbarToggleStyleButtonOptions clipboardPaste;
import 'package:flutter/foundation.dart' show immutable;
import 'package:meta/meta.dart';

import '../base_button_options.dart';

class QuillToolbarToggleStyleButtonExtraOptions
    extends QuillToolbarBaseButtonExtraOptions
    implements QuillToolbarBaseButtonExtraOptionsIsToggled {
  const QuillToolbarToggleStyleButtonExtraOptions({
    required super.controller,
    required super.context,
    required super.onPressed,
    required this.isToggled,
  });

  @override
  final bool isToggled;
}

@immutable
class QuillToolbarToggleStyleButtonOptions
    extends QuillToolbarBaseButtonOptions<QuillToolbarToggleStyleButtonOptions,
        QuillToolbarToggleStyleButtonExtraOptions> {
  const QuillToolbarToggleStyleButtonOptions({
    super.iconData,
    super.iconSize,
    super.iconButtonFactor,
    super.tooltip,
    super.afterButtonPressed,
    super.iconTheme,
    super.childBuilder,
  });
}

@Maksim-Nikolaev
Copy link
Contributor Author

Maksim-Nikolaev commented Jan 5, 2025

Could you minimize the changes to only override the can paste state (whether the button is enabled)?

I would love to, but since it's coded in the way that we have little control over ClipboardMonitor it's difficult to update it's state or to stop the timer when the button already exists.

To stop the timer we need to trigger "didUpdateWidget" and add/remove extra listeners but it only happens when controller changes.
I kept the original handler to imitate the same behavior, as well as added some custom code to add/remove listener if "enableClipboardPaste" value changed from parent.

  @override
  void didUpdateWidget(QuillToolbarClipboardButton oldWidget) {
    super.didUpdateWidget(oldWidget);

    // Default didUpdateWidget handler, otherwise simple flag change didn't stop the monitor
    if (oldWidget.controller != controller) {
      oldWidget.controller.removeListener(didChangeEditingValue);
      removeExtraListener(oldWidget);
      controller.addListener(didChangeEditingValue);
      addExtraListener();
      currentValue = currentStateValue;
    }
    // If controller didn't change, but something else did (enableClipboardPaste)
    else if (widget.clipboardAction == ClipboardAction.paste) {
      // If enableClipboardPaste is not null, disable monitors
      if (widget.enableClipboardPaste != null) {
        _monitor.monitorClipboard(false, _listenClipboardStatus);
        currentValue = currentStateValue;
      } else {
        // Otherwise remove old listener and add a new one
        _monitor.monitorClipboard(true, _listenClipboardStatus);
        currentValue = currentStateValue;
      }
    }
  }

@Maksim-Nikolaev
Copy link
Contributor Author

I think adding "isEnabled" to QuillToolbarBaseButtonOptions would be the best option, but this would require adding the functionality of this option to all the buttons. I can do it if you give me a green light.
Otherwise I'm open for recommendations how to handle it for just 1 type of button

@Maksim-Nikolaev Maksim-Nikolaev changed the title Removed ClipboardMonitor from ClipboardAction.paste type button Added an option for ClipboardPaste button to disable ClipboardMonitor Jan 5, 2025
@Maksim-Nikolaev
Copy link
Contributor Author

Maksim-Nikolaev commented Jan 5, 2025

  • Moved enableClipboardPaste to QuillToolbarBaseButtonOptions

On the video you can see that it works for all 3 cases (null, true, false) and the timer will continue as normal if it is set back to "null"
null = clipboardmonitor enabled
false = clipboard paste disabled, monitor disabled
true = clipboard paste enabled, monitor enabled

flutter.quill.clipboard.monitor.mp4

@singerdmx
Copy link
Owner

@EchoEllet can we merge this?

@EchoEllet
Copy link
Collaborator

Updated ClipboardMonitor periodic timer from 1 second to 5 seconds

This is still a breaking change.
Could you minimize the changes to only override the can paste state (whether the button is enabled)?

I added bool? enableClipboardPaste in lib\src\toolbar\config\simple_toolbar_config.dart for QuillSimpleToolbarConfig

This option should be only in the button config, not the toolbar config.

can we merge this?

I will work on it soon. However, it's not a high-priorty issue given that I have disabled the buttons by default (revert to the default behavior).

@Maksim-Nikolaev
Copy link
Contributor Author

This is still a breaking change.

I adjusted the code changes followed by your requests. Check some comments above.
Timer is reverted back to 1 second
The flag is now in the button config

I really appreciate your time and work on this!

lib/src/toolbar/buttons/clipboard_button.dart Outdated Show resolved Hide resolved
lib/src/toolbar/config/base_button_options.dart Outdated Show resolved Hide resolved
lib/src/toolbar/buttons/clipboard_button.dart Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
@EchoEllet

This comment was marked as outdated.

@Maksim-Nikolaev
Copy link
Contributor Author

I applied some changes as you requested. Obviously feel free to change the wording/comments to your liking! Thanks again

Comment on lines 125 to 135
if (widget.clipboardAction == ClipboardAction.paste &&
widget.options.enableClipboardPaste == null) {
_monitor.monitorClipboard(true, _listenClipboardStatus);
}
}

@override
void removeExtraListener(covariant QuillToolbarClipboardButton oldWidget) {
if (widget.clipboardAction == ClipboardAction.paste) {
oldWidget._monitor.monitorClipboard(false, _listenClipboardStatus);
if (widget.clipboardAction == ClipboardAction.paste &&
widget.options.enableClipboardPaste == null) {
_monitor.monitorClipboard(false, _listenClipboardStatus);
Copy link
Collaborator

@EchoEllet EchoEllet Jan 10, 2025

Choose a reason for hiding this comment

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

Could you add a private getter (name suggestion: shouldUseClipboardMonitor) that is only true when enableClipboardPaste is null and then update related parts such as those to make use of this to make it easier and faster to understand from other developers's perspective. Also, it makes it easier to track related logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added it. Note that it should also check if the clipboard action is paste, it costs nothing but a bit more specific.

  bool get shouldUseClipboardMonitor {
    return widget.clipboardAction == ClipboardAction.paste &&
        widget.options.enableClipboardPaste == null;
  }

I adjusted the code to replace such statements, as well as the readability for didUpdateWidget function.

Comment on lines +69 to +75
/// Determines if the paste button is enabled. The button is disabled and cannot be clicked if set to `false`.
///
/// Defaults to [ClipboardMonitor] in case of `null`, which checks if the clipboard has content to paste every second and only then enables the button, indicating to the user that they can paste something.
///
/// Set it to `true` to enable it even if the clipboard has no content to paste, which will do nothing on a press.
final bool? enableClipboardPaste;

Copy link
Collaborator

@EchoEllet EchoEllet Jan 10, 2025

Choose a reason for hiding this comment

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

  • I'm confused, why is this change in the QuillToolbarBaseButtonOptions? This is the base for all buttons, and any configuration specific to the paste button should be in QuillToolbarClipboardButton instead. All the other buttons don't need this.
  • It's necessary to document if this is only applicable if the clipboard action is ClipboardAction.paste. Since the QuillToolbarClipboardButton is the same widget for cut, copy, and paste buttons.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you please take care of the location where to put the "enableClipboardPaste" flag?

ClipboardButtons use "QuillToolbarToggleStyleButtonOptions" and there is no "QuillToolbarPasteButton" class.
I'm not 100% familiar with how to introduce it without changes to be breaking.

  final QuillToolbarToggleStyleButtonOptions clipboardCut;
  final QuillToolbarToggleStyleButtonOptions clipboardCopy;
  final QuillToolbarToggleStyleButtonOptions clipboardPaste;

Copy link
Collaborator

@EchoEllet EchoEllet Jan 10, 2025

Choose a reason for hiding this comment

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

ClipboardButtons use "QuillToolbarToggleStyleButtonOptions".

It looks like the QuillToolbarClipboardButton doesn't have a custom config class (QuillToolbarClipboardButtonConfig), I'm not sure why but it shouldn't even if it doesn't require extra parameters. I will create a config class for this button and you can move this flag there instead.

here is no "QuillToolbarPasteButton" class.

I updated my message, I meant the config class of QuillToolbarClipboardButton which is missing.

Copy link
Collaborator

@EchoEllet EchoEllet Jan 10, 2025

Choose a reason for hiding this comment

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

ntroduce it without changes to be breaking.

You're right, it's not possible without any breaking in case users were already using QuillToolbarToggleStyleButtonOptions for those buttons (see #2433).

To be honest I'm not motivated to work on anything related to QuillSimpleToolbar, the design is a pain to work with and almost always confusing despite it's not very complex, our long plan is to completely rework the toolbar with a new modern one, better API design and usability while keeping the old toolbar backward compatible.

If #2433 merged, you can access enableClipboardPaste inside QuillToolbarClipboardButtonState:

widget._options.enableClipboardPaste;

But again, wouldn't it be better to mark this feature as experimental, rework on it later (to avoid breaking)? It's almost not a priority at least IMO. Working on it is not a good time investment which is why I updated README.md to include a note about contributing: https://github.com/singerdmx/flutter-quill#-contributing

@CatHood0 Do you think it worth introducing this breaking (changing options from QuillToolbarToggleStyleButtonOptions to QuillToolbarClipboardButtonOptions and slightly inconsistent API) to support disabling ClipboardMonitor introduced in #1843.

I have already disabled those buttons by default and mark them as experimental.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But again, wouldn't it be better to mark this feature as experimental, rework on it later (to avoid breaking)? It's almost not a priority at least IMO. Working on it is not a good time investment

I can understand the pain and now I realize more how hard it is to maintain open source project. I discovered that the paste button caused extreme lag in the app I'm working on, therefore I thought I'd contribute.

Otherwise in my case the usage of such button can be simplified to calling "controller.clipboardPaste" and can be achieved with custom button.

Once again, really appreciate your time and patience with this PR and I hope I don't cause too much issues for you

Copy link
Collaborator

@EchoEllet EchoEllet Jan 10, 2025

Choose a reason for hiding this comment

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

I don't cause too much issues for you

This is a project issue and not exclusive to this PR.

I realize more how hard it is to maintain open source project.

It's not very hard if we have a good solid foundation from the start, but there was a time when we kept merging PRs without much review and also introducing changes without much consideration or any tests at all, and that made it harder for us to fix those issues or maintain the project.

For example, the magnifier feature was introduced and merged quickly that people started depending on it (see #2406 for a list of regressions and issues), which caused major issues that affected the editor experience, and reverting to the old behavior is no longer trivial (see #2413), introducing breaking changes caused many issues for the app developers. That's why I think adding new features is not a priority, especially those that add more on the surface for more issues, this is not an issue with your code.

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.

ClipboardMonitor causes background lag when enabling clipboard buttons
3 participants