commit
4cd6f3360f
14 changed files with 570 additions and 181 deletions
Binary file not shown.
@ -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<void> _sharedOnPressed(BuildContext context) async { |
||||||
|
final customCallback = options.customOnPressedCallback; |
||||||
|
if (customCallback != null) { |
||||||
|
await customCallback( |
||||||
|
controller, |
||||||
|
); |
||||||
|
return; |
||||||
|
} |
||||||
|
await showDialog<String>( |
||||||
|
barrierColor: _dialogBarrierColor(context), |
||||||
|
context: context, |
||||||
|
builder: (_) => FlutterQuillLocalizationsWidget( |
||||||
|
child: QuillToolbarLegacySearchDialog( |
||||||
|
controller: controller, |
||||||
|
dialogTheme: _dialogTheme(context), |
||||||
|
text: '', |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -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<QuillToolbarLegacySearchDialog> { |
||||||
|
late String _text; |
||||||
|
late TextEditingController _controller; |
||||||
|
late List<int>? _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; |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue