! Support clipboard actions from the toolbar. (#1843)
parent
b7711bb46d
commit
1a4109fb7a
27 changed files with 488 additions and 243 deletions
@ -1,3 +1,4 @@ |
||||
export 'editor/editor_configurations.dart'; |
||||
export 'quill_controller_configurations.dart'; |
||||
export 'quill_shared_configurations.dart'; |
||||
export 'toolbar/simple_toolbar_configurations.dart'; |
||||
|
@ -0,0 +1,8 @@ |
||||
class QuillControllerConfigurations { |
||||
const QuillControllerConfigurations({this.onClipboardPaste}); |
||||
|
||||
/// Callback when the user pastes and data has not already been processed |
||||
/// |
||||
/// Return true if the paste operation was handled |
||||
final Future<bool> Function()? onClipboardPaste; |
||||
} |
@ -0,0 +1,144 @@ |
||||
import 'dart:async'; |
||||
|
||||
import 'package:flutter/foundation.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:super_clipboard/super_clipboard.dart'; |
||||
|
||||
import '../../../../extensions.dart'; |
||||
import '../../../../flutter_quill.dart'; |
||||
import '../../../l10n/extensions/localizations.dart'; |
||||
import '../base_button/base_value_button.dart'; |
||||
|
||||
enum ClipboardAction { cut, copy, paste } |
||||
|
||||
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 reader = await SystemClipboard.instance?.read(); |
||||
if (reader != null) { |
||||
final available = reader.platformFormats; |
||||
if (_canPaste != available.isNotEmpty) { |
||||
_canPaste = available.isNotEmpty; |
||||
listener(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
class QuillToolbarClipboardButton extends QuillToolbarToggleStyleBaseButton { |
||||
QuillToolbarClipboardButton( |
||||
{required super.controller, |
||||
required this.clipboardAction, |
||||
super.options = const QuillToolbarToggleStyleButtonOptions(), |
||||
super.key}); |
||||
|
||||
final ClipboardAction clipboardAction; |
||||
|
||||
final ClipboardMonitor _monitor = ClipboardMonitor(); |
||||
|
||||
@override |
||||
State<StatefulWidget> createState() => QuillToolbarClipboardButtonState(); |
||||
} |
||||
|
||||
class QuillToolbarClipboardButtonState |
||||
extends QuillToolbarToggleStyleBaseButtonState< |
||||
QuillToolbarClipboardButton> { |
||||
@override |
||||
bool get currentStateValue { |
||||
switch (widget.clipboardAction) { |
||||
case ClipboardAction.cut: |
||||
return !controller.readOnly && !controller.selection.isCollapsed; |
||||
case ClipboardAction.copy: |
||||
return !controller.selection.isCollapsed; |
||||
case ClipboardAction.paste: |
||||
return !controller.readOnly && (kIsWeb || widget._monitor.canPaste); |
||||
} |
||||
} |
||||
|
||||
void _listenClipboardStatus() => didChangeEditingValue(); |
||||
|
||||
@override |
||||
void addExtraListener() { |
||||
if (widget.clipboardAction == ClipboardAction.paste) { |
||||
widget._monitor.monitorClipboard(true, _listenClipboardStatus); |
||||
} |
||||
} |
||||
|
||||
@override |
||||
void removeExtraListener(covariant QuillToolbarClipboardButton oldWidget) { |
||||
if (widget.clipboardAction == ClipboardAction.paste) { |
||||
oldWidget._monitor.monitorClipboard(false, _listenClipboardStatus); |
||||
} |
||||
} |
||||
|
||||
@override |
||||
String get defaultTooltip => switch (widget.clipboardAction) { |
||||
ClipboardAction.cut => context.loc.cut, |
||||
ClipboardAction.copy => context.loc.copy, |
||||
ClipboardAction.paste => context.loc.paste, |
||||
}; |
||||
|
||||
IconData get _icon => switch (widget.clipboardAction) { |
||||
ClipboardAction.cut => Icons.cut_outlined, |
||||
ClipboardAction.copy => Icons.copy_outlined, |
||||
ClipboardAction.paste => Icons.paste_outlined, |
||||
}; |
||||
|
||||
void _onPressed() { |
||||
switch (widget.clipboardAction) { |
||||
case ClipboardAction.cut: |
||||
controller.clipboardSelection(false); |
||||
break; |
||||
case ClipboardAction.copy: |
||||
controller.clipboardSelection(true); |
||||
break; |
||||
case ClipboardAction.paste: |
||||
controller.clipboardPaste(); |
||||
break; |
||||
} |
||||
afterButtonPressed?.call(); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final childBuilder = options.childBuilder ?? |
||||
context.quillToolbarBaseButtonOptions?.childBuilder; |
||||
if (childBuilder != null) { |
||||
return childBuilder( |
||||
options, |
||||
QuillToolbarToggleStyleButtonExtraOptions( |
||||
context: context, |
||||
controller: controller, |
||||
onPressed: _onPressed, |
||||
isToggled: currentValue, |
||||
), |
||||
); |
||||
} |
||||
|
||||
return UtilityWidgets.maybeTooltip( |
||||
message: tooltip, |
||||
child: QuillToolbarIconButton( |
||||
icon: Icon( |
||||
_icon, |
||||
size: iconSize * iconButtonFactor, |
||||
), |
||||
isSelected: false, |
||||
onPressed: currentValue ? _onPressed : null, |
||||
afterPressed: afterButtonPressed, |
||||
iconTheme: iconTheme, |
||||
)); |
||||
} |
||||
} |
Loading…
Reference in new issue