diff --git a/example/assets/fonts/SF-Pro-Display-Regular.otf b/example/assets/fonts/SF-Pro-Display-Regular.otf deleted file mode 100755 index 1279121f..00000000 Binary files a/example/assets/fonts/SF-Pro-Display-Regular.otf and /dev/null differ diff --git a/example/lib/gen/fonts.gen.dart b/example/lib/gen/fonts.gen.dart index f61cfa18..5a0580f1 100644 --- a/example/lib/gen/fonts.gen.dart +++ b/example/lib/gen/fonts.gen.dart @@ -10,9 +10,6 @@ class FontFamily { FontFamily._(); - /// Font family: SF-UI-Display - static const String sFUIDisplay = 'SF-UI-Display'; - /// Font family: ibarra-real-nova static const String ibarraRealNova = 'ibarra-real-nova'; diff --git a/example/lib/screens/quill/my_quill_editor.dart b/example/lib/screens/quill/my_quill_editor.dart index 21791c7c..a0617f74 100644 --- a/example/lib/screens/quill/my_quill_editor.dart +++ b/example/lib/screens/quill/my_quill_editor.dart @@ -54,11 +54,9 @@ class MyQuillEditor extends StatelessWidget { ), sizeSmall: TextStyle(fontSize: 9), subscript: TextStyle( - fontFamily: 'SF-UI-Display', fontFeatures: [FontFeature.subscripts()], ), superscript: TextStyle( - fontFamily: 'SF-UI-Display', fontFeatures: [FontFeature.superscripts()], ), ), 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/example/pubspec.yaml b/example/pubspec.yaml index 2c979d07..06bfbf14 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -108,8 +108,5 @@ flutter: - family: roboto-mono fonts: - asset: assets/fonts/RobotoMono-Regular.ttf - - family: SF-UI-Display - fonts: - - asset: assets/fonts/SF-Pro-Display-Regular.otf flutter_gen: \ No newline at end of file diff --git a/lib/src/models/config/toolbar/buttons/search_configurations.dart b/lib/src/models/config/toolbar/buttons/search_configurations.dart index e110c3ea..8d791fcc 100644 --- a/lib/src/models/config/toolbar/buttons/search_configurations.dart +++ b/lib/src/models/config/toolbar/buttons/search_configurations.dart @@ -1,4 +1,4 @@ -import 'package:flutter/widgets.dart' show Color; +import 'package:flutter/material.dart'; import '../../../../../flutter_quill.dart'; @@ -24,6 +24,7 @@ class QuillToolbarSearchButtonOptions extends QuillToolbarBaseButtonOptions< super.iconButtonFactor, this.dialogBarrierColor, this.customOnPressedCallback, + this.searchBarAlignment, }); final QuillDialogTheme? dialogTheme; @@ -34,6 +35,8 @@ class QuillToolbarSearchButtonOptions extends QuillToolbarBaseButtonOptions< /// By default we will show simple search dialog ui /// you can pass value to this callback to change this final QuillToolbarSearchButtonOnPressedCallback? customOnPressedCallback; + + final AlignmentGeometry? searchBarAlignment; } typedef QuillToolbarSearchButtonOnPressedCallback = Future Function( 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/raw_editor/raw_editor_actions.dart b/lib/src/widgets/raw_editor/raw_editor_actions.dart index 40580d70..80774d20 100644 --- a/lib/src/widgets/raw_editor/raw_editor_actions.dart +++ b/lib/src/widgets/raw_editor/raw_editor_actions.dart @@ -462,7 +462,6 @@ class QuillEditorOpenSearchAction extends ContextAction { context: context, builder: (_) => QuillToolbarSearchDialog( controller: state.controller, - text: '', ), ); } 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 c063c6d6..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; @@ -60,9 +62,7 @@ class QuillToolbarSearchButton extends StatelessWidget { } Color _dialogBarrierColor(BuildContext context) { - return options.dialogBarrierColor ?? - context.quillSharedConfigurations?.dialogBarrierColor ?? - Colors.black54; + return options.dialogBarrierColor ?? Colors.transparent; } QuillDialogTheme? _dialogTheme(BuildContext context) { @@ -96,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), @@ -130,7 +124,7 @@ class QuillToolbarSearchButton extends StatelessWidget { child: QuillToolbarSearchDialog( controller: controller, dialogTheme: _dialogTheme(context), - text: '', + searchBarAlignment: options.searchBarAlignment, ), ), ); diff --git a/lib/src/widgets/toolbar/buttons/search/search_dialog.dart b/lib/src/widgets/toolbar/buttons/search/search_dialog.dart index 7d28d413..19e041ef 100644 --- a/lib/src/widgets/toolbar/buttons/search/search_dialog.dart +++ b/lib/src/widgets/toolbar/buttons/search/search_dialog.dart @@ -1,5 +1,8 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import '../../../../../extensions.dart'; import '../../../../l10n/extensions/localizations.dart'; import '../../../../l10n/widgets/localizations.dart'; import '../../../../models/documents/document.dart'; @@ -44,6 +47,7 @@ class QuillToolbarSearchDialog extends StatefulWidget { this.dialogTheme, this.text, this.childBuilder, + this.searchBarAlignment, super.key, }); @@ -51,6 +55,7 @@ class QuillToolbarSearchDialog extends StatefulWidget { final QuillDialogTheme? dialogTheme; final String? text; final QuillToolbarSearchDialogChildBuilder? childBuilder; + final AlignmentGeometry? searchBarAlignment; @override QuillToolbarSearchDialogState createState() => @@ -58,39 +63,30 @@ class QuillToolbarSearchDialog extends StatefulWidget { } class QuillToolbarSearchDialogState extends State { + final TextEditingController _textController = TextEditingController(); late String _text; - late TextEditingController _controller; - late List? _offsets; - late int _index; + List _offsets = []; + int _index = 0; bool _caseSensitive = false; bool _wholeWord = false; + bool _searchSettingsUnfolded = false; + Timer? _searchTimer; @override void initState() { super.initState(); _text = widget.text ?? ''; - _offsets = null; - _index = 0; - _controller = TextEditingController(text: _text); } @override void dispose() { - _controller.dispose(); + _textController.dispose(); + _searchTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { - var matchShown = ''; - if (_offsets != null) { - if (_offsets!.isEmpty) { - matchShown = '0/0'; - } else { - matchShown = '${_index + 1}/${_offsets!.length}'; - } - } - final childBuilder = widget.childBuilder; if (childBuilder != null) { return childBuilder( @@ -99,7 +95,7 @@ class QuillToolbarSearchDialogState extends State { onEditingComplete: _findText, onTextChanged: _textChanged, caseSensitive: _caseSensitive, - textEditingController: _controller, + textEditingController: _textController, index: _index, offsets: _offsets, text: _text, @@ -110,90 +106,139 @@ class QuillToolbarSearchDialogState extends State { ); } + final searchBarAlignment = + widget.searchBarAlignment ?? Alignment.bottomCenter; + final searchBarAtBottom = (searchBarAlignment == Alignment.bottomCenter) || + (searchBarAlignment == Alignment.bottomLeft) || + (searchBarAlignment == Alignment.bottomRight); + final addBottomPadding = searchBarAtBottom && isMobile(supportWeb: true); + var matchShown = ''; + if (_text.isNotEmpty) { + if (_offsets.isEmpty) { + matchShown = '0/0'; + } else { + matchShown = '${_index + 1}/${_offsets.length}'; + } + } + + final searchBar = Container( + height: addBottomPadding ? 50 : 45, + padding: addBottomPadding ? const EdgeInsets.only(bottom: 12) : null, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.close), + tooltip: context.loc.close, + visualDensity: VisualDensity.compact, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + IconButton( + icon: const Icon(Icons.more_vert), + isSelected: _caseSensitive || _wholeWord, + tooltip: context.loc.searchSettings, + visualDensity: VisualDensity.compact, + onPressed: () { + setState(() { + _searchSettingsUnfolded = !_searchSettingsUnfolded; + }); + }, + ), + Expanded( + child: TextField( + style: widget.dialogTheme?.inputTextStyle, + decoration: InputDecoration( + isDense: true, + suffixText: matchShown, + suffixStyle: widget.dialogTheme?.labelTextStyle, + ), + autofocus: true, + onChanged: _textChanged, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + controller: _textController, + ), + ), + IconButton( + icon: const Icon(Icons.keyboard_arrow_up), + tooltip: context.loc.moveToPreviousOccurrence, + onPressed: (_offsets.isNotEmpty) ? _moveToPrevious : null, + ), + IconButton( + icon: const Icon(Icons.keyboard_arrow_down), + tooltip: context.loc.moveToNextOccurrence, + onPressed: (_offsets.isNotEmpty) ? _moveToNext : null, + ), + ], + ), + ); + + final searchSettings = SizedBox( + height: 45, + child: Row( + children: [ + Expanded( + child: CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + visualDensity: VisualDensity.compact, + contentPadding: EdgeInsets.zero, + title: Text( + context.loc.caseSensitive, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + value: _caseSensitive, + onChanged: (value) { + setState(() { + _caseSensitive = value!; + _findText(); + }); + }, + ), + ), + Expanded( + child: CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + visualDensity: VisualDensity.compact, + contentPadding: EdgeInsets.zero, + title: Text( + context.loc.wholeWord, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + value: _wholeWord, + onChanged: (value) { + setState(() { + _wholeWord = value!; + _findText(); + }); + }, + ), + ), + ], + ), + ); + return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), ), backgroundColor: widget.dialogTheme?.dialogBackgroundColor, - alignment: Alignment.bottomCenter, + alignment: searchBarAlignment, 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, - ), - ], - ), + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (_searchSettingsUnfolded && searchBarAtBottom) + searchSettings, + searchBar, + if (_searchSettingsUnfolded && !searchBarAtBottom) + searchSettings, + ], ); }, ), @@ -201,9 +246,30 @@ class QuillToolbarSearchDialogState extends State { ); } + void _textChanged(String text) { + _text = text; + if (_searchTimer?.isActive ?? false) { + _searchTimer?.cancel(); + } + _searchTimer = Timer( + const Duration(milliseconds: 300), + _findText, + ); + } + void _findText() { - _text = _controller.text; if (_text.isEmpty) { + setState(() { + _offsets = []; + _index = 0; + widget.controller.updateSelection( + TextSelection( + baseOffset: widget.controller.selection.baseOffset, + extentOffset: widget.controller.selection.baseOffset, + ), + ChangeSource.local, + ); + }); return; } setState(() { @@ -214,7 +280,7 @@ class QuillToolbarSearchDialogState extends State { ); _index = 0; }); - if (_offsets!.isNotEmpty) { + if (_offsets.isNotEmpty) { _moveToPosition(); } } @@ -222,33 +288,33 @@ class QuillToolbarSearchDialogState extends State { void _moveToPosition() { widget.controller.updateSelection( TextSelection( - baseOffset: _offsets![_index], - extentOffset: _offsets![_index] + _text.length, + baseOffset: _offsets[_index], + extentOffset: _offsets[_index] + _text.length, ), ChangeSource.local, ); } void _moveToPrevious() { - if (_offsets!.isEmpty) { + if (_offsets.isEmpty) { return; } setState(() { if (_index > 0) { _index -= 1; } else { - _index = _offsets!.length - 1; + _index = _offsets.length - 1; } }); _moveToPosition(); } void _moveToNext() { - if (_offsets!.isEmpty) { + if (_offsets.isEmpty) { return; } setState(() { - if (_index < _offsets!.length - 1) { + if (_index < _offsets.length - 1) { _index += 1; } else { _index = 0; @@ -256,28 +322,4 @@ class QuillToolbarSearchDialogState extends State { }); _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/simple_toolbar.dart b/lib/src/widgets/toolbar/simple_toolbar.dart index 789717be..b4d986a0 100644 --- a/lib/src/widgets/toolbar/simple_toolbar.dart +++ b/lib/src/widgets/toolbar/simple_toolbar.dart @@ -277,10 +277,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 +312,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, - // ), - // ], ], ]; }