Skip to content

Commit

Permalink
Merge pull request #121 from maheshmnj/issue-118
Browse files Browse the repository at this point in the history
Expose onScroll event listener
  • Loading branch information
maheshj01 authored Mar 9, 2024
2 parents 2e820cf + d7f0c1e commit a4e3c09
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 34 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#### [0.9.7] - March 09, 2024

- Expose `onScroll` event listener
- Add `showEmpty` parameter to hide/show emptyWidget

#### [0.9.6] - March 07, 2024

- Add `hoverColor` and `selectionColor` property to SuggestionDecoration. [Issue #112](https://github.com/maheshmnj/searchfield/issues/112)
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# [searchfield: ^0.9.6](https://pub.dev/packages/searchfield)
# [searchfield: ^0.9.7](https://pub.dev/packages/searchfield)

<a href="https://github.com/maheshmnj/searchfield" rel="noopener" target="_blank"><img src="https://img.shields.io/badge/platform-flutter-ff69b4.svg" alt="Flutter Platform Badge"></a>
<a href="https://pub.dev/packages/searchfield"><img src="https://img.shields.io/pub/v/searchfield.svg" alt="Pub"></a>
Expand Down Expand Up @@ -261,7 +261,7 @@ The position of suggestions is dynamic based on the space available for the sugg
- `autoValidateMode`: Used to enable/disable this form field auto validation and update its error text.defaults to `AutoValidateMode.disabled`
- `controller`: TextEditing Controller to interact with the searchfield.
- `comparator` property to filter out the suggestions with a custom logic (Comparator is deprecated Use `onSearchTextChanged` instead).
- `emptyWidget`: Custom Widget to show when search returns empty Results (defaults to `SizedBox.shrink`)
- `emptyWidget`: Custom Widget to show when search returns empty Results or when `showEmpty` is true. (defaults to `SizedBox.shrink`)
- `enabled`: Defines whether to enable the searchfield defaults to `true`
- `focusNode` : FocusNode to interact with the searchfield.
- `hint` : hint for the search Input.
Expand All @@ -275,11 +275,13 @@ The position of suggestions is dynamic based on the space available for the sugg
- `offset` : suggestion List offset from the searchfield, The top left corner of the searchfield is the origin (0,0).
- `onOutSideTap` : callback when the user taps outside the searchfield.
- `onSaved` : An optional method to call with the final value when the form is saved via FormState.save.
- `onScroll` : callback when the suggestion list is scrolled. It returns the current scroll position and the max scroll position.
- `onSearchTextChanged`: callback when the searchfield text changes, it returns the current text in the searchfield.
- `onSuggestionTap` : callback when a sugestion is tapped it also returns the tapped value.
- `onSubmit` : callback when the searchfield is submitted, it returns the current text in the searchfield.
- `onTap`: callback when the searchfield is tapped or brought into focus.
- `scrollbarDecoration`: decoration for the scrollbar.
- `showEmpty`: Boolean to show/hide the emptyWidget.
- `suggestions`**(required)** : List of SearchFieldListItem to search from.
each `SearchFieldListItem` in the list requires a unique searchKey, which is used to search the list and an optional Widget, Custom Object to display custom widget and to associate a object with the suggestion list.
- `suggestionState`: enum to hide/show the suggestion on focusing the searchfield defaults to `SuggestionState.expand`.
Expand Down
5 changes: 5 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:example/network_sample.dart';
import 'package:example/pagination.dart';
import 'package:flutter/material.dart';
import 'package:searchfield/searchfield.dart';

Expand Down Expand Up @@ -107,6 +108,10 @@ class _SearchFieldSampleState extends State<SearchFieldSample> {
SizedBox(
height: 50,
),
Pagination(),
SizedBox(
height: 50,
),
NetworkSample(),
SizedBox(
height: 50,
Expand Down
142 changes: 142 additions & 0 deletions example/lib/pagination.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:searchfield/searchfield.dart';

class Pagination extends StatefulWidget {
const Pagination({Key? key}) : super(key: key);

@override
State<Pagination> createState() => _PaginationState();
}

class _PaginationState extends State<Pagination> {
final focus = FocusNode();

Future<List<String>> getPaginatedSuggestions({int pageLength = 5}) async {
await Future.delayed(const Duration(seconds: 2));
int total = suggestions.length;
return List.generate(pageLength, (index) {
total++;
return 'Item $total';
});
}

static const surfaceGreen = Color.fromARGB(255, 237, 255, 227);
static const surfaceBlue = Color(0xffd3e8fb);
static const skyBlue = Color(0xfff3ddec);

var suggestions = <String>[];

static const gradient = LinearGradient(
colors: [surfaceBlue, surfaceGreen, skyBlue],
stops: [0.25, 0.35, 0.9],
begin: Alignment.bottomRight,
end: Alignment.topLeft,
);
final suggestionDecoration = SuggestionDecoration(
border: Border.all(color: Colors.grey),
gradient: gradient,
borderRadius: BorderRadius.circular(24),
);

bool isLoading = false;
@override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final result = await getPaginatedSuggestions();
setState(() {
suggestions.addAll(result);
});
});
}

@override
Widget build(BuildContext context) {
Widget searchChild(x) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 12),
child: Text(x, style: TextStyle(fontSize: 20, color: Colors.black)),
);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SearchField(
onSearchTextChanged: (query) {
final filter = suggestions
.where((element) =>
element.toLowerCase().contains(query.toLowerCase()))
.toList();
return filter
.map((e) =>
SearchFieldListItem<String>(e, child: searchChild(e)))
.toList();
},
showEmpty: isLoading,
onScroll: (offset, maxOffset) async {
if (offset < maxOffset) return;
setState(() {
isLoading = true;
});
final result = await getPaginatedSuggestions();
setState(() {
suggestions.addAll(result);
isLoading = false;
});
},
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if (value == null || value.length < 4) {
return 'error';
}
return null;
},
emptyWidget: Container(
decoration: suggestionDecoration,
height: 250, // item*maxItems
child: const Center(
child: CircularProgressIndicator(
color: Colors.black,
))),
key: const Key('searchfield'),
hint: 'Load Paginated suggestions from network',
itemHeight: 50,
scrollbarDecoration: ScrollbarDecoration(),
onTapOutside: (x) {},
suggestionStyle: const TextStyle(fontSize: 20, color: Colors.black),
searchInputDecoration: InputDecoration(
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
borderSide: const BorderSide(
width: 1,
color: Colors.grey,
style: BorderStyle.solid,
),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
borderSide: const BorderSide(
width: 1,
color: Colors.black,
style: BorderStyle.solid,
),
),
fillColor: Colors.white,
filled: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
),
suggestionsDecoration: suggestionDecoration,
suggestions: suggestions
.map((e) => SearchFieldListItem<String>(e, child: searchChild(e)))
.toList(),
focusNode: focus,
suggestionState: Suggestion.expand,
onSuggestionTap: (SearchFieldListItem<String> x) {
focus.unfocus();
},
),
],
);
}
}
33 changes: 31 additions & 2 deletions lib/src/searchfield.dart
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,23 @@ class SearchField<T> extends StatefulWidget {
/// An optional method to call with the final value when the form is saved via FormState.save.
final void Function(String?)? onSaved;

/// Callback when the suggestions are scrolled
/// The callback returns the current scroll offset and the maximum scroll extent
/// of the suggestions list. The callback can be used to implement feature like
/// lazy loading of suggestions.
/// see example in [example/lib/pagination](https://github.com/maheshmnj/searchfield/blob/master/example/lib/pagination.dart)
final void Function(double, double)? onScroll;

/// Callback when the searchfield is tapped
/// or brought into focus
final void Function()? onTap;

// boolean to hide/show empty widget
// defaults to false
final bool showEmpty;

/// Widget to show when the search returns
/// empty results.
/// empty results or when showEmpty: true.
/// defaults to [SizedBox.shrink]
final Widget emptyWidget;

Expand Down Expand Up @@ -307,6 +318,7 @@ class SearchField<T> extends StatefulWidget {
this.readOnly = false,
this.onSearchTextChanged,
this.onSaved,
this.onScroll,
this.onTap,
this.onSubmit,
this.onTapOutside,
Expand All @@ -315,6 +327,7 @@ class SearchField<T> extends StatefulWidget {
this.searchInputDecoration,
this.searchStyle,
this.scrollbarDecoration,
this.showEmpty = false,
this.suggestionStyle,
this.suggestionsDecoration,
this.suggestionDirection = SuggestionDirection.down,
Expand Down Expand Up @@ -406,6 +419,17 @@ class _SearchFieldState<T> extends State<SearchField<T>> {
});
}

void listenToScrollEvents() {
if (widget.onScroll != null) {
_scrollController.addListener(() {
widget.onScroll!(_scrollController.offset,
_scrollController.position.maxScrollExtent);
});
} else {
_scrollController.removeListener(() {});
}
}

/// With SuggestionDirection.flex, the widget will automatically decide the direction of the
/// suggestion list based on the space available in the viewport. If the suggestions have enough
/// space below the searchfield, the list will be shown below the searchfield, else it will be
Expand Down Expand Up @@ -444,6 +468,7 @@ class _SearchFieldState<T> extends State<SearchField<T>> {
_scrollController = ScrollController();
searchController = widget.controller ?? TextEditingController();
_suggestionDirection = widget.suggestionDirection;
listenToScrollEvents();
initialize();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
Expand Down Expand Up @@ -539,6 +564,9 @@ class _SearchFieldState<T> extends State<SearchField<T>> {
_scrollbarDecoration = widget.scrollbarDecoration;
}
}
if (oldWidget.onScroll != widget.onScroll) {
listenToScrollEvents();
}
super.didUpdateWidget(oldWidget);
}

Expand Down Expand Up @@ -572,14 +600,15 @@ class _SearchFieldState<T> extends State<SearchField<T>> {

/// length of the suggestions
int length = 0;
double scrolloffset = 0.0;
Widget _suggestionsBuilder() {
return StreamBuilder<List<SearchFieldListItem<T>?>?>(
stream: suggestionStream.stream,
builder: (BuildContext context,
AsyncSnapshot<List<SearchFieldListItem<T>?>?> snapshot) {
if (snapshot.data == null || !isSuggestionExpanded) {
return SizedBox();
} else if (snapshot.data!.isEmpty) {
} else if (snapshot.data!.isEmpty || widget.showEmpty) {
return widget.emptyWidget;
} else {
final paddingHeight = widget.suggestionsDecoration != null
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: searchfield
description: A highly customizable, simple and easy to use flutter Widget to add a searchfield to your Flutter Application. This Widget allows you to search and select from list of suggestions.
version: 0.9.6
version: 0.9.7
homepage: https://github.com/maheshmnj/searchfield
repository: https://github.com/maheshmnj/searchfield
issue_tracker: https://github.com/maheshmnj/searchfield/issues
Expand Down
Loading

0 comments on commit a4e3c09

Please sign in to comment.