Merge branch 'singerdmx:master' into master

pull/1110/head
Cierra_Runis 2 years ago committed by GitHub
commit 6ea9cd0526
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      CHANGELOG.md
  2. 5
      README.md
  3. 1
      flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart
  4. 2
      lib/src/models/rules/insert.dart
  5. 36
      lib/src/translations/toolbar.i18n.dart
  6. 4
      lib/src/utils/platform.dart
  7. 45
      lib/src/widgets/delegate.dart
  8. 56
      lib/src/widgets/editor.dart
  9. 137
      lib/src/widgets/raw_editor.dart
  10. 9
      lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart
  11. 93
      lib/src/widgets/text_selection.dart
  12. 85
      lib/src/widgets/toolbar.dart
  13. 80
      lib/src/widgets/toolbar/arrow_indicated_button_list.dart
  14. 97
      lib/src/widgets/toolbar/select_header_style_button.dart
  15. 18
      pubspec.yaml

@ -1,3 +1,33 @@
# [6.4.4]
* Increased compatibility with Flutter widget tests.
# [6.4.3]
* Update dependencies (collection: 1.17.0, flutter_keyboard_visibility: 5.4.0, quiver: 3.2.1, tuple: 2.0.1, url_launcher: 6.1.9, characters: 1.2.1, i18n_extension: 7.0.0, device_info_plus: 8.1.0)
# [6.4.2]
* Replace `buildToolbar` with `contextMenuBuilder`.
# [6.4.1]
* Control the detect word boundary behaviour.
# [6.4.0]
* Use `axis` to make the toolbar vertical.
* Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis.
* Breaking change: `QuillToolbar`'s parameter `toolbarHeight` was renamed to `toolbarSize`.
# [6.3.5]
* Ability to add custom shortcuts.
# [6.3.4]
* Update clipboard status prior to showing selected text overlay.
# [6.3.3]
* Fixed handling of mac intents.
# [6.3.2]
* Added `unknownEmbedBuilder` to QuillEditor.
* Fix error style when input chinese japanese or korean.
# [6.3.1] # [6.3.1]
* Add color property to the basic factory function. * Add color property to the basic factory function.

