diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 9c0358df..57f0c2f9 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -84,21 +84,15 @@ class InvenTreeModel { // Return the API detail endpoint for this Model object String get url => "${URL}/${pk}/"; - /* + // Search this Model type in the database - Future> search(String searchTerm) async { + Future> search(BuildContext context, String searchTerm) async { - String addr = url + "?search=" + search; + final results = list(context, filters: {"cascade": "true", "search": searchTerm}); - print("Searching endpoint: $url"); - - // TODO - Add "timeout" - // TODO - Add error catching - - var response = + return results; } - */ Map defaultListFilters() { return Map(); } diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index 40e70631..d2a30477 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -36,7 +36,13 @@ class InvenTreeDrawer extends StatelessWidget { void _search() { _closeDrawer(); - Navigator.push(context, MaterialPageRoute(builder: (context) => SearchWidget())); + + showSearch( + context: context, + delegate: PartSearchDelegate() + ); + + //Navigator.push(context, MaterialPageRoute(builder: (context) => SearchWidget())); } /* @@ -101,62 +107,59 @@ class InvenTreeDrawer extends StatelessWidget { @override Widget build(BuildContext context) { return Drawer( - child: new ListView( + child: ListView( children: ListTile.divideTiles( context: context, tiles: [ - new ListTile( - leading: new Image.asset( + ListTile( + leading: Image.asset( "assets/image/icon.png", fit: BoxFit.scaleDown, width: 40, ), - title: new Text(I18N.of(context).appTitle), + title: Text(I18N.of(context).appTitle), onTap: _home, ), - /* - // TODO - Add search functionality! - new ListTile( - title: new Text("Search"), - leading: new FaIcon(FontAwesomeIcons.search), + ListTile( + title: Text(I18N.of(context).scanBarcode), + onTap: _scan, + leading: FaIcon(FontAwesomeIcons.barcode), + ), + ListTile( + title: Text(I18N.of(context).search), + leading: FaIcon(FontAwesomeIcons.search), onTap: _search, ), - */ - new ListTile( - title: new Text(I18N.of(context).scanBarcode), - onTap: _scan, - leading: new FaIcon(FontAwesomeIcons.barcode), - ), - new ListTile( - title: new Text(I18N.of(context).parts), - leading: new Icon(Icons.category), + ListTile( + title: Text(I18N.of(context).parts), + leading: Icon(Icons.category), onTap: _showParts, ), - new ListTile( - title: new Text(I18N.of(context).stock), - leading: new FaIcon(FontAwesomeIcons.boxes), + ListTile( + title: Text(I18N.of(context).stock), + leading: FaIcon(FontAwesomeIcons.boxes), onTap: _showStock, ), /* - new ListTile( - title: new Text("Suppliers"), - leading: new FaIcon(FontAwesomeIcons.building), + ListTile( + title: Text("Suppliers"), + leading: FaIcon(FontAwesomeIcons.building), onTap: _showSuppliers, ), - new ListTile( + ListTile( title: Text("Manufacturers"), - leading: new FaIcon(FontAwesomeIcons.industry), + leading: FaIcon(FontAwesomeIcons.industry), onTap: _showManufacturers, ), - new ListTile( - title: new Text("Customers"), - leading: new FaIcon(FontAwesomeIcons.users), + ListTile( + title: Text("Customers"), + leading: FaIcon(FontAwesomeIcons.users), onTap: _showCustomers, ), */ - new ListTile( - title: new Text(I18N.of(context).settings), - leading: new Icon(Icons.settings), + ListTile( + title: Text(I18N.of(context).settings), + leading: Icon(Icons.settings), onTap: _settings, ), ] diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 3955668e..bab1dd3b 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -43,8 +43,10 @@ class _InvenTreeHomePageState extends State { void _search() { if (!InvenTreeAPI().checkConnection(context)) return; - Navigator.push(context, MaterialPageRoute(builder: (context) => SearchWidget())); - + showSearch( + context: context, + delegate: PartSearchDelegate() + ); } void _scan(BuildContext context) { @@ -204,13 +206,11 @@ class _InvenTreeHomePageState extends State { appBar: AppBar( title: Text(I18N.of(context).appTitle), actions: [ - /* IconButton( icon: FaIcon(FontAwesomeIcons.search), - tooltip: 'Search', + tooltip: I18N.of(context).search, onPressed: _search, ), - */ ], ), drawer: new InvenTreeDrawer(context), diff --git a/lib/widget/search.dart b/lib/widget/search.dart index 86e6488e..0f3f2da0 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -1,4 +1,7 @@ +import 'package:InvenTree/widget/part_detail.dart'; +import 'package:InvenTree/widget/progress.dart'; +import 'package:InvenTree/widget/snacks.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -7,6 +10,156 @@ import 'package:InvenTree/widget/refreshable_state.dart'; import 'package:InvenTree/inventree/part.dart'; import 'package:InvenTree/inventree/stock.dart'; +import '../api.dart'; + + +class PartSearchDelegate extends SearchDelegate { + + final key = GlobalKey(); + + bool _searching = false; + + // List of part results + List partResults = []; + + Future search(BuildContext context) async { + + // Search string too short! + if (query.length < 3) { + partResults.clear(); + showResults(context); + return; + } + + _searching = true; + + print("Searching..."); + + showResults(context); + + final results = await InvenTreePart().search(context, query); + + partResults.clear(); + + for (int idx = 0; idx < results.length; idx++) { + if (results[idx] is InvenTreePart) { + partResults.add(results[idx]); + } + } + + print("Searching complete! Results: ${partResults.length}"); + _searching = false; + + // TODO: Show a snackbar detailing number of results... + //showSnackIcon("Found ${partResults.length} parts", context: context); + + // For some reason, need to toggle between suggestions and results here... + showSuggestions(context); + showResults(context); + } + + @override + List buildActions(BuildContext context) { + return [ + IconButton( + icon: FaIcon(FontAwesomeIcons.search), + onPressed: () { + search(context); + } + ), + IconButton( + icon: FaIcon(FontAwesomeIcons.backspace), + onPressed: () { + query = ''; + search(context); + }, + ), + ]; + } + + @override + Widget buildLeading(BuildContext context) { + return IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + this.close(context, null); + } + ); + } + + Widget _partResult(BuildContext context, int index) { + + InvenTreePart part = partResults[index]; + + return ListTile( + title: Text(part.fullname), + subtitle: Text(part.description), + leading: InvenTreeAPI().getImage( + part.thumbnail, + width: 40, + height: 40 + ), + onTap: () { + InvenTreePart().get(context, part.pk).then((var prt) { + if (prt is InvenTreePart) { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => PartDetailWidget(prt)) + ); + } + }); + } + ); + } + + @override + Widget buildResults(BuildContext context) { + + print("Build results called..."); + + if (_searching) { + return progressIndicator(); + } + + if (query.length < 3) { + return ListTile( + title: Text("Query too short"), + subtitle: Text("Enter a query of at least three characters") + ); + } + + if (partResults.length == 0) { + return ListTile( + title: Text("No Results"), + subtitle: Text("No results matching query") + ); + } + + return ListView.separated( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + separatorBuilder: (_, __) => const Divider(height: 3), + itemBuilder: _partResult, + itemCount: partResults.length, + ); + } + + @override + Widget buildSuggestions(BuildContext context) { + // TODO - Implement + return Column(); + } + + // Ensure the search theme matches the app theme + @override + ThemeData appBarTheme(BuildContext context) { + assert(context != null); + final ThemeData theme = Theme.of(context); + assert(theme != null); + return theme; + } +} + class SearchWidget extends StatefulWidget {