rsflutterapp

Bahnhofsfoto-App for Android and iOS using Flutter
git clone git://archive.git.mtrnord.blog/MTRNord/rsflutterapp.git
Log | Files | Refs | README

commit 7b2220109b020f42e36e6f9e15eacaeadad30c1f
parent 6324b0444b261b965421da237540e6353b7d3a94
Author: Marcel <mtrnord1@gmail.com>
Date:   Sat, 29 Feb 2020 21:40:06 +0100

Add Search to StationsTab

Took 47 minutes

Diffstat:
Alib/components/SearchBar.dart | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/views/HomePage.dart | 9++-------
Mlib/views/tabs/StationsTab.dart | 148++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
3 files changed, 189 insertions(+), 54 deletions(-)

diff --git a/lib/components/SearchBar.dart b/lib/components/SearchBar.dart @@ -0,0 +1,86 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class SearchBar extends StatefulWidget { + final TextEditingController filterTextController; + final onTextChanged; + + SearchBar({ + this.filterTextController, + this.onTextChanged, + Key key, + }) : super(key: key); + + @override + SearchBarState createState() => SearchBarState(); +} + +class SearchBarState extends State<SearchBar> { + String text; + + @override + build(BuildContext context) { + return TextField( + controller: widget.filterTextController, + keyboardType: TextInputType.text, + maxLines: 1, + style: TextStyle( + color: Colors.blueGrey, + fontSize: 15, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + letterSpacing: 0, + ), + onChanged: (String newText) { + setState(() { + text = newText; + }); + if (widget.onTextChanged != null) widget.onTextChanged(newText); + }, + decoration: InputDecoration( + fillColor: Color(0xffedf0f2), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), + filled: true, + contentPadding: EdgeInsets.all(11), + prefixIcon: Icon( + Icons.search, + color: Color(0xffedf0f2), + ), + suffixIcon: text != null && text != "" + ? IconButton( + icon: Icon( + Icons.cancel, + color: Colors.blueGrey, + ), + onPressed: () { + // workaround + // see https://github.com/flutter/flutter/issues/35848#issuecomment-527854562 + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.filterTextController.clear(); // clear text + FocusScope.of(context) + .requestFocus(FocusNode()); // hide keyboard + setState(() { + text = ""; + }); + }); + }, + ) + : null, + hintText: "Search", + hintStyle: TextStyle( + color: Colors.blueGrey, + fontSize: 15, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + letterSpacing: 0, + ), + ), + ); + } +} diff --git a/lib/views/HomePage.dart b/lib/views/HomePage.dart @@ -86,13 +86,8 @@ class HomePage extends StatelessWidget { railwayStationsRepository: railwayStationsRepository, ), ), - Scaffold( - appBar: AppBar( - title: Text("Bahnhöfe"), - ), - body: StationsTab( - railwayStationsRepository: railwayStationsRepository, - ), + StationsTab( + railwayStationsRepository: railwayStationsRepository, ), Scaffold( appBar: AppBar( diff --git a/lib/views/tabs/StationsTab.dart b/lib/views/tabs/StationsTab.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:rs_flutter_app/components/SearchBar.dart'; import '../../models/models.dart'; import '../../repos/repositories.dart'; @@ -17,11 +18,52 @@ class StationsTab extends StatefulWidget { class StationsTabState extends State<StationsTab> with AutomaticKeepAliveClientMixin<StationsTab> { + final filterTextController = TextEditingController(); List<Station> stations; + String filterText; - Future<List<Station>> getStations() async { + List<Station> filter() { + var stationsL = stations; + if (filterText != null && filterText != "") { + stationsL = stations + .where( + (station) => (filterText != null && + filterText != "" && + station.title + .toLowerCase() + .startsWith(filterText.toLowerCase())), + ) + .toList(growable: false); + } + return stationsL; + } + + void handleFilter() { + setState(() { + filterText = filterTextController.text; + }); + } + + @override + initState() { + super.initState(); + filterTextController.addListener(handleFilter); + } + + @override + void dispose() { + filterTextController.removeListener(handleFilter); + super.dispose(); + } + + Future<List<Station>> getStations(String filterText) async { if (stations == null) { - stations = await widget.railwayStationsRepository.getStations(); + List<Station> stationsL = + await widget.railwayStationsRepository.getStations(); + stationsL.sort( + (a, b) => (a.title).compareTo(b.title), + ); + stations = stationsL; } return stations; } @@ -30,55 +72,67 @@ class StationsTabState extends State<StationsTab> Widget build(BuildContext context) { super.build(context); - return FutureBuilder( - future: getStations(), - builder: (BuildContext context, AsyncSnapshot<List<Station>> snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.done: - if (snapshot.hasError) { - // TODO handle error - debugPrint(snapshot.error.toString()); - } - if (snapshot.hasData) { - List<Station> stations = snapshot.data; - stations.sort( - (a, b) => (a.title).compareTo(b.title), - ); + return Scaffold( + appBar: AppBar( + title: Text("Bahnhöfe"), + bottom: PreferredSize( + preferredSize: Size.fromHeight(56), + child: Padding( + padding: EdgeInsets.all(8), + child: SearchBar( + filterTextController: filterTextController, + ), + ), + ), + ), + body: FutureBuilder<List<Station>>( + future: getStations(filterText), + builder: (BuildContext context, AsyncSnapshot<List<Station>> snapshot) { + if (snapshot.hasError) { + // TODO handle error + debugPrint(snapshot.error.toString()); + } + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator(), + ); + } + var filtered = filter(); - return ListView.separated( - itemCount: stations.length, - separatorBuilder: (BuildContext context, int index) { - return Divider(); - }, - itemBuilder: (BuildContext context, int index) { - return ListTile( - trailing: stations[index].photoUrl != "" - ? Icon(Icons.image) - : null, - title: Text(stations[index].title), - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) => DetailsPage( - station: stations[index], - railwayStationsRepository: - widget.railwayStationsRepository, - ), - ), - ); - }, + if (filtered == null || filtered.isEmpty) { + return Container(); + } + + if (stations.isEmpty) { + return Container(); + } + + return ListView.separated( + itemCount: filtered.length, + separatorBuilder: (BuildContext context, int index) { + return Divider(); + }, + itemBuilder: (BuildContext context, int index) { + return ListTile( + trailing: + filtered[index].photoUrl != "" ? Icon(Icons.image) : null, + title: Text(filtered[index].title), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => DetailsPage( + station: filtered[index], + railwayStationsRepository: + widget.railwayStationsRepository, + ), + ), ); }, ); - } - // TODO handle missing date - return Container(); - default: - return Center( - child: CircularProgressIndicator(), - ); - } - }, + }, + ); + }, + ), ); }