From 47c7c4cef94eeff0fae08949f920b4539304db57 Mon Sep 17 00:00:00 2001 From: AYM1607 Date: Fri, 5 Apr 2019 20:29:59 -0600 Subject: [PATCH] Modified the search functionality so teh resulting list updates as the user types. Added a clearing button to the search box. --- lib/src/blocs/home_bloc.dart | 32 +++++++--- lib/src/widgets/search-box.dart | 106 ++++++++++++++++++++++---------- 2 files changed, 95 insertions(+), 43 deletions(-) diff --git a/lib/src/blocs/home_bloc.dart b/lib/src/blocs/home_bloc.dart index 933bdda..9fbc567 100644 --- a/lib/src/blocs/home_bloc.dart +++ b/lib/src/blocs/home_bloc.dart @@ -25,14 +25,27 @@ class HomeBloc { /// A subject of list of task model. final _tasks = BehaviorSubject>(); - /// Current text from the search box. - String _searchBoxText = ''; + /// A subject of the search box updates. + final _searchBoxText = BehaviorSubject(seedValue: ''); // Stream getters. + + // The result has to be a combination of the tasks streams and the text + // stream, because otherwise, the tasks stream has no way of knowing when + // there's a new update in the text. + // + // The search box transformation has to be applied after the combination, + // otherwhise the value used for filtering is outdated and the list output is + // not synchronized with the current value of the searhc box text. /// An observalbe of the taks of a user. - Observable> get userTasks => _tasks.stream - .transform(prioritySortTransformer()) - .transform(searchBoxTransformer()); + Observable> get userTasks => + Observable.combineLatest2, List>( + _searchBoxText.stream, + _tasks.stream.transform(prioritySortTransformer()), + (text, tasks) { + return tasks; + }, + ).transform(searchBoxTransformer()); /// An observable of the current logged in user. Observable get userStream => _auth.userStream; @@ -55,17 +68,17 @@ class HomeBloc { sink.add( taskList.where( (TaskModel task) { - if (_searchBoxText == '') { + if (_searchBoxText.value == '') { return true; } // Return true if the text in the search box matches the title // or the text of the task. return task.event .toLowerCase() - .contains(_searchBoxText.toLowerCase()) || + .contains(_searchBoxText.value.toLowerCase()) || task.text .toLowerCase() - .contains(_searchBoxText.toLowerCase()); + .contains(_searchBoxText.value.toLowerCase()); }, ).toList(), ); @@ -106,10 +119,11 @@ class HomeBloc { /// Updates the serach box text. void updateSearchBoxText(String newText) { - _searchBoxText = newText; + _searchBoxText.add(newText); } void dispose() { + _searchBoxText.close(); _tasks.close(); } } diff --git a/lib/src/widgets/search-box.dart b/lib/src/widgets/search-box.dart index d7e4a8a..eea2b68 100644 --- a/lib/src/widgets/search-box.dart +++ b/lib/src/widgets/search-box.dart @@ -6,27 +6,87 @@ import './gradient_touchable_container.dart'; //TODO: Add neccessary properties to be able to inform of changes in text field. /// A search box that mathces the app mocks. -class SearchBox extends StatelessWidget { +class SearchBox extends StatefulWidget { /// Height of the sarch box. final double height; /// Function to be called when the text changes. final Function(String) onChanged; + /// Creates a search box. + /// + /// the height should be equal or larger than 50. SearchBox({ @required this.height, @required this.onChanged, }) : assert(height >= 50); + @override + _SearchBoxState createState() => _SearchBoxState(); +} + +class _SearchBoxState extends State { + /// Controller for the [TextFiel]. + final TextEditingController _controller = TextEditingController(); + + initState() { + _controller.addListener(() => widget.onChanged(_controller.text)); + super.initState(); + } + Widget build(BuildContext context) { + final List containerRowChildren = [ + SizedBox( + width: 10, + ), + Icon( + FontAwesomeIcons.sistrix, + color: Colors.white, + ), + SizedBox( + width: 8, + ), + Expanded( + child: TextField( + controller: _controller, + decoration: InputDecoration( + border: InputBorder.none, + hintText: 'Search...', + hintStyle: TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + cursorColor: Colors.white, + scrollPadding: EdgeInsets.zero, + style: TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + ), + ]; + + if (_controller.text != '') { + containerRowChildren.add( + IconButton( + icon: Icon( + FontAwesomeIcons.timesCircle, + color: Colors.white, + ), + onPressed: onClearButtonPressed, + ), + ); + } + return Row( children: [ Spacer(flex: 1), Expanded( flex: 8, child: GradientTouchableContainer( - radius: height / 2, - height: height, + radius: widget.height / 2, + height: widget.height, shadow: BoxShadow( color: Color(0x20FFFFFF), offset: Offset(0, 3), @@ -34,37 +94,7 @@ class SearchBox extends StatelessWidget { spreadRadius: 1, ), child: Row( - children: [ - SizedBox( - width: 10, - ), - Icon( - FontAwesomeIcons.sistrix, - color: Colors.white, - ), - SizedBox( - width: 8, - ), - Expanded( - child: TextField( - onChanged: onChanged, - decoration: InputDecoration( - border: InputBorder.none, - hintText: 'Search...', - hintStyle: TextStyle( - fontSize: 16, - color: Colors.white, - ), - ), - cursorColor: Colors.white, - scrollPadding: EdgeInsets.zero, - style: TextStyle( - fontSize: 16, - color: Colors.white, - ), - ), - ), - ], + children: containerRowChildren, ), ), ), @@ -72,4 +102,12 @@ class SearchBox extends StatelessWidget { ], ); } + + void onClearButtonPressed() { + _controller.text = ''; + // The controller does not notify its listeners when the text is set + // explicitely. We have to do it manually. + widget.onChanged(_controller.text); + setState(() {}); + } }