@@ -5,7 +5,6 @@ import 'package:text_search_field/src/text_search_field_data_model.dart';
5
5
import 'package:touch_ripple_effect/touch_ripple_effect.dart' ;
6
6
import 'global_key.dart' ;
7
7
8
-
9
8
/// this is a search for searching or filtering item from list, server or network
10
9
/// [hint] is a string to show hint to user in textSearchField,
11
10
/// using [inputBorder] you can change your textSearchField broder colors and style,
@@ -27,7 +26,7 @@ import 'global_key.dart';
27
26
/// you can define height of suggestion item with [suggestionItemContainerHeight]
28
27
/// you can add alignment of text with [suggestionTextAlignment]
29
28
/// with [suggestionContainerHeight] you can define suggestion height
30
- ///
29
+ /// with [caseSensitive] you can enable and disable case sensitive of default query filter
31
30
///
32
31
class TextSearchField extends StatefulWidget {
33
32
final String ? hint;
@@ -39,29 +38,46 @@ class TextSearchField extends StatefulWidget {
39
38
final bool fullTextSearch;
40
39
final TextInputAction ? textInputAction;
41
40
final Future <List <TextSearchFieldDataModel >?> Function (String query)? fetch;
42
- final List <TextSearchFieldDataModel >? Function (List <TextSearchFieldDataModel >? filterItems, String query)? query;
41
+ final List <TextSearchFieldDataModel >? Function (
42
+ List <TextSearchFieldDataModel >? filterItems, String query)? query;
43
43
final Color ? rippleColor;
44
- final Future <void > Function (bool isPrimary, int index, TextSearchFieldDataModel selectedItem)? onSelected;
44
+ final Future <void > Function (
45
+ bool isPrimary, int index, TextSearchFieldDataModel selectedItem)?
46
+ onSelected;
45
47
final TextSearchFieldController ? controller;
46
48
final TextSearchFieldController ? dependency;
47
- final Future <List <TextSearchFieldDataModel >> Function (TextSearchFieldDataModel modelItem)? dependencyFetch;
49
+ final Future <List <TextSearchFieldDataModel >> Function (
50
+ TextSearchFieldDataModel modelItem)? dependencyFetch;
48
51
final TextStyle ? suggestionTextStyle;
49
52
final BoxDecoration ? suggestionItemDecoration;
50
53
final double suggestionItemContainerHeight;
51
54
final Alignment ? suggestionTextAlignment;
52
55
final double suggestionContainerHeight;
56
+ final bool caseSensitive;
53
57
// constructor
54
58
const TextSearchField ({
55
- super .key, this .hint, this .inputBorder, this .searchFieldHintTextStyle,
56
- this .initialValue, this .filterItems, this .fetch, this .query, this .fullTextSearch = false ,
57
- this .rippleColor, this .onSelected, this .controller, this .dependency, this .textInputAction,
59
+ super .key,
60
+ this .hint,
61
+ this .inputBorder,
62
+ this .searchFieldHintTextStyle,
63
+ this .initialValue,
64
+ this .filterItems,
65
+ this .fetch,
66
+ this .query,
67
+ this .fullTextSearch = false ,
68
+ this .rippleColor,
69
+ this .onSelected,
70
+ this .controller,
71
+ this .dependency,
72
+ this .textInputAction,
58
73
this .dependencyFetch,
59
74
this .searchFieldTextStyle,
60
75
this .suggestionTextStyle,
61
76
this .suggestionItemDecoration,
62
77
this .suggestionItemContainerHeight = 50 ,
63
78
this .suggestionTextAlignment,
64
79
this .suggestionContainerHeight = 250 ,
80
+ this .caseSensitive = false
65
81
});
66
82
67
83
@override
@@ -75,38 +91,38 @@ class _SearchFieldState extends State<TextSearchField> {
75
91
TextSearchFieldDataModel ? currentValue;
76
92
bool isSelected = false ;
77
93
78
- final OverlayPortalController _overlayPortalController = OverlayPortalController ();
94
+ final OverlayPortalController _overlayPortalController =
95
+ OverlayPortalController ();
79
96
final _controller = TextEditingController ();
80
97
final _globalKey = GlobalKey ();
81
98
82
-
83
99
@override
84
100
void initState () {
85
101
_focusNode = FocusNode ();
86
102
// setting up initial value if there any
87
- if (widget.initialValue != null ){
103
+ if (widget.initialValue != null ) {
88
104
setCurrentValue (widget.initialValue! );
89
105
}
90
106
91
107
_focusNode.addListener (() {
92
108
// showing and hiding search suggestion list
93
- if (_focusNode.hasFocus){
109
+ if (_focusNode.hasFocus) {
94
110
_overlayPortalController.show ();
95
- }else {
111
+ } else {
96
112
_overlayPortalController.hide ();
97
113
}
98
114
});
99
115
items = widget.filterItems;
100
- if (widget.dependency != null ){
116
+ if (widget.dependency != null ) {
101
117
widget.dependency! .selected = (TextSearchFieldDataModel item) async {
102
118
// enabling loader and search field
103
119
setState (() {
104
120
isLoading = true ;
105
121
isSelected = true ;
106
122
});
107
123
// calling dependency fetch method
108
- if (widget.dependencyFetch != null ){
109
- items = await widget.dependencyFetch !(item);
124
+ if (widget.dependencyFetch != null ) {
125
+ items = await widget.dependencyFetch !(item);
110
126
}
111
127
// disabling loader after content fetch
112
128
setState (() {
@@ -117,11 +133,11 @@ class _SearchFieldState extends State<TextSearchField> {
117
133
super .initState ();
118
134
}
119
135
120
- void setCurrentValue (TextSearchFieldDataModel value){
136
+ void setCurrentValue (TextSearchFieldDataModel value) {
121
137
currentValue = value;
122
138
_controller.text = value.value! ;
123
- if (widget.controller != null ){
124
- if (widget.controller! .selected != null ){
139
+ if (widget.controller != null ) {
140
+ if (widget.controller! .selected != null ) {
125
141
widget.controller! .selected !(value);
126
142
}
127
143
}
@@ -137,78 +153,116 @@ class _SearchFieldState extends State<TextSearchField> {
137
153
child: Container (
138
154
alignment: Alignment .center,
139
155
height: widget.suggestionContainerHeight,
140
- decoration: const BoxDecoration (color: Colors .white, boxShadow: [BoxShadow (color: Colors .grey, blurRadius: 15 , offset: Offset (2 , 3 ))]),
141
- child: isLoading ? const CircularProgressIndicator (): ListView .builder (
142
- itemCount: items != null ? items! .length : 0 ,
143
- scrollDirection: Axis .vertical,
144
- itemBuilder: (BuildContext context, int index) {
145
- return TouchRippleEffect (
146
- rippleColor: widget.rippleColor ?? Colors .grey,
147
- onTap: (){
148
- _focusNode.unfocus ();
149
- setCurrentValue (items! [index]);
150
- if (widget.onSelected != null ){
151
- widget.onSelected !(index == 0 ? true : false , index, items! [index]);
152
- }
153
- },
154
- child: Container (
155
- key: Key (items! [index].key! ),
156
- alignment: widget.suggestionTextAlignment ?? Alignment .center,
157
- height: widget.suggestionItemContainerHeight,
158
- decoration: widget.suggestionItemDecoration ?? const BoxDecoration (color: Colors .white, shape: BoxShape .rectangle, border: Border (bottom: BorderSide (color: Colors .grey, width: 0.5 ))),
159
- child: Text (items! [index].value! , style: widget.suggestionTextStyle ?? const TextStyle (color: Colors .black87, fontWeight: FontWeight .bold, fontSize: 18 ), textDirection: TextDirection .ltr,),
160
- ),
161
- );
162
- }),
156
+ decoration: const BoxDecoration (color: Colors .white, boxShadow: [
157
+ BoxShadow (color: Colors .grey, blurRadius: 15 , offset: Offset (2 , 3 ))
158
+ ]),
159
+ child: isLoading
160
+ ? const CircularProgressIndicator ()
161
+ : ListView .builder (
162
+ itemCount: items != null ? items! .length : 0 ,
163
+ scrollDirection: Axis .vertical,
164
+ itemBuilder: (BuildContext context, int index) {
165
+ return TouchRippleEffect (
166
+ rippleColor: widget.rippleColor ?? Colors .grey,
167
+ onTap: () {
168
+ _focusNode.unfocus ();
169
+ setCurrentValue (items! [index]);
170
+ if (widget.onSelected != null ) {
171
+ widget.onSelected !(
172
+ index == 0 ? true : false , index, items! [index]);
173
+ }
174
+ },
175
+ child: Container (
176
+ key: Key (items! [index].key! ),
177
+ alignment:
178
+ widget.suggestionTextAlignment ?? Alignment .center,
179
+ height: widget.suggestionItemContainerHeight,
180
+ decoration: widget.suggestionItemDecoration ??
181
+ const BoxDecoration (
182
+ color: Colors .white,
183
+ shape: BoxShape .rectangle,
184
+ border: Border (
185
+ bottom: BorderSide (
186
+ color: Colors .grey, width: 0.5 ))),
187
+ child: Text (
188
+ items! [index].value! ,
189
+ style: widget.suggestionTextStyle ??
190
+ const TextStyle (
191
+ color: Colors .black87,
192
+ fontWeight: FontWeight .bold,
193
+ fontSize: 18 ),
194
+ textDirection: TextDirection .ltr,
195
+ ),
196
+ ),
197
+ );
198
+ }),
163
199
),
164
200
),
165
201
controller: _overlayPortalController,
166
202
child: TextField (
167
203
key: _globalKey,
168
204
controller: _controller,
169
205
enabled: widget.dependency == null ? true : isSelected,
170
- style: widget.searchFieldTextStyle ?? const TextStyle (color: Colors .black,fontSize: 18 , fontWeight: FontWeight .w400),
206
+ style: widget.searchFieldTextStyle ??
207
+ const TextStyle (
208
+ color: Colors .black, fontSize: 18 , fontWeight: FontWeight .w400),
171
209
focusNode: _focusNode,
172
210
textInputAction: widget.textInputAction ?? TextInputAction .go,
173
211
keyboardType: TextInputType .text,
174
212
maxLines: 1 ,
175
- onEditingComplete: (){
213
+ onEditingComplete: () {
176
214
// on keyboard go button press we are treating this request as a initial / primary item selected in the list
177
- setCurrentValue (TextSearchFieldDataModel (key: Utils .buildKey (_controller.value.text), value: _controller.value.text));
215
+ setCurrentValue (TextSearchFieldDataModel (
216
+ key: Utils .buildKey (_controller.value.text),
217
+ value: _controller.value.text));
178
218
// triggering onSelected function
179
- if (widget.onSelected != null ){
219
+ if (widget.onSelected != null ) {
180
220
widget.onSelected !(true , 0 , currentValue! );
181
221
}
182
222
// removing focus from search field
183
223
_focusNode.unfocus ();
184
224
},
185
- onChanged: (String value) async {
225
+ onChanged: (String value) async {
186
226
// enabling loader
187
227
setState (() {
188
228
isLoading = true ;
189
229
});
190
230
191
- if (widget.query != null ){
231
+ if (widget.query != null ) {
192
232
// if user wants to add custom filter on query item then this statement is going to trigger
193
233
items = widget.query !(widget.filterItems, value);
194
- }else if (widget.fetch != null ) {
234
+ } else if (widget.fetch != null ) {
195
235
// if user wants to add server / network query filter then this statement is going to trigger
196
236
final res = await widget.fetch !(value);
197
- if (res! .isNotEmpty){
237
+ if (res! .isNotEmpty) {
198
238
// if there is an item in the response then trigger this
199
- items = [TextSearchFieldDataModel (key: Utils .buildKey (value), value: value), ...res];
200
- }else {
239
+ items = [
240
+ TextSearchFieldDataModel (
241
+ key: Utils .buildKey (value), value: value),
242
+ ...res
243
+ ];
244
+ } else {
201
245
// if there is no items in the response then add on default item
202
- items = [TextSearchFieldDataModel (key: Utils .buildKey (value), value: value)];
246
+ items = [
247
+ TextSearchFieldDataModel (
248
+ key: Utils .buildKey (value), value: value)
249
+ ];
203
250
}
204
- }else {
251
+ } else {
205
252
// if none of the function has defined then it's going to trigger as a default query filter function
206
- if (value.isNotEmpty){
253
+ if (value.isNotEmpty) {
207
254
String pattern = r"\b" + value + r"\b" ;
208
- RegExp wordRegex = RegExp (pattern, caseSensitive: false );
209
- final lists = widget.filterItems? .where ((element) => element.value! .contains (widget.fullTextSearch ? wordRegex: value)).toList ();
210
- items = [TextSearchFieldDataModel (key: Utils .buildKey (value), value: value), ...? lists];
211
- }else {
255
+ RegExp wordRegex = RegExp (pattern, caseSensitive: widget.caseSensitive);
256
+ final lists = widget.filterItems
257
+ ? .where ((element) => element.value!
258
+ .contains (widget.fullTextSearch ? wordRegex : RegExp (value, caseSensitive: widget.caseSensitive)))
259
+ .toList ();
260
+ items = [
261
+ TextSearchFieldDataModel (
262
+ key: Utils .buildKey (value), value: value),
263
+ ...? lists
264
+ ];
265
+ } else {
212
266
// if query string is empty then add default filter list in the request
213
267
items = widget.filterItems;
214
268
}
@@ -218,18 +272,26 @@ class _SearchFieldState extends State<TextSearchField> {
218
272
isLoading = false ;
219
273
});
220
274
},
221
- onTap: (){
275
+ onTap: () {
222
276
// this function is going to trigger on select search field
223
277
_focusNode.requestFocus ();
224
278
},
225
279
decoration: InputDecoration (
226
- border: widget.inputBorder ?? const OutlineInputBorder (borderSide: BorderSide (color: Colors .black87, width: 1.0 , style: BorderStyle .solid)),
280
+ border: widget.inputBorder ??
281
+ const OutlineInputBorder (
282
+ borderSide: BorderSide (
283
+ color: Colors .black87,
284
+ width: 1.0 ,
285
+ style: BorderStyle .solid)),
227
286
hintText: widget.hint ?? "please type your query" ,
228
- hintStyle: widget.searchFieldHintTextStyle ?? const TextStyle (color: Colors .grey, fontSize: 16 , fontWeight: FontWeight .w400),
287
+ hintStyle: widget.searchFieldHintTextStyle ??
288
+ const TextStyle (
289
+ color: Colors .grey,
290
+ fontSize: 16 ,
291
+ fontWeight: FontWeight .w400),
229
292
),
230
293
textDirection: TextDirection .ltr,
231
294
),
232
295
);
233
296
}
234
297
}
235
-
0 commit comments