diff --git a/lib/barcode.dart b/lib/barcode.dart index 7716d7de..975fd088 100644 --- a/lib/barcode.dart +++ b/lib/barcode.dart @@ -6,9 +6,9 @@ import 'package:InvenTree/inventree/stock.dart'; import 'package:InvenTree/inventree/part.dart'; import 'package:InvenTree/widget/location_display.dart'; -import 'package:InvenTree/widget/part_display.dart'; +import 'package:InvenTree/widget/part_detail.dart'; import 'package:InvenTree/widget/category_display.dart'; -import 'package:InvenTree/widget/stock_display.dart'; +import 'package:InvenTree/widget/stock_detail.dart'; import 'dart:convert'; @@ -59,12 +59,12 @@ void _handleInvenTreeBarcode(BuildContext context, Map data) { } else if (codeType == 'stockitem') { InvenTreeStockItem().get(pk).then((var item) { - Navigator.push(context, MaterialPageRoute(builder: (context) => StockItemDisplayWidget(item))); + Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item))); }); } else if (codeType == 'part') { InvenTreePart().get(pk).then((var part) { Navigator.push(context, - MaterialPageRoute(builder: (context) => PartDisplayWidget(part))); + MaterialPageRoute(builder: (context) => PartDetailWidget(part))); }); } } \ No newline at end of file diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 31a1d14c..74ff87c8 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -10,6 +10,18 @@ class InvenTreeCompany extends InvenTreeModel { InvenTreeCompany() : super(); + String get image => jsondata['image'] ?? ''; + + String get website => jsondata['website'] ?? ''; + + String get phone => jsondata['phone'] ?? ''; + + String get email => jsondata['email'] ?? ''; + + bool get isSupplier => jsondata['is_supplier'] ?? false; + + bool get isCustomer => jsondata['is_customer'] ?? false; + InvenTreeCompany.fromJson(Map json) : super.fromJson(json) { // TODO } diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 38680ba7..7b62ac0e 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -172,7 +172,16 @@ class InvenTreeModel { // TODO - Define a 'save' / 'update' function // Override this function for each sub-class - bool matchAgainstString(String filter) => false; + bool matchAgainstString(String filter) { + // Default implementation matches name and description + // Override this behaviour in sub-class if required + + if (name.toLowerCase().contains(filter)) return true; + if (description.toLowerCase().contains(filter)) return true; + + // No matches! + return false; + } // Filter this item against a list of provided filters // Each filter must be matched diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 60aaaecd..2365b74b 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -195,13 +195,4 @@ class InvenTreeStockLocation extends InvenTreeModel { return loc; } - @override - bool matchAgainstString(String filter) { - - if (name.toLowerCase().contains(filter)) return true; - - if (description.toLowerCase().contains(filter)) return true; - - return false; - } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 0c6775fc..2f400069 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:InvenTree/inventree/stock.dart'; import 'package:InvenTree/widget/category_display.dart'; +import 'package:InvenTree/widget/company_list.dart'; import 'package:InvenTree/widget/location_display.dart'; import 'package:InvenTree/widget/drawer.dart'; import 'package:flutter/cupertino.dart'; @@ -27,7 +28,7 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); // Load login details - InvenTreeUserPreferences().loadLoginDetails(); + InvenTreePreferences().loadLoginDetails(); runApp(MyApp()); } @@ -207,12 +208,17 @@ class _MyHomePageState extends State { } void _stock() { - if (!InvenTreeAPI().checkConnection(context)) return; Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))); } + void _suppliers() { + if (!InvenTreeAPI().checkConnection(context)) return; + + Navigator.push(context, MaterialPageRoute(builder: (context) => SupplierListWidget())); + } + void _unsupported() { showDialog( context: context, @@ -243,7 +249,7 @@ class _MyHomePageState extends State { title: Text(widget.title), actions: [ IconButton( - icon: Icon(Icons.search), + icon: FaIcon(FontAwesomeIcons.search), tooltip: 'Search', onPressed: null, ), @@ -311,7 +317,7 @@ class _MyHomePageState extends State { IconButton( icon: new FaIcon(FontAwesomeIcons.industry), tooltip: 'Suppliers', - onPressed: _unsupported, + onPressed: _suppliers, ), Text("Suppliers"), ] diff --git a/lib/preferences.dart b/lib/preferences.dart index 53aa19bc..158224d6 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -2,20 +2,37 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'api.dart'; -class InvenTreeUserPreferences { +class InvenTreePreferences { static const String _SERVER = 'server'; static const String _USERNAME = 'username'; static const String _PASSWORD = 'password'; - // Ensure we only ever create a single instance of the preferences class - static final InvenTreeUserPreferences _api = new InvenTreeUserPreferences._internal(); + /* The following settings are not stored to persistent storage, + * instead they are only used as 'session preferences'. + * They are kept here as a convenience only. + */ - factory InvenTreeUserPreferences() { + // Expand subcategory list in PartCategory view + bool expandCategoryList = false; + + // Expand part list in PartCategory view + bool expandPartList = true; + + // Expand sublocation list in StockLocation view + bool expandLocationList = false; + + // Expand item list in StockLocation view + bool expandStockList = true; + + // Ensure we only ever create a single instance of the preferences class + static final InvenTreePreferences _api = new InvenTreePreferences._internal(); + + factory InvenTreePreferences() { return _api; } - InvenTreeUserPreferences._internal(); + InvenTreePreferences._internal(); // Load saved login details, and attempt connection void loadLoginDetails() async { diff --git a/lib/settings/login.dart b/lib/settings/login.dart index 8415e2a4..15ae6db0 100644 --- a/lib/settings/login.dart +++ b/lib/settings/login.dart @@ -130,7 +130,7 @@ class _InvenTreeLoginSettingsState extends State { if (_formKey.currentState.validate()) { _formKey.currentState.save(); - await InvenTreeUserPreferences().saveLoginDetails(_server, _username, _password); + await InvenTreePreferences().saveLoginDetails(_server, _username, _password); } } diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index f1f02823..7f7f47d2 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -1,8 +1,9 @@ import 'package:InvenTree/api.dart'; import 'package:InvenTree/inventree/part.dart'; +import 'package:InvenTree/preferences.dart'; -import 'package:InvenTree/widget/part_display.dart'; +import 'package:InvenTree/widget/part_detail.dart'; import 'package:InvenTree/widget/drawer.dart'; import 'package:flutter/cupertino.dart'; @@ -83,9 +84,6 @@ class _CategoryDisplayState extends State { }); } - bool _subcategoriesExpanded = false; - bool _partListExpanded = true; - Widget getCategoryDescriptionCard() { if (category == null) { return Card( @@ -144,10 +142,10 @@ class _CategoryDisplayState extends State { switch (index) { case 0: - _subcategoriesExpanded = !isExpanded; + InvenTreePreferences().expandCategoryList = !isExpanded; break; case 1: - _partListExpanded = !isExpanded; + InvenTreePreferences().expandPartList = !isExpanded; break; default: break; @@ -163,13 +161,16 @@ class _CategoryDisplayState extends State { trailing: Text("${_subcategories.length}"), onTap: () { setState(() { - _subcategoriesExpanded = !_subcategoriesExpanded; + InvenTreePreferences().expandCategoryList = !InvenTreePreferences().expandCategoryList; }); }, + onLongPress: () { + // TODO - Context menu for e.g. creating a new PartCategory + }, ); }, body: SubcategoryList(_subcategories), - isExpanded: _subcategoriesExpanded && _subcategories.length > 0, + isExpanded: InvenTreePreferences().expandCategoryList && _subcategories.length > 0, ), ExpansionPanel( headerBuilder: (BuildContext context, bool isExpanded) { @@ -179,13 +180,16 @@ class _CategoryDisplayState extends State { trailing: Text("${_parts.length}"), onTap: () { setState(() { - _partListExpanded = !_partListExpanded; + InvenTreePreferences().expandPartList = !InvenTreePreferences().expandPartList; }); }, + onLongPress: () { + // TODO - Context menu for e.g. creating a new Part + }, ); }, body: PartList(_parts), - isExpanded: _partListExpanded && _parts.length > 0, + isExpanded: InvenTreePreferences().expandPartList && _parts.length > 0, ) ], ), @@ -251,7 +255,7 @@ class PartList extends StatelessWidget { InvenTreePart().get(pk).then((var part) { if (part is InvenTreePart) { - Navigator.push(context, MaterialPageRoute(builder: (context) => PartDisplayWidget(part))); + Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part))); } }); } diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart new file mode 100644 index 00000000..56d16d7e --- /dev/null +++ b/lib/widget/company_detail.dart @@ -0,0 +1,139 @@ + +import 'package:InvenTree/api.dart'; +import 'package:InvenTree/inventree/company.dart'; +import 'package:InvenTree/widget/drawer.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +class CompanyDetailWidget extends StatefulWidget { + + final InvenTreeCompany company; + + CompanyDetailWidget(this.company, {Key key}) : super(key: key); + + @override + _CompanyDetailState createState() => _CompanyDetailState(company); + +} + + +class _CompanyDetailState extends State { + + final InvenTreeCompany company; + + _CompanyDetailState(this.company) { + // TODO + } + + List _companyTiles() { + + var tiles = List(); + + bool sep = false; + + tiles.add(Card( + child: ListTile( + title: Text("${company.name}"), + subtitle: Text("${company.description}"), + leading: Image( + image: InvenTreeAPI().getImage(company.image), + width: 48, + ), + ), + )); + + if (company.website.isNotEmpty) { + tiles.add(ListTile( + title: Text("${company.website}"), + leading: FaIcon(FontAwesomeIcons.globe), + onTap: () { + // TODO - Open website + }, + )); + + sep = true; + } + + if (company.email.isNotEmpty) { + tiles.add(ListTile( + title: Text("${company.email}"), + leading: FaIcon(FontAwesomeIcons.at), + onTap: () { + // TODO - Open email + }, + )); + + sep = true; + } + + if (company.phone.isNotEmpty) { + tiles.add(ListTile( + title: Text("${company.phone}"), + leading: FaIcon(FontAwesomeIcons.phone), + onTap: () { + // TODO - Call phone number + }, + )); + + sep = true; + } + + // External link + if (company.link.isNotEmpty) { + tiles.add(ListTile( + title: Text("${company.link}"), + leading: FaIcon(FontAwesomeIcons.link), + onTap: () { + // TODO - Open external link + }, + )); + + sep = true; + } + + if (sep) { + tiles.add(Divider()); + } + + if (company.isSupplier) { + // TODO - Add list of supplier parts + // TODO - Add list of purchase orders + + tiles.add(Divider()); + } + + if (company.isCustomer) { + + // TODO - Add list of sales orders + + tiles.add(Divider()); + } + + if (company.notes.isNotEmpty) { + tiles.add(ListTile( + title: Text("Notes"), + leading: FaIcon(FontAwesomeIcons.stickyNote), + onTap: null, + )); + } + + return tiles; + } + + @override + Widget build(BuildContext context) { + + return Scaffold( + appBar: AppBar( + title: Text("${company.name}"), + ), + drawer: new InvenTreeDrawer(context), + body: Center( + child: ListView( + children: _companyTiles(), + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/widget/company_list.dart b/lib/widget/company_list.dart new file mode 100644 index 00000000..512c198f --- /dev/null +++ b/lib/widget/company_list.dart @@ -0,0 +1,131 @@ + +import 'package:InvenTree/widget/company_detail.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'package:InvenTree/api.dart'; +import 'package:InvenTree/inventree/company.dart'; +import 'package:InvenTree/widget/drawer.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +abstract class CompanyListWidget extends StatefulWidget { + + String title; + Map filters; + + @override + _CompanyListState createState() => _CompanyListState(title, filters); + +} + +class SupplierListWidget extends CompanyListWidget { + @override + _CompanyListState createState() => _CompanyListState("Suppliers", {"is_supplier": "true"}); +} + + +class CustomerListWidget extends CompanyListWidget { + @override + _CompanyListState createState() => _CompanyListState("Customers", {"is_customer": "true"}); +} + + +class _CompanyListState extends State { + + var _companies = new List(); + + var _filteredCompanies = new List(); + + var _title = "Companies"; + + Map _filters = Map(); + + _CompanyListState(this._title, this._filters) { + _requestData(); + } + + void _requestData() { + + InvenTreeCompany().list(filters: _filters).then((var companies) { + + _companies.clear(); + + for (var c in companies) { + if (c is InvenTreeCompany) { + _companies.add(c); + } + } + + setState(() { + _filterResults(""); + }); + + }); + } + + void _filterResults(String text) { + + if (text.isEmpty) { + _filteredCompanies = _companies; + } else { + _filteredCompanies = _companies.where((c) => c.filter(text)).toList(); + } + } + + Widget _showCompany(BuildContext context, int index) { + + InvenTreeCompany company = _filteredCompanies[index]; + + return ListTile( + title: Text("${company.name}"), + subtitle: Text("${company.description}"), + leading: Image( + image: InvenTreeAPI().getImage(company.image), + width: 40, + ), + onTap: () { + if (company.pk > 0) { + InvenTreeCompany().get(company.pk).then((var c) { + Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyDetailWidget(c))); + }); + } + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("$_title"), + actions: [ + IconButton( + icon: FaIcon(FontAwesomeIcons.plus), + tooltip: 'New', + onPressed: null, + ) + ], + ), + drawer: new InvenTreeDrawer(context), + body: ListView( + children: [ + TextField( + decoration: InputDecoration( + hintText: 'Filter results', + ), + onChanged: (String text) { + setState(() { + _filterResults(text); + }); + }, + ), + ListView.builder( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + itemBuilder: _showCompany, itemCount: _filteredCompanies.length) + ], + ) + ); + } + +} \ No newline at end of file diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index f60b5497..f5db6e59 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -1,5 +1,6 @@ import 'package:InvenTree/api.dart'; import 'package:InvenTree/barcode.dart'; +import 'package:InvenTree/widget/company_list.dart'; import 'package:flutter/material.dart'; import 'package:InvenTree/api.dart'; @@ -61,6 +62,20 @@ class InvenTreeDrawer extends StatelessWidget { Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))); } + void _showSuppliers() { + if (!InvenTreeAPI().checkConnection(context)) return; + _closeDrawer(); + + Navigator.push(context, MaterialPageRoute(builder: (context) => SupplierListWidget())); + } + + void _showCustomers() { + if (!InvenTreeAPI().checkConnection(context)) return; + _closeDrawer(); + + Navigator.push(context, MaterialPageRoute(builder: (context) => CustomerListWidget())); + } + /* * Load settings widget */ @@ -108,7 +123,12 @@ class InvenTreeDrawer extends StatelessWidget { new ListTile( title: new Text("Suppliers"), leading: new FaIcon(FontAwesomeIcons.industry), - onTap: null, + onTap: _showSuppliers, + ), + new ListTile( + title: new Text("Customers"), + leading: new FaIcon(FontAwesomeIcons.users), + onTap: _showCustomers, ), new Divider(), new ListTile( diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 8cfd141c..fa1456ac 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -1,7 +1,8 @@ import 'package:InvenTree/api.dart'; import 'package:InvenTree/inventree/stock.dart'; +import 'package:InvenTree/preferences.dart'; import 'package:InvenTree/widget/drawer.dart'; -import 'package:InvenTree/widget/stock_display.dart'; +import 'package:InvenTree/widget/stock_detail.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; @@ -91,9 +92,6 @@ class _LocationDisplayState extends State { }); } - bool _locationListExpanded = false; - bool _stockListExpanded = true; - Widget locationDescriptionCard() { if (location == null) { return Card( @@ -150,10 +148,10 @@ class _LocationDisplayState extends State { setState(() { switch (index) { case 0: - _locationListExpanded = !isExpanded; + InvenTreePreferences().expandLocationList = !isExpanded; break; case 1: - _stockListExpanded = !isExpanded; + InvenTreePreferences().expandStockList = !isExpanded; break; default: break; @@ -170,13 +168,13 @@ class _LocationDisplayState extends State { trailing: Text("${_sublocations.length}"), onTap: () { setState(() { - _locationListExpanded = !_locationListExpanded; + InvenTreePreferences().expandLocationList = !InvenTreePreferences().expandLocationList; }); }, ); }, body: SublocationList(_sublocations), - isExpanded: _locationListExpanded && _sublocations.length > 0, + isExpanded: InvenTreePreferences().expandLocationList && _sublocations.length > 0, ), ExpansionPanel( headerBuilder: (BuildContext context, bool isExpanded) { @@ -186,13 +184,13 @@ class _LocationDisplayState extends State { trailing: Text("${_items.length}"), onTap: () { setState(() { - _stockListExpanded = !_stockListExpanded; + InvenTreePreferences().expandStockList = !InvenTreePreferences().expandStockList; }); }, ); }, body: StockList(_items), - isExpanded: _stockListExpanded && _items.length > 0, + isExpanded: InvenTreePreferences().expandStockList && _items.length > 0, ) ] ), @@ -248,7 +246,7 @@ class StockList extends StatelessWidget { void _openItem(BuildContext context, int pk) { InvenTreeStockItem().get(pk).then((var item) { if (item is InvenTreeStockItem) { - Navigator.push(context, MaterialPageRoute(builder: (context) => StockItemDisplayWidget(item))); + Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item))); } }); } diff --git a/lib/widget/part_display.dart b/lib/widget/part_detail.dart similarity index 95% rename from lib/widget/part_display.dart rename to lib/widget/part_detail.dart index d987d91b..13a7d498 100644 --- a/lib/widget/part_display.dart +++ b/lib/widget/part_detail.dart @@ -9,9 +9,9 @@ import 'package:InvenTree/api.dart'; import 'package:InvenTree/widget/drawer.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -class PartDisplayWidget extends StatefulWidget { +class PartDetailWidget extends StatefulWidget { - PartDisplayWidget(this.part, {Key key}) : super(key: key); + PartDetailWidget(this.part, {Key key}) : super(key: key); final InvenTreePart part; @@ -21,7 +21,7 @@ class PartDisplayWidget extends StatefulWidget { } -class _PartDisplayState extends State { +class _PartDisplayState extends State { _PartDisplayState(this.part) { // TODO diff --git a/lib/widget/stock_display.dart b/lib/widget/stock_detail.dart similarity index 93% rename from lib/widget/stock_display.dart rename to lib/widget/stock_detail.dart index 6a4c5938..507cb72a 100644 --- a/lib/widget/stock_display.dart +++ b/lib/widget/stock_detail.dart @@ -3,7 +3,7 @@ import 'package:InvenTree/inventree/stock.dart'; import 'package:InvenTree/inventree/part.dart'; import 'package:InvenTree/widget/location_display.dart'; -import 'package:InvenTree/widget/part_display.dart'; +import 'package:InvenTree/widget/part_detail.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -12,9 +12,9 @@ import 'package:InvenTree/api.dart'; import 'package:InvenTree/widget/drawer.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -class StockItemDisplayWidget extends StatefulWidget { +class StockDetailWidget extends StatefulWidget { - StockItemDisplayWidget(this.item, {Key key}) : super(key: key); + StockDetailWidget(this.item, {Key key}) : super(key: key); final InvenTreeStockItem item; @@ -23,7 +23,7 @@ class StockItemDisplayWidget extends StatefulWidget { } -class _StockItemDisplayState extends State { +class _StockItemDisplayState extends State { _StockItemDisplayState(this.item) { // TODO @@ -64,7 +64,7 @@ class _StockItemDisplayState extends State { if (item.partId > 0) { InvenTreePart().get(item.partId).then((var part) { if (part is InvenTreePart) { - Navigator.push(context, MaterialPageRoute(builder: (context) => PartDisplayWidget(part))); + Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part))); } }); }