@ -90,7 +90,7 @@ You can then write this to storage.
To open a FlutterQuill editor with an existing JSON representation that you've previously stored, you can do something like this: To open a FlutterQuill editor with an existing JSON representation that you've previously stored, you can do something like this:
```dart ```dart
var myJSON = jsonDecode(incomingJSONText); var myJSON = jsonDecode(r'{"insert":"hello\n"}');
_controller = QuillController( _controller = QuillController(
document: Document.fromJson(myJSON), document: Document.fromJson(myJSON),
selection: TextSelection.collapsed(offset: 0), selection: TextSelection.collapsed(offset: 0),
@ -346,7 +346,7 @@ QuillToolbar(locale: Locale('fr'), ...)
QuillEditor(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...)
``` ```
Currently, translations are available for these 25 locales: Currently, translations are available for these 26 locales:
* `Locale('en')` * `Locale('en')`
* `Locale('ar')` * `Locale('ar')`
@ -367,6 +367,7 @@ Currently, translations are available for these 25 locales:
* `Locale('pl')` * `Locale('pl')`
* `Locale('vi')` * `Locale('vi')`
* `Locale('id')` * `Locale('id')`
* `Locale('ms')`
* `Locale('nl')` * `Locale('nl')`
* `Locale('no')` * `Locale('no')`
* `Locale('fa')` * `Locale('fa')`

@ -80,7 +80,6 @@ class ImageVideoUtils {
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
backgroundColor: Colors.transparent,
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [

@ -477,7 +477,7 @@ class PreserveInlineStylesRule extends InsertRule {
} }
final itr = DeltaIterator(document); final itr = DeltaIterator(document);
final prev = itr.skip(index); final prev = itr.skip(len == 0 ? index : index + 1);
if (prev == null || if (prev == null ||
prev.data is! String || prev.data is! String ||
(prev.data as String).contains('\n')) { (prev.data as String).contains('\n')) {

@ -898,7 +898,41 @@ extension Localization on String {
'Next': 'הבא', 'Next': 'הבא',
'Camera': 'מצלמה', 'Camera': 'מצלמה',
'Video': 'וידאו', 'Video': 'וידאו',
} },
'ms': {
'Paste a link': 'Tampal Pautan',
'Ok': 'Ok',
'Select Color': 'Pilih Warna',
'Gallery': 'Galeri',
'Link': 'Pautan',
'Please first select some text to transform into a link.':
'Sila pilih beberapa patah perkataan'
' untuk diubah menjadi pautan.',
'Open': 'Buka',
'Copy': 'Salin',
'Remove': 'Buang',
'Save': 'Simpan',
'Zoom': 'Zum',
'Saved': 'Telah Disimpan',
'Text': 'Perkataan',
'What is entered is not a link': 'Apa yang diisi bukan pautan',
'Resize': 'Ubah saiz',
'Width': 'Lebar',
'Height': 'Tinggi',
'Size': 'Saiz',
'Small': 'Kecil',
'Large': 'Besar',
'Huge': 'Amat Besar',
'Clear': 'Padam',
'Font': 'Fon',
'Search': 'Carian',
'matches': 'padanan',
'showing match': 'menunjukkan padanan',
'Prev': 'Sebelum',
'Next': 'Seterusnya',
'Camera': 'Kamera',
'Video': 'Video',
},
}; };
String get i18n => localize(this, _t); String get i18n => localize(this, _t);

@ -26,6 +26,10 @@ bool isAppleOS([TargetPlatform? targetPlatform]) {
} }
Future<bool> isIOSSimulator() async { Future<bool> isIOSSimulator() async {
if (!isAppleOS()) {
return false;
}
final deviceInfo = DeviceInfoPlugin(); final deviceInfo = DeviceInfoPlugin();
final osInfo = await deviceInfo.deviceInfo; final osInfo = await deviceInfo.deviceInfo;

@ -66,7 +66,8 @@ class EditorTextSelectionGestureDetectorBuilder {
/// Creates a [EditorTextSelectionGestureDetectorBuilder]. /// Creates a [EditorTextSelectionGestureDetectorBuilder].
/// ///
/// The [delegate] must not be null. /// The [delegate] must not be null.
EditorTextSelectionGestureDetectorBuilder({required this.delegate}); EditorTextSelectionGestureDetectorBuilder(
{required this.delegate, this.detectWordBoundary = true});
/// The delegate for this [EditorTextSelectionGestureDetectorBuilder]. /// The delegate for this [EditorTextSelectionGestureDetectorBuilder].
/// ///
@ -83,6 +84,8 @@ class EditorTextSelectionGestureDetectorBuilder {
/// a stylus. /// a stylus.
bool shouldShowSelectionToolbar = true; bool shouldShowSelectionToolbar = true;
bool detectWordBoundary = true;
/// The [State] of the [EditableText] for which the builder will provide a /// The [State] of the [EditableText] for which the builder will provide a
/// [EditorTextSelectionGestureDetector]. /// [EditorTextSelectionGestureDetector].
@protected @protected
@ -337,24 +340,28 @@ class EditorTextSelectionGestureDetectorBuilder {
/// ///
/// The [child] or its subtree should contain [EditableText]. /// The [child] or its subtree should contain [EditableText].
Widget build( Widget build(
{required HitTestBehavior behavior, required Widget child, Key? key}) { {required HitTestBehavior behavior,
required Widget child,
Key? key,
bool detectWordBoundary = true}) {
return EditorTextSelectionGestureDetector( return EditorTextSelectionGestureDetector(
key: key, key: key,
onTapDown: onTapDown, onTapDown: onTapDown,
onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null, onForcePressStart:
onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null, delegate.forcePressEnabled ? onForcePressStart : null,
onSingleTapUp: onSingleTapUp, onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null,
onSingleTapCancel: onSingleTapCancel, onSingleTapUp: onSingleTapUp,
onSingleLongTapStart: onSingleLongTapStart, onSingleTapCancel: onSingleTapCancel,
onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate, onSingleLongTapStart: onSingleLongTapStart,
onSingleLongTapEnd: onSingleLongTapEnd, onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate,
onDoubleTapDown: onDoubleTapDown, onSingleLongTapEnd: onSingleLongTapEnd,
onSecondarySingleTapUp: onSecondarySingleTapUp, onDoubleTapDown: onDoubleTapDown,
onDragSelectionStart: onDragSelectionStart, onSecondarySingleTapUp: onSecondarySingleTapUp,
onDragSelectionUpdate: onDragSelectionUpdate, onDragSelectionStart: onDragSelectionStart,
onDragSelectionEnd: onDragSelectionEnd, onDragSelectionUpdate: onDragSelectionUpdate,
behavior: behavior, onDragSelectionEnd: onDragSelectionEnd,
child: child, behavior: behavior,
); detectWordBoundary: detectWordBoundary,
child: child);
} }
} }

@ -180,6 +180,9 @@ class QuillEditor extends StatefulWidget {
this.floatingCursorDisabled = false, this.floatingCursorDisabled = false,
this.textSelectionControls, this.textSelectionControls,
this.onImagePaste, this.onImagePaste,
this.customShortcuts,
this.customActions,
this.detectWordBoundary = true,
Key? key}) Key? key})
: super(key: key); : super(key: key);
@ -394,6 +397,11 @@ class QuillEditor extends StatefulWidget {
/// Returns the url of the image if the image should be inserted. /// Returns the url of the image if the image should be inserted.
final Future<String?> Function(Uint8List imageBytes)? onImagePaste; final Future<String?> Function(Uint8List imageBytes)? onImagePaste;
final Map<LogicalKeySet, Intent>? customShortcuts;
final Map<Type, Action<Intent>>? customActions;
final bool detectWordBoundary;
@override @override
QuillEditorState createState() => QuillEditorState(); QuillEditorState createState() => QuillEditorState();
} }
@ -408,7 +416,8 @@ class QuillEditorState extends State<QuillEditor>
void initState() { void initState() {
super.initState(); super.initState();
_selectionGestureDetectorBuilder = _selectionGestureDetectorBuilder =
_QuillEditorSelectionGestureDetectorBuilder(this); _QuillEditorSelectionGestureDetectorBuilder(
this, widget.detectWordBoundary);
} }
@override @override
@ -458,12 +467,8 @@ class QuillEditorState extends State<QuillEditor>
readOnly: widget.readOnly, readOnly: widget.readOnly,
placeholder: widget.placeholder, placeholder: widget.placeholder,
onLaunchUrl: widget.onLaunchUrl, onLaunchUrl: widget.onLaunchUrl,
toolbarOptions: ToolbarOptions( contextMenuBuilder:
copy: showSelectionToolbar, showSelectionToolbar ? RawEditor.defaultContextMenuBuilder : null,
cut: showSelectionToolbar,
paste: showSelectionToolbar,
selectAll: showSelectionToolbar,
),
showSelectionHandles: isMobile(theme.platform), showSelectionHandles: isMobile(theme.platform),
showCursor: widget.showCursor, showCursor: widget.showCursor,
cursorStyle: CursorStyle( cursorStyle: CursorStyle(
@ -494,16 +499,18 @@ class QuillEditorState extends State<QuillEditor>
readOnly, readOnly,
) => ) =>
_buildCustomBlockEmbed( _buildCustomBlockEmbed(
node, node,
context, context,
controller, controller,
readOnly, readOnly,
widget.unknownEmbedBuilder, widget.unknownEmbedBuilder,
), ),
linkActionPickerDelegate: widget.linkActionPickerDelegate, linkActionPickerDelegate: widget.linkActionPickerDelegate,
customStyleBuilder: widget.customStyleBuilder, customStyleBuilder: widget.customStyleBuilder,
floatingCursorDisabled: widget.floatingCursorDisabled, floatingCursorDisabled: widget.floatingCursorDisabled,
onImagePaste: widget.onImagePaste, onImagePaste: widget.onImagePaste,
customShortcuts: widget.customShortcuts,
customActions: widget.customActions,
); );
final editor = I18n( final editor = I18n(
@ -511,6 +518,7 @@ class QuillEditorState extends State<QuillEditor>
child: selectionEnabled child: selectionEnabled
? _selectionGestureDetectorBuilder.build( ? _selectionGestureDetectorBuilder.build(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
detectWordBoundary: widget.detectWordBoundary,
child: child, child: child,
) )
: child, : child,
@ -541,7 +549,7 @@ class QuillEditorState extends State<QuillEditor>
EmbedsBuilder? unknownEmbedBuilder, EmbedsBuilder? unknownEmbedBuilder,
) { ) {
final builders = widget.embedBuilders; final builders = widget.embedBuilders;
var _node = node; var _node = node;
// Creates correct node for custom embed // Creates correct node for custom embed
if (node.value.type == BlockEmbed.customType) { if (node.value.type == BlockEmbed.customType) {
@ -555,7 +563,7 @@ class QuillEditorState extends State<QuillEditor>
} }
} }
} }
if (unknownEmbedBuilder != null) { if (unknownEmbedBuilder != null) {
return unknownEmbedBuilder(context, controller, _node, readOnly); return unknownEmbedBuilder(context, controller, _node, readOnly);
} }
@ -584,10 +592,12 @@ class QuillEditorState extends State<QuillEditor>
class _QuillEditorSelectionGestureDetectorBuilder class _QuillEditorSelectionGestureDetectorBuilder
extends EditorTextSelectionGestureDetectorBuilder { extends EditorTextSelectionGestureDetectorBuilder {
_QuillEditorSelectionGestureDetectorBuilder(this._state) _QuillEditorSelectionGestureDetectorBuilder(
: super(delegate: _state); this._state, this._detectWordBoundary)
: super(delegate: _state, detectWordBoundary: _detectWordBoundary);
final QuillEditorState _state; final QuillEditorState _state;
final bool _detectWordBoundary;
@override @override
void onForcePressStart(ForcePressDetails details) { void onForcePressStart(ForcePressDetails details) {
@ -705,9 +715,15 @@ class _QuillEditorSelectionGestureDetectorBuilder
case PointerDeviceKind.unknown: case PointerDeviceKind.unknown:
// On macOS/iOS/iPadOS a touch tap places the cursor at the edge // On macOS/iOS/iPadOS a touch tap places the cursor at the edge
// of the word. // of the word.
renderEditor! if (_detectWordBoundary) {
..selectWordEdge(SelectionChangedCause.tap) renderEditor!
..onSelectionCompleted(); ..selectWordEdge(SelectionChangedCause.tap)
..onSelectionCompleted();
} else {
renderEditor!
..selectPosition(cause: SelectionChangedCause.tap)
..onSelectionCompleted();
}
break; break;
case PointerDeviceKind.trackpad: case PointerDeviceKind.trackpad:
// TODO: Handle this case. // TODO: Handle this case.

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'dart:math' as math; import 'dart:math' as math;
// ignore: unnecessary_import // ignore: unnecessary_import
import 'dart:typed_data'; import 'dart:typed_data';
@ -57,12 +58,7 @@ class RawEditor extends StatefulWidget {
this.readOnly = false, this.readOnly = false,
this.placeholder, this.placeholder,
this.onLaunchUrl, this.onLaunchUrl,
this.toolbarOptions = const ToolbarOptions( this.contextMenuBuilder = defaultContextMenuBuilder,
copy: true,
cut: true,
paste: true,
selectAll: true,
),
this.showSelectionHandles = false, this.showSelectionHandles = false,
bool? showCursor, bool? showCursor,
this.textCapitalization = TextCapitalization.none, this.textCapitalization = TextCapitalization.none,
@ -70,6 +66,8 @@ class RawEditor extends StatefulWidget {
this.minHeight, this.minHeight,
this.maxContentWidth, this.maxContentWidth,
this.customStyles, this.customStyles,
this.customShortcuts,
this.customActions,
this.expands = false, this.expands = false,
this.autoFocus = false, this.autoFocus = false,
this.keyboardAppearance = Brightness.light, this.keyboardAppearance = Brightness.light,
@ -112,11 +110,24 @@ class RawEditor extends StatefulWidget {
/// a link in the document. /// a link in the document.
final ValueChanged<String>? onLaunchUrl; final ValueChanged<String>? onLaunchUrl;
/// Configuration of toolbar options. /// Builds the text selection toolbar when requested by the user.
///
/// See also:
/// * [EditableText.contextMenuBuilder], which builds the default
/// text selection toolbar for [EditableText].
/// ///
/// By default, all options are enabled. If [readOnly] is true, /// If not provided, no context menu will be shown.
/// paste and cut will be disabled regardless. final QuillEditorContextMenuBuilder? contextMenuBuilder;
final ToolbarOptions toolbarOptions;
static Widget defaultContextMenuBuilder(
BuildContext context,
RawEditorState state,
) {
return AdaptiveTextSelectionToolbar.buttonItems(
buttonItems: state.contextMenuButtonItems,
anchors: state.contextMenuAnchors,
);
}
/// Whether to show selection handles. /// Whether to show selection handles.
/// ///
@ -227,6 +238,9 @@ class RawEditor extends StatefulWidget {
final Future<String?> Function(Uint8List imageBytes)? onImagePaste; final Future<String?> Function(Uint8List imageBytes)? onImagePaste;
final Map<LogicalKeySet, Intent>? customShortcuts;
final Map<Type, Action<Intent>>? customActions;
/// Builder function for embeddable objects. /// Builder function for embeddable objects.
final EmbedsBuilder embedBuilder; final EmbedsBuilder embedBuilder;
final LinkActionPickerDelegate linkActionPickerDelegate; final LinkActionPickerDelegate linkActionPickerDelegate;
@ -288,6 +302,74 @@ class RawEditorState extends EditorState
TextDirection get _textDirection => Directionality.of(context); TextDirection get _textDirection => Directionality.of(context);
/// Returns the [ContextMenuButtonItem]s representing the buttons in this
/// platform's default selection menu for [RawEditor].
///
/// Copied from [EditableTextState].
List<ContextMenuButtonItem> get contextMenuButtonItems {
return EditableText.getEditableButtonItems(
clipboardStatus: _clipboardStatus.value,
onCopy: copyEnabled
? () => copySelection(SelectionChangedCause.toolbar)
: null,
onCut:
cutEnabled ? () => cutSelection(SelectionChangedCause.toolbar) : null,
onPaste:
pasteEnabled ? () => pasteText(SelectionChangedCause.toolbar) : null,
onSelectAll: selectAllEnabled
? () => selectAll(SelectionChangedCause.toolbar)
: null,
);
}
/// Returns the anchor points for the default context menu.
///
/// Copied from [EditableTextState].
TextSelectionToolbarAnchors get contextMenuAnchors {
final glyphHeights = _getGlyphHeights();
final selection = textEditingValue.selection;
final points = renderEditor.getEndpointsForSelection(selection);
return TextSelectionToolbarAnchors.fromSelection(
renderBox: renderEditor,
startGlyphHeight: glyphHeights.item1,
endGlyphHeight: glyphHeights.item2,
selectionEndpoints: points,
);
}
/// Gets the line heights at the start and end of the selection for the given
/// [RawEditorState].
///
/// Copied from [EditableTextState].
Tuple2<double, double> _getGlyphHeights() {
final selection = textEditingValue.selection;
// Only calculate handle rects if the text in the previous frame
// is the same as the text in the current frame. This is done because
// widget.renderObject contains the renderEditable from the previous frame.
// If the text changed between the current and previous frames then
// widget.renderObject.getRectForComposingRange might fail. In cases where
// the current frame is different from the previous we fall back to
// renderObject.preferredLineHeight.
final prevText = renderEditor.document.toPlainText();
final currText = textEditingValue.text;
if (prevText != currText || !selection.isValid || selection.isCollapsed) {
return Tuple2(
renderEditor.preferredLineHeight(selection.base),
renderEditor.preferredLineHeight(selection.base),
);
}
final startCharacterRect =
renderEditor.getLocalRectForCaret(selection.base);
final endCharacterRect =
renderEditor.getLocalRectForCaret(selection.extent);
return Tuple2(
startCharacterRect.height,
endCharacterRect.height,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMediaQuery(context));
@ -428,9 +510,14 @@ class RawEditorState extends EditorState
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift,
LogicalKeyboardKey.keyL): const ApplyCheckListIntent(), LogicalKeyboardKey.keyL): const ApplyCheckListIntent(),
if (widget.customShortcuts != null) ...widget.customShortcuts!,
}, },
child: Actions( child: Actions(
actions: _actions, actions: {
..._actions,
if (widget.customActions != null) ...widget.customActions!,
},
child: Focus( child: Focus(
focusNode: widget.focusNode, focusNode: widget.focusNode,
onKey: _onKey, onKey: _onKey,
@ -758,6 +845,9 @@ class RawEditorState extends EditorState
if (isKeyboardOS()) { if (isKeyboardOS()) {
_keyboardVisible = true; _keyboardVisible = true;
} else if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) {
// treat tests like a keyboard OS
_keyboardVisible = true;
} else { } else {
// treat iOS Simulator like a keyboard OS // treat iOS Simulator like a keyboard OS
isIOSSimulator().then((isIosSimulator) { isIOSSimulator().then((isIosSimulator) {
@ -964,13 +1054,15 @@ class RawEditorState extends EditorState
value: textEditingValue, value: textEditingValue,
context: context, context: context,
debugRequiredFor: widget, debugRequiredFor: widget,
toolbarLayerLink: _toolbarLayerLink,
startHandleLayerLink: _startHandleLayerLink, startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink, endHandleLayerLink: _endHandleLayerLink,
renderObject: renderEditor, renderObject: renderEditor,
selectionCtrls: widget.selectionCtrls, selectionCtrls: widget.selectionCtrls,
selectionDelegate: this, selectionDelegate: this,
clipboardStatus: _clipboardStatus, clipboardStatus: _clipboardStatus,
contextMenuBuilder: widget.contextMenuBuilder == null
? null
: (context) => widget.contextMenuBuilder!(context, this),
); );
_selectionOverlay!.handlesVisible = _shouldShowSelectionHandles(); _selectionOverlay!.handlesVisible = _shouldShowSelectionHandles();
_selectionOverlay!.showHandles(); _selectionOverlay!.showHandles();
@ -1431,7 +1523,14 @@ class RawEditorState extends EditorState
@override @override
void performSelector(String selectorName) { void performSelector(String selectorName) {
// TODO: implement performSelector final intent = intentForMacOSSelector(selectorName);
if (intent != null) {
final primaryContext = primaryFocus?.context;
if (primaryContext != null) {
Actions.invoke(primaryContext, intent);
}
}
} }
} }
@ -2316,3 +2415,15 @@ class _ApplyCheckListAction extends Action<ApplyCheckListIntent> {
@override @override
bool get isActionEnabled => true; bool get isActionEnabled => true;
} }
/// Signature for a widget builder that builds a context menu for the given
/// [RawEditorState].
///
/// See also:
///
/// * [EditableTextContextMenuBuilder], which performs the same role for
/// [EditableText]
typedef QuillEditorContextMenuBuilder = Widget Function(
BuildContext context,
RawEditorState rawEditorState,
);

@ -150,14 +150,15 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState
} }
@override @override
bool get cutEnabled => widget.toolbarOptions.cut && !widget.readOnly; bool get cutEnabled => widget.contextMenuBuilder != null && !widget.readOnly;
@override @override
bool get copyEnabled => widget.toolbarOptions.copy; bool get copyEnabled => widget.contextMenuBuilder != null;
@override @override
bool get pasteEnabled => widget.toolbarOptions.paste && !widget.readOnly; bool get pasteEnabled =>
widget.contextMenuBuilder != null && !widget.readOnly;
@override @override
bool get selectAllEnabled => widget.toolbarOptions.selectAll; bool get selectAllEnabled => widget.contextMenuBuilder != null;
} }

@ -69,7 +69,6 @@ class EditorTextSelectionOverlay {
EditorTextSelectionOverlay({ EditorTextSelectionOverlay({
required this.value, required this.value,
required this.context, required this.context,
required this.toolbarLayerLink,
required this.startHandleLayerLink, required this.startHandleLayerLink,
required this.endHandleLayerLink, required this.endHandleLayerLink,
required this.renderObject, required this.renderObject,
@ -77,14 +76,18 @@ class EditorTextSelectionOverlay {
required this.selectionCtrls, required this.selectionCtrls,
required this.selectionDelegate, required this.selectionDelegate,
required this.clipboardStatus, required this.clipboardStatus,
required this.contextMenuBuilder,
this.onSelectionHandleTapped, this.onSelectionHandleTapped,
this.dragStartBehavior = DragStartBehavior.start, this.dragStartBehavior = DragStartBehavior.start,
this.handlesVisible = false, this.handlesVisible = false,
}) { }) {
final overlay = Overlay.of(context, rootOverlay: true); // Clipboard status is only checked on first instance of
// ClipboardStatusNotifier
_toolbarController = AnimationController( // if state has changed after creation, but prior to
duration: const Duration(milliseconds: 150), vsync: overlay); // our listener being created
// we won't know the status unless there is forced update
// i.e. occasionally no paste
clipboardStatus.update();
} }
TextEditingValue value; TextEditingValue value;
@ -114,10 +117,6 @@ class EditorTextSelectionOverlay {
/// Debugging information for explaining why the [Overlay] is required. /// Debugging information for explaining why the [Overlay] is required.
final Widget debugRequiredFor; final Widget debugRequiredFor;
/// The object supplied to the [CompositedTransformTarget] that wraps the text
/// field.
final LayerLink toolbarLayerLink;
/// The objects supplied to the [CompositedTransformTarget] that wraps the /// The objects supplied to the [CompositedTransformTarget] that wraps the
/// location of start selection handle. /// location of start selection handle.
final LayerLink startHandleLayerLink; final LayerLink startHandleLayerLink;
@ -136,6 +135,11 @@ class EditorTextSelectionOverlay {
/// text field. /// text field.
final TextSelectionDelegate selectionDelegate; final TextSelectionDelegate selectionDelegate;
/// {@macro flutter.widgets.EditableText.contextMenuBuilder}
///
/// If not provided, no context menu will be built.
final WidgetBuilder? contextMenuBuilder;
/// Determines the way that drag start behavior is handled. /// Determines the way that drag start behavior is handled.
/// ///
/// If set to [DragStartBehavior.start], handle drag behavior will /// If set to [DragStartBehavior.start], handle drag behavior will
@ -169,7 +173,6 @@ class EditorTextSelectionOverlay {
/// Useful because the actual value of the clipboard can only be checked /// Useful because the actual value of the clipboard can only be checked
/// asynchronously (see [Clipboard.getData]). /// asynchronously (see [Clipboard.getData]).
final ClipboardStatusNotifier clipboardStatus; final ClipboardStatusNotifier clipboardStatus;
late AnimationController _toolbarController;
/// A pair of handles. If this is non-null, there are always 2, though the /// A pair of handles. If this is non-null, there are always 2, though the
/// second is hidden when the selection is collapsed. /// second is hidden when the selection is collapsed.
@ -180,8 +183,6 @@ class EditorTextSelectionOverlay {
TextSelection get _selection => value.selection; TextSelection get _selection => value.selection;
Animation<double> get _toolbarOpacity => _toolbarController.view;
void setHandlesVisible(bool visible) { void setHandlesVisible(bool visible) {
if (handlesVisible == visible) { if (handlesVisible == visible) {
return; return;
@ -212,7 +213,6 @@ class EditorTextSelectionOverlay {
/// To hide the whole overlay, see [hide]. /// To hide the whole overlay, see [hide].
void hideToolbar() { void hideToolbar() {
assert(toolbar != null); assert(toolbar != null);
_toolbarController.stop();
toolbar!.remove(); toolbar!.remove();
toolbar = null; toolbar = null;
} }
@ -220,10 +220,12 @@ class EditorTextSelectionOverlay {
/// Shows the toolbar by inserting it into the [context]'s overlay. /// Shows the toolbar by inserting it into the [context]'s overlay.
void showToolbar() { void showToolbar() {
assert(toolbar == null); assert(toolbar == null);
toolbar = OverlayEntry(builder: _buildToolbar); if (contextMenuBuilder == null) return;
toolbar = OverlayEntry(builder: (context) {
return contextMenuBuilder!(context);
});
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor) Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)
.insert(toolbar!); .insert(toolbar!);
_toolbarController.forward(from: 0);
// make sure handles are visible as well // make sure handles are visible as well
if (_handles == null) { if (_handles == null) {
@ -311,63 +313,6 @@ class EditorTextSelectionOverlay {
..bringIntoView(textPosition); ..bringIntoView(textPosition);
} }
Widget _buildToolbar(BuildContext context) {
// Find the horizontal midpoint, just above the selected text.
List<TextSelectionPoint> endpoints;
try {
// building with an invalid selection with throw an exception
// This happens where the selection has changed, but the toolbar
// hasn't been dismissed yet.
endpoints = renderObject.getEndpointsForSelection(_selection);
} catch (_) {
return Container();
}
final editingRegion = Rect.fromPoints(
renderObject.localToGlobal(Offset.zero),
renderObject.localToGlobal(renderObject.size.bottomRight(Offset.zero)),
);
final baseLineHeight = renderObject.preferredLineHeight(_selection.base);
final extentLineHeight =
renderObject.preferredLineHeight(_selection.extent);
final smallestLineHeight = math.min(baseLineHeight, extentLineHeight);
final isMultiline = endpoints.last.point.dy - endpoints.first.point.dy >
smallestLineHeight / 2;
// If the selected text spans more than 1 line,
// horizontally center the toolbar.
// Derived from both iOS and Android.
final midX = isMultiline
? editingRegion.width / 2
: (endpoints.first.point.dx + endpoints.last.point.dx) / 2;
final midpoint = Offset(
midX,
// The y-coordinate won't be made use of most likely.
endpoints[0].point.dy - baseLineHeight,
);
return FadeTransition(
opacity: _toolbarOpacity,
child: CompositedTransformFollower(
link: toolbarLayerLink,
showWhenUnlinked: false,
offset: -editingRegion.topLeft,
child: selectionCtrls.buildToolbar(
context,
editingRegion,
baseLineHeight,
midpoint,
endpoints,
selectionDelegate,
clipboardStatus,
null),
),
);
}
void markNeedsBuild([Duration? duration]) { void markNeedsBuild([Duration? duration]) {
if (_handles != null) { if (_handles != null) {
_handles![0].markNeedsBuild(); _handles![0].markNeedsBuild();
@ -391,7 +336,6 @@ class EditorTextSelectionOverlay {
/// Final cleanup. /// Final cleanup.
void dispose() { void dispose() {
hide(); hide();
_toolbarController.dispose();
} }
/// Builds the handles by inserting them into the [context]'s overlay. /// Builds the handles by inserting them into the [context]'s overlay.
@ -706,6 +650,7 @@ class EditorTextSelectionGestureDetector extends StatefulWidget {
this.onDragSelectionUpdate, this.onDragSelectionUpdate,
this.onDragSelectionEnd, this.onDragSelectionEnd,
this.behavior, this.behavior,
this.detectWordBoundary = true,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -781,6 +726,8 @@ class EditorTextSelectionGestureDetector extends StatefulWidget {
/// Child below this widget. /// Child below this widget.
final Widget child; final Widget child;
final bool detectWordBoundary;
@override @override
State<StatefulWidget> createState() => State<StatefulWidget> createState() =>
_EditorTextSelectionGestureDetectorState(); _EditorTextSelectionGestureDetectorState();

@ -45,8 +45,10 @@ const double kIconButtonFactor = 1.77;
class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
const QuillToolbar({ const QuillToolbar({
required this.children, required this.children,
this.toolbarHeight = 36, this.axis = Axis.horizontal,
this.toolbarSize = 36,
this.toolbarIconAlignment = WrapAlignment.center, this.toolbarIconAlignment = WrapAlignment.center,
this.toolbarIconCrossAlignment = WrapCrossAlignment.center,
this.toolbarSectionSpacing = 4, this.toolbarSectionSpacing = 4,
this.multiRowsDisplay = true, this.multiRowsDisplay = true,
this.color, this.color,
@ -58,9 +60,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
factory QuillToolbar.basic({ factory QuillToolbar.basic({
required QuillController controller, required QuillController controller,
Axis axis = Axis.horizontal,
double toolbarIconSize = kDefaultIconSize, double toolbarIconSize = kDefaultIconSize,
double toolbarSectionSpacing = 4, double toolbarSectionSpacing = 4,
WrapAlignment toolbarIconAlignment = WrapAlignment.center, WrapAlignment toolbarIconAlignment = WrapAlignment.center,
WrapCrossAlignment toolbarIconCrossAlignment = WrapCrossAlignment.center,
bool showDividers = true, bool showDividers = true,
bool showFontFamily = true, bool showFontFamily = true,
bool showFontSize = true, bool showFontSize = true,
@ -170,10 +174,12 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
return QuillToolbar( return QuillToolbar(
key: key, key: key,
axis: axis,
color: color, color: color,
toolbarHeight: toolbarIconSize * 2, toolbarSize: toolbarIconSize * 2,
toolbarSectionSpacing: toolbarSectionSpacing, toolbarSectionSpacing: toolbarSectionSpacing,
toolbarIconAlignment: toolbarIconAlignment, toolbarIconAlignment: toolbarIconAlignment,
toolbarIconCrossAlignment: toolbarIconCrossAlignment,
multiRowsDisplay: multiRowsDisplay, multiRowsDisplay: multiRowsDisplay,
customButtons: customButtons, customButtons: customButtons,
locale: locale, locale: locale,
@ -334,11 +340,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
isButtonGroupShown[3] || isButtonGroupShown[3] ||
isButtonGroupShown[4] || isButtonGroupShown[4] ||
isButtonGroupShown[5])) isButtonGroupShown[5]))
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showAlignmentButtons) if (showAlignmentButtons)
SelectAlignmentButton( SelectAlignmentButton(
controller: controller, controller: controller,
@ -365,14 +367,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
isButtonGroupShown[3] || isButtonGroupShown[3] ||
isButtonGroupShown[4] || isButtonGroupShown[4] ||
isButtonGroupShown[5])) isButtonGroupShown[5]))
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showHeaderStyle) if (showHeaderStyle)
SelectHeaderStyleButton( SelectHeaderStyleButton(
controller: controller, controller: controller,
axis: axis,
iconSize: toolbarIconSize, iconSize: toolbarIconSize,
iconTheme: iconTheme, iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed, afterButtonPressed: afterButtonPressed,
@ -383,11 +382,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
(isButtonGroupShown[3] || (isButtonGroupShown[3] ||
isButtonGroupShown[4] || isButtonGroupShown[4] ||
isButtonGroupShown[5])) isButtonGroupShown[5]))
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showListNumbers) if (showListNumbers)
ToggleStyleButton( ToggleStyleButton(
attribute: Attribute.ol, attribute: Attribute.ol,
@ -427,11 +422,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
if (showDividers && if (showDividers &&
isButtonGroupShown[3] && isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5])) (isButtonGroupShown[4] || isButtonGroupShown[5]))
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showQuote) if (showQuote)
ToggleStyleButton( ToggleStyleButton(
attribute: Attribute.blockQuote, attribute: Attribute.blockQuote,
@ -460,11 +451,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
afterButtonPressed: afterButtonPressed, afterButtonPressed: afterButtonPressed,
), ),
if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5])
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showLink) if (showLink)
LinkStyleButton( LinkStyleButton(
controller: controller, controller: controller,
@ -483,12 +470,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
afterButtonPressed: afterButtonPressed, afterButtonPressed: afterButtonPressed,
), ),
if (customButtons.isNotEmpty) if (customButtons.isNotEmpty)
if (showDividers) if (showDividers) _dividerOnAxis(axis),
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
for (var customButton in customButtons) for (var customButton in customButtons)
QuillIconButton( QuillIconButton(
highlightElevation: 0, highlightElevation: 0,
@ -503,10 +485,28 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
); );
} }
static Widget _dividerOnAxis(Axis axis) {
if (axis == Axis.horizontal) {
return const VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey,
);
} else {
return const Divider(
indent: 12,
endIndent: 12,
color: Colors.grey,
);
}
}
final List<Widget> children; final List<Widget> children;
final double toolbarHeight; final Axis axis;
final double toolbarSize;
final double toolbarSectionSpacing; final double toolbarSectionSpacing;
final WrapAlignment toolbarIconAlignment; final WrapAlignment toolbarIconAlignment;
final WrapCrossAlignment toolbarIconCrossAlignment;
final bool multiRowsDisplay; final bool multiRowsDisplay;
/// The color of the toolbar. /// The color of the toolbar.
@ -523,7 +523,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
final List<QuillCustomButton> customButtons; final List<QuillCustomButton> customButtons;
@override @override
Size get preferredSize => Size.fromHeight(toolbarHeight); Size get preferredSize => axis == Axis.horizontal
? Size.fromHeight(toolbarSize)
: Size.fromWidth(toolbarSize);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -531,16 +533,23 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
initialLocale: locale, initialLocale: locale,
child: multiRowsDisplay child: multiRowsDisplay
? Wrap( ? Wrap(
direction: axis,
alignment: toolbarIconAlignment, alignment: toolbarIconAlignment,
crossAxisAlignment: toolbarIconCrossAlignment,
runSpacing: 4, runSpacing: 4,
spacing: toolbarSectionSpacing, spacing: toolbarSectionSpacing,
children: children, children: children,
) )
: Container( : Container(
constraints: constraints: BoxConstraints.tightFor(
BoxConstraints.tightFor(height: preferredSize.height), height: axis == Axis.horizontal ? toolbarSize : null,
width: axis == Axis.vertical ? toolbarSize : null,
),
color: color ?? Theme.of(context).canvasColor, color: color ?? Theme.of(context).canvasColor,
child: ArrowIndicatedButtonList(buttons: children), child: ArrowIndicatedButtonList(
axis: axis,
buttons: children,
),
), ),
); );
} }

@ -7,9 +7,13 @@ import 'package:flutter/material.dart';
/// The arrow indicators are automatically hidden if the list is not /// The arrow indicators are automatically hidden if the list is not
/// scrollable in the direction of the respective arrow. /// scrollable in the direction of the respective arrow.
class ArrowIndicatedButtonList extends StatefulWidget { class ArrowIndicatedButtonList extends StatefulWidget {
const ArrowIndicatedButtonList({required this.buttons, Key? key}) const ArrowIndicatedButtonList({
: super(key: key); required this.axis,
required this.buttons,
Key? key,
}) : super(key: key);
final Axis axis;
final List<Widget> buttons; final List<Widget> buttons;
@override @override
@ -20,8 +24,8 @@ class ArrowIndicatedButtonList extends StatefulWidget {
class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList> class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
with WidgetsBindingObserver { with WidgetsBindingObserver {
final ScrollController _controller = ScrollController(); final ScrollController _controller = ScrollController();
bool _showLeftArrow = false; bool _showBackwardArrow = false;
bool _showRightArrow = false; bool _showForwardArrow = false;
@override @override
void initState() { void initState() {
@ -40,13 +44,19 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( final children = <Widget>[
children: <Widget>[ _buildBackwardArrow(),
_buildLeftArrow(), _buildScrollableList(),
_buildScrollableList(), _buildForwardArrow(),
_buildRightColor(), ];
],
); return widget.axis == Axis.horizontal
? Row(
children: children,
)
: Column(
children: children,
);
} }
@override @override
@ -63,20 +73,29 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_showLeftArrow = _showBackwardArrow =
_controller.position.minScrollExtent != _controller.position.pixels; _controller.position.minScrollExtent != _controller.position.pixels;
_showRightArrow = _showForwardArrow =
_controller.position.maxScrollExtent != _controller.position.pixels; _controller.position.maxScrollExtent != _controller.position.pixels;
}); });
} }
Widget _buildLeftArrow() { Widget _buildBackwardArrow() {
IconData? icon;
if (_showBackwardArrow) {
if (widget.axis == Axis.horizontal) {
icon = Icons.arrow_left;
} else {
icon = Icons.arrow_drop_up;
}
}
return SizedBox( return SizedBox(
width: 8, width: 8,
child: Transform.translate( child: Transform.translate(
// Move the icon a few pixels to center it // Move the icon a few pixels to center it
offset: const Offset(-5, 0), offset: const Offset(-5, 0),
child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null, child: icon != null ? Icon(icon, size: 18) : null,
), ),
); );
} }
@ -87,18 +106,24 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
// Remove the glowing effect, as we already have the arrow indicators // Remove the glowing effect, as we already have the arrow indicators
behavior: _NoGlowBehavior(), behavior: _NoGlowBehavior(),
// The CustomScrollView is necessary so that the children are not // The CustomScrollView is necessary so that the children are not
// stretched to the height of the toolbar, https://bit.ly/3uC3bjI // stretched to the height of the toolbar:
// https://stackoverflow.com/a/65998731/7091839
child: CustomScrollView( child: CustomScrollView(
scrollDirection: Axis.horizontal, scrollDirection: widget.axis,
controller: _controller, controller: _controller,
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
slivers: [ slivers: [
SliverFillRemaining( SliverFillRemaining(
hasScrollBody: false, hasScrollBody: false,
child: Row( child: widget.axis == Axis.horizontal
mainAxisAlignment: MainAxisAlignment.spaceEvenly, ? Row(
children: widget.buttons, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
), children: widget.buttons,
)
: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: widget.buttons,
),
) )
], ],
), ),
@ -106,13 +131,22 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
); );
} }
Widget _buildRightColor() { Widget _buildForwardArrow() {
IconData? icon;
if (_showForwardArrow) {
if (widget.axis == Axis.horizontal) {
icon = Icons.arrow_right;
} else {
icon = Icons.arrow_drop_down;
}
}
return SizedBox( return SizedBox(
width: 8, width: 8,
child: Transform.translate( child: Transform.translate(
// Move the icon a few pixels to center it // Move the icon a few pixels to center it
offset: const Offset(-5, 0), offset: const Offset(-5, 0),
child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null, child: icon != null ? Icon(icon, size: 18) : null,
), ),
); );
} }

@ -10,6 +10,7 @@ import '../toolbar.dart';
class SelectHeaderStyleButton extends StatefulWidget { class SelectHeaderStyleButton extends StatefulWidget {
const SelectHeaderStyleButton({ const SelectHeaderStyleButton({
required this.controller, required this.controller,
this.axis = Axis.horizontal,
this.iconSize = kDefaultIconSize, this.iconSize = kDefaultIconSize,
this.iconTheme, this.iconTheme,
this.attributes = const [ this.attributes = const [
@ -23,6 +24,7 @@ class SelectHeaderStyleButton extends StatefulWidget {
}) : super(key: key); }) : super(key: key);
final QuillController controller; final QuillController controller;
final Axis axis;
final double iconSize; final double iconSize;
final QuillIconTheme? iconTheme; final QuillIconTheme? iconTheme;
final List<Attribute> attributes; final List<Attribute> attributes;
@ -67,53 +69,60 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
fontSize: widget.iconSize * 0.7, fontSize: widget.iconSize * 0.7,
); );
return Row( final children = widget.attributes.map((attribute) {
mainAxisSize: MainAxisSize.min, final isSelected = _selectedAttribute == attribute;
children: widget.attributes.map((attribute) { return Padding(
final isSelected = _selectedAttribute == attribute; // ignore: prefer_const_constructors
return Padding( padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0),
// ignore: prefer_const_constructors child: ConstrainedBox(
padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), constraints: BoxConstraints.tightFor(
child: ConstrainedBox( width: widget.iconSize * kIconButtonFactor,
constraints: BoxConstraints.tightFor( height: widget.iconSize * kIconButtonFactor,
width: widget.iconSize * kIconButtonFactor, ),
height: widget.iconSize * kIconButtonFactor, child: RawMaterialButton(
), hoverElevation: 0,
child: RawMaterialButton( highlightElevation: 0,
hoverElevation: 0, elevation: 0,
highlightElevation: 0, visualDensity: VisualDensity.compact,
elevation: 0, shape: RoundedRectangleBorder(
visualDensity: VisualDensity.compact, borderRadius:
shape: RoundedRectangleBorder( BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)),
borderRadius: BorderRadius.circular( fillColor: isSelected
widget.iconTheme?.borderRadius ?? 2)), ? (widget.iconTheme?.iconSelectedFillColor ??
fillColor: isSelected Theme.of(context).primaryColor)
? (widget.iconTheme?.iconSelectedFillColor ?? : (widget.iconTheme?.iconUnselectedFillColor ??
Theme.of(context).primaryColor) theme.canvasColor),
: (widget.iconTheme?.iconUnselectedFillColor ?? onPressed: () {
theme.canvasColor), final _attribute = _selectedAttribute == attribute
onPressed: () { ? Attribute.header
final _attribute = _selectedAttribute == attribute : attribute;
? Attribute.header widget.controller.formatSelection(_attribute);
: attribute; widget.afterButtonPressed?.call();
widget.controller.formatSelection(_attribute); },
widget.afterButtonPressed?.call(); child: Text(
}, _valueToText[attribute] ?? '',
child: Text( style: style.copyWith(
_valueToText[attribute] ?? '', color: isSelected
style: style.copyWith( ? (widget.iconTheme?.iconSelectedColor ??
color: isSelected theme.primaryIconTheme.color)
? (widget.iconTheme?.iconSelectedColor ?? : (widget.iconTheme?.iconUnselectedColor ??
theme.primaryIconTheme.color) theme.iconTheme.color),
: (widget.iconTheme?.iconUnselectedColor ??
theme.iconTheme.color),
),
), ),
), ),
), ),
); ),
}).toList(), );
); }).toList();
return widget.axis == Axis.horizontal
? Row(
mainAxisSize: MainAxisSize.min,
children: children,
)
: Column(
mainAxisSize: MainAxisSize.min,
children: children,
);
} }
void _didChangeEditingValue() { void _didChangeEditingValue() {

@ -1,6 +1,6 @@
name: flutter_quill name: flutter_quill
description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us)
version: 6.3.1 version: 6.4.4
#author: bulletjournal #author: bulletjournal
homepage: https://bulletjournal.us/home/index.html homepage: https://bulletjournal.us/home/index.html
repository: https://github.com/singerdmx/flutter-quill repository: https://github.com/singerdmx/flutter-quill
@ -12,17 +12,17 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
collection: ^1.16.0 collection: ^1.17.0
flutter_colorpicker: ^1.0.3 flutter_colorpicker: ^1.0.3
flutter_keyboard_visibility: ^5.2.0 flutter_keyboard_visibility: ^5.4.0
quiver: ^3.1.0 quiver: ^3.2.1
tuple: ^2.0.0 tuple: ^2.0.1
url_launcher: ^6.1.2 url_launcher: ^6.1.9
pedantic: ^1.11.1 pedantic: ^1.11.1
characters: ^1.2.0 characters: ^1.2.1
diff_match_patch: ^0.4.1 diff_match_patch: ^0.4.1
i18n_extension: ^6.0.0 i18n_extension: ^7.0.0
device_info_plus: ^8.0.0 device_info_plus: ^8.1.0
platform: ^3.1.0 platform: ^3.1.0
pasteboard: ^0.2.0 pasteboard: ^0.2.0

Loading…
Cancel
Save