@ -1,5 +1,8 @@
import ' dart:async ' ;
import ' package:flutter/material.dart ' ;
import ' package:flutter/material.dart ' ;
import ' ../../../../../extensions.dart ' ;
import ' ../../../../l10n/extensions/localizations.dart ' ;
import ' ../../../../l10n/extensions/localizations.dart ' ;
import ' ../../../../l10n/widgets/localizations.dart ' ;
import ' ../../../../l10n/widgets/localizations.dart ' ;
import ' ../../../../models/documents/document.dart ' ;
import ' ../../../../models/documents/document.dart ' ;
@ -42,15 +45,15 @@ class QuillToolbarSearchDialog extends StatefulWidget {
const QuillToolbarSearchDialog ( {
const QuillToolbarSearchDialog ( {
required this . controller ,
required this . controller ,
this . dialogTheme ,
this . dialogTheme ,
this . text ,
this . childBuilder ,
this . childBuilder ,
this . searchBarAlignment ,
super . key ,
super . key ,
} ) ;
} ) ;
final QuillController controller ;
final QuillController controller ;
final QuillDialogTheme ? dialogTheme ;
final QuillDialogTheme ? dialogTheme ;
final String ? text ;
final QuillToolbarSearchDialogChildBuilder ? childBuilder ;
final QuillToolbarSearchDialogChildBuilder ? childBuilder ;
final AlignmentGeometry ? searchBarAlignment ;
@ override
@ override
QuillToolbarSearchDialogState createState ( ) = >
QuillToolbarSearchDialogState createState ( ) = >
@ -58,39 +61,24 @@ class QuillToolbarSearchDialog extends StatefulWidget {
}
}
class QuillToolbarSearchDialogState extends State < QuillToolbarSearchDialog > {
class QuillToolbarSearchDialogState extends State < QuillToolbarSearchDialog > {
late String _text ;
final TextEditingController _textController = TextEditingController ( ) ;
late TextEditingController _controller ;
String _text = ' ' ;
late List < int > ? _offsets ;
List < int > _offsets = [ ] ;
late int _index ;
int _index = 0 ;
bool _caseSensitive = false ;
bool _caseSensitive = false ;
bool _wholeWord = false ;
bool _wholeWord = false ;
bool _searchSettingsUnfolded = false ;
@ override
Timer ? _searchTimer ;
void initState ( ) {
super . initState ( ) ;
_text = widget . text ? ? ' ' ;
_offsets = null ;
_index = 0 ;
_controller = TextEditingController ( text: _text ) ;
}
@ override
@ override
void dispose ( ) {
void dispose ( ) {
_controller . dispose ( ) ;
_textController . dispose ( ) ;
_searchTimer ? . cancel ( ) ;
super . dispose ( ) ;
super . dispose ( ) ;
}
}
@ override
@ override
Widget build ( BuildContext context ) {
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 ;
final childBuilder = widget . childBuilder ;
if ( childBuilder ! = null ) {
if ( childBuilder ! = null ) {
return childBuilder (
return childBuilder (
@ -99,7 +87,7 @@ class QuillToolbarSearchDialogState extends State<QuillToolbarSearchDialog> {
onEditingComplete: _findText ,
onEditingComplete: _findText ,
onTextChanged: _textChanged ,
onTextChanged: _textChanged ,
caseSensitive: _caseSensitive ,
caseSensitive: _caseSensitive ,
textEditingController: _c ontroller ,
textEditingController: _textC ontroller ,
index: _index ,
index: _index ,
offsets: _offsets ,
offsets: _offsets ,
text: _text ,
text: _text ,
@ -110,100 +98,170 @@ class QuillToolbarSearchDialogState extends State<QuillToolbarSearchDialog> {
) ;
) ;
}
}
return Dialog (
final searchBarAlignment =
shape: RoundedRectangleBorder (
widget . searchBarAlignment ? ? Alignment . bottomCenter ;
borderRadius: BorderRadius . circular ( 5 ) ,
final searchBarAtBottom = ( searchBarAlignment = = Alignment . bottomCenter ) | |
) ,
( searchBarAlignment = = Alignment . bottomLeft ) | |
backgroundColor: widget . dialogTheme ? . dialogBackgroundColor ,
( searchBarAlignment = = Alignment . bottomRight ) ;
alignment: Alignment . bottomCenter ,
final addBottomPadding = searchBarAtBottom & & isMobile ( supportWeb: true ) ;
insetPadding: EdgeInsets . zero ,
var matchShown = ' ' ;
child: FlutterQuillLocalizationsWidget (
if ( _text . isNotEmpty ) {
child: Builder (
if ( _offsets . isEmpty ) {
builder: ( context ) {
matchShown = ' 0/0 ' ;
return SizedBox (
} else {
height: 45 ,
matchShown = ' ${ _index + 1 } / ${ _offsets . length } ' ;
}
}
final searchBar = Container (
height: addBottomPadding ? 50 : 45 ,
padding: addBottomPadding ? const EdgeInsets . only ( bottom: 12 ) : null ,
child: Row (
child: Row (
children: [
children: [
Tooltip (
IconButton (
message: context . loc . caseSensitivityAndWholeWordSearch ,
icon: const Icon ( Icons . close ) ,
child: ToggleButtons (
tooltip: ' Close ' ,
onPressed: ( index ) {
visualDensity: VisualDensity . compact ,
if ( index = = 0 ) {
onPressed: ( ) {
_changeCaseSensitivity ( ) ;
Navigator . of ( context ) . pop ( ) ;
} 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 ,
) ,
) ,
] ,
) ,
) ,
IconButton (
icon: const Icon ( Icons . more_vert ) ,
isSelected: _caseSensitive | | _wholeWord ,
tooltip: ' Search settings ' ,
visualDensity: VisualDensity . compact ,
onPressed: ( ) {
setState ( ( ) {
_searchSettingsUnfolded = ! _searchSettingsUnfolded ;
} ) ;
} ,
) ,
) ,
Expanded (
Expanded (
child: Padding (
padding: const EdgeInsets . only ( bottom: 12 , left: 5 ) ,
child: TextField (
child: TextField (
style: widget . dialogTheme ? . inputTextStyle ,
style: widget . dialogTheme ? . inputTextStyle ,
decoration: InputDecoration (
decoration: InputDecoration (
isDense: true ,
isDense: true ,
suffixText: ( _offsets ! = null ) ? matchShown : ' ' ,
suffixText: matchShown ,
suffixStyle: widget . dialogTheme ? . labelTextStyle ,
suffixStyle: widget . dialogTheme ? . labelTextStyle ,
) ,
) ,
autofocus: true ,
autofocus: true ,
onChanged: _textChanged ,
onChanged: _textChanged ,
textInputAction: TextInputAction . done ,
textInputAction: TextInputAction . done ,
keyboardType: TextInputType . text ,
keyboardType: TextInputType . text ,
onEditingComplete: _findText ,
controller: _textController ,
controller: _controller ,
) ,
) ,
) ,
) ,
) ,
if ( _offsets = = null )
IconButton (
icon: const Icon ( Icons . search ) ,
tooltip: context . loc . findText ,
onPressed: _findText ,
) ,
if ( _offsets ! = null )
IconButton (
IconButton (
icon: const Icon ( Icons . keyboard_arrow_up ) ,
icon: const Icon ( Icons . keyboard_arrow_up ) ,
tooltip: context . loc . moveToPreviousOccurrence ,
tooltip: context . loc . moveToPreviousOccurrence ,
onPressed:
onPressed: ( _offsets . isNotEmpty ) ? _moveToPrevious : null ,
( _offsets ! . isNotEmpty ) ? _moveToPrevious : null ,
) ,
) ,
if ( _offsets ! = null )
IconButton (
IconButton (
icon: const Icon ( Icons . keyboard_arrow_down ) ,
icon: const Icon ( Icons . keyboard_arrow_down ) ,
tooltip: context . loc . moveToNextOccurrence ,
tooltip: context . loc . moveToNextOccurrence ,
onPressed: ( _offsets ! . isNotEmpty ) ? _moveToNext : null ,
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: const Text (
' Case sensitive ' ,
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: const Text (
' Whole word ' ,
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: searchBarAlignment ,
insetPadding: EdgeInsets . zero ,
child: FlutterQuillLocalizationsWidget (
child: Builder (
builder: ( context ) {
return Column (
mainAxisSize: MainAxisSize . min ,
children: [
if ( _searchSettingsUnfolded & & searchBarAtBottom )
searchSettings ,
searchBar ,
if ( _searchSettingsUnfolded & & ! searchBarAtBottom )
searchSettings ,
] ,
) ;
} ,
} ,
) ,
) ,
) ,
) ,
) ;
) ;
}
}
void _textChanged ( String text ) {
_text = text ;
if ( _searchTimer ? . isActive ? ? false ) {
_searchTimer ? . cancel ( ) ;
}
_searchTimer = Timer (
const Duration ( milliseconds: 300 ) ,
_findText ,
) ;
}
void _findText ( ) {
void _findText ( ) {
_text = _controller . text ;
if ( _text . isEmpty ) {
if ( _text . isEmpty ) {
setState ( ( ) {
_offsets = [ ] ;
_index = 0 ;
widget . controller . updateSelection (
TextSelection (
baseOffset: widget . controller . selection . baseOffset ,
extentOffset: widget . controller . selection . baseOffset ,
) ,
ChangeSource . local ,
) ;
} ) ;
return ;
return ;
}
}
setState ( ( ) {
setState ( ( ) {
@ -214,7 +272,7 @@ class QuillToolbarSearchDialogState extends State<QuillToolbarSearchDialog> {
) ;
) ;
_index = 0 ;
_index = 0 ;
} ) ;
} ) ;
if ( _offsets ! . isNotEmpty ) {
if ( _offsets . isNotEmpty ) {
_moveToPosition ( ) ;
_moveToPosition ( ) ;
}
}
}
}
@ -222,33 +280,33 @@ class QuillToolbarSearchDialogState extends State<QuillToolbarSearchDialog> {
void _moveToPosition ( ) {
void _moveToPosition ( ) {
widget . controller . updateSelection (
widget . controller . updateSelection (
TextSelection (
TextSelection (
baseOffset: _offsets ! [ _index ] ,
baseOffset: _offsets [ _index ] ,
extentOffset: _offsets ! [ _index ] + _text . length ,
extentOffset: _offsets [ _index ] + _text . length ,
) ,
) ,
ChangeSource . local ,
ChangeSource . local ,
) ;
) ;
}
}
void _moveToPrevious ( ) {
void _moveToPrevious ( ) {
if ( _offsets ! . isEmpty ) {
if ( _offsets . isEmpty ) {
return ;
return ;
}
}
setState ( ( ) {
setState ( ( ) {
if ( _index > 0 ) {
if ( _index > 0 ) {
_index - = 1 ;
_index - = 1 ;
} else {
} else {
_index = _offsets ! . length - 1 ;
_index = _offsets . length - 1 ;
}
}
} ) ;
} ) ;
_moveToPosition ( ) ;
_moveToPosition ( ) ;
}
}
void _moveToNext ( ) {
void _moveToNext ( ) {
if ( _offsets ! . isEmpty ) {
if ( _offsets . isEmpty ) {
return ;
return ;
}
}
setState ( ( ) {
setState ( ( ) {
if ( _index < _offsets ! . length - 1 ) {
if ( _index < _offsets . length - 1 ) {
_index + = 1 ;
_index + = 1 ;
} else {
} else {
_index = 0 ;
_index = 0 ;
@ -256,28 +314,4 @@ class QuillToolbarSearchDialogState extends State<QuillToolbarSearchDialog> {
} ) ;
} ) ;
_moveToPosition ( ) ;
_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 ;
} ) ;
}
}
}