From dd5109d973460b9379540e54f70675be8a473b39 Mon Sep 17 00:00:00 2001 From: Ellet Date: Wed, 12 Jun 2024 17:17:50 +0300 Subject: [PATCH] chore: provide an option to use the legacy search dialog and button, two todos, remove outdated comments, import missing files to fix docs --- .../lib/screens/quill/my_quill_toolbar.dart | 20 +- .../simple_toolbar_configurations.dart | 18 ++ lib/src/widgets/toolbar/base_toolbar.dart | 1 + .../search/legacy/legacy_search_button.dart | 136 +++++++++++ .../search/legacy/legacy_search_dialog.dart | 230 ++++++++++++++++++ .../toolbar/buttons/search/search_button.dart | 8 +- lib/src/widgets/toolbar/simple_toolbar.dart | 29 +-- 7 files changed, 399 insertions(+), 43 deletions(-) create mode 100644 lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_button.dart create mode 100644 lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_dialog.dart diff --git a/example/lib/screens/quill/my_quill_toolbar.dart b/example/lib/screens/quill/my_quill_toolbar.dart index 7e177956..7d88f914 100644 --- a/example/lib/screens/quill/my_quill_toolbar.dart +++ b/example/lib/screens/quill/my_quill_toolbar.dart @@ -222,25 +222,7 @@ class MyQuillToolbar extends StatelessWidget { '35': '35.0', '40': '40.0' }, - // headerStyleType: HeaderStyleType.buttons, - // buttonOptions: QuillSimpleToolbarButtonOptions( - // base: QuillToolbarBaseButtonOptions( - // afterButtonPressed: focusNode.requestFocus, - // // iconSize: 20, - // iconTheme: QuillIconTheme( - // iconButtonSelectedData: IconButtonData( - // style: IconButton.styleFrom( - // foregroundColor: Colors.blue, - // ), - // ), - // iconButtonUnselectedData: IconButtonData( - // style: IconButton.styleFrom( - // foregroundColor: Colors.red, - // ), - // ), - // ), - // ), - //), + searchButtonType: SearchButtonType.modern, customButtons: [ QuillToolbarCustomButtonOptions( icon: const Icon(Icons.add_alarm_rounded), diff --git a/lib/src/models/config/toolbar/simple_toolbar_configurations.dart b/lib/src/models/config/toolbar/simple_toolbar_configurations.dart index 89175980..6df75977 100644 --- a/lib/src/models/config/toolbar/simple_toolbar_configurations.dart +++ b/lib/src/models/config/toolbar/simple_toolbar_configurations.dart @@ -4,6 +4,12 @@ import 'package:flutter/widgets.dart' import '../../../widgets/quill/embeds.dart'; import '../../../widgets/quill/quill_controller.dart'; +import '../../../widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart'; +import '../../../widgets/toolbar/buttons/hearder_style/select_header_style_dropdown_button.dart'; +import '../../../widgets/toolbar/buttons/link_style2_button.dart'; +import '../../../widgets/toolbar/buttons/link_style_button.dart'; +import '../../../widgets/toolbar/buttons/search/legacy/legacy_search_button.dart'; +import '../../../widgets/toolbar/buttons/search/search_button.dart'; import '../../themes/quill_dialog_theme.dart'; import '../../themes/quill_icon_theme.dart'; import 'simple_toolbar_button_options.dart'; @@ -62,6 +68,14 @@ enum HeaderStyleType { bool get isButtons => this == HeaderStyleType.buttons; } +enum SearchButtonType { + /// Will use [QuillToolbarSearchButton] + legacy, + + /// Will use [QuillToolbarLegacySearchButton] + modern, +} + /// The configurations for the toolbar widget of flutter quill @immutable class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties { @@ -112,6 +126,7 @@ class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties { this.showClipboardPaste = true, this.linkStyleType = LinkStyleType.original, this.headerStyleType = HeaderStyleType.original, + this.searchButtonType = SearchButtonType.modern, /// The decoration to use for the toolbar. super.decoration, @@ -218,6 +233,9 @@ class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties { /// Defines which dialog is used for applying header attribute. final HeaderStyleType headerStyleType; + /// Define which button type should be used for the [showSearchButton] + final SearchButtonType searchButtonType; + @override List get props => [ buttonOptions, diff --git a/lib/src/widgets/toolbar/base_toolbar.dart b/lib/src/widgets/toolbar/base_toolbar.dart index e9ee529e..14e1b3eb 100644 --- a/lib/src/widgets/toolbar/base_toolbar.dart +++ b/lib/src/widgets/toolbar/base_toolbar.dart @@ -22,6 +22,7 @@ export 'buttons/indent_button.dart'; export 'buttons/link_style2_button.dart'; export 'buttons/link_style_button.dart'; export 'buttons/quill_icon_button.dart'; +export 'buttons/search/legacy/legacy_search_button.dart'; export 'buttons/search/search_button.dart'; export 'buttons/toggle_check_list_button.dart'; export 'buttons/toggle_style_button.dart'; diff --git a/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_button.dart b/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_button.dart new file mode 100644 index 00000000..fd86303d --- /dev/null +++ b/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_button.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; + +import '../../../../../extensions/quill_configurations_ext.dart'; +import '../../../../../l10n/extensions/localizations.dart'; +import '../../../../../l10n/widgets/localizations.dart'; +import '../../../../../models/themes/quill_dialog_theme.dart'; +import '../../../../../models/themes/quill_icon_theme.dart'; +import '../../../../quill/quill_controller.dart'; +import '../../../base_toolbar.dart'; +import 'legacy_search_dialog.dart'; + +/// We suggest to see [QuillToolbarSearchButton] before using this widget. +class QuillToolbarLegacySearchButton extends StatelessWidget { + const QuillToolbarLegacySearchButton({ + required QuillController controller, + this.options = const QuillToolbarSearchButtonOptions(), + super.key, + }) : _controller = controller; + + final QuillController _controller; + final QuillToolbarSearchButtonOptions options; + + QuillController get controller { + return _controller; + } + + // TODO: The logic is common and can be extracted + + double _iconSize(BuildContext context) { + final baseFontSize = baseButtonExtraOptions(context)?.iconSize; + final iconSize = options.iconSize; + return iconSize ?? baseFontSize ?? kDefaultIconSize; + } + + double _iconButtonFactor(BuildContext context) { + final baseIconFactor = baseButtonExtraOptions(context)?.iconButtonFactor; + final iconButtonFactor = options.iconButtonFactor; + return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; + } + + VoidCallback? _afterButtonPressed(BuildContext context) { + return options.afterButtonPressed ?? + baseButtonExtraOptions(context)?.afterButtonPressed; + } + + QuillIconTheme? _iconTheme(BuildContext context) { + return options.iconTheme ?? baseButtonExtraOptions(context)?.iconTheme; + } + + QuillToolbarBaseButtonOptions? baseButtonExtraOptions(BuildContext context) { + return context.quillToolbarBaseButtonOptions; + } + + IconData _iconData(BuildContext context) { + return options.iconData ?? + baseButtonExtraOptions(context)?.iconData ?? + Icons.search; + } + + String _tooltip(BuildContext context) { + return options.tooltip ?? + baseButtonExtraOptions(context)?.tooltip ?? + (context.loc.search); + } + + Color _dialogBarrierColor(BuildContext context) { + return options.dialogBarrierColor ?? + context.quillSharedConfigurations?.dialogBarrierColor ?? + Colors.black54; + } + + QuillDialogTheme? _dialogTheme(BuildContext context) { + return options.dialogTheme ?? + context.quillSharedConfigurations?.dialogTheme; + } + + @override + Widget build(BuildContext context) { + final iconTheme = _iconTheme(context); + final tooltip = _tooltip(context); + final iconData = _iconData(context); + final iconSize = _iconSize(context); + final iconButtonFactor = _iconButtonFactor(context); + final afterButtonPressed = _afterButtonPressed(context); + + final childBuilder = + options.childBuilder ?? baseButtonExtraOptions(context)?.childBuilder; + + if (childBuilder != null) { + return childBuilder( + options, + QuillToolbarSearchButtonExtraOptions( + controller: controller, + context: context, + onPressed: () { + _sharedOnPressed(context); + afterButtonPressed?.call(); + }, + ), + ); + } + + return QuillToolbarIconButton( + tooltip: tooltip, + icon: Icon( + iconData, + size: iconSize * iconButtonFactor, + ), + isSelected: false, + onPressed: () => _sharedOnPressed(context), + afterPressed: afterButtonPressed, + iconTheme: iconTheme, + ); + } + + Future _sharedOnPressed(BuildContext context) async { + final customCallback = options.customOnPressedCallback; + if (customCallback != null) { + await customCallback( + controller, + ); + return; + } + await showDialog( + barrierColor: _dialogBarrierColor(context), + context: context, + builder: (_) => FlutterQuillLocalizationsWidget( + child: QuillToolbarLegacySearchDialog( + controller: controller, + dialogTheme: _dialogTheme(context), + text: '', + ), + ), + ); + } +} diff --git a/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_dialog.dart b/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_dialog.dart new file mode 100644 index 00000000..de92e27f --- /dev/null +++ b/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_dialog.dart @@ -0,0 +1,230 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../translations.dart'; +import '../../../../../models/documents/document.dart'; +import '../../../../../models/themes/quill_dialog_theme.dart'; +import '../../../../quill/quill_controller.dart'; + +class QuillToolbarLegacySearchDialog extends StatefulWidget { + const QuillToolbarLegacySearchDialog({ + required this.controller, + this.dialogTheme, + this.text, + super.key, + }); + + final QuillController controller; + final QuillDialogTheme? dialogTheme; + final String? text; + + @override + QuillToolbarLegacySearchDialogState createState() => + QuillToolbarLegacySearchDialogState(); +} + +class QuillToolbarLegacySearchDialogState + extends State { + late String _text; + late TextEditingController _controller; + late List? _offsets; + late int _index; + bool _caseSensitive = false; + bool _wholeWord = false; + + @override + void initState() { + super.initState(); + _text = widget.text ?? ''; + _offsets = null; + _index = 0; + _controller = TextEditingController(text: _text); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var matchShown = ''; + if (_offsets != null) { + if (_offsets!.isEmpty) { + matchShown = '0/0'; + } else { + matchShown = '${_index + 1}/${_offsets!.length}'; + } + } + + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + backgroundColor: widget.dialogTheme?.dialogBackgroundColor, + alignment: Alignment.bottomCenter, + insetPadding: EdgeInsets.zero, + child: FlutterQuillLocalizationsWidget( + child: Builder( + builder: (context) { + return SizedBox( + height: 45, + child: Row( + children: [ + Tooltip( + message: context.loc.caseSensitivityAndWholeWordSearch, + child: ToggleButtons( + onPressed: (index) { + if (index == 0) { + _changeCaseSensitivity(); + } else if (index == 1) { + _changeWholeWord(); + } + }, + borderRadius: const BorderRadius.all(Radius.circular(2)), + isSelected: [_caseSensitive, _wholeWord], + children: const [ + Text( + '\u0391\u03b1', + style: TextStyle( + fontFamily: 'MaterialIcons', + fontSize: 24, + ), + ), + Text( + '\u201c\u2026\u201d', + style: TextStyle( + fontFamily: 'MaterialIcons', + fontSize: 24, + ), + ), + ], + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 12, left: 5), + child: TextField( + style: widget.dialogTheme?.inputTextStyle, + decoration: InputDecoration( + isDense: true, + suffixText: (_offsets != null) ? matchShown : '', + suffixStyle: widget.dialogTheme?.labelTextStyle, + ), + autofocus: true, + onChanged: _textChanged, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + onEditingComplete: _findText, + controller: _controller, + ), + ), + ), + if (_offsets == null) + IconButton( + icon: const Icon(Icons.search), + tooltip: context.loc.findText, + onPressed: _findText, + ), + if (_offsets != null) + IconButton( + icon: const Icon(Icons.keyboard_arrow_up), + tooltip: context.loc.moveToPreviousOccurrence, + onPressed: + (_offsets!.isNotEmpty) ? _moveToPrevious : null, + ), + if (_offsets != null) + IconButton( + icon: const Icon(Icons.keyboard_arrow_down), + tooltip: context.loc.moveToNextOccurrence, + onPressed: (_offsets!.isNotEmpty) ? _moveToNext : null, + ), + ], + ), + ); + }, + ), + ), + ); + } + + void _findText() { + _text = _controller.text; + if (_text.isEmpty) { + return; + } + setState(() { + _offsets = widget.controller.document.search( + _text, + caseSensitive: _caseSensitive, + wholeWord: _wholeWord, + ); + _index = 0; + }); + if (_offsets!.isNotEmpty) { + _moveToPosition(); + } + } + + void _moveToPosition() { + widget.controller.updateSelection( + TextSelection( + baseOffset: _offsets![_index], + extentOffset: _offsets![_index] + _text.length, + ), + ChangeSource.local, + ); + } + + void _moveToPrevious() { + if (_offsets!.isEmpty) { + return; + } + setState(() { + if (_index > 0) { + _index -= 1; + } else { + _index = _offsets!.length - 1; + } + }); + _moveToPosition(); + } + + void _moveToNext() { + if (_offsets!.isEmpty) { + return; + } + setState(() { + if (_index < _offsets!.length - 1) { + _index += 1; + } else { + _index = 0; + } + }); + _moveToPosition(); + } + + void _textChanged(String value) { + setState(() { + _text = value; + _offsets = null; + _index = 0; + }); + } + + void _changeCaseSensitivity() { + setState(() { + _caseSensitive = !_caseSensitive; + _offsets = null; + _index = 0; + }); + } + + void _changeWholeWord() { + setState(() { + _wholeWord = !_wholeWord; + _offsets = null; + _index = 0; + }); + } +} diff --git a/lib/src/widgets/toolbar/buttons/search/search_button.dart b/lib/src/widgets/toolbar/buttons/search/search_button.dart index ade8edce..05f7779b 100644 --- a/lib/src/widgets/toolbar/buttons/search/search_button.dart +++ b/lib/src/widgets/toolbar/buttons/search/search_button.dart @@ -22,6 +22,8 @@ class QuillToolbarSearchButton extends StatelessWidget { return _controller; } + // TODO: The logic is common and can be extracted + double _iconSize(BuildContext context) { final baseFontSize = baseButtonExtraOptions(context)?.iconSize; final iconSize = options.iconSize; @@ -94,17 +96,11 @@ class QuillToolbarSearchButton extends StatelessWidget { ); } - // final theme = Theme.of(context); - - // final iconColor = - // iconTheme?.iconUnselectedFillColor ?? theme.iconTheme.color; - return QuillToolbarIconButton( tooltip: tooltip, icon: Icon( iconData, size: iconSize * iconButtonFactor, - // color: iconColor, ), isSelected: false, onPressed: () => _sharedOnPressed(context), diff --git a/lib/src/widgets/toolbar/simple_toolbar.dart b/lib/src/widgets/toolbar/simple_toolbar.dart index 789717be..8885fec7 100644 --- a/lib/src/widgets/toolbar/simple_toolbar.dart +++ b/lib/src/widgets/toolbar/simple_toolbar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../../../flutter_quill.dart'; import '../../extensions/quill_configurations_ext.dart'; import '../../models/config/toolbar/toolbar_configurations.dart'; import '../../models/documents/attribute.dart'; @@ -277,10 +278,16 @@ class QuillSimpleToolbar extends StatelessWidget options: toolbarConfigurations.buttonOptions.linkStyle2, ), if (configurations.showSearchButton) - QuillToolbarSearchButton( - controller: globalController, - options: toolbarConfigurations.buttonOptions.search, - ), + switch (configurations.searchButtonType) { + SearchButtonType.legacy => QuillToolbarLegacySearchButton( + controller: globalController, + options: toolbarConfigurations.buttonOptions.search, + ), + SearchButtonType.modern => QuillToolbarSearchButton( + controller: globalController, + options: toolbarConfigurations.buttonOptions.search, + ), + }, if (configurations.showClipboardCut) QuillToolbarClipboardButton( options: toolbarConfigurations.buttonOptions.clipboardCut, @@ -306,20 +313,6 @@ class QuillSimpleToolbar extends StatelessWidget options: customButton, controller: globalController, ), - // if (customButton.child != null) ...[ - // InkWell( - // onTap: customButton.onTap, - // child: customButton.child, - // ), - // ] else ...[ - // QuillToolbarCustomButton( - // options: - // toolbarConfigurations.buttonOptions.customButtons, - // controller: toolbarConfigurations - // .buttonOptions.customButtons.controller ?? - // globalController, - // ), - // ], ], ]; }