Skip to content

Commit

Permalink
Merge pull request #4 from presswink/#1
Browse files Browse the repository at this point in the history
#1: dependency fetch call on null issue fixed
  • Loading branch information
Adityapanther authored Dec 24, 2023
2 parents 5243add + 2a319cd commit 5b805fd
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 95 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ initial release
* other searchFiled dependency
* fetch content on dependency search resoved
* select item from suggestion

# 0.0.2
* null pointer exception issue fixed for dependencyFetch
* added more description & improved pub points
* case sensitive toggle field added for default filter
5 changes: 3 additions & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class MyApp extends StatefulWidget {
}

class _MyAppState extends State<MyApp> {

final _firstController = TextSearchFieldController();

@override
Expand All @@ -35,10 +34,12 @@ class _MyAppState extends State<MyApp> {
filterItems: [
TextSearchFieldDataModel(key: "hey", value: "hello"),
TextSearchFieldDataModel(key: "hey", value: "bro"),
TextSearchFieldDataModel(key: "hey", value: "HELLO"),
TextSearchFieldDataModel(key: "hey", value: "how are"),
TextSearchFieldDataModel(key: "hey", value: "hello"),
TextSearchFieldDataModel(key: "hey", value: "bro"),
TextSearchFieldDataModel(key: "hey", value: "how are")
TextSearchFieldDataModel(key: "hey", value: "how are"),

],
onSelected: (primarySelected, index, item) async {
print("primary item selected: $primarySelected");
Expand Down
4 changes: 1 addition & 3 deletions lib/src/Utils.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
class Utils {
/// this function is going to build and return key from [text]
static String buildKey(String text){
static String buildKey(String text) {
return text.replaceAll(" ", "_");
}
}


2 changes: 1 addition & 1 deletion lib/src/global_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ extension GlobalKeyExtension on GlobalKey {
return null;
}
}
}
}
188 changes: 125 additions & 63 deletions lib/src/text_search_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:text_search_field/src/text_search_field_data_model.dart';
import 'package:touch_ripple_effect/touch_ripple_effect.dart';
import 'global_key.dart';


/// this is a search for searching or filtering item from list, server or network
/// [hint] is a string to show hint to user in textSearchField,
/// using [inputBorder] you can change your textSearchField broder colors and style,
Expand All @@ -27,7 +26,7 @@ import 'global_key.dart';
/// you can define height of suggestion item with [suggestionItemContainerHeight]
/// you can add alignment of text with [suggestionTextAlignment]
/// with [suggestionContainerHeight] you can define suggestion height
///
/// with [caseSensitive] you can enable and disable case sensitive of default query filter
///
class TextSearchField extends StatefulWidget {
final String? hint;
Expand All @@ -39,29 +38,46 @@ class TextSearchField extends StatefulWidget {
final bool fullTextSearch;
final TextInputAction? textInputAction;
final Future<List<TextSearchFieldDataModel>?> Function(String query)? fetch;
final List<TextSearchFieldDataModel>? Function(List<TextSearchFieldDataModel>? filterItems, String query)? query;
final List<TextSearchFieldDataModel>? Function(
List<TextSearchFieldDataModel>? filterItems, String query)? query;
final Color? rippleColor;
final Future<void> Function(bool isPrimary, int index, TextSearchFieldDataModel selectedItem)? onSelected;
final Future<void> Function(
bool isPrimary, int index, TextSearchFieldDataModel selectedItem)?
onSelected;
final TextSearchFieldController? controller;
final TextSearchFieldController? dependency;
final Future<List<TextSearchFieldDataModel>> Function(TextSearchFieldDataModel modelItem)? dependencyFetch;
final Future<List<TextSearchFieldDataModel>> Function(
TextSearchFieldDataModel modelItem)? dependencyFetch;
final TextStyle? suggestionTextStyle;
final BoxDecoration? suggestionItemDecoration;
final double suggestionItemContainerHeight;
final Alignment? suggestionTextAlignment;
final double suggestionContainerHeight;
final bool caseSensitive;
// constructor
const TextSearchField({
super.key, this.hint, this.inputBorder, this.searchFieldHintTextStyle,
this.initialValue, this.filterItems, this.fetch, this.query, this.fullTextSearch = false,
this.rippleColor, this.onSelected, this.controller, this.dependency, this.textInputAction,
super.key,
this.hint,
this.inputBorder,
this.searchFieldHintTextStyle,
this.initialValue,
this.filterItems,
this.fetch,
this.query,
this.fullTextSearch = false,
this.rippleColor,
this.onSelected,
this.controller,
this.dependency,
this.textInputAction,
this.dependencyFetch,
this.searchFieldTextStyle,
this.suggestionTextStyle,
this.suggestionItemDecoration,
this.suggestionItemContainerHeight = 50,
this.suggestionTextAlignment,
this.suggestionContainerHeight = 250,
this.caseSensitive = false
});

@override
Expand All @@ -75,38 +91,38 @@ class _SearchFieldState extends State<TextSearchField> {
TextSearchFieldDataModel? currentValue;
bool isSelected = false;

final OverlayPortalController _overlayPortalController = OverlayPortalController();
final OverlayPortalController _overlayPortalController =
OverlayPortalController();
final _controller = TextEditingController();
final _globalKey = GlobalKey();


@override
void initState() {
_focusNode = FocusNode();
// setting up initial value if there any
if(widget.initialValue != null){
if (widget.initialValue != null) {
setCurrentValue(widget.initialValue!);
}

_focusNode.addListener(() {
// showing and hiding search suggestion list
if(_focusNode.hasFocus){
if (_focusNode.hasFocus) {
_overlayPortalController.show();
}else {
} else {
_overlayPortalController.hide();
}
});
items = widget.filterItems;
if(widget.dependency != null){
if (widget.dependency != null) {
widget.dependency!.selected = (TextSearchFieldDataModel item) async {
// enabling loader and search field
setState(() {
isLoading = true;
isSelected = true;
});
// calling dependency fetch method
if(widget.dependencyFetch != null){
items = await widget.dependencyFetch!(item);
if (widget.dependencyFetch != null) {
items = await widget.dependencyFetch!(item);
}
// disabling loader after content fetch
setState(() {
Expand All @@ -117,11 +133,11 @@ class _SearchFieldState extends State<TextSearchField> {
super.initState();
}

void setCurrentValue(TextSearchFieldDataModel value){
void setCurrentValue(TextSearchFieldDataModel value) {
currentValue = value;
_controller.text = value.value!;
if(widget.controller != null){
if(widget.controller!.selected != null){
if (widget.controller != null) {
if (widget.controller!.selected != null) {
widget.controller!.selected!(value);
}
}
Expand All @@ -137,78 +153,116 @@ class _SearchFieldState extends State<TextSearchField> {
child: Container(
alignment: Alignment.center,
height: widget.suggestionContainerHeight,
decoration: const BoxDecoration(color: Colors.white, boxShadow: [BoxShadow(color: Colors.grey, blurRadius: 15, offset: Offset(2, 3))]),
child: isLoading ? const CircularProgressIndicator(): ListView.builder(
itemCount: items != null ? items!.length : 0,
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
return TouchRippleEffect(
rippleColor: widget.rippleColor ?? Colors.grey,
onTap: (){
_focusNode.unfocus();
setCurrentValue(items![index]);
if(widget.onSelected != null){
widget.onSelected!(index == 0 ? true: false, index, items![index]);
}
},
child: Container(
key: Key(items![index].key!),
alignment:widget.suggestionTextAlignment ?? Alignment.center,
height: widget.suggestionItemContainerHeight,
decoration: widget.suggestionItemDecoration ?? const BoxDecoration(color: Colors.white, shape: BoxShape.rectangle, border: Border(bottom: BorderSide(color: Colors.grey, width: 0.5))),
child: Text(items![index].value!, style: widget.suggestionTextStyle ?? const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 18), textDirection: TextDirection.ltr,),
),
);
}),
decoration: const BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(color: Colors.grey, blurRadius: 15, offset: Offset(2, 3))
]),
child: isLoading
? const CircularProgressIndicator()
: ListView.builder(
itemCount: items != null ? items!.length : 0,
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
return TouchRippleEffect(
rippleColor: widget.rippleColor ?? Colors.grey,
onTap: () {
_focusNode.unfocus();
setCurrentValue(items![index]);
if (widget.onSelected != null) {
widget.onSelected!(
index == 0 ? true : false, index, items![index]);
}
},
child: Container(
key: Key(items![index].key!),
alignment:
widget.suggestionTextAlignment ?? Alignment.center,
height: widget.suggestionItemContainerHeight,
decoration: widget.suggestionItemDecoration ??
const BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
border: Border(
bottom: BorderSide(
color: Colors.grey, width: 0.5))),
child: Text(
items![index].value!,
style: widget.suggestionTextStyle ??
const TextStyle(
color: Colors.black87,
fontWeight: FontWeight.bold,
fontSize: 18),
textDirection: TextDirection.ltr,
),
),
);
}),
),
),
controller: _overlayPortalController,
child: TextField(
key: _globalKey,
controller: _controller,
enabled: widget.dependency == null ? true : isSelected,
style: widget.searchFieldTextStyle ?? const TextStyle(color: Colors.black,fontSize: 18, fontWeight: FontWeight.w400),
style: widget.searchFieldTextStyle ??
const TextStyle(
color: Colors.black, fontSize: 18, fontWeight: FontWeight.w400),
focusNode: _focusNode,
textInputAction: widget.textInputAction ?? TextInputAction.go,
keyboardType: TextInputType.text,
maxLines: 1,
onEditingComplete: (){
onEditingComplete: () {
// on keyboard go button press we are treating this request as a initial / primary item selected in the list
setCurrentValue(TextSearchFieldDataModel(key: Utils.buildKey(_controller.value.text), value: _controller.value.text));
setCurrentValue(TextSearchFieldDataModel(
key: Utils.buildKey(_controller.value.text),
value: _controller.value.text));
// triggering onSelected function
if(widget.onSelected != null){
if (widget.onSelected != null) {
widget.onSelected!(true, 0, currentValue!);
}
// removing focus from search field
_focusNode.unfocus();
},
onChanged: (String value) async{
onChanged: (String value) async {
// enabling loader
setState(() {
isLoading = true;
});

if(widget.query != null){
if (widget.query != null) {
// if user wants to add custom filter on query item then this statement is going to trigger
items = widget.query!(widget.filterItems, value);
}else if(widget.fetch != null) {
} else if (widget.fetch != null) {
// if user wants to add server / network query filter then this statement is going to trigger
final res = await widget.fetch!(value);
if(res!.isNotEmpty){
if (res!.isNotEmpty) {
// if there is an item in the response then trigger this
items = [TextSearchFieldDataModel(key: Utils.buildKey(value), value: value), ...res];
}else {
items = [
TextSearchFieldDataModel(
key: Utils.buildKey(value), value: value),
...res
];
} else {
// if there is no items in the response then add on default item
items = [TextSearchFieldDataModel(key: Utils.buildKey(value), value: value)];
items = [
TextSearchFieldDataModel(
key: Utils.buildKey(value), value: value)
];
}
}else {
} else {
// if none of the function has defined then it's going to trigger as a default query filter function
if(value.isNotEmpty){
if (value.isNotEmpty) {
String pattern = r"\b" + value + r"\b";
RegExp wordRegex = RegExp(pattern, caseSensitive: false);
final lists = widget.filterItems?.where((element) => element.value!.contains(widget.fullTextSearch ? wordRegex: value)).toList();
items = [TextSearchFieldDataModel(key: Utils.buildKey(value), value: value), ...?lists];
}else {
RegExp wordRegex = RegExp(pattern, caseSensitive: widget.caseSensitive);
final lists = widget.filterItems
?.where((element) => element.value!
.contains(widget.fullTextSearch ? wordRegex : RegExp(value, caseSensitive: widget.caseSensitive)))
.toList();
items = [
TextSearchFieldDataModel(
key: Utils.buildKey(value), value: value),
...?lists
];
} else {
// if query string is empty then add default filter list in the request
items = widget.filterItems;
}
Expand All @@ -218,18 +272,26 @@ class _SearchFieldState extends State<TextSearchField> {
isLoading = false;
});
},
onTap: (){
onTap: () {
// this function is going to trigger on select search field
_focusNode.requestFocus();
},
decoration: InputDecoration(
border: widget.inputBorder ?? const OutlineInputBorder(borderSide: BorderSide(color: Colors.black87, width: 1.0, style: BorderStyle.solid)),
border: widget.inputBorder ??
const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.black87,
width: 1.0,
style: BorderStyle.solid)),
hintText: widget.hint ?? "please type your query",
hintStyle: widget.searchFieldHintTextStyle ?? const TextStyle(color: Colors.grey, fontSize: 16, fontWeight: FontWeight.w400),
hintStyle: widget.searchFieldHintTextStyle ??
const TextStyle(
color: Colors.grey,
fontSize: 16,
fontWeight: FontWeight.w400),
),
textDirection: TextDirection.ltr,
),
);
}
}

5 changes: 2 additions & 3 deletions lib/src/text_search_field_controller.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@

import 'package:text_search_field/src/text_search_field_data_model.dart';

class TextSearchFieldController {
String text;
bool isLoading;
void Function(TextSearchFieldDataModel model)? selected;
TextSearchFieldController({this.text ="", this.isLoading = false, this.selected});
TextSearchFieldController(
{this.text = "", this.isLoading = false, this.selected});
}

3 changes: 1 addition & 2 deletions lib/src/text_search_field_data_model.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
class TextSearchFieldDataModel{
class TextSearchFieldDataModel {
String? key;
String? value;
TextSearchFieldDataModel({this.key, this.value});
}

2 changes: 1 addition & 1 deletion lib/text_search_field.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export 'src/text_search_field.dart';
export 'src/text_search_field_data_model.dart';
export 'src/text_search_field_controller.dart';
export 'src/text_search_field_controller.dart';
Loading

0 comments on commit 5b805fd

Please sign in to comment